@saltcorn/data 0.6.1-beta.0 → 0.6.1

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.
Files changed (60) hide show
  1. package/base-plugin/actions.js +172 -1
  2. package/base-plugin/fieldviews.js +63 -0
  3. package/base-plugin/fileviews.js +41 -0
  4. package/base-plugin/index.js +35 -0
  5. package/base-plugin/types.js +345 -9
  6. package/base-plugin/viewtemplates/edit.js +107 -0
  7. package/base-plugin/viewtemplates/feed.js +46 -0
  8. package/base-plugin/viewtemplates/filter.js +43 -0
  9. package/base-plugin/viewtemplates/list.js +73 -1
  10. package/base-plugin/viewtemplates/listshowlist.js +54 -3
  11. package/base-plugin/viewtemplates/room.js +100 -0
  12. package/base-plugin/viewtemplates/show.js +86 -0
  13. package/base-plugin/viewtemplates/viewable_fields.js +124 -0
  14. package/contracts.js +58 -0
  15. package/db/connect.js +13 -5
  16. package/db/db.test.js +0 -154
  17. package/db/fixtures.js +13 -1
  18. package/db/index.js +42 -3
  19. package/db/reset_schema.js +11 -0
  20. package/db/state.js +105 -36
  21. package/index.js +13 -0
  22. package/migrate.js +4 -1
  23. package/models/backup.js +78 -0
  24. package/models/config.js +113 -22
  25. package/models/crash.js +44 -0
  26. package/models/discovery.js +13 -11
  27. package/models/email.js +17 -0
  28. package/models/eventlog.js +51 -0
  29. package/models/expression.js +49 -1
  30. package/models/field.js +88 -9
  31. package/models/fieldrepeat.js +33 -0
  32. package/models/file.js +23 -4
  33. package/models/form.js +34 -0
  34. package/models/index.js +42 -0
  35. package/models/layout.js +33 -0
  36. package/models/library.js +44 -0
  37. package/models/pack.js +88 -0
  38. package/models/page.js +13 -3
  39. package/models/plugin.js +9 -2
  40. package/models/random.js +36 -0
  41. package/models/role.js +36 -0
  42. package/models/scheduler.js +28 -0
  43. package/models/table.js +34 -15
  44. package/models/table_constraints.js +44 -0
  45. package/models/tenant.js +46 -9
  46. package/models/trigger.js +24 -11
  47. package/models/user.js +33 -8
  48. package/models/view.js +89 -8
  49. package/models/workflow.js +31 -0
  50. package/package.json +7 -5
  51. package/plugin-helper.js +102 -44
  52. package/plugin-testing.js +4 -0
  53. package/tests/exact_views.test.js +5 -5
  54. package/utils.js +4 -0
  55. package/db/internal.js +0 -229
  56. package/db/multi-tenant.js +0 -24
  57. package/db/pg.js +0 -374
  58. package/db/single-tenant.js +0 -8
  59. package/db/sqlite.js +0 -280
  60. package/db/tenants.js +0 -6
@@ -128,7 +128,7 @@ describe("Show view", () => {
128
128
  configuration: { code: 'console.log("1")' },
129
129
  },
130
130
  ],
131
- response: `<div class="row"><div class="col-6">1</div><div class="col-6"><a href="javascript:view_post('testshow', 'run_action', {rndid:'1a8ac3', id:1});" class="btn btn-success btn-sm">you're my number</a></div></div>`,
131
+ response: `<div class="row w-100"><div class="col-6">1</div><div class="col-6"><a href="javascript:view_post('testshow', 'run_action', {rndid:'1a8ac3', id:1});" class="btn btn-success btn-sm">you're my number</a></div></div>`,
132
132
  });
