@opengis/fastify-table 1.2.72 → 1.2.74
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/package.json +1 -1
- package/server/plugins/table/funcs/getFilterSQL/index.js +8 -9
- package/server/plugins/table/funcs/getFilterSQL/util/formatValue.js +68 -207
- package/server/plugins/table/funcs/getFilterSQL/util/getFilterQuery.js +4 -4
- package/server/plugins/table/funcs/getFilterSQL/util/getOptimizedQuery.js +9 -5
- package/server/plugins/table/funcs/getFilterSQL/util/getRangeQuery.js +154 -0
- package/server/routes/table/controllers/filter.js +12 -20
package/package.json
CHANGED
|
@@ -11,11 +11,11 @@ const checkInline = {};
|
|
|
11
11
|
const defaultTable = 'crm.extra_data';
|
|
12
12
|
|
|
13
13
|
function getExtraQuery(mainColumns, schema, table, pk) {
|
|
14
|
-
const
|
|
14
|
+
const extraColumns = Object.keys(schema || {}).filter(key => !mainColumns.map(el => el?.name).includes(key)).map(key => ({ name: key, type: schema[key]?.type }));
|
|
15
15
|
|
|
16
16
|
return {
|
|
17
|
-
q:
|
|
18
|
-
|
|
17
|
+
q: extraColumns.map((el) => `,(select value_text as "${el.name.replace(/'/g, "''")}" from ${table} where property_key='${el.name.replace(/'/g, "''")}' and object_id=t.${pk} limit 1)`).join(' '),
|
|
18
|
+
extraColumns,
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -37,7 +37,7 @@ export default async function getFilterSQL({
|
|
|
37
37
|
: undefined;
|
|
38
38
|
|
|
39
39
|
const { fields: fieldsModel = [] } = body?.table && pg.pk[body.table] ? await pg.query(`select * from ${body.table} limit 0`) : {};
|
|
40
|
-
const { q: extraSqlColumns,
|
|
40
|
+
const { q: extraSqlColumns, extraColumns = [] } = pg.pk?.[extraDataTable] && pg.pk?.[body.table || table]
|
|
41
41
|
? getExtraQuery(fieldsModel, loadTemplate?.schema, extraDataTable, pg.pk[body.table || table])
|
|
42
42
|
: {};
|
|
43
43
|
|
|
@@ -66,7 +66,7 @@ export default async function getFilterSQL({
|
|
|
66
66
|
const sqlInline = body.sql?.filter?.(el => el.inline).map(el => `,(${el.sql})`).join('');
|
|
67
67
|
|
|
68
68
|
const fieldQuery = `select * ${extraSqlColumns || ''} ${sqlInline || ''} from ${body?.table || table} t ${sqlTable || ''} limit 0`;
|
|
69
|
-
const { fields = [] } =
|
|
69
|
+
const { fields = [] } = await pg.query(fieldQuery);
|
|
70
70
|
const autoSearchColumn = fields?.filter((el) => pg.pgType?.[el.dataTypeID] === 'text')?.map((el) => el.name).join(',');
|
|
71
71
|
const searchColumn = body?.search_column || body?.meta?.search || autoSearchColumn;
|
|
72
72
|
const fieldsList = (fieldsModel || fields)?.map((el) => el.name);
|
|
@@ -92,8 +92,8 @@ export default async function getFilterSQL({
|
|
|
92
92
|
)?.filter(el => el.id || el.name)?.map(async (el) => {
|
|
93
93
|
el.name = el.name || el.id;
|
|
94
94
|
|
|
95
|
-
if (el.name &&
|
|
96
|
-
Object.assign(el, { extra: { table: extraDataTable } });
|
|
95
|
+
if (el.name && extraColumns.find(item => item?.name === el?.name)) {
|
|
96
|
+
Object.assign(el, { extra: { table: extraDataTable, input: extraColumns.find(item => item?.name === el?.name)?.type } });
|
|
97
97
|
}
|
|
98
98
|
if (!el?.data) return el;
|
|
99
99
|
const cls = await getTemplate(['cls', 'select'], el.data);
|
|
@@ -128,7 +128,7 @@ export default async function getFilterSQL({
|
|
|
128
128
|
|
|
129
129
|
// table
|
|
130
130
|
|
|
131
|
-
const obj = {
|
|
131
|
+
const obj = { body, extraSqlColumns, table, q };
|
|
132
132
|
const optimizedSQL = `select * from ${getOptimizedQuery(obj)} `;
|
|
133
133
|
const tableCount = getOptimizedQuery(obj, true);
|
|
134
134
|
|
|
@@ -139,7 +139,6 @@ export default async function getFilterSQL({
|
|
|
139
139
|
optimizedSQL,
|
|
140
140
|
sqlTable,
|
|
141
141
|
extraSqlColumns,
|
|
142
|
-
extraKeys,
|
|
143
142
|
tableCount,
|
|
144
143
|
table: modelQuery,
|
|
145
144
|
// filter parts
|
|
@@ -1,194 +1,110 @@
|
|
|
1
|
-
|
|
2
|
-
const numberTypeList = ['float8', 'int4', 'int8', 'numeric', 'double precision', 'integer'];
|
|
1
|
+
import getRangeQuery from "./getRangeQuery.js";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// iso date: 2024-01-01
|
|
7
|
-
if (dateStr?.indexOf('-') !== -1) {
|
|
8
|
-
const [yyyy, mm, dd] = dateStr.split('-');
|
|
9
|
-
return new Date(mm + '/' + dd + '/' + yyyy).toString() !== 'Invalid Date';
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// locale date: 01.01.2024
|
|
13
|
-
const [dd, mm, yyyy] = dateStr.split('.');
|
|
14
|
-
return new Date(mm + '/' + dd + '/' + yyyy).toString() !== 'Invalid Date';
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
function dt(y, m, d) {
|
|
18
|
-
return new Date(Date.UTC(y, m, d)).toISOString().slice(0, 10);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const dp = {
|
|
22
|
-
d: new Date().getDate(),
|
|
23
|
-
w: new Date().getDate() - (new Date().getDay() || 7) + 1,
|
|
24
|
-
m: new Date().getMonth(),
|
|
25
|
-
q: (new Date().getMonth() / 4).toFixed() * 3,
|
|
26
|
-
y: new Date().getFullYear(),
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
function formatDateISOString(date) {
|
|
30
|
-
if (!date?.includes('.')) return date;
|
|
31
|
-
const [day, month, year] = date.split('.');
|
|
32
|
-
return `${year}-${month}-${day}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function getDates(value) {
|
|
36
|
-
// date range, specific options: current day, week, month, year etc.
|
|
37
|
-
if (value === 'cd') {
|
|
38
|
-
return [dt(dp.y, dp.m, dp.d)];
|
|
39
|
-
}
|
|
40
|
-
if (value === 'cw') {
|
|
41
|
-
return [dt(dp.y, dp.m, dp.w), dt(dp.y, dp.m, dp.w + 6)];
|
|
42
|
-
}
|
|
43
|
-
if (value === 'cm') {
|
|
44
|
-
return [dt(dp.y, dp.m, 1), dt(dp.y, dp.m + 1, 0)];
|
|
45
|
-
}
|
|
46
|
-
if (value === 'cq') {
|
|
47
|
-
return [dt(dp.y, dp.q, 1), dt(dp.y, dp.q + 3, 0)];
|
|
48
|
-
}
|
|
49
|
-
if (value === 'cy') {
|
|
50
|
-
return [dt(dp.y, 0, 1), dt(dp.y, 11, 31)];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// date range, example: 01.01.2024-31.12.2024
|
|
54
|
-
const [startDate, endDate] = value.split('-');
|
|
55
|
-
const min = formatDateISOString(startDate);
|
|
56
|
-
const max = formatDateISOString(endDate);
|
|
57
|
-
return [min, max];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function formatValue({
|
|
61
|
-
pg, table, filter = {}, name, value, operator = '=', dataTypeID, uid = 1, optimize,
|
|
3
|
+
export default function formatValue({
|
|
4
|
+
pg, table, filter = {}, name, value, dataTypeID, uid = 1, optimize,
|
|
62
5
|
}) {
|
|
63
|
-
const { extra, sql, select } = filter;
|
|
6
|
+
const { extra, sql, select, strict } = filter;
|
|
64
7
|
const pk = pg?.pk && table ? pg.pk[table] : undefined;
|
|
65
8
|
|
|
66
|
-
if (!dataTypeID && !extra) return {};
|
|
67
|
-
|
|
68
9
|
const filterType = filter.type?.toLowerCase() || 'text';
|
|
69
|
-
const fieldType = pg
|
|
10
|
+
const fieldType = pg?.pgType?.[dataTypeID] || pg?.pgType?.[{ date: 1114 }[filterType] || 25];
|
|
70
11
|
|
|
71
|
-
if (!name
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// check date is valid
|
|
78
|
-
if (!isValidDate(min) || (!isValidDate(max) && value !== 'cd')) {
|
|
79
|
-
return { op: 'between', query: 'false' };
|
|
80
|
-
}
|
|
12
|
+
if (!name
|
|
13
|
+
|| !value
|
|
14
|
+
|| !dataTypeID && !extra && !sql || extra?.input?.toLowerCase?.() === 'datatable') {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
81
17
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
18
|
+
// my rows
|
|
19
|
+
if (value === 'me' && uid) {
|
|
20
|
+
return { op: '=', query: `${name}::text = '${uid}'` };
|
|
21
|
+
}
|
|
86
22
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
23
|
+
const matchNull = { null: 'is null', notnull: 'is not null' }[value];
|
|
24
|
+
// null = true is null, not false
|
|
25
|
+
const matchBoolean = fieldType === 'boolean' ? { true: 'is true', false: 'is false' }[value] : null;
|
|
26
|
+
const matchMulti = fieldType.includes('[]') ? `::text[] && '{${value}}'::text[]` : null;
|
|
27
|
+
const match = matchNull || matchBoolean || matchMulti || `::text=any('{${value}}'::text[])`;
|
|
91
28
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
29
|
+
// geometry
|
|
30
|
+
if (['geometry'].includes(fieldType)) {
|
|
31
|
+
if (matchNull) {
|
|
32
|
+
return { op: 'nullable', query: `${name} ${matchNull}` };
|
|
95
33
|
}
|
|
96
34
|
|
|
97
|
-
|
|
98
|
-
if (extra?.table && pk) {
|
|
99
|
-
const query = extra?.table && pk
|
|
100
|
-
? `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_date::date between '${min}'::date and '${max}'::date)`
|
|
101
|
-
: `${pk} in (select ${pk} from ${extra.table} where ${name}::date between '${min}'::date and '${max}'::date)`;
|
|
102
|
-
return { op: 'between', query };
|
|
103
|
-
}
|
|
35
|
+
const bbox = value[0].split('_');
|
|
104
36
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return { op: 'strict', query: `${name}::date = '${min}'::date` };
|
|
37
|
+
if (bbox?.length !== 4) {
|
|
38
|
+
return {};
|
|
108
39
|
}
|
|
109
40
|
|
|
110
|
-
|
|
111
|
-
return { op: '
|
|
41
|
+
const query = ` ${name} && 'box(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d `;
|
|
42
|
+
return { op: '&&', query };
|
|
112
43
|
}
|
|
113
44
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const [min, max] = value?.indexOf('_') !== -1 ? value.split('_') : value.split('-');
|
|
45
|
+
const querysql = sql?.replace?.(/= ?any\(\$1\)/g, `::text=any('{ ${value} }'::text[])`)?.replace?.(/\$1/g, `'${value}'`);
|
|
46
|
+
const op = filterType === 'text' && !strict ? '~' : '=';
|
|
117
47
|
|
|
48
|
+
// text
|
|
49
|
+
if (['text'].includes(filterType)) {
|
|
118
50
|
// with sql subquery
|
|
119
51
|
if (sql) {
|
|
120
|
-
return { op: '=', query:
|
|
52
|
+
return { op: '=', query: querysql };
|
|
121
53
|
}
|
|
122
54
|
|
|
123
|
-
|
|
124
|
-
|
|
55
|
+
// extra table, example: crm.extra_data
|
|
56
|
+
if (extra?.table && pk) {
|
|
57
|
+
const query = `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text ${matchNull || `${op} '${value}'`} )`;
|
|
58
|
+
return { op, query };
|
|
125
59
|
}
|
|
126
60
|
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
61
|
+
// select / cls
|
|
62
|
+
if (select) {
|
|
63
|
+
const subquery = matchNull ? `name ${matchNull}` : `lower(name::text) ~ lower('${value}')`;
|
|
64
|
+
return {
|
|
65
|
+
op,
|
|
66
|
+
query: `${name}::text in ( ( with q(id,name) as (${select}) select id from q where ${subquery}) )`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
131
69
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const query = sql.replace(/= ?any\(\$1\)/g, `::text=any('{ ${value} }'::text[])`);
|
|
137
|
-
return { op: '=', query };
|
|
70
|
+
// nullable
|
|
71
|
+
if (matchNull) {
|
|
72
|
+
const query = `${name} ${matchNull}`;
|
|
73
|
+
return { op, query };
|
|
138
74
|
}
|
|
139
75
|
|
|
140
76
|
// default
|
|
141
|
-
const query =
|
|
142
|
-
|
|
77
|
+
const query = `lower(${name}::text) ${op} lower('${value}')`;
|
|
78
|
+
|
|
79
|
+
return { op, query };
|
|
143
80
|
}
|
|
144
81
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
82
|
+
// date: 01.01.2024-31.12.2024 / number range: 100-500
|
|
83
|
+
if (['date', 'datepicker', 'range'].includes(filterType)) {
|
|
84
|
+
const query = getRangeQuery(value, name, fieldType, filterType, sql, extra, pk);
|
|
85
|
+
return { op: 'between', query };
|
|
86
|
+
}
|
|
149
87
|
|
|
150
88
|
/* select query - from admin.cls / filter options */
|
|
151
|
-
if (['check', 'autocomplete', 'text',
|
|
89
|
+
if (['check', 'autocomplete', 'text', 'tags', 'avatar', 'radio'].includes(filterType)) {
|
|
90
|
+
// with sql subquery
|
|
91
|
+
if (sql) {
|
|
92
|
+
return { op: '=', query: querysql };
|
|
93
|
+
}
|
|
152
94
|
|
|
153
95
|
// extra table, example: crm.extra_data
|
|
154
96
|
if (extra?.table && pk) {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
? `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text ${subquery} )`
|
|
158
|
-
: `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text ${match})`;
|
|
159
|
-
return { op: operator, query };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// default
|
|
163
|
-
const query = select && filterType === 'text'
|
|
164
|
-
? `${name}::text in ( ( with q(id,name) as (${select}) select id from q where name::text ${match}) )` // filter with cls
|
|
165
|
-
: `${name}::text ${match}`; // simple filter
|
|
166
|
-
return { op: operator, query };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// my rows
|
|
170
|
-
if (value === 'me' && uid) {
|
|
171
|
-
if (sql) {
|
|
172
|
-
return { op: '=', query: sql.replace(/\$1/g, `'${uid}'`) };
|
|
97
|
+
const query = `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text ${matchNull || `='${value}'`} )`;
|
|
98
|
+
return { op: '=', query };
|
|
173
99
|
}
|
|
174
100
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
: `${name}::text = '${uid}'`;
|
|
101
|
+
// nullable + default
|
|
102
|
+
const query = `${name} ${match}`;
|
|
178
103
|
return { op: '=', query };
|
|
179
104
|
}
|
|
180
105
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
int4: 'numeric',
|
|
184
|
-
int8: 'numeric',
|
|
185
|
-
varchar: 'text',
|
|
186
|
-
bool: 'boolean',
|
|
187
|
-
geometry: 'geom',
|
|
188
|
-
}[fieldType] || 'text';
|
|
189
|
-
|
|
190
|
-
// sql column?
|
|
191
|
-
if (optimize && optimize.name !== optimize.pk) {
|
|
106
|
+
// sql column
|
|
107
|
+
if (optimize && optimize.name !== optimize.pk && false) {
|
|
192
108
|
const val = filterType === 'text' ? `ilike '%${value}%'` : `= any('{${value}}')`;
|
|
193
109
|
return {
|
|
194
110
|
op: '~',
|
|
@@ -199,60 +115,5 @@ function formatValue({
|
|
|
199
115
|
};
|
|
200
116
|
}
|
|
201
117
|
|
|
202
|
-
// filter text array column
|
|
203
|
-
if (fieldType?.includes('[]')) {
|
|
204
|
-
return { op: 'in', query: `'{${value}}'::text[] && ${name}::text[]` };
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// filter multiple values, example: 1,2,3
|
|
208
|
-
if (value?.indexOf(',') !== -1) {
|
|
209
|
-
const values = value.split(',').filter((el) => el !== 'null');
|
|
210
|
-
|
|
211
|
-
// extra table, example: crm.extra_data
|
|
212
|
-
if (extra?.table && pk) {
|
|
213
|
-
const query = value?.indexOf('null') !== -1
|
|
214
|
-
? `${pk} in (select object_id from ${extra.table} where property_key='${name}' and ( value_text is null or value_text in (${values?.map((el) => `'"${el}"'`).join(',')}) ) )`
|
|
215
|
-
: `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text in (${values?.map((el) => `'"${el}"'`).join(',')}) )`;
|
|
216
|
-
return { op: 'in', query };
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// default
|
|
220
|
-
const query = value?.indexOf('null') !== -1
|
|
221
|
-
? `( ${name} is null or ${name}::text in (${values?.map((el) => `'${el}'`).join(',')}) )`
|
|
222
|
-
: `${name}::text in (${value.split(',')?.map((el) => `'${el}'`).join(',')})`;
|
|
223
|
-
return { op: 'in', query };
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (['<', '>'].includes(operator)) {
|
|
227
|
-
const query = `${name} ${operator} '${value}'::${formatType}`;
|
|
228
|
-
return { op: operator, query };
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (operator === '=' && filterType !== 'text' && !filter?.data) {
|
|
232
|
-
const query = {
|
|
233
|
-
null: `${name} is null`,
|
|
234
|
-
notnull: `${name} is not null`,
|
|
235
|
-
}[value] || `${name}::${formatType}='${value}'::${formatType}`;
|
|
236
|
-
return { op: '=', query };
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// json
|
|
240
|
-
if (name.includes('.')) {
|
|
241
|
-
const [col, prop] = name.split('.');
|
|
242
|
-
const query = ` ${col}->>'${prop}' in ('${value.join("','")}')`;
|
|
243
|
-
return { op: 'in', query };
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// geometry
|
|
247
|
-
if (['geometry'].includes(fieldType)) {
|
|
248
|
-
const bbox = value[0].split('_');
|
|
249
|
-
|
|
250
|
-
if (bbox?.length === 4) {
|
|
251
|
-
const query = ` ${name} && 'box(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d `;
|
|
252
|
-
return { op: '&&', query };
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
118
|
return {};
|
|
256
119
|
}
|
|
257
|
-
|
|
258
|
-
export default formatValue;
|
|
@@ -32,7 +32,6 @@ function getQuery({
|
|
|
32
32
|
// filter
|
|
33
33
|
const filter = filterList?.find?.((el) => el.type && el.name === name) || { type: 'text' };
|
|
34
34
|
const { strict, extra } = filter;
|
|
35
|
-
const operator = filter.strict || ['check', 'autocomplete'].includes(filter.type.toLowerCase()) ? '=' : (splitby !== '=' ? splitby : '~');
|
|
36
35
|
|
|
37
36
|
// find all value
|
|
38
37
|
const value = filterQueryArray.filter((el) => el.split(splitby)?.[0] === name).map((el) => el.substring(name.length + 1)).join(',');
|
|
@@ -40,7 +39,9 @@ function getQuery({
|
|
|
40
39
|
const optimize = fields?.find((el) => el.name === name) ? null : tableSQL.find((el) => el.name === name);
|
|
41
40
|
|
|
42
41
|
// find field and skip not exists
|
|
43
|
-
const { dataTypeID } = fields?.find((el) => el.name === name)
|
|
42
|
+
const { dataTypeID } = fields?.find((el) => el.name === name)
|
|
43
|
+
|| fields?.find((el) => el.name === optimize?.pk)
|
|
44
|
+
|| {};
|
|
44
45
|
|
|
45
46
|
// format query
|
|
46
47
|
const {
|
|
@@ -49,12 +50,11 @@ function getQuery({
|
|
|
49
50
|
pg,
|
|
50
51
|
table,
|
|
51
52
|
filter,
|
|
52
|
-
optimize,
|
|
53
53
|
name,
|
|
54
54
|
value,
|
|
55
|
-
operator,
|
|
56
55
|
dataTypeID,
|
|
57
56
|
uid,
|
|
57
|
+
optimize,
|
|
58
58
|
}) || {};
|
|
59
59
|
if (!query) continue;
|
|
60
60
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
export default function getOptimizedQuery({
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
export default function getOptimizedQuery({ body, extraSqlColumns, table, q }, count) {
|
|
2
|
+
const order = body?.orderby || body?.order ? `order by ${body?.orderby || body?.order}` : '';
|
|
3
|
+
|
|
4
|
+
const tableName = body?.table || body?.model || table;
|
|
5
|
+
|
|
6
|
+
const sqlList = body?.sql?.filter((el) => !el.disabled && el?.sql?.replace && (count ? el.count !== false : true))
|
|
7
|
+
.map((el) => ` left join lateral (${el.filter ? el.sql.replace(/limit 1/ig, '') : el.sql}) as ${el.name} on 1=1 `).join(' ');
|
|
8
|
+
|
|
9
|
+
return `(select * ${extraSqlColumns || ''} from ${tableName} t ${sqlList ? ` ${sqlList}` : ''} where 1=1 and ${q?.replace('q.', 't.') || '1=1'} ${order})q`;
|
|
6
10
|
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const dateTypeList = ['date', 'timestamp', 'timestamp without time zone', 'timestamp with time zone'];
|
|
2
|
+
const numberTypeList = ['float8', 'int4', 'int8', 'numeric', 'double precision', 'integer', 'bigint'];
|
|
3
|
+
|
|
4
|
+
const isValidDate = (dateStr) => {
|
|
5
|
+
if (!dateStr) return false;
|
|
6
|
+
if (['min', 'max'].includes(dateStr)) return true;
|
|
7
|
+
// iso date: 2024-01-01
|
|
8
|
+
if (dateStr?.indexOf('-') !== -1) {
|
|
9
|
+
const [yyyy, mm, dd] = dateStr.split('-');
|
|
10
|
+
return new Date(mm + '/' + dd + '/' + yyyy).toString() !== 'Invalid Date';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// locale date: 01.01.2024
|
|
14
|
+
const [dd, mm, yyyy] = dateStr.split('.');
|
|
15
|
+
return new Date(mm + '/' + dd + '/' + yyyy).toString() !== 'Invalid Date';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const isValidNumber = (numStr) => ['min', 'max'].includes(numStr) ? true : !isNaN(numStr);
|
|
19
|
+
|
|
20
|
+
function dt(y, m, d) {
|
|
21
|
+
return new Date(Date.UTC(y, m, d)).toISOString().slice(0, 10);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const dp = {
|
|
25
|
+
d: new Date().getDate(),
|
|
26
|
+
w: new Date().getDate() - (new Date().getDay() || 7) + 1,
|
|
27
|
+
m: new Date().getMonth(),
|
|
28
|
+
q: (new Date().getMonth() / 4).toFixed() * 3,
|
|
29
|
+
y: new Date().getFullYear(),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function formatDateISOString(date) {
|
|
33
|
+
if (!date?.includes('.')) return date;
|
|
34
|
+
const [day, month, year] = date.split('.');
|
|
35
|
+
return `${year}-${month}-${day}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getRangeValues(value = '', filterType = 'range') {
|
|
39
|
+
if (['range'].includes(filterType)) {
|
|
40
|
+
return value?.indexOf('_') !== -1
|
|
41
|
+
? value.split('_')
|
|
42
|
+
: value.split('-');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// date range, specific options: current day, week, month, year etc.
|
|
46
|
+
if (value === 'cd') {
|
|
47
|
+
return [dt(dp.y, dp.m, dp.d)];
|
|
48
|
+
}
|
|
49
|
+
if (value === 'cw') {
|
|
50
|
+
return [dt(dp.y, dp.m, dp.w), dt(dp.y, dp.m, dp.w + 6)];
|
|
51
|
+
}
|
|
52
|
+
if (value === 'cm') {
|
|
53
|
+
return [dt(dp.y, dp.m, 1), dt(dp.y, dp.m + 1, 0)];
|
|
54
|
+
}
|
|
55
|
+
if (value === 'cq') {
|
|
56
|
+
return [dt(dp.y, dp.q, 1), dt(dp.y, dp.q + 3, 0)];
|
|
57
|
+
}
|
|
58
|
+
if (value === 'cy') {
|
|
59
|
+
return [dt(dp.y, 0, 1), dt(dp.y, 11, 31)];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// date range, example: 01.01.2024-31.12.2024
|
|
63
|
+
const [startDate, endDate] = value.split('-');
|
|
64
|
+
const min = startDate === 'min' ? startDate : formatDateISOString(startDate);
|
|
65
|
+
const max = endDate === 'max' ? endDate : formatDateISOString(endDate);
|
|
66
|
+
return [min, max];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function checkValid(value, fieldType, filterType, sql, extra) {
|
|
70
|
+
const [min, max] = getRangeValues(value, filterType);
|
|
71
|
+
|
|
72
|
+
if (['date', 'datepicker'].includes(filterType)) {
|
|
73
|
+
const isvalid = (dateTypeList.includes(fieldType) || sql || extra) && (isValidDate(min) && (isValidDate(max) || value === 'cd'));
|
|
74
|
+
return { min, max, isvalid };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (['range'].includes(filterType)) {
|
|
78
|
+
const isvalid = (numberTypeList.includes(fieldType) || sql || extra) && isValidNumber(min) && isValidNumber(max);
|
|
79
|
+
return { min, max, isvalid };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { isvalid: false };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default function getRangeQuery(value, name, fieldType, filterType, sql, extra, pk) {
|
|
86
|
+
const { min, max, isvalid } = checkValid(value, fieldType, filterType, sql, extra);
|
|
87
|
+
if (!isvalid) return 'false';
|
|
88
|
+
|
|
89
|
+
// with sql subquery
|
|
90
|
+
if (['date', 'datepicker'].includes(filterType) && sql) {
|
|
91
|
+
return sql.replace(/\$1/g, `'${min}'::date`).replace(/\$2/g, `'${max}'::date`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// with sql subquery
|
|
95
|
+
if (['range'].includes(filterType) && sql) {
|
|
96
|
+
return sql.replace(/\$1/g, min).replace(/\$2/g, max);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// preset date values
|
|
100
|
+
if (['date', 'datepicker'].includes(filterType) && ['cd', 'cw', 'cm', 'cq', 'cy'].includes(value)) {
|
|
101
|
+
return value === 'cd'
|
|
102
|
+
? `${name}::date = '${min}'::date`
|
|
103
|
+
: `${name}::date between '${min}'::date and '${max}'::date`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// extra columns, example: crm.extra_data
|
|
107
|
+
if (['date', 'datepicker'].includes(filterType) && extra?.table && pk) {
|
|
108
|
+
const subquery = (max && min && min === max ? `value_date = '${value}'::date` : null)
|
|
109
|
+
|| (max === 'max' ? `value_date::date > '${min}'::date` : null)
|
|
110
|
+
|| (min === 'min' ? `value_date::date < '${max}'::date` : null)
|
|
111
|
+
|| `value_date::date between '${min}'::date and '${max}'::date`;
|
|
112
|
+
const query = `${pk} in (select object_id from ${extra.table} where property_key='${name}' and ${subquery})`;
|
|
113
|
+
return query;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// extra columns, example: crm.extra_data
|
|
117
|
+
if (['range'].includes(filterType) && extra?.table && pk) {
|
|
118
|
+
const subquery = (max && min && min === max ? `value_text = '${value}'` : null)
|
|
119
|
+
|| (max === 'max' ? `value_text::numeric > ${min}` : null)
|
|
120
|
+
|| (min === 'min' ? `value_text::numeric < ${max}` : null)
|
|
121
|
+
|| `value_text::numeric between ${min} and ${max}`;
|
|
122
|
+
const query = `${pk} in (select object_id from ${extra.table} where property_key='${name}' and ${subquery} )`;
|
|
123
|
+
return query;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// same date
|
|
127
|
+
if (['date', 'datepicker'].includes(filterType) && max && min && min === max) {
|
|
128
|
+
return `${name}::date = '${min}'::date`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// skip, same as 1=1
|
|
132
|
+
if (['range'].includes(filterType) && min === 'min' && max === 'max') {
|
|
133
|
+
return 'true';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// default
|
|
137
|
+
if (max === 'max') {
|
|
138
|
+
return ['date', 'datepicker'].includes(filterType)
|
|
139
|
+
? `${name} > '${min}'::date`
|
|
140
|
+
: `${name} > ${min}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (min === 'min') {
|
|
144
|
+
return ['date', 'datepicker'].includes(filterType)
|
|
145
|
+
? `${name} < '${max}'::date`
|
|
146
|
+
: `${name} < ${max}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (['date', 'datepicker'].includes(filterType)) {
|
|
150
|
+
return `${name}::date between '${min}'::date and '${max}'::date`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return `${name} between ${min} and ${max}`;
|
|
154
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { logger, autoIndex, getSelect,
|
|
1
|
+
import { logger, autoIndex, getSelect, getTemplate, getSelectVal, pgClients } from '../../../../utils.js';
|
|
2
2
|
|
|
3
3
|
export default async function filterAPI(req) {
|
|
4
4
|
const time = Date.now();
|
|
5
5
|
|
|
6
6
|
const {
|
|
7
|
-
params,
|
|
7
|
+
params, pg = pgClients.client, user = {},
|
|
8
8
|
} = req;
|
|
9
9
|
|
|
10
10
|
const loadTable = await getTemplate('table', params.table);
|
|
@@ -14,20 +14,7 @@ export default async function filterAPI(req) {
|
|
|
14
14
|
const { fields: columns = [] } = await pg.query(`select * from ${loadTable.table} t ${sqlTable} limit 0`);
|
|
15
15
|
const { fields = [] } = await pg.query(`select * from ${loadTable.table} t limit 0`);
|
|
16
16
|
|
|
17
|
-
const {
|
|
18
|
-
filter, custom, state, search,
|
|
19
|
-
} = query;
|
|
20
|
-
|
|
21
|
-
// const { extra } = loadTable?.form ? await getTemplate('form', loadTable?.form) || {} : {};
|
|
22
|
-
|
|
23
|
-
const { optimizedSQL = `select * from ${loadTable.table}` } = false ? await getFilterSQL({
|
|
24
|
-
pg,
|
|
25
|
-
table: params.table,
|
|
26
|
-
filter,
|
|
27
|
-
custom,
|
|
28
|
-
state,
|
|
29
|
-
search,
|
|
30
|
-
}) : {};
|
|
17
|
+
const optimizedSQL = `select * from ${loadTable.table} t ${sqlTable}`;
|
|
31
18
|
|
|
32
19
|
const filters = (loadTable?.filter_list || loadTable?.filters || loadTable?.filterList || []).concat(loadTable?.filterSql || []);
|
|
33
20
|
|
|
@@ -42,7 +29,10 @@ export default async function filterAPI(req) {
|
|
|
42
29
|
logger.file('autoindex/error', { name: params?.table, error: err.toString(), stack: err.stack });
|
|
43
30
|
});
|
|
44
31
|
|
|
45
|
-
|
|
32
|
+
const loadTemplate = loadTable?.form ? await getTemplate('form', loadTable.form) : {};
|
|
33
|
+
const extraColumns = loadTemplate?.extra ? Object.keys(loadTemplate?.schema || {}).filter(key => !columns.find(col => col.name === key)) : [];
|
|
34
|
+
|
|
35
|
+
filters?.forEach?.(el => Object.assign(el, { id: el.id || el.name, extra: extraColumns.includes(el.id || el.name) }));
|
|
46
36
|
|
|
47
37
|
await Promise.all(filters.filter((el) => el.data && el.id && el.type !== 'Autocomplete').map(async (el) => {
|
|
48
38
|
const cls = await getSelect(el.data, pg);
|
|
@@ -50,13 +40,14 @@ export default async function filterAPI(req) {
|
|
|
50
40
|
if (!cls || !loadTable.table) return;
|
|
51
41
|
const { dataTypeID } = columns.find((item) => item.name === el.id) || {};
|
|
52
42
|
|
|
53
|
-
if (el.extra &&
|
|
54
|
-
const countArr = await pg.query(`select value_text as id, count(*) from crm.extra_data where property_key=$1 and property_entity=$2 group by value_text`, [el.id,
|
|
43
|
+
if (el.extra && Array.isArray(cls?.arr || cls)) {
|
|
44
|
+
const countArr = await pg.query(`select value_text as id, count(*) from crm.extra_data where property_key=$1 and property_entity=$2 group by value_text`, [el.id, loadTable.table]);
|
|
55
45
|
const options = countArr.rows.map(cel => {
|
|
56
|
-
const data = cls.find(c => c.id === cel.id);
|
|
46
|
+
const data = (cls?.arr || cls).find(c => c.id === cel.id);
|
|
57
47
|
return { ...cel, ...data };
|
|
58
48
|
});
|
|
59
49
|
Object.assign(el, { options });
|
|
50
|
+
return;
|
|
60
51
|
}
|
|
61
52
|
|
|
62
53
|
const q = pg.pgType[dataTypeID]?.includes('[]')
|
|
@@ -64,6 +55,7 @@ export default async function filterAPI(req) {
|
|
|
64
55
|
: `select ${el.id}::text as id,count(*) from (${optimizedSQL})q group by ${el.id} limit 100`;
|
|
65
56
|
|
|
66
57
|
const countArr = await pg.queryCache(q, { table: loadTable.table });
|
|
58
|
+
|
|
67
59
|
if (countArr.timeout) {
|
|
68
60
|
Object.assign(el, { timeout: countArr.timeout });
|
|
69
61
|
console.log('timeout filter', params.table, el.id);
|