@opengis/fastify-table 1.2.68 → 1.2.70
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/crud/funcs/validateData.js +3 -3
- package/server/plugins/pg/funcs/autoIndex.js +2 -2
- package/server/plugins/pg/funcs/init.js +2 -1
- package/server/plugins/table/funcs/getData.js +3 -3
- package/server/plugins/table/funcs/getFilterSQL/index.js +69 -50
- package/server/plugins/table/funcs/getFilterSQL/util/formatValue.js +143 -119
- package/server/plugins/table/funcs/getFilterSQL/util/getFilterQuery.js +9 -6
- package/server/plugins/table/funcs/getFilterSQL/util/getOptimizedQuery.js +5 -11
- package/server/plugins/table/funcs/metaFormat/getSelectVal.js +3 -1
- package/server/routes/crud/controllers/table.js +6 -2
- package/server/routes/table/controllers/data.js +28 -16
- package/server/routes/table/controllers/filter.js +21 -4
package/package.json
CHANGED
|
@@ -34,7 +34,7 @@ function checkBody({ body = {}, arr = [], idx }) {
|
|
|
34
34
|
const input = arr.find(el => el.key === key) || {};
|
|
35
35
|
|
|
36
36
|
if (input.colModel?.length && input.type?.toLowerCase() === 'datatable') {
|
|
37
|
-
const result = body[key]
|
|
37
|
+
const result = body[key]?.reduce?.((acc, item, i) => {
|
|
38
38
|
const check = checkBody({ body: item, arr: input.colModel, idx: i });
|
|
39
39
|
acc.push(check);
|
|
40
40
|
return acc;
|
|
@@ -48,10 +48,10 @@ function checkBody({ body = {}, arr = [], idx }) {
|
|
|
48
48
|
return acc1;
|
|
49
49
|
}, []);
|
|
50
50
|
|
|
51
|
-
const invalidField = res.find(el => el
|
|
51
|
+
const invalidField = res.find(el => el?.error);
|
|
52
52
|
|
|
53
53
|
if (invalidField) {
|
|
54
|
-
console.warn('invalid field: ', invalidField
|
|
54
|
+
console.warn('invalid field: ', invalidField?.key, invalidField?.error);
|
|
55
55
|
return invalidField;
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -16,8 +16,8 @@ async function autoIndex({
|
|
|
16
16
|
|
|
17
17
|
if (!filter?.length || !table) return null;
|
|
18
18
|
|
|
19
|
-
const attrs = filter.map((el) => (el.name || el).replace(/'/g, '')).sort();
|
|
20
|
-
const types = filter.map((el) => (el.name ? el : { name: el }))
|
|
19
|
+
const attrs = filter.map((el) => (el.name || el).replace(/'/g, '')).filter(el => el?.indexOf('(') === -1).sort();
|
|
20
|
+
const types = filter.filter(el => (el?.name || el)?.indexOf?.('(') === -1).map((el) => (el.name ? el : { name: el }))
|
|
21
21
|
.reduce((p, el) => ({ ...p, [el.name]: el.type || '-' }), {});
|
|
22
22
|
|
|
23
23
|
const redisKey = `autoindex1:${table}:${attrs.join(';')}`;
|
|
@@ -70,7 +70,7 @@ async function init(client) {
|
|
|
70
70
|
|
|
71
71
|
async function queryCache(q, param = {}) {
|
|
72
72
|
const { table, args = [], time = 15 } = param;
|
|
73
|
-
const seconds = typeof time !== 'number' || time < 0 ?
|
|
73
|
+
const seconds = typeof time !== 'number' || time < 0 ? 0 : time * 60;
|
|
74
74
|
|
|
75
75
|
// CRUD table state
|
|
76
76
|
const keyCacheTable = `pg:${table}:crud`;
|
|
@@ -92,6 +92,7 @@ async function init(client) {
|
|
|
92
92
|
if (seconds > 0) {
|
|
93
93
|
rclient.set(keyCache, JSON.stringify(data), 'EX', seconds);
|
|
94
94
|
}
|
|
95
|
+
|
|
95
96
|
// console.log('no cache', table, crudInc, query);
|
|
96
97
|
return data;
|
|
97
98
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import routeData from '../../../routes/table/controllers/data.js';
|
|
2
2
|
|
|
3
|
-
export default async function getData({ id, table, pg, filter, limit, page, search, user }) {
|
|
3
|
+
export default async function getData({ id, table, pg, filter, limit, page, search, user, sql }, reply, called) {
|
|
4
4
|
const params = { table, id };
|
|
5
|
-
const query = { filter, limit, page, search };
|
|
6
|
-
const result = await routeData({ pg, params, query, user });
|
|
5
|
+
const query = { filter, limit, page, search, sql };
|
|
6
|
+
const result = await routeData({ pg, params, query, user, sql }, reply, called);
|
|
7
7
|
return result;
|
|
8
8
|
}
|
|
@@ -7,24 +7,20 @@ import getTableSql from './util/getTableSql.js';
|
|
|
7
7
|
import getFilterQuery from './util/getFilterQuery.js';
|
|
8
8
|
import getOptimizedQuery from './util/getOptimizedQuery.js';
|
|
9
9
|
|
|
10
|
+
const checkInline = {};
|
|
10
11
|
const defaultTable = 'crm.extra_data';
|
|
11
12
|
|
|
12
|
-
function getExtraQuery(mainColumns,
|
|
13
|
-
const extraKeys =
|
|
14
|
-
? Object.keys(schema || {}).filter(key => !mainColumns.map(el => el?.name).includes(key) && extraColumns.map(el => el?.name).includes(key))
|
|
15
|
-
: Object.keys(schema || {}).filter(key => !mainColumns.map(el => el?.name).includes(key));
|
|
13
|
+
function getExtraQuery(mainColumns, schema, table, pk) {
|
|
14
|
+
const extraKeys = Object.keys(schema || {}).filter(key => !mainColumns.map(el => el?.name).includes(key));
|
|
16
15
|
|
|
17
|
-
if (mode === 'column') {
|
|
18
|
-
return { q: `left join lateral (select ${extraKeys.map(key => `"${key}"`).join(',')} from ${table} where ${pk}=t.${pk} limit 1) extra on 1=1`, extraKeys };
|
|
19
|
-
}
|
|
20
16
|
return {
|
|
21
|
-
q: extraKeys.map((el
|
|
17
|
+
q: extraKeys.map((el) => `,(select value_text as "${el.replace(/'/g, "''")}" from ${table} where property_key='${el.replace(/'/g, "''")}' and object_id=t.${pk} limit 1)`).join(' '),
|
|
22
18
|
extraKeys,
|
|
23
19
|
};
|
|
24
20
|
}
|
|
25
21
|
|
|
26
22
|
export default async function getFilterSQL({
|
|
27
|
-
table, filter, pg = pgClients.client, search, filterList, query, custom, state,
|
|
23
|
+
table, filter, pg = pgClients.client, search, filterList, query, custom, state, uid,
|
|
28
24
|
}) {
|
|
29
25
|
if (!table) return { error: 'param table is required', status: 400 };
|
|
30
26
|
|
|
@@ -40,32 +36,46 @@ export default async function getFilterSQL({
|
|
|
40
36
|
|| defaultTable
|
|
41
37
|
: undefined;
|
|
42
38
|
|
|
43
|
-
const { fields:
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const { fields: fieldsModel = [] } = body?.table && pg.pk[body?.table] ? await pg.query(`select * from ${body.table} limit 0`) : {};
|
|
47
|
-
const { q: extraSqlList, extraKeys } = pg.pk?.[extraDataTable] && pg.pk?.[body?.table]
|
|
48
|
-
? getExtraQuery(fieldsModel, fieldsExtra, loadTemplate?.schema, extraDataTable, pg.pk[body?.table || table], mode)
|
|
39
|
+
const { fields: fieldsModel = [] } = body?.table && pg.pk[body.table] ? await pg.query(`select * from ${body.table} limit 0`) : {};
|
|
40
|
+
const { q: extraSqlColumns, extraKeys } = pg.pk?.[extraDataTable] && pg.pk?.[body.table || table]
|
|
41
|
+
? getExtraQuery(fieldsModel, loadTemplate?.schema, extraDataTable, pg.pk[body.table || table])
|
|
49
42
|
: {};
|
|
50
43
|
|
|
51
44
|
// console.log('extra getFilterSQL', extraDataTable, pg.pk?.[extraDataTable]);
|
|
52
45
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
46
|
+
// check sql inline fields count
|
|
47
|
+
if (!checkInline[body?.table] && body?.sql?.length && body.table) {
|
|
48
|
+
const filterSql = body.sql.filter(el => !el?.disabled && (el.inline ?? true));
|
|
49
|
+
const sqlTable = filterSql.map((el, i) => ` left join lateral (${el.sql}) ${el.name || `t${i}`} on 1=1 `)?.join('') || '';
|
|
50
|
+
const d = await Promise.all(filterSql.map((el, i) => pg.query(`select ${el.name || `t${i}`}.* from(select * from ${body.table})t ${sqlTable} limit 0`).then(el => el.fields)))
|
|
51
|
+
d.forEach((el, i) => {
|
|
52
|
+
filterSql[i].inline = el.length == 1 ? true : false;
|
|
53
|
+
filterSql[i].fields = el.map(f => f.name);
|
|
54
|
+
});
|
|
55
|
+
checkInline[body?.table] = body.sql;
|
|
56
|
+
} else if (checkInline[body?.table]) {
|
|
57
|
+
body.sql = checkInline[body?.table]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const sqlTable = body?.sql?.length
|
|
61
|
+
? body.sql
|
|
62
|
+
.filter((el) => !el?.disabled && !el.inline && el?.sql?.replace && (!el.sql.includes('{{uid}}') || uid))
|
|
63
|
+
.map((el, i) => ` left join lateral (${el.sql.replace('{{uid}}', uid)}) ${el.name || `t${i}`} on 1=1 `)
|
|
64
|
+
.join('')
|
|
65
|
+
: null;
|
|
66
|
+
const sqlInline = body.sql?.filter?.(el => el.inline).map(el => `,(${el.sql})`).join('');
|
|
67
|
+
|
|
68
|
+
const fieldQuery = `select * ${extraSqlColumns || ''} ${sqlInline || ''} from ${body?.table || table} t ${sqlTable || ''} limit 0`;
|
|
69
|
+
const { fields = [] } = !extra ? await pg.query(fieldQuery) : { fields: extraKeys.map(el => ({ name: el.name, dataTypeID: 25 })) };
|
|
62
70
|
const autoSearchColumn = fields?.filter((el) => pg.pgType?.[el.dataTypeID] === 'text')?.map((el) => el.name).join(',');
|
|
63
71
|
const searchColumn = body?.search_column || body?.meta?.search || autoSearchColumn;
|
|
64
72
|
const fieldsList = (fieldsModel || fields)?.map((el) => el.name);
|
|
73
|
+
|
|
65
74
|
try {
|
|
66
75
|
const tableSQL = await getTableSql({
|
|
67
76
|
pg, body, table, fields,
|
|
68
77
|
});
|
|
78
|
+
|
|
69
79
|
const sval = `ilike '%${decodeURIComponent(search?.replace(/%/g, '%25')).replace(/'/g, "''").replace(/%/g, '\\%')}%'`;
|
|
70
80
|
const searchQuery = search && searchColumn
|
|
71
81
|
? ` (${searchColumn.split(',')?.map((name) => {
|
|
@@ -73,33 +83,40 @@ export default async function getFilterSQL({
|
|
|
73
83
|
return pk && !fieldsList.includes(name) ? `${pk} in (select ${pk} from (${fieldQuery.replace(/limit 0/g, '')} where ${name} ${sval} )q where 1=1)` : `${name} ${sval}`;
|
|
74
84
|
}).join(' or ')} )` : '';
|
|
75
85
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
.concat(body?.
|
|
80
|
-
|
|
81
|
-
?.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
const filterList1 = await Promise.all((filterList || (body?.filter_list || [])
|
|
87
|
+
.concat(body?.filterInline || [])
|
|
88
|
+
.concat(body?.filterCustom || [])
|
|
89
|
+
.concat(body?.filterState || [])
|
|
90
|
+
.concat(body?.filterList || [])
|
|
91
|
+
.concat(body?.filters || [])
|
|
92
|
+
)?.filter(el => el.id || el.name)?.map(async (el) => {
|
|
93
|
+
el.name = el.name || el.id;
|
|
94
|
+
|
|
95
|
+
if (el.name && extraKeys?.includes?.(el.name)) {
|
|
96
|
+
Object.assign(el, { extra: { table: extraDataTable } });
|
|
97
|
+
}
|
|
98
|
+
if (!el?.data) return el;
|
|
99
|
+
const cls = await getTemplate(['cls', 'select'], el.data);
|
|
100
|
+
|
|
101
|
+
if (Array.isArray(cls) && cls?.length) {
|
|
102
|
+
Object.assign(el, { cls: el.data, options: cls, select: `select code, name from admin.cls where parent='${el.data}'` });
|
|
103
|
+
}
|
|
104
|
+
else if (typeof (cls?.sql || cls) === 'string') {
|
|
105
|
+
Object.assign(el, { select: cls?.sql || cls });
|
|
106
|
+
}
|
|
107
|
+
return el;
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
const modelQuery = body?.model || body?.table || table;
|
|
95
111
|
|
|
96
112
|
const filters = getFilterQuery({
|
|
97
113
|
pg,
|
|
98
114
|
filter,
|
|
99
|
-
table:
|
|
115
|
+
table: modelQuery,
|
|
100
116
|
tableSQL,
|
|
101
117
|
fields,
|
|
102
118
|
filterList: filterList1,
|
|
119
|
+
uid,
|
|
103
120
|
});
|
|
104
121
|
|
|
105
122
|
// filter
|
|
@@ -110,16 +127,18 @@ export default async function getFilterSQL({
|
|
|
110
127
|
const q = [body?.query, query, searchQuery, filterQuery, stateQuery, customQuery].filter((el) => el).join(' and ');
|
|
111
128
|
|
|
112
129
|
// table
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
|
|
130
|
+
|
|
131
|
+
const obj = { table: modelQuery, orderby: body?.order || body?.orderby, sqlTable, sqlInline, extraSqlColumns, q };
|
|
132
|
+
const optimizedSQL = `select * from ${getOptimizedQuery(obj)} `;
|
|
133
|
+
const tableCount = getOptimizedQuery(obj, true);
|
|
134
|
+
|
|
117
135
|
return {
|
|
118
136
|
filterList,
|
|
119
|
-
|
|
137
|
+
filters,
|
|
120
138
|
q,
|
|
121
139
|
optimizedSQL,
|
|
122
|
-
|
|
140
|
+
sqlTable,
|
|
141
|
+
extraSqlColumns,
|
|
123
142
|
extraKeys,
|
|
124
143
|
tableCount,
|
|
125
144
|
table: modelQuery,
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
const dateTypeList = ['date', 'timestamp', 'timestamp without time zone', 'timestamp with time zone'];
|
|
2
2
|
const numberTypeList = ['float8', 'int4', 'int8', 'numeric', 'double precision', 'integer'];
|
|
3
|
+
|
|
3
4
|
const isValidDate = (dateStr) => {
|
|
5
|
+
if (!dateStr) return false;
|
|
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
|
|
4
13
|
const [dd, mm, yyyy] = dateStr.split('.');
|
|
5
14
|
return new Date(mm + '/' + dd + '/' + yyyy).toString() !== 'Invalid Date';
|
|
6
15
|
};
|
|
@@ -8,6 +17,7 @@ const isValidDate = (dateStr) => {
|
|
|
8
17
|
function dt(y, m, d) {
|
|
9
18
|
return new Date(Date.UTC(y, m, d)).toISOString().slice(0, 10);
|
|
10
19
|
}
|
|
20
|
+
|
|
11
21
|
const dp = {
|
|
12
22
|
d: new Date().getDate(),
|
|
13
23
|
w: new Date().getDate() - (new Date().getDay() || 7) + 1,
|
|
@@ -22,96 +32,150 @@ function formatDateISOString(date) {
|
|
|
22
32
|
return `${year}-${month}-${day}`;
|
|
23
33
|
}
|
|
24
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
|
+
|
|
25
60
|
function formatValue({
|
|
26
61
|
pg, table, filter = {}, name, value, operator = '=', dataTypeID, uid = 1, optimize,
|
|
27
62
|
}) {
|
|
28
|
-
const {
|
|
63
|
+
const { extra, sql, select } = filter;
|
|
29
64
|
const pk = pg?.pk && table ? pg.pk[table] : undefined;
|
|
30
65
|
|
|
31
66
|
if (!dataTypeID && !extra) return {};
|
|
32
|
-
|
|
33
|
-
const
|
|
67
|
+
|
|
68
|
+
const filterType = filter.type?.toLowerCase() || 'text';
|
|
69
|
+
const fieldType = pg.pgType?.[dataTypeID] || pg.pgType?.[{ date: 1114 }[filterType] || 25];
|
|
34
70
|
|
|
35
71
|
if (!name || !value || !fieldType) return {};
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const query = sql.replace(/\$1/g, `'${min}'::date`).replace(/\$2/g, `'${max}'::date`);
|
|
51
|
-
return { op: '=', query, extra, sql: true };
|
|
72
|
+
|
|
73
|
+
// Date filter
|
|
74
|
+
if (filterType === 'date' /* && dateTypeList.includes(fieldType) */) {
|
|
75
|
+
const [min, max] = getDates(value, name);
|
|
76
|
+
|
|
77
|
+
// check date is valid
|
|
78
|
+
if (!isValidDate(min) || (!isValidDate(max) && value !== 'cd')) {
|
|
79
|
+
return { op: 'between', query: 'false' };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// with sql subquery
|
|
83
|
+
if (sql) {
|
|
84
|
+
return { op: '=', query: sql.replace(/\$1/g, `'${min}'::date`).replace(/\$2/g, `'${max}'::date`) };
|
|
52
85
|
}
|
|
53
86
|
|
|
54
|
-
//
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
const query = sql.replace(/\$1/g, min).replace(/\$2/g, max);
|
|
58
|
-
return { op: 'between', query, extra, sql: true };
|
|
87
|
+
// preset values
|
|
88
|
+
if (['cw', 'cm', 'cq', 'cy'].includes(value)) {
|
|
89
|
+
return { op: 'between', query: `${name}::date between '${min}'::date and '${max}'::date` };
|
|
59
90
|
}
|
|
60
91
|
|
|
92
|
+
// preset values: current day
|
|
93
|
+
if (value === 'cd') {
|
|
94
|
+
return { op: '=', query: `${name}::date = '${min}'::date` };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// extra columns, example: crm.extra_data
|
|
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
|
+
}
|
|
104
|
+
|
|
105
|
+
// same date
|
|
106
|
+
if (max && min && min === max) {
|
|
107
|
+
return { op: 'strict', query: `${name}::date = '${min}'::date` };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// default
|
|
111
|
+
return { op: 'between', query: `${name}::date between '${min}'::date and '${max}'::date` };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Range filter, example: 100-500
|
|
115
|
+
if (filterType === 'range' /* && numberTypeList.includes(fieldType) */) {
|
|
116
|
+
const [min, max] = value?.indexOf('_') !== -1 ? value.split('_') : value.split('-');
|
|
117
|
+
|
|
118
|
+
// with sql subquery
|
|
119
|
+
if (sql) {
|
|
120
|
+
return { op: '=', query: sql.replace(/\$1/g, min).replace(/\$2/g, max) };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (min === 'min' && max === 'max') {
|
|
124
|
+
return { op: 'between', query: 'false' };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// default
|
|
128
|
+
const query = (max === 'max' ? `${name} > ${min}` : null) || (min === 'min' ? `${name} < ${max}` : null) || `${name} between ${min} and ${max}`;
|
|
129
|
+
return { op: 'between', query };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Autocomplete, Check, Text filter with sql subquery
|
|
133
|
+
if (sql) {
|
|
61
134
|
// autocomplete, check
|
|
62
135
|
if (sql?.indexOf('any($1)') !== -1) {
|
|
63
136
|
const query = sql.replace(/= ?any\(\$1\)/g, `::text=any('{ ${value} }'::text[])`);
|
|
64
|
-
return { op: '=', query
|
|
137
|
+
return { op: '=', query };
|
|
65
138
|
}
|
|
66
139
|
|
|
67
|
-
//
|
|
140
|
+
// default
|
|
68
141
|
const query = sql.replace(/\$1/g, `'${value}'`);
|
|
69
|
-
return { op: '=', query
|
|
142
|
+
return { op: '=', query };
|
|
70
143
|
}
|
|
71
144
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return { op: '=', query, extra };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// date range
|
|
85
|
-
if (dateTypeList.includes(fieldType) && value?.includes('_')) {
|
|
86
|
-
const [min, max] = value.split('_');
|
|
87
|
-
const query = `${name} >= '${min}'::date and ${name} <= '${max}'::date`;
|
|
88
|
-
return { op: 'between', query, extra };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// v3 filter date range, example - "01.01.2024-31.12.2024"
|
|
92
|
-
if (dateTypeList.includes(fieldType) && value?.includes('.') && value?.indexOf('-') === 10 && value?.length === 21) {
|
|
93
|
-
const [startDate, endDate] = value.split('-');
|
|
94
|
-
const min = formatDateISOString(startDate);
|
|
95
|
-
const max = formatDateISOString(endDate);
|
|
96
|
-
|
|
97
|
-
if (!isValidDate(startDate) || !isValidDate(endDate)) {
|
|
98
|
-
return { op: 'between', query: 'false', extra };
|
|
99
|
-
}
|
|
145
|
+
const matchNull = { null: 'is null', notnull: 'is not null' }[value];
|
|
146
|
+
const matchBoolean = fieldType === 'boolean' ? { true: 'is true', false: 'is false' }[value] : null;
|
|
147
|
+
const matchMulti = value?.indexOf(',') !== -1 ? `= any('{${value}}'::text[])` : null;
|
|
148
|
+
const match = matchNull || matchBoolean || matchMulti || (operator === '=' ? `='${value}'` : `ilike '%${value}%'`);
|
|
149
|
+
|
|
150
|
+
/* select query - from admin.cls / filter options */
|
|
151
|
+
if (['check', 'autocomplete', 'text', 'tags', 'avatar', 'radio'].includes(filterType)) {
|
|
152
|
+
|
|
153
|
+
// extra table, example: crm.extra_data
|
|
100
154
|
if (extra?.table && pk) {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
155
|
+
const subquery = filterType === 'autocomplete' ? match : `in ( ( with q(id,name) as (${select}) select id from q where name::text ${match}))`;
|
|
156
|
+
const query = select
|
|
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 };
|
|
105
160
|
}
|
|
106
|
-
|
|
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 };
|
|
107
167
|
}
|
|
108
168
|
|
|
109
169
|
// my rows
|
|
110
|
-
if (value === 'me' && uid
|
|
111
|
-
|
|
170
|
+
if (value === 'me' && uid) {
|
|
171
|
+
if (sql) {
|
|
172
|
+
return { op: '=', query: sql.replace(/\$1/g, `'${uid}'`) };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const query = extra?.table && pk && fieldType === 'text'
|
|
112
176
|
? `${pk} in (select ${pk} from ${extra.table} where uid = '${uid}')`
|
|
113
177
|
: `${name}::text = '${uid}'`;
|
|
114
|
-
return { op: '=', query
|
|
178
|
+
return { op: '=', query };
|
|
115
179
|
}
|
|
116
180
|
|
|
117
181
|
const formatType = {
|
|
@@ -123,6 +187,7 @@ function formatValue({
|
|
|
123
187
|
geometry: 'geom',
|
|
124
188
|
}[fieldType] || 'text';
|
|
125
189
|
|
|
190
|
+
// sql column?
|
|
126
191
|
if (optimize && optimize.name !== optimize.pk) {
|
|
127
192
|
const val = filterType === 'text' ? `ilike '%${value}%'` : `= any('{${value}}')`;
|
|
128
193
|
return {
|
|
@@ -134,49 +199,33 @@ function formatValue({
|
|
|
134
199
|
};
|
|
135
200
|
}
|
|
136
201
|
|
|
202
|
+
// filter text array column
|
|
137
203
|
if (fieldType?.includes('[]')) {
|
|
138
|
-
return { op: 'in', query: `'{${value}}'::text[] && ${name}::text[]
|
|
204
|
+
return { op: 'in', query: `'{${value}}'::text[] && ${name}::text[]` };
|
|
139
205
|
}
|
|
140
206
|
|
|
141
|
-
// multiple
|
|
207
|
+
// filter multiple values, example: 1,2,3
|
|
142
208
|
if (value?.indexOf(',') !== -1) {
|
|
143
209
|
const values = value.split(',').filter((el) => el !== 'null');
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
: `${pk} in (select object_id from ${extra?.table} where property_key='${name}' and value_text in (${values?.map((el) => `'"${el}"'`).join(',')}) )`;
|
|
148
|
-
return { op: 'in', query, extra };
|
|
149
|
-
} else if (extra?.mode === 'property' && pk) {
|
|
210
|
+
|
|
211
|
+
// extra table, example: crm.extra_data
|
|
212
|
+
if (extra?.table && pk) {
|
|
150
213
|
const query = value?.indexOf('null') !== -1
|
|
151
|
-
? `${pk} in (select
|
|
152
|
-
: `${pk} in (select
|
|
153
|
-
return { op: 'in', query
|
|
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 };
|
|
154
217
|
}
|
|
155
218
|
|
|
219
|
+
// default
|
|
156
220
|
const query = value?.indexOf('null') !== -1
|
|
157
221
|
? `( ${name} is null or ${name}::text in (${values?.map((el) => `'${el}'`).join(',')}) )`
|
|
158
222
|
: `${name}::text in (${value.split(',')?.map((el) => `'${el}'`).join(',')})`;
|
|
159
|
-
return { op: 'in', query
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// v3 filter number range, example - "100_500"
|
|
163
|
-
if (numberTypeList.includes(fieldType) && value?.indexOf('_') !== -1) {
|
|
164
|
-
const [min, max] = value.split('_');
|
|
165
|
-
const query = (max === 'max' ? `${name} > ${min}` : null) || (min === 'min' ? `${name} <= ${max}` : null) || `${name} between ${min} and ${max}`;
|
|
166
|
-
return { op: 'between', query, extra };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// number range
|
|
170
|
-
if (numberTypeList.includes(fieldType) && value?.indexOf('-') !== -1) {
|
|
171
|
-
const [min, max] = value.split('-');
|
|
172
|
-
if (min === 'min' && max === 'max') return {};
|
|
173
|
-
const query = (max === 'max' ? `${name} > ${min}` : null) || (min === 'min' ? `${name} < ${max}` : null) || `${name} between ${min} and ${max}`;
|
|
174
|
-
return { op: 'between', query, extra };
|
|
223
|
+
return { op: 'in', query };
|
|
175
224
|
}
|
|
176
225
|
|
|
177
226
|
if (['<', '>'].includes(operator)) {
|
|
178
227
|
const query = `${name} ${operator} '${value}'::${formatType}`;
|
|
179
|
-
return { op: operator, query
|
|
228
|
+
return { op: operator, query };
|
|
180
229
|
}
|
|
181
230
|
|
|
182
231
|
if (operator === '=' && filterType !== 'text' && !filter?.data) {
|
|
@@ -184,39 +233,14 @@ function formatValue({
|
|
|
184
233
|
null: `${name} is null`,
|
|
185
234
|
notnull: `${name} is not null`,
|
|
186
235
|
}[value] || `${name}::${formatType}='${value}'::${formatType}`;
|
|
187
|
-
return { op: '=', query
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (['~', '='].includes(operator)) {
|
|
191
|
-
const operator1 = (filterType === 'text' && (filter?.id || filter?.name) && operator === '=' ? '~' : operator);
|
|
192
|
-
const matchNull = { null: 'is null', notnull: 'is not null' }[value];
|
|
193
|
-
const matchBoolean = fieldType === 'boolean' ? { true: 'is true', false: 'is false' }[value] : null;
|
|
194
|
-
const match = matchNull || matchBoolean || ((operator1 === '=' || filterType === 'autocomplete') ? `='${value}'` : `ilike '%${value}%'`);
|
|
195
|
-
|
|
196
|
-
if (extra?.mode === 'property' && pk) {
|
|
197
|
-
const query = data && sql
|
|
198
|
-
? `${pk} in (select object_id from ${extra?.table} where property_key='${name}' and value_text in ( ( with q(id,name) as (${sql}) select id from q where ${filterType === 'autocomplete' ? 'id' : 'name'} ${match})))`
|
|
199
|
-
: `${pk} in (select object_id from ${extra?.table} where property_key='${name}' and value_text ${match})`;
|
|
200
|
-
return { op: 'ilike', query, extra };
|
|
201
|
-
} else if (extra?.mode === 'column') {
|
|
202
|
-
const query = data && sql
|
|
203
|
-
? `${pk} in (select ${pk} from ${extra?.table} where "${name}" in ( ( with q(id,name) as (${sql}) select id from q where ${filterType === 'autocomplete' ? 'id' : 'name'} ${match})))`
|
|
204
|
-
: `${pk} in (select ${pk} from ${extra?.table} where "${name}" ${match})`;
|
|
205
|
-
return { op: 'ilike', query, extra };
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const query = data && sql && filterType == 'text'
|
|
209
|
-
? `${name || filter?.id} in ( ( with q(id,name) as (${sql}) select id from q where ${filterType === 'autocomplete' ? 'id' : 'name'}::text ${match}) )` // filter with cls
|
|
210
|
-
: `${name}::text ${match}`; // simple filter
|
|
211
|
-
// console.log(query);
|
|
212
|
-
return { op: 'ilike', query, extra };
|
|
236
|
+
return { op: '=', query };
|
|
213
237
|
}
|
|
214
238
|
|
|
215
239
|
// json
|
|
216
240
|
if (name.includes('.')) {
|
|
217
241
|
const [col, prop] = name.split('.');
|
|
218
242
|
const query = ` ${col}->>'${prop}' in ('${value.join("','")}')`;
|
|
219
|
-
return { op: 'in', query
|
|
243
|
+
return { op: 'in', query };
|
|
220
244
|
}
|
|
221
245
|
|
|
222
246
|
// geometry
|
|
@@ -225,7 +249,7 @@ function formatValue({
|
|
|
225
249
|
|
|
226
250
|
if (bbox?.length === 4) {
|
|
227
251
|
const query = ` ${name} && 'box(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d `;
|
|
228
|
-
return { op: '&&', query
|
|
252
|
+
return { op: '&&', query };
|
|
229
253
|
}
|
|
230
254
|
}
|
|
231
255
|
return {};
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import formatValue from './formatValue.js';
|
|
10
10
|
|
|
11
11
|
function getQuery({
|
|
12
|
-
pg, filter: filterStr, table, tableSQL, fields, filterList,
|
|
12
|
+
pg, filter: filterStr, table, tableSQL, fields, filterList, uid,
|
|
13
13
|
}) {
|
|
14
14
|
if (!filterStr) return null; // filter list API
|
|
15
15
|
|
|
@@ -21,8 +21,8 @@ function getQuery({
|
|
|
21
21
|
|
|
22
22
|
for (let i = 0; i < filterQueryArray.length; i += 1) {
|
|
23
23
|
const item = filterQueryArray[i];
|
|
24
|
-
const
|
|
25
|
-
const [name] = item.split(
|
|
24
|
+
const splitby = mainOperators?.find((el) => item.indexOf(el) !== -1) || '=';
|
|
25
|
+
const [name] = item.split(splitby);
|
|
26
26
|
|
|
27
27
|
// skip already added filter
|
|
28
28
|
if (resultList.find((el) => el.name === name)) {
|
|
@@ -30,10 +30,12 @@ function getQuery({
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
// filter
|
|
33
|
-
const filter = filterList?.find((el) =>
|
|
33
|
+
const filter = filterList?.find?.((el) => el.type && el.name === name) || { type: 'text' };
|
|
34
|
+
const { strict, extra } = filter;
|
|
35
|
+
const operator = filter.strict || ['check', 'autocomplete'].includes(filter.type.toLowerCase()) ? '=' : (splitby !== '=' ? splitby : '~');
|
|
34
36
|
|
|
35
37
|
// find all value
|
|
36
|
-
const value = filterQueryArray.filter((el) => el.
|
|
38
|
+
const value = filterQueryArray.filter((el) => el.split(splitby)?.[0] === name).map((el) => el.substring(name.length + 1)).join(',');
|
|
37
39
|
|
|
38
40
|
const optimize = fields?.find((el) => el.name === name) ? null : tableSQL.find((el) => el.name === name);
|
|
39
41
|
|
|
@@ -52,11 +54,12 @@ function getQuery({
|
|
|
52
54
|
value,
|
|
53
55
|
operator,
|
|
54
56
|
dataTypeID,
|
|
57
|
+
uid,
|
|
55
58
|
}) || {};
|
|
56
59
|
if (!query) continue;
|
|
57
60
|
|
|
58
61
|
resultList.push({
|
|
59
|
-
name, value, query, operator: op, filterType, type: fieldType,
|
|
62
|
+
name, value, query, operator: op, filterType, type: fieldType, strict, extra,
|
|
60
63
|
});
|
|
61
64
|
}
|
|
62
65
|
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
function getOptimizedQuery({
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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 * from ${tableName} t ${sqlList ? ` ${sqlList}` : ''} ${extraSqlList || ''} where 1=1 and ${q?.replace('q.', 't.') || '1=1'} ${order})q`;
|
|
1
|
+
export default function getOptimizedQuery({ table, orderby, sqlTable, sqlInline, extraSqlColumns, q }, count) {
|
|
2
|
+
if (sqlInline) {
|
|
3
|
+
return `(select * ${extraSqlColumns || ''} ${sqlInline ? sqlInline : ''} from ${table} t ${sqlTable && count ? ` ${sqlTable}` : ''})q where 1=1 and ${q ? q.replace('q.', 't.') : '1=1'} ${orderby ? `order by ${orderby}` : ''}`;
|
|
4
|
+
}
|
|
5
|
+
return `(select * ${extraSqlColumns || ''} ${sqlInline ? sqlInline : ''} from ${table} t ${sqlTable && count ? ` ${sqlTable}` : ''} where 1=1 and ${q ? q.replace('q.', 't.') : '1=1'} ${orderby ? `order by ${orderby}` : ''})q`;
|
|
10
6
|
}
|
|
11
|
-
|
|
12
|
-
export default getOptimizedQuery;
|
|
@@ -30,7 +30,9 @@ export default async function getSelectVal({
|
|
|
30
30
|
const filteredValues = values.filter(el => !cache[el]);
|
|
31
31
|
|
|
32
32
|
// query select
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
const q = `with c(id,text) as (select * from (${cls.sql})q where ${id} = any('{${filteredValues.join(',').replace(/\"/g, '\\"')}}')) select * from c`;
|
|
35
|
+
|
|
34
36
|
const data = filteredValues.length ? await pg.query(q).then(el => el.rows) : [];
|
|
35
37
|
|
|
36
38
|
const clsObj = { ...cache, ...data.reduce((p, el) => ({ ...p, [el.id.toString()]: el.color ? el : el.text }), {}) };
|
|
@@ -46,9 +46,9 @@ export default async function tableAPI(req) {
|
|
|
46
46
|
// const cols = columns.map((el) => el.name || el).join(',');
|
|
47
47
|
const formName = hookData?.form || tokenData?.form || form;
|
|
48
48
|
const formData = await getTemplate('form', formName) || {};
|
|
49
|
-
const schema = formData?.schema || formData;
|
|
49
|
+
const schema = formData?.schema || formData || {};
|
|
50
50
|
// skip DataTable from another table
|
|
51
|
-
const extraKeys = Object.keys(schema)
|
|
51
|
+
const extraKeys = Object.keys(schema).filter((key) => schema[key]?.type === 'DataTable' && schema[key]?.table && schema[key]?.parent_id && schema[key]?.colModel?.length);
|
|
52
52
|
// skip non-existing columns
|
|
53
53
|
const columnList = dbColumns.map((el) => el.name || el).join(',');
|
|
54
54
|
|
|
@@ -65,6 +65,10 @@ export default async function tableAPI(req) {
|
|
|
65
65
|
const data = await pg.query(q, [id]).then(el => el.rows[0]);
|
|
66
66
|
if (!data) return { message: 'not found', status: 404 };
|
|
67
67
|
|
|
68
|
+
Object.keys(schema).filter(key => schema[key]?.type === 'DataTable').forEach(key => {
|
|
69
|
+
if (data[key] && !Array.isArray(data[key])) { data[key] = null };
|
|
70
|
+
});
|
|
71
|
+
|
|
68
72
|
if (extraKeys?.length) {
|
|
69
73
|
await Promise.all(extraKeys?.map(async (key) => {
|
|
70
74
|
const { colModel, table: extraTable, parent_id: parentId } = schema[key];
|
|
@@ -19,7 +19,7 @@ export default async function dataAPI(req, reply, called) {
|
|
|
19
19
|
} = req;
|
|
20
20
|
|
|
21
21
|
const time = Date.now();
|
|
22
|
-
|
|
22
|
+
const timeArr = [Date.now()];
|
|
23
23
|
const { uid } = user;
|
|
24
24
|
|
|
25
25
|
const hookData = await applyHook('preData', {
|
|
@@ -34,9 +34,10 @@ export default async function dataAPI(req, reply, called) {
|
|
|
34
34
|
const loadTable = await getTemplate('table', tokenData?.table || hookData?.table || params.table);
|
|
35
35
|
|
|
36
36
|
// check sql inline fields count
|
|
37
|
-
if (!checkInline[params?.table] && loadTable
|
|
38
|
-
const filterSql = loadTable.sql.filter(el => el.inline ?? true);
|
|
39
|
-
const
|
|
37
|
+
if (!checkInline[params?.table] && loadTable?.sql?.length && loadTable.table) {
|
|
38
|
+
const filterSql = loadTable.sql.filter(el => !el?.disabled && (el.inline ?? true));
|
|
39
|
+
const sqlTable = filterSql.map((el, i) => ` left join lateral (${el.sql}) ${el.name || `t${i}`} on 1=1 `)?.join('') || '';
|
|
40
|
+
const d = await Promise.all(filterSql.map((el, i) => pg.query(`select ${el.name || `t${i}`}.* from(select * from ${loadTable.table})t ${sqlTable} limit 0`).then(el => el.fields)))
|
|
40
41
|
d.forEach((el, i) => {
|
|
41
42
|
filterSql[i].inline = el.length == 1 ? true : false;
|
|
42
43
|
filterSql[i].fields = el.map(f => f.name);
|
|
@@ -45,9 +46,9 @@ export default async function dataAPI(req, reply, called) {
|
|
|
45
46
|
} else if (checkInline[params?.table]) {
|
|
46
47
|
loadTable.sql = checkInline[params?.table]
|
|
47
48
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
|
|
50
|
+
if (query.sql === '0') return loadTable;
|
|
51
|
+
|
|
51
52
|
if (!loadTable && !(tokenData?.table && pg.pk?.[tokenData?.table])) { return { message: 'template not found', status: 404 }; }
|
|
52
53
|
|
|
53
54
|
const id = tokenData?.id || hookData?.id || params?.id;
|
|
@@ -62,7 +63,7 @@ export default async function dataAPI(req, reply, called) {
|
|
|
62
63
|
} = loadTable || tokenData;
|
|
63
64
|
|
|
64
65
|
const tableMeta = await getMeta({ pg, table });
|
|
65
|
-
|
|
66
|
+
timeArr.push(Date.now())
|
|
66
67
|
if (tableMeta?.view) {
|
|
67
68
|
if (!loadTable?.key && !tokenData?.key) return { message: `key not found: ${table}`, status: 404 };
|
|
68
69
|
Object.assign(tableMeta, { pk: loadTable?.key || tokenData?.key });
|
|
@@ -105,12 +106,13 @@ export default async function dataAPI(req, reply, called) {
|
|
|
105
106
|
search: query.search,
|
|
106
107
|
state: query.state,
|
|
107
108
|
custom: query.custom,
|
|
109
|
+
uid,
|
|
108
110
|
json: 1,
|
|
109
111
|
}) : {};
|
|
110
|
-
|
|
112
|
+
timeArr.push(Date.now())
|
|
111
113
|
const keyQuery = query.key && (loadTable?.key || tokenData?.key) && !(hookData?.id || tokenData?.id || params.id) ? `${loadTable?.key || tokenData?.key}=$1` : null;
|
|
112
114
|
|
|
113
|
-
const limit = Math.min(maxLimit, +(query.limit || 20));
|
|
115
|
+
const limit = called ? (query.limit || 20) : Math.min(maxLimit, +(query.limit || 20));
|
|
114
116
|
|
|
115
117
|
const offset = query.page && query.page > 0 ? ` offset ${(query.page - 1) * limit}` : '';
|
|
116
118
|
// id, query, filter
|
|
@@ -133,10 +135,9 @@ export default async function dataAPI(req, reply, called) {
|
|
|
133
135
|
const q = `select ${pk ? `"${pk}" as id,` : ''}
|
|
134
136
|
${params.id || query.key ? '*' : sqlColumns || cols || '*'}
|
|
135
137
|
${metaCols}
|
|
136
|
-
${sql?.filter(el => el.inline).map(el => `,(${el.sql})`).join('') || ''}
|
|
137
138
|
|
|
138
139
|
${dbColumns.find((el) => el.name === 'geom' && pg.pgType?.[el.dataTypeID] === 'geometry') ? ',st_asgeojson(geom)::json as geom' : ''}
|
|
139
|
-
from (select *
|
|
140
|
+
from (select * ${sql?.filter(el => el.inline).map(el => `,(${el.sql})`).join('') || ''} from ${table} t ) t
|
|
140
141
|
${sqlTable}
|
|
141
142
|
${params.id ? cardSqlTable : ''}
|
|
142
143
|
where ${where.join(' and ') || 'true'}
|
|
@@ -149,6 +150,8 @@ export default async function dataAPI(req, reply, called) {
|
|
|
149
150
|
|
|
150
151
|
const { rows = [] } = await pg.query(q, (tokenData?.id || hookData?.id || params.id ? [tokenData?.id || hookData?.id || params.id] : null) || (query.key && loadTable.key ? [query.key] : []));
|
|
151
152
|
|
|
153
|
+
timeArr.push(Date.now())
|
|
154
|
+
|
|
152
155
|
if (uid && rows.length && editable) {
|
|
153
156
|
rows.forEach(row => {
|
|
154
157
|
row.token = setToken({
|
|
@@ -167,7 +170,7 @@ export default async function dataAPI(req, reply, called) {
|
|
|
167
170
|
count(*)::int as total,
|
|
168
171
|
count(*) FILTER(WHERE ${filterWhere.join(' and ') || 'true'})::int as filtered
|
|
169
172
|
${aggregates.length ? `,${aggregates.map((el) => `${aggColumns[el.name]}(${el.name}) FILTER(WHERE ${filterWhere.join(' and ') || 'true'}) as ${el.name}`).join(',')}` : ''}
|
|
170
|
-
from ${table} t ${sqlTable}
|
|
173
|
+
from (select * ${sql?.filter(el => el.inline).map(el => `,(${el.sql})`).join('') || ''} from ${table} t ${sqlTable})q
|
|
171
174
|
where ${[loadTable?.query, tokenData?.query, accessQuery].filter(el => el).join(' and ') || 'true'} `
|
|
172
175
|
.replace(/{{uid}}/g, uid);
|
|
173
176
|
|
|
@@ -175,15 +178,16 @@ export default async function dataAPI(req, reply, called) {
|
|
|
175
178
|
|
|
176
179
|
const counts = keyQuery || tokenData?.id || hookData?.id || params.id
|
|
177
180
|
? { total: rows.length, filtered: rows.length }
|
|
178
|
-
: await pg.queryCache?.(qCount, { table: loadTable?.table || tokenData?.table, time: 5 }).then(el => el?.rows[0] || {});
|
|
181
|
+
: await pg.queryCache?.(qCount, { table: loadTable?.table || tokenData?.table, time: 5 * 60 }).then(el => el?.rows[0] || {});
|
|
179
182
|
|
|
183
|
+
timeArr.push(Date.now())
|
|
180
184
|
const { total, filtered } = counts || {};
|
|
181
185
|
const agg = Object.keys(counts).filter(el => !['total', 'filtered'].includes(el)).reduce((acc, el) => ({ ...acc, [el]: counts[el] }), {});
|
|
182
186
|
|
|
183
187
|
await extraDataGet({ rows, table: loadTable?.table, form }, pg);
|
|
184
188
|
|
|
185
189
|
await metaFormat({ rows, table: tokenData?.table || hookData?.table || params.table }, pg);
|
|
186
|
-
|
|
190
|
+
timeArr.push(Date.now())
|
|
187
191
|
const status = [];
|
|
188
192
|
if (loadTable?.meta?.status) {
|
|
189
193
|
const statusColumn = loadTable.meta?.cls?.[loadTable.meta?.status]
|
|
@@ -270,7 +274,15 @@ export default async function dataAPI(req, reply, called) {
|
|
|
270
274
|
}
|
|
271
275
|
|
|
272
276
|
const res = {
|
|
273
|
-
time:
|
|
277
|
+
time: {
|
|
278
|
+
total: Date.now() - time,
|
|
279
|
+
init: timeArr[1] - timeArr[0],
|
|
280
|
+
filter: timeArr[2] - timeArr[1],
|
|
281
|
+
data: timeArr[3] - timeArr[2],
|
|
282
|
+
count: timeArr[4] - timeArr[3],
|
|
283
|
+
format: timeArr[5] - timeArr[4],
|
|
284
|
+
},
|
|
285
|
+
|
|
274
286
|
public: ispublic,
|
|
275
287
|
tokens,
|
|
276
288
|
card: loadTable?.card,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { logger, autoIndex, getSelect, getFilterSQL, getTemplate, getSelectVal, pgClients } from '../../../../utils.js';
|
|
2
2
|
|
|
3
|
+
const checkInline = {};
|
|
4
|
+
|
|
3
5
|
export default async function filterAPI(req) {
|
|
4
6
|
const time = Date.now();
|
|
5
7
|
|
|
@@ -10,8 +12,22 @@ export default async function filterAPI(req) {
|
|
|
10
12
|
const loadTable = await getTemplate('table', params.table);
|
|
11
13
|
if (!loadTable) { return { status: 404, message: 'not found' }; }
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
// check sql inline fields count
|
|
16
|
+
if (!checkInline[params.table] && loadTable.sql?.length && loadTable.table) {
|
|
17
|
+
const filterSql = loadTable.sql.filter(el => el.inline ?? true);
|
|
18
|
+
const d = await Promise.all(filterSql.map(el => pg.query(`select q.* from(select * from ${loadTable.table})t left join lateral(${el.sql})q on 1=1 limit 0`).then(el => el.fields)))
|
|
19
|
+
d.forEach((el, i) => {
|
|
20
|
+
filterSql[i].inline = el.length == 1 ? true : false;
|
|
21
|
+
filterSql[i].fields = el.map(f => f.name);
|
|
22
|
+
});
|
|
23
|
+
checkInline[params?.table] = loadTable.sql;
|
|
24
|
+
} else if (checkInline[params?.table]) {
|
|
25
|
+
loadTable.sql = checkInline[params?.table]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const sqlTable = false ? loadTable.sql?.filter?.((el) => !el.inline && !el?.disabled && el?.sql?.replace).map((el, i) => ` left join lateral (${el.sql.replace('{{uid}}', user?.uid)}) ${el.name || `t${i}`} on 1=1 `)?.join('') || '' : '';
|
|
29
|
+
const sqlInline = loadTable.sql?.filter?.(el => el.inline).map(el => `,(${el.sql})`).join('');
|
|
30
|
+
const { fields: columns = [] } = await pg.query(`select q.* from (select * ${sqlInline || ''} from ${loadTable.table} t ${sqlTable} limit 0)q`);
|
|
15
31
|
const { fields = [] } = await pg.query(`select * from ${loadTable.table} t limit 0`);
|
|
16
32
|
|
|
17
33
|
const {
|
|
@@ -20,13 +36,14 @@ export default async function filterAPI(req) {
|
|
|
20
36
|
|
|
21
37
|
const { extra } = loadTable?.form ? await getTemplate('form', loadTable?.form) || {} : {};
|
|
22
38
|
|
|
23
|
-
const { optimizedSQL = `select * from ${loadTable.table}` } = loadTable?.sql || filter || custom || state || search || extra ? await getFilterSQL({
|
|
39
|
+
const { optimizedSQL = `select * ${sqlInline || ''} from ${loadTable.table}` } = loadTable?.sql || filter || custom || state || search || extra ? await getFilterSQL({
|
|
24
40
|
pg,
|
|
25
41
|
table: params.table,
|
|
26
42
|
filter,
|
|
27
43
|
custom,
|
|
28
44
|
state,
|
|
29
45
|
search,
|
|
46
|
+
uid: user?.uid,
|
|
30
47
|
}) : {};
|
|
31
48
|
|
|
32
49
|
const filters = (loadTable?.filter_list || loadTable?.filters || loadTable?.filterList || []).concat(loadTable?.filterSql || []);
|
|
@@ -47,8 +64,8 @@ export default async function filterAPI(req) {
|
|
|
47
64
|
await Promise.all(filters.filter((el) => el.data && el.id && el.type !== 'Autocomplete').map(async (el) => {
|
|
48
65
|
const cls = await getSelect(el.data, pg);
|
|
49
66
|
|
|
50
|
-
if (!cls || !loadTable.table) return;
|
|
51
67
|
const { dataTypeID } = columns.find((item) => item.name === el.id) || {};
|
|
68
|
+
if (!cls || !loadTable.table || !dataTypeID) return;
|
|
52
69
|
|
|
53
70
|
if (el.extra && el.type === 'select' && Array.isArray(cls)) {
|
|
54
71
|
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, params.table]);
|