133
133
  await test_show({
134
134
  layout: {
@@ -179,7 +179,7 @@ describe("Show view", () => {
179
179
  in_modal: true,
180
180
  },
181
181
  ],
182
- response: `<div class="row"><div class="col-6"><div class="card mt-4 shadow"><div class="card-body"><button class="btn btn-link" onClick="ajax_modal('/view/authorshow?id=1')">foo it</button></div></div></div><div class="col-6"><div class="text-left" style="min-height: 100px;border: 1px solid black; background-color: #a9a7a7; "><a href="https://countto.com/967">Herman Melville</a></div></div></div>`,
182
+ response: `<div class="row w-100"><div class="col-6"><div class="card mt-4 shadow"><div class="card-body"><button class="btn btn-link" onClick="ajax_modal('/view/authorshow?id=1')">foo it</button></div></div></div><div class="col-6"><div class="text-left" style="min-height: 100px;border: 1px solid black; background-color: #a9a7a7; "><a href="https://countto.com/967">Herman Melville</a></div></div></div>`,
183
183
  });
184
184
  await test_show({
185
185
  layout: {
@@ -287,13 +287,13 @@ describe("Edit view", () => {
287
287
  await test_edit({
288
288
  layout,
289
289
  columns,
290
- response: `<form action="/view/testedit" class="form-namespace " method="post"><input type="hidden" name="_csrf" value=""><div class="row"><div class="col-2">Name</div><div class="col-10"><input type="text" class="form-control " data-fieldname="name" name="name" id="inputname"></div></div><br /><div class="row"><div class="col-2">Favourite book</div><div class="col-10"><select class="form-control " data-fieldname="favbook" name="favbook" id="inputfavbook"><option value=""></option><option value="1">Herman Melville</option><option value="2">Leo Tolstoy</option></select></div></div><br /><div class="row"><div class="col-2">Parent</div><div class="col-10"><select class="form-control " data-fieldname="parent" name="parent" id="inputparent"><option value=""></option><option value="1">1</option><option value="2">2</option></select></div></div><br /><button type="submit" class="btn btn-primary ">Save</button></form>`,
290
+ response: `<form action="/view/testedit" class="form-namespace " method="post"><input type="hidden" name="_csrf" value=""><div class="row w-100"><div class="col-2">Name</div><div class="col-10"><input type="text" class="form-control " data-fieldname="name" name="name" id="inputname"></div></div><br /><div class="row w-100"><div class="col-2">Favourite book</div><div class="col-10"><select class="form-control " data-fieldname="favbook" name="favbook" id="inputfavbook"><option value=""></option><option value="1">Herman Melville</option><option value="2">Leo Tolstoy</option></select></div></div><br /><div class="row w-100"><div class="col-2">Parent</div><div class="col-10"><select class="form-control " data-fieldname="parent" name="parent" id="inputparent"><option value=""></option><option value="1">1</option><option value="2">2</option></select></div></div><br /><button type="submit" class="btn btn-primary ">Save</button></form>`,
291
291
  });
292
292
  await test_edit({
293
293
  id: 1,
294
294
  layout,
295
295
  columns,
296
- response: `<form action="/view/testedit" class="form-namespace " method="post"><input type="hidden" name="_csrf" value=""><input type="hidden" class="form-control " name="id" value="1"><div class="row"><div class="col-2">Name</div><div class="col-10"><input type="text" class="form-control " data-fieldname="name" name="name" id="inputname" value="Kirk Douglas"></div></div><br /><div class="row"><div class="col-2">Favourite book</div><div class="col-10"><select class="form-control " data-fieldname="favbook" name="favbook" id="inputfavbook"><option value=""></option><option value="1" selected>Herman Melville</option><option value="2">Leo Tolstoy</option></select></div></div><br /><div class="row"><div class="col-2">Parent</div><div class="col-10"><select class="form-control " data-fieldname="parent" name="parent" id="inputparent"><option value=""></option><option value="1">1</option><option value="2">2</option></select></div></div><br /><button type="submit" class="btn btn-primary ">Save</button></form>`,
296
+ response: `<form action="/view/testedit" class="form-namespace " method="post"><input type="hidden" name="_csrf" value=""><input type="hidden" class="form-control " name="id" value="1"><div class="row w-100"><div class="col-2">Name</div><div class="col-10"><input type="text" class="form-control " data-fieldname="name" name="name" id="inputname" value="Kirk Douglas"></div></div><br /><div class="row w-100"><div class="col-2">Favourite book</div><div class="col-10"><select class="form-control " data-fieldname="favbook" name="favbook" id="inputfavbook"><option value=""></option><option value="1" selected>Herman Melville</option><option value="2">Leo Tolstoy</option></select></div></div><br /><div class="row w-100"><div class="col-2">Parent</div><div class="col-10"><select class="form-control " data-fieldname="parent" name="parent" id="inputparent"><option value=""></option><option value="1">1</option><option value="2">2</option></select></div></div><br /><button type="submit" class="btn btn-primary ">Save</button></form>`,
297
297
  });
298
298
  });
299
299
  });
@@ -394,7 +394,7 @@ describe("Filter view", () => {
394
394
  ],
395
395
  viewname: "testfilter",
396
396
  response:
397
- '<div class="row"><div class="col-6"><select name="ddfilterpatients.favbook.name" class="form-control d-inline" style="width: unset;" onchange="this.value==\'\' ? unset_state_field(\'patients.favbook.name\'): set_state_field(\'patients.favbook.name\', this.value)"><option value="" class="text-muted"></option><option value="Kirk Douglas">Kirk Douglas</option><option value="Michael Douglas">Michael Douglas</option></select></div><div class="col-6"><button class="btn btn-outline-primary" onClick="set_state_field(\'pages\', \'13\')">thirteen</button></div></div><button class="btn btn-outline-primary" onClick="set_state_field(\'patients.favbook.name\', \'Jim\')">Jim</button><select name="ddfilterauthor" class="form-control d-inline" style="width: unset;" onchange="this.value==\'\' ? unset_state_field(\'author\'): set_state_field(\'author\', this.value)"><option value="" class="text-muted"></option><option value="Herman Melville">Herman Melville</option><option value="Leo Tolstoy">Leo Tolstoy</option></select>',
397
+ '<div class="row w-100"><div class="col-6"><select name="ddfilterpatients.favbook.name" class="form-control d-inline" style="width: unset;" onchange="this.value==\'\' ? unset_state_field(\'patients.favbook.name\'): set_state_field(\'patients.favbook.name\', this.value)"><option value="" class="text-muted"></option><option value="Kirk Douglas">Kirk Douglas</option><option value="Michael Douglas">Michael Douglas</option></select></div><div class="col-6"><button class="btn btn-outline-primary" onClick="set_state_field(\'pages\', \'13\')">thirteen</button></div></div><button class="btn btn-outline-primary" onClick="set_state_field(\'patients.favbook.name\', \'Jim\')">Jim</button><select name="ddfilterauthor" class="form-control d-inline" style="width: unset;" onchange="this.value==\'\' ? unset_state_field(\'author\'): set_state_field(\'author\', this.value)"><option value="" class="text-muted"></option><option value="Herman Melville">Herman Melville</option><option value="Leo Tolstoy">Leo Tolstoy</option></select>',
398
398
  });
399
399
  });
