@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "1.2.68",
3
+ "version": "1.2.70",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "keywords": [
@@ -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].reduce((acc, item, i) => {
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.error);
51
+ const invalidField = res.find(el => el?.error);
52
52
 
53
53
  if (invalidField) {
54
- console.warn('invalid field: ', invalidField.key, invalidField.error);
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 ? 15 : (config.local ? 0 : time * 60);
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, extraColumns, schema, table, pk, mode = 'property') {
13
- const extraKeys = mode === 'column'
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, idx) => `left join lateral (select value_text as "${el.replace(/'/g, "''")}" from ${table} where property_key='${el.replace(/'/g, "''")}' and object_id=t.${pk} limit 1) extra${idx} on 1=1`).join(' '),
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: fieldsExtra = [] } = pg.pk?.[extraDataTable] ? await pg.query(`select * from ${extraDataTable} limit 0`) : {};
44
- const mode = fieldsExtra.find((col) => col?.name === pg.pk?.[body?.table || table]) ? 'column' : 'property';
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
- const sqlList = body?.sql?.length
54
- ? body?.sql?.filter((el) => !el.disabled && el?.sql?.replace)
55
- .map((el, i) => {
56
- Object.assign(el, { name: el.name || `t${i + 1}` });
57
- return ` left join lateral (${el.filter ? el.sql.replace(/limit 1/ig, '') : el.sql}) as ${el.name} on 1=1 `;
58
- }).join(' ')
59
- : '';
60
- const fieldQuery = `select * from ${body?.table || table} t ${sqlList || ''} ${extraSqlList || ''} limit 0`;
61
- const { fields = [] } = !extra || mode === 'column' ? await pg.query(fieldQuery) : { fields: extraKeys.map(el => ({ name: el.name, dataTypeID: 25 })) };
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
- body?.filterSql?.forEach?.((el) => Object.assign(el, { filtersql: 1 }));
77
-
78
- const filterList1 = await Promise.all((filterList || (body?.filter_list || []).concat(body?.filterInline || []).concat(body?.filterCustom || []).concat(body?.filterState || []).concat(body?.filterList || [])
79
- .concat(body?.filters || [])).concat(body?.filterSql || []) /* .concat(extraFilters || []).concat(customFilters || []) */
80
-
81
- ?.map(async (el) => {
82
- if (el.name && extraKeys?.includes?.(el.name)) {
83
- Object.assign(el, { extra: { table: extraDataTable, mode } });
84
- }
85
- if (!el?.data) return el;
86
- const cls = await getTemplate(['cls', 'select'], el.data);
87
- if (Array.isArray(cls) && cls?.length) {
88
- Object.assign(el, { options: cls });
89
- }
90
- else if (typeof (cls?.sql || cls) === 'string') {
91
- Object.assign(el, { sql: cls?.sql || cls });
92
- }
93
- return el;
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: body?.table || 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
- const modelQuery = body?.model || body?.table || table;
114
- const optimizedSQL = `select * from ${getOptimizedQuery({ body, extraSqlList, table, q })} `;
115
- const tableCount = getOptimizedQuery({ body, extraSqlList, table, q }, true);
116
- // console.log(optimizedSQL);
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
- extraSqlList,
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 { data, sql, extra, filtersql } = filter;
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
- // const fieldType = extra ? pg.pgType?.[{ Date: 1114 }[filter?.type] || 25] : pg.pgType?.[dataTypeID];
33
- const fieldType = pg.pgType?.[dataTypeID] || pg.pgType?.[{ Date: 1114 }[filter?.type || ''] || 25];
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
- const filterType = filter.type?.toLowerCase();
37
-
38
- // body.filterSql
39
- if (sql && filtersql) {
40
- // date range
41
- if (filterType === 'date') {
42
- const [startDate, endDate] = value.split('-');
43
- const min = formatDateISOString(startDate);
44
- const max = formatDateISOString(endDate);
45
-
46
- if (!isValidDate(startDate) || !isValidDate(endDate)) {
47
- return { op: 'between', query: 'false', extra, sql: true };
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
- // num range
55
- if (filterType === 'range') {
56
- const [min, max] = value?.indexOf('_') !== -1 ? value.split('_') : value.split('-');
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, extra, sql: true };
137
+ return { op: '=', query };
65
138
  }
66
139
 
67
- // text
140
+ // default
68
141
  const query = sql.replace(/\$1/g, `'${value}'`);
69
- return { op: '=', query, extra, sql: true };
142
+ return { op: '=', query };
70
143
  }
71
144
 
72
- // current day, week, month, year etc.
73
- if (dateTypeList.includes(fieldType) && !value?.includes('_') && ['cd', 'cw', 'cm', 'cq', 'cy'].includes(value)) {
74
- const query = {
75
- cd: `${name}::date = '${dt(dp.y, dp.m, dp.d)}'::date`,
76
- cw: `${name}::date >= '${dt(dp.y, dp.m, dp.w)}'::date and ${name} <= '${dt(dp.y, dp.m, dp.w + 6)}'::date`,
77
- cm: `${name}::date >= '${dt(dp.y, dp.m, 1)}'::date and ${name} <= '${dt(dp.y, dp.m + 1, 0)}'::date`,
78
- cq: `${name}::date >= '${dt(dp.y, dp.q, 1)}'::date and ${name} <= '${dt(dp.y, dp.q + 3, 0)}'::date`,
79
- cy: `${name}::date >= '${dt(dp.y, 0, 1)}'::date and ${name}::date <= '${dt(dp.y, 11, 31)}'::date`,
80
- }[value];
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 query = extra?.mode === 'property' && pk
102
- ? `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_date::date >= '${min}'::date and value_date::date <= '${max}'::date)`
103
- : `${pk} in (select ${pk} from ${extra.table} where ${name}::date >= '${min}'::date and ${name}::date <= '${max}'::date)`;
104
- return { op: 'between', query, extra };
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
- return { op: 'between', query: `${name}::date >= '${min}'::date and ${name}::date <= '${max}'::date`, extra };
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 && fieldType === 'text') {
111
- const query = extra?.table && pk
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, extra };
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[]`, extra };
204
+ return { op: 'in', query: `'{${value}}'::text[] && ${name}::text[]` };
139
205
  }
140
206
 
141
- // multiple items of 1 param
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
- if (extra?.mode === 'property' && pk) {
145
- const query = value?.indexOf('null') !== -1
146
- ? `${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(',')}) ) )`
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 ${pk} from ${extra?.table} where ( "${name}" is null or "${name}" in (${values?.map((el) => `'"${el}"'`).join(',')}) ) )`
152
- : `${pk} in (select ${pk} from ${extra?.table} where "${name}" in (${values?.map((el) => `'"${el}"'`).join(',')}) )`;
153
- return { op: 'in', query, extra };
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, extra };
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, extra };
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, extra };
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, extra };
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, extra };
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 operator = mainOperators?.find((el) => item.indexOf(el) !== -1) || '=';
25
- const [name] = item.split(operator);
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) => [el.id, el.name].includes(name)) || { type: 'text' };
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.startsWith(name)).map((el) => el.substring(name.length + 1)).join(',');
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({ body, extraSqlList, 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 * 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
- const q = `with c(id,text) as (select * from (${cls.sql})q where ${id} = any('{${filteredValues}}')) select * from c`;
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)?.filter((key) => schema[key]?.type === 'DataTable' && schema[key]?.table && schema[key]?.parent_id && schema[key]?.colModel?.length);
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.sql?.length && loadTable.table) {
38
- const filterSql = loadTable.sql.filter(el => el.inline ?? true);
39
- 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)))
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
- if (query.sql == 2) {
49
- return loadTable;
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 * from ${table} where ${sqlTable ? 'true' : (where.join(' and ') || 'true')} ) t
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: Date.now() - 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
- const sqlTable = loadTable.sql?.filter?.((el) => !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('') || '';
14
- const { fields: columns = [] } = await pg.query(`select * from ${loadTable.table} t ${sqlTable} limit 0`);
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]);