400
400
  });
package/utils.js CHANGED
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @category saltcorn-data
3
+ * @module utils
4
+ */
1
5
  const v8 = require("v8");
2
6
  const fs = require("fs");
3
7
 
package/db/internal.js DELETED
@@ -1,229 +0,0 @@
1
- const { footer } = require("@saltcorn/markup/tags");
2
- const { contract, is } = require("contractis");
3
- const { is_sqlite } = require("./connect");
4
-
5
- //https://stackoverflow.com/questions/15300704/regex-with-my-jquery-function-for-sql-variable-name-validation
6
- /**
7
- * Transform value to correct sql name.
8
- * Note! Dont use other symbols than ^A-Za-z_0-9
9
- * @type {*|(function(...[*]=): *)}
10
- */
11
- const sqlsanitize = contract(is.fun(is.str, is.str), (nm) => {
12
- const s = nm.replace(/[^A-Za-z_0-9]*/g, "");
13
- if (s[0] >= "0" && s[0] <= "9") return `_${s}`;
14
- else return s;
15
- });
16
- /**
17
- * Transform value to correct sql name.
18
- * Instead of sqlsanitize also allows .
19
- * For e.g. table name
20
- * Note! Dont use other symbols than ^A-Za-z_0-9.
21
- * @type {*|(function(...[*]=): *)}
22
- */
23
- const sqlsanitizeAllowDots = contract(is.fun(is.str, is.str), (nm) => {
24
- const s = nm.replace(/[^A-Za-z_0-9."]*/g, "");
25
- if (s[0] >= "0" && s[0] <= "9") return `_${s}`;
26
- else return s;
27
- });
28
- /**
29
- *
30
- * @param v
31
- * @param i
32
- * @param is_sqlite
33
- * @returns {`to_tsvector('english', ${*}) @@ plainto_tsquery('english', $${string})`|`${*} LIKE '%' || ? || '%'`}
34
- */
35
- const whereFTS = (v, i, is_sqlite) => {
36
- const { fields, table } = v;
37
- var flds = fields
38
- .filter((f) => f.type && f.type.sql_name === "text")
39
- .map(
40
- (f) =>
41
- "coalesce(" +
42
- (table
43
- ? `"${sqlsanitize(table)}"."${sqlsanitize(f.name)}"`
44
- : `"${sqlsanitize(f.name)}"`) +
45
- ",'')"
46
- )
47
- .join(" || ' ' || ");
48
- if (flds === "") flds = "''";
49
- if (is_sqlite) return `${flds} LIKE '%' || ? || '%'`;
50
- else
51
- return `to_tsvector('english', ${flds}) @@ plainto_tsquery('english', $${i})`;
52
- };
53
-
54
- const placeHolder = (is_sqlite, i) => (is_sqlite ? `?` : `$${i}`);
55
-
56
- const mkCounter = () => {
57
- let i = 0;
58
- return () => {
59
- i += 1;
60
- return i;
61
- };
62
- };
63
- const subSelectWhere = (is_sqlite, i) => (k, v) => {
64
- const whereObj = v.inSelect.where;
65
- const wheres = whereObj ? Object.entries(whereObj) : [];
66
- const where =
67
- whereObj && wheres.length > 0
68
- ? "where " + wheres.map(whereClause(is_sqlite, i)).join(" and ")
69
- : "";
70
- return `${quote(sqlsanitizeAllowDots(k))} in (select ${
71
- v.inSelect.field
72
- } from ${v.inSelect.table} ${where})`;
73
- };
74
- const subSelectVals = (v) => {
75
- const whereObj = v.inSelect.where;
76
- const wheres = whereObj ? Object.entries(whereObj) : [];
77
- const xs = wheres
78
- .map(getVal)
79
- .flat(1)
80
- .filter((v) => v !== null);
81
- return xs;
82
- };
83
- const wrapParens = (s) => (s ? `(${s})` : s);
84
- const quote = (s) => (s.includes(".") || s.includes('"') ? s : `"${s}"`);
85
- const whereOr = (is_sqlite, i) => (ors) =>
86
- wrapParens(
87
- ors
88
- .map((vi) =>
89
- Object.entries(vi)
90
- .map((kv) => whereClause(is_sqlite, i)(kv))
91
- .join(" and ")
92
- )
93
- .join(" or ")
94
- );
95
-
96
- const whereClause = (is_sqlite, i) => ([k, v]) =>
97
- k === "_fts"
98
- ? whereFTS(v, i(), is_sqlite)
99
- : typeof (v || {}).in !== "undefined"
100
- ? `${quote(sqlsanitizeAllowDots(k))} = ${
101
- is_sqlite ? "" : "ANY"
102
- } (${placeHolder(is_sqlite, i())})`
103
- : k === "or" && Array.isArray(v)
104
- ? whereOr(is_sqlite, i)(v)
105
- : k === "not" && typeof v === "object"
106
- ? `not (${Object.entries(v)
107
- .map((kv) => whereClause(is_sqlite, i)(kv))
108
- .join(" and ")})`
109
- : v && v.or && Array.isArray(v.or)
110
- ? wrapParens(
111
- v.or.map((vi) => whereClause(is_sqlite, i)([k, vi])).join(" or ")
112
- )
113
- : Array.isArray(v)
114
- ? v.map((vi) => whereClause(is_sqlite, i)([k, vi])).join(" and ")
115
- : typeof (v || {}).ilike !== "undefined"
116
- ? `${quote(sqlsanitizeAllowDots(k))} ${
117
- is_sqlite ? "LIKE" : "ILIKE"
118
- } '%' || ${placeHolder(is_sqlite, i())} || '%'`
119
- : typeof (v || {}).gt !== "undefined"
120
- ? `${quote(sqlsanitizeAllowDots(k))}>${v.equal ? "=" : ""}${placeHolder(
121
- is_sqlite,
122
- i()
123
- )}`
124
- : typeof (v || {}).lt !== "undefined"
125
- ? `${quote(sqlsanitizeAllowDots(k))}<${v.equal ? "=" : ""}${placeHolder(
126
- is_sqlite,
127
- i()
128
- )}`
129
- : typeof (v || {}).inSelect !== "undefined"
130
- ? subSelectWhere(is_sqlite, i)(k, v)
131
- : typeof (v || {}).json !== "undefined"
132
- ? is_sqlite
133
- ? `json_extract(${quote(
134
- sqlsanitizeAllowDots(k)
135
- )}, '$.${sqlsanitizeAllowDots(v.json[0])}')=${placeHolder(
136
- is_sqlite,
137
- i()
138
- )}`
139
- : `${quote(sqlsanitizeAllowDots(k))}->>'${sqlsanitizeAllowDots(
140
- v.json[0]
141
- )}'=${placeHolder(is_sqlite, i())}`
142
- : v === null
143
- ? `${quote(sqlsanitizeAllowDots(k))} is null`
144
- : `${quote(sqlsanitizeAllowDots(k))}=${placeHolder(is_sqlite, i())}`;
145
-
146
- const getVal = ([k, v]) =>
147
- k === "_fts"
148
- ? v.searchTerm
149
- : typeof (v || {}).in !== "undefined"
150
- ? [v.in]
151
- : k === "not" && typeof v === "object"
152
- ? Object.entries(v).map(getVal).flat(1)
153
- : k === "or" && Array.isArray(v)
154
- ? v.map((vi) => Object.entries(vi).map(getVal)).flat(1)
155
- : v && v.or && Array.isArray(v.or)
156
- ? v.or.map((vi) => getVal([k, vi])).flat(1)
157
- : Array.isArray(v)
158
- ? v.map((vi) => getVal([k, vi])).flat(1)
159
- : typeof (v || {}).ilike !== "undefined"
160
- ? v.ilike
161
- : typeof (v || {}).inSelect !== "undefined"
162
- ? subSelectVals(v)
163
- : typeof (v || {}).lt !== "undefined"
164
- ? v.lt
165
- : typeof (v || {}).gt !== "undefined"
166
- ? v.gt
167
- : typeof (v || {}).sql !== "undefined"
168
- ? null
169
- : typeof (v || {}).json !== "undefined"
170
- ? v.json[1]
171
- : v;
172
-
173
- const mkWhere = (whereObj, is_sqlite) => {
174
- const wheres = whereObj ? Object.entries(whereObj) : [];
175
- //console.log({ wheres });
176
- const where =
177
- whereObj && wheres.length > 0
178
- ? "where " + wheres.map(whereClause(is_sqlite, mkCounter())).join(" and ")
179
- : "";
180
- const values = wheres
181
- .map(getVal)
182
- .flat(1)
183
- .filter((v) => v !== null);
184
- return { where, values };
185
- };
186
-
187
- const toInt = (x) =>
188
- typeof x === "number"
189
- ? Math.round(x)
190
- : typeof x === "string"
191
- ? parseInt(x)
192
- : null;
193
-
194
- const getDistanceOrder = ({ latField, longField, lat, long }) => {
195
- const cos_lat_2 = Math.pow(Math.cos((+lat * Math.PI) / 180), 2);
196
- return `((${sqlsanitizeAllowDots(
197
- latField
198
- )} - ${+lat})*(${sqlsanitizeAllowDots(
199
- latField
200
- )} - ${+lat})) + ((${sqlsanitizeAllowDots(
201
- longField
202
- )} - ${+long})*(${sqlsanitizeAllowDots(longField)} - ${+long})*${cos_lat_2})`;
203
- };
204
- const mkSelectOptions = (selopts) => {
205
- const orderby =
206
- selopts.orderBy === "RANDOM()"
207
- ? "order by RANDOM()"
208
- : selopts.orderBy && selopts.orderBy.distance
209
- ? `order by ${getDistanceOrder(selopts.orderBy.distance)}`
210
- : selopts.orderBy && selopts.nocase
211
- ? `order by lower(${sqlsanitizeAllowDots(selopts.orderBy)})${
212
- selopts.orderDesc ? " DESC" : ""
213
- }`
214
- : selopts.orderBy
215
- ? `order by ${sqlsanitizeAllowDots(selopts.orderBy)}${
216
- selopts.orderDesc ? " DESC" : ""
217
- }`
218
- : "";
219
- const limit = selopts.limit ? `limit ${toInt(selopts.limit)}` : "";
220
- const offset = selopts.offset ? `offset ${toInt(selopts.offset)}` : "";
221
- return [orderby, limit, offset].filter((s) => s).join(" ");
222
- };
223
-
224
- module.exports = {
225
- sqlsanitize,
226
- mkWhere,
227
- mkSelectOptions,
228
- sqlsanitizeAllowDots,
229
- };
@@ -1,24 +0,0 @@
1
- const { AsyncLocalStorage } = require("async_hooks");
2
- const { sqlsanitize } = require("./internal");
3
-
4
- var is_multi_tenant = true;
5
- const is_it_multi_tenant = () => is_multi_tenant;
6
-
7
- const tenantNamespace = new AsyncLocalStorage();
8
-
9
- const enable_multi_tenant = () => {};
10
-
11
- const runWithTenant = (tenant, f) => {
12
- if (!is_multi_tenant) return f();
13
- else return tenantNamespace.run(sqlsanitize(tenant).toLowerCase(), f);
14
- };
15
-
16
- module.exports = (connObj) => ({
17
- getTenantSchema() {
18
- const storeVal = tenantNamespace.getStore();
19
- return storeVal || connObj.default_schema;
20
- },
21
- enable_multi_tenant,
22
- runWithTenant,
23
- is_it_multi_tenant,
24
- });
package/db/pg.js DELETED
@@ -1,374 +0,0 @@
1
- /**
2
- * PostgreSQL data access layer
3
- */
4
- // TODO move postgresql specific to this module
5
- const { Pool } = require("pg");
6
- const copyStreams = require("pg-copy-streams");
7
- const { promisify } = require("util");
8
- const { pipeline } = require("stream");
9
- const { sqlsanitize, mkWhere, mkSelectOptions } = require("./internal");
10
- const { getConnectObject } = require("./connect");
11
- const { getTenantSchema } = require("./tenants");
12
-
13
- var connectObj = getConnectObject();
14
-
15
- var pool;
16
- if (connectObj) pool = new Pool(connectObj);
17
-
18
- var log_sql_enabled = false;
19
-
20
- /**
21
- * Control Logging sql statements to console
22
- * @param val - if true then log sql statements to console
23
- */
24
- function set_sql_logging(val = true) {
25
- log_sql_enabled = val;
26
- }
27
-
28
- /**
29
- * Get sql logging state
30
- * @returns {boolean} if true then sql logging eabled
31
- */
32
- function get_sql_logging() {
33
- return log_sql_enabled;
34
- }
35
-
36
- /**
37
- * Log SQL statement to console
38
- * @param sql - SQL statement
39
- * @param vs - any additional parameter
40
- */
41
- function sql_log(sql, vs) {
42
- if (log_sql_enabled)
43
- if (typeof vs === "undefined") console.log(sql);
44
- else console.log(sql, vs);
45
- }
46
-
47
- /**
48
- * Close database connection
49
- * @returns {Promise<void>}
50
- */
51
- const close = async () => {
52
- if (pool) await pool.end();
53
- };
54
-
55
- /**
56
- * Change connection (close connection and open new connection from connObj)
57
- * @param connObj - connection object
58
- * @returns {Promise<void>}
59
- */
60
- const changeConnection = async (connObj = {}) => {
61
- await close();
62
- pool = new Pool(getConnectObject(connObj));
63
- };
64
-
65
- /**
66
- * Execute Select statement
67
- * @param tbl - table name
68
- * @param whereObj - where object
69
- * @param selectopts - select options
70
- * @returns {Promise<*>} return rows
71
- */
72
- const select = async (tbl, whereObj, selectopts = {}) => {
73
- const { where, values } = mkWhere(whereObj);
74
- const sql = `SELECT * FROM "${getTenantSchema()}"."${sqlsanitize(
75
- tbl
76
- )}" ${where} ${mkSelectOptions(selectopts)}`;
77
- sql_log(sql, values);
78
- const tq = await pool.query(sql, values);
79
-
80
- return tq.rows;
81
- };
82
-
83
- /**
84
- * Reset DB Schema using drop schema and recreate it
85
- * Atterntion! You will lost data after call this function!
86
- * @param schema - db schema name
87
- * @returns {Promise<void>} no result
88
- */
89
- const drop_reset_schema = async (schema) => {
90
- const sql = `DROP SCHEMA IF EXISTS "${schema}" CASCADE;
91
- CREATE SCHEMA "${schema}";
92
- GRANT ALL ON SCHEMA "${schema}" TO postgres;
93
- GRANT ALL ON SCHEMA "${schema}" TO "public" ;
94
- COMMENT ON SCHEMA "${schema}" IS 'standard public schema';`;
95
- sql_log(sql);
96
-
97
- await pool.query(sql);
98
- };
99
-
100
- /**
101
- * Get count of rows in table
102
- * @param tbl - table name
103
- * @param whereObj - where object
104
- * @returns {Promise<number>} count of tables
105
- */
106
- const count = async (tbl, whereObj) => {
107
- const { where, values } = mkWhere(whereObj);
108
- const sql = `SELECT COUNT(*) FROM "${getTenantSchema()}"."${sqlsanitize(
109
- tbl
110
- )}" ${where}`;
111
- sql_log(sql, values);
112
- const tq = await pool.query(sql, values);
113
-
114
- return parseInt(tq.rows[0].count);
115
- };
116
-
117
- /**
118
- * Get version of PostgreSQL
119
- * @param short - if true return short version info else full version info
120
- * @returns {Promise<*>} returns version
121
- */
122
- const getVersion = async (short) => {
123
- const sql = `SELECT version();`;
124
- sql_log(sql);
125
- const tq = await pool.query(sql);
126
- const v = tq.rows[0].version;
127
- if (short) {
128
- const ws = v.split(" ");
129
- return ws[1];
130
- }
131
- return v;
132
- };
133
-
134
- /**
135
- * Delete rows in table
136
- * @param tbl - table name
137
- * @param whereObj - where object
138
- * @returns {Promise<*>} result of delete execution
139
- */
140
- const deleteWhere = async (tbl, whereObj, opts = {}) => {
141
- const { where, values } = mkWhere(whereObj);
142
- const sql = `delete FROM "${getTenantSchema()}"."${sqlsanitize(
143
- tbl
144
- )}" ${where}`;
145
- sql_log(sql, values);
146
-
147
- const tq = await (opts.client || pool).query(sql, values);
148
-
149
- return tq.rows;
150
- };
151
-
152
- /**
153
- * Insert rows into table
154
- * @param tbl - table name
155
- * @param obj - columns names and data
156
- * @param opts - columns attributes
157
- * @returns {Promise<*>} returns primary key column or Id column value. If primary key column is not defined then return value of Id column.
158
- */
159
- const insert = async (tbl, obj, opts = {}) => {
160
- const kvs = Object.entries(obj);
161
- const fnameList = kvs.map(([k, v]) => `"${sqlsanitize(k)}"`).join();
162
- var valPosList = [];
163
- var valList = [];
164
- const schema = getTenantSchema();
165
- kvs.forEach(([k, v]) => {
166
- if (v && v.next_version_by_id) {
167
- valPosList.push(
168
- `coalesce((select max(_version) from "${schema}"."${sqlsanitize(
169
- tbl
170
- )}" where id=${+v.next_version_by_id}), 0)+1`
171
- );
172
- } else {
173
- valList.push(v);
174
- valPosList.push(`$${valList.length}`);
175
- }
176
- });
177
- const sql =
178
- valPosList.length > 0
179
- ? `insert into "${schema}"."${sqlsanitize(
180
- tbl
181
- )}"(${fnameList}) values(${valPosList.join()}) returning ${
182
- opts.noid ? "*" : opts.pk_name || "id"
183
- }`
184
- : `insert into "${schema}"."${sqlsanitize(
185
- tbl
186
- )}" DEFAULT VALUES returning ${opts.noid ? "*" : opts.pk_name || "id"}`;
187
- sql_log(sql, valList);
188
- const { rows } = await (opts.client || pool).query(sql, valList);
189
- if (opts.noid) return;
190
- else return rows[0][opts.pk_name || "id"];
191
- };
192
-
193
- /**
194
- * Update table records
195
- * @param tbl - table name
196
- * @param obj - columns names and data
197
- * @param id - id of record (primary key column value)
198
- * @param opts - columns attributes
199
- * @returns {Promise<void>} no result
200
- */
201
- const update = async (tbl, obj, id, opts = {}) => {
202
- const kvs = Object.entries(obj);
203
- const assigns = kvs
204
- .map(([k, v], ix) => `"${sqlsanitize(k)}"=$${ix + 1}`)
205
- .join();
206
- var valList = kvs.map(([k, v]) => v);
207
- // TBD check that is correct - because in insert function opts.noid ? "*" : opts.pk_name || "id"
208
- //valList.push(id === "undefined"? obj[opts.pk_name]: id);
209
- valList.push(id === "undefined" ? obj[opts.pk_name || "id"] : id);
210
- const q = `update "${getTenantSchema()}"."${sqlsanitize(
211
- tbl
212
- )}" set ${assigns} where ${opts.pk_name || "id"}=$${kvs.length + 1}`;
213
- sql_log(q, valList);
214
- await pool.query(q, valList);
215
- };
216
-
217
- /**
218
- * Select one record
219
- * @param tbl - table name
220
- * @param where - where object
221
- * @returns {Promise<*>} return first record from sql result
222
- */
223
- const selectOne = async (tbl, where) => {
224
- const rows = await select(tbl, where);
225
- if (rows.length === 0) {
226
- const w = mkWhere(where);
227
- throw new Error(`no ${tbl} ${w.where} are ${w.values}`);
228
- } else return rows[0];
229
- };
230
-
231
- /**
232
- * Select one record or null if no records
233
- * @param tbl - table name
234
- * @param where - where object
235
- * @returns {Promise<null|*>} - null if no record or first record data
236
- */
237
- const selectMaybeOne = async (tbl, where) => {
238
- const rows = await select(tbl, where);
239
- if (rows.length === 0) return null;
240
- else return rows[0];
241
- };
242
-
243
- /**
244
- * Open db connection
245
- * Only for PG.
246
- * @returns {Promise<*>} db connection object
247
- */
248
- // TBD Currently this function supported only for PG
249
- const getClient = async () => await pool.connect();
250
-
251
- /**
252
- * Reset sequence
253
- * Only for PG
254
- * @param tblname - table name
255
- * @returns {Promise<void>} no result
256
- */
257
- const reset_sequence = async (tblname) => {
258
- const sql = `SELECT setval(pg_get_serial_sequence('"${getTenantSchema()}"."${sqlsanitize(
259
- tblname
260
- )}"', 'id'), coalesce(max(id),0) + 1, false) FROM "${getTenantSchema()}"."${sqlsanitize(
261
- tblname
262
- )}";`;
263
- await pool.query(sql);
264
- };
265
-
266
- /**
267
- * Add unique constraint
268
- * @param table_name - table name
269
- * @param field_names - list of columns (members of constraint)
270
- * @returns {Promise<void>} no result
271
- */
272
- const add_unique_constraint = async (table_name, field_names) => {
273
- // TBD check that there are no problems with lenght of constraint name
274
- const sql = `alter table "${getTenantSchema()}"."${sqlsanitize(
275
- table_name
276
- )}" add CONSTRAINT "${sqlsanitize(table_name)}_${field_names
277
- .map((f) => sqlsanitize(f))
278
- .join("_")}_unique" UNIQUE (${field_names
279
- .map((f) => `"${sqlsanitize(f)}"`)
280
- .join(",")});`;
281
- sql_log(sql);
282
- await pool.query(sql);
283
- };
284
-
285
- /**
286
- * Drop unique constraint
287
- * @param table_name - table name
288
- * @param field_names - list of columns (members of constraint)
289
- * @returns {Promise<void>} no results
290
- */
291
- const drop_unique_constraint = async (table_name, field_names) => {
292
- // TBD check that there are no problems with lenght of constraint name
293
- const sql = `alter table "${getTenantSchema()}"."${sqlsanitize(
294
- table_name
295
- )}" drop CONSTRAINT "${sqlsanitize(table_name)}_${field_names
296
- .map((f) => sqlsanitize(f))
297
- .join("_")}_unique";`;
298
- sql_log(sql);
299
- await pool.query(sql);
300
- };
301
- /**
302
- * Copy data from CSV to table?
303
- * Only for PG
304
- * @param fileStream - file stream
305
- * @param tableName - table name
306
- * @param fieldNames - list of columns
307
- * @param client - db connection
308
- * @returns {Promise<unknown>} new Promise
309
- */
310
- const copyFrom1 = (fileStream, tableName, fieldNames, client) => {
311
- // TBD describe difference between CopyFrom and CopyFrom1
312
- const quote = (s) => `"${s}"`;
313
- const sql = `COPY "${sqlsanitize(tableName)}" (${fieldNames
314
- .map(quote)
315
- .join(",")}) FROM STDIN CSV HEADER`;
316
- sql_log(sql);
317
-
318
- var stream = client.query(copyStreams.from(sql));
319
-
320
- return new Promise((resolve, reject) => {
321
- fileStream.on("error", reject);
322
- stream.on("error", reject);
323
- stream.on("finish", resolve);
324
- fileStream.pipe(stream).on("error", reject);
325
- });
326
- };
327
- /**
328
- * Copy data from CSV to table?
329
- * Only for PG
330
- * @param fileStream - file stream
331
- * @param tableName - table name
332
- * @param fieldNames - list of columns
333
- * @param client - db connection
334
- * @returns {Promise<void>} no results
335
- */
336
- const copyFrom = async (fileStream, tableName, fieldNames, client) => {
337
- // TBD describe difference between CopyFrom and CopyFrom1
338
- const quote = (s) => `"${s}"`;
339
- const sql = `COPY "${sqlsanitize(tableName)}" (${fieldNames
340
- .map(quote)
341
- .join(",")}) FROM STDIN CSV HEADER`;
342
- sql_log(sql);
343
-
344
- const stream = client.query(copyStreams.from(sql));
345
- return await promisify(pipeline)(fileStream, stream);
346
- };
347
-
348
- module.exports = {
349
- pool,
350
- query: (text, params) => {
351
- sql_log(text, params);
352
- return pool.query(text, params);
353
- },
354
- select,
355
- selectOne,
356
- selectMaybeOne,
357
- count,
358
- insert,
359
- update,
360
- deleteWhere,
361
- close,
362
- sql_log,
363
- changeConnection,
364
- set_sql_logging,
365
- get_sql_logging,
366
- getClient,
367
- mkWhere,
368
- drop_reset_schema,
369
- add_unique_constraint,
370
- drop_unique_constraint,
371
- reset_sequence,
372
- getVersion,
373
- copyFrom,
374
- };