@saltcorn/sql 0.5.7 → 0.6.0
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/index.js +28 -21
- package/package.json +10 -2
- package/table-provider.js +78 -25
- package/tests/data.js +85 -0
- package/tests/sqlview.test.js +81 -0
- package/tests/table-provider.test.js +107 -0
package/index.js
CHANGED
|
@@ -20,8 +20,8 @@ const {
|
|
|
20
20
|
} = require("@saltcorn/markup/tags");
|
|
21
21
|
const { mkTable } = require("@saltcorn/markup");
|
|
22
22
|
const { readState } = require("@saltcorn/data/plugin-helper");
|
|
23
|
-
|
|
24
|
-
const
|
|
23
|
+
const { features } = require("@saltcorn/data/db/state");
|
|
24
|
+
const Handlebars = require("handlebars");
|
|
25
25
|
|
|
26
26
|
const configuration_workflow = () =>
|
|
27
27
|
new Workflow({
|
|
@@ -51,7 +51,9 @@ const configuration_workflow = () =>
|
|
|
51
51
|
label: "Output type",
|
|
52
52
|
type: "String",
|
|
53
53
|
required: true,
|
|
54
|
-
attributes: {
|
|
54
|
+
attributes: {
|
|
55
|
+
options: ["Table", "JSON", "HTML", "HTML with handlebars"],
|
|
56
|
+
},
|
|
55
57
|
},
|
|
56
58
|
{
|
|
57
59
|
name: "html_code",
|
|
@@ -59,14 +61,17 @@ const configuration_workflow = () =>
|
|
|
59
61
|
input_type: "code",
|
|
60
62
|
attributes: { mode: "text/html" },
|
|
61
63
|
showIf: { output_type: "HTML" },
|
|
64
|
+
sublabel:
|
|
65
|
+
"Use interpolations (<code>{{ }}</code>) to access query result in he <code>rows</code> variable. Example: <code><script>const rows = {{ JSON.stringify(rows) }}</script></code>",
|
|
62
66
|
},
|
|
63
67
|
{
|
|
64
|
-
|
|
65
|
-
label: " ",
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
name: "html_code",
|
|
69
|
+
label: "HTML Code",
|
|
70
|
+
input_type: "code",
|
|
71
|
+
attributes: { mode: "text/html" },
|
|
72
|
+
showIf: { output_type: "HTML with handlebars" },
|
|
73
|
+
sublabel:
|
|
74
|
+
"Use handlebars to access query result in the <code>rows</code> variable. Example: <code>{{#each rows}}<h1>{{this.name}}</h1>{{/each}}</code>",
|
|
70
75
|
},
|
|
71
76
|
],
|
|
72
77
|
});
|
|
@@ -82,7 +87,7 @@ const run = async (
|
|
|
82
87
|
viewname,
|
|
83
88
|
{ sql, output_type, state_parameters, html_code },
|
|
84
89
|
state,
|
|
85
|
-
{ req }
|
|
90
|
+
{ req },
|
|
86
91
|
) => {
|
|
87
92
|
const is_sqlite = db.isSQLite;
|
|
88
93
|
|
|
@@ -104,7 +109,7 @@ const run = async (
|
|
|
104
109
|
if (!is_sqlite) {
|
|
105
110
|
await client.query(`SET LOCAL search_path TO "${db.getTenantSchema()}";`);
|
|
106
111
|
await client.query(
|
|
107
|
-
`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY
|
|
112
|
+
`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;`,
|
|
108
113
|
);
|
|
109
114
|
}
|
|
110
115
|
qres = await client.query(sql, phValues);
|
|
@@ -114,26 +119,23 @@ const run = async (
|
|
|
114
119
|
}
|
|
115
120
|
switch (output_type) {
|
|
116
121
|
case "HTML":
|
|
117
|
-
/*const template = _.template(html_code || "", {
|
|
118
|
-
evaluate: /\{\{#(.+?)\}\}/g,
|
|
119
|
-
interpolate: /\{\{([^#].+?)\}\}/g,
|
|
120
|
-
});
|
|
121
|
-
console.log("template", viewname, state, html_code);*/
|
|
122
122
|
return interpolate(
|
|
123
123
|
html_code,
|
|
124
124
|
{ rows: qres.rows },
|
|
125
125
|
req.user,
|
|
126
|
-
`HTML code interpolation in view ${viewname}
|
|
126
|
+
`HTML code interpolation in view ${viewname}`,
|
|
127
127
|
);
|
|
128
128
|
//return template();
|
|
129
|
-
|
|
129
|
+
case "HTML with handlebars":
|
|
130
|
+
const template = Handlebars.compile(html_code || "");
|
|
131
|
+
return template({ rows: qres.rows });
|
|
130
132
|
case "JSON":
|
|
131
133
|
return `<pre>${JSON.stringify(qres.rows, null, 2)}</pre>`;
|
|
132
134
|
|
|
133
135
|
default: //Table
|
|
134
136
|
return mkTable(
|
|
135
137
|
qres.fields.map((field) => ({ label: field.name, key: field.name })),
|
|
136
|
-
qres.rows
|
|
138
|
+
qres.rows,
|
|
137
139
|
);
|
|
138
140
|
}
|
|
139
141
|
};
|
|
@@ -157,21 +159,26 @@ module.exports = {
|
|
|
157
159
|
if (!is_sqlite) {
|
|
158
160
|
db.sql_log(`SET LOCAL search_path TO "${db.getTenantSchema()}";`);
|
|
159
161
|
await client.query(
|
|
160
|
-
`SET LOCAL search_path TO "${db.getTenantSchema()}"
|
|
162
|
+
`SET LOCAL search_path TO "${db.getTenantSchema()}";`,
|
|
161
163
|
);
|
|
162
164
|
db.sql_log(`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;`);
|
|
163
165
|
await client.query(
|
|
164
|
-
`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY
|
|
166
|
+
`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;`,
|
|
165
167
|
);
|
|
166
168
|
}
|
|
167
169
|
db.sql_log(query, parameters || []);
|
|
168
170
|
const qres = await client.query(query, parameters || []);
|
|
169
171
|
db.sql_log("ROLLBACK;");
|
|
170
172
|
await client.query(`ROLLBACK;`);
|
|
173
|
+
if (!is_sqlite) client.release();
|
|
171
174
|
return qres;
|
|
172
175
|
},
|
|
173
176
|
isAsync: true,
|
|
174
177
|
description: "Run an SQL query",
|
|
178
|
+
arguments: [
|
|
179
|
+
{ name: "sql_query", type: "String", required: true },
|
|
180
|
+
{ name: "parameters", type: "JSON", tstype: "any[]" },
|
|
181
|
+
],
|
|
175
182
|
},
|
|
176
183
|
},
|
|
177
184
|
viewtemplates: [
|
package/package.json
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/sql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Actions and views based on SQL",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@saltcorn/markup": "^0.6.0",
|
|
8
8
|
"@saltcorn/data": "^0.6.0",
|
|
9
9
|
"underscore": "1.13.6",
|
|
10
|
+
"handlebars": "4.7.7",
|
|
10
11
|
"node-sql-parser": "5.3.10",
|
|
11
12
|
"sqlstring": "^2.3.3"
|
|
12
13
|
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"jest": "^29.7.0"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "jest tests --runInBand"
|
|
19
|
+
},
|
|
13
20
|
"author": "Tom Nielsen",
|
|
14
21
|
"license": "MIT",
|
|
15
22
|
"eslintConfig": {
|
|
@@ -19,7 +26,8 @@
|
|
|
19
26
|
},
|
|
20
27
|
"env": {
|
|
21
28
|
"node": true,
|
|
22
|
-
"es6": true
|
|
29
|
+
"es6": true,
|
|
30
|
+
"jest/globals": true
|
|
23
31
|
},
|
|
24
32
|
"rules": {
|
|
25
33
|
"no-unused-vars": "off",
|
package/table-provider.js
CHANGED
|
@@ -12,9 +12,27 @@ const { mkTable } = require("@saltcorn/markup");
|
|
|
12
12
|
const { pre, code } = require("@saltcorn/markup/tags");
|
|
13
13
|
const parser = new Parser();
|
|
14
14
|
const _ = require("underscore");
|
|
15
|
+
const { features } = require("@saltcorn/data/db/state");
|
|
16
|
+
|
|
17
|
+
const on_create = async (table) => {
|
|
18
|
+
if (table?.provider_cfg?.sql_view) {
|
|
19
|
+
await db.query(
|
|
20
|
+
`CREATE OR REPLACE VIEW "${db.sqlsanitize(table.name)}" AS ${table?.provider_cfg?.sql}`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
15
24
|
|
|
16
25
|
const configuration_workflow = (req) =>
|
|
17
26
|
new Workflow({
|
|
27
|
+
onDone: async (ctx) => {
|
|
28
|
+
const table = Table.findOne(ctx.table_id);
|
|
29
|
+
if (table && ctx?.sql_view) {
|
|
30
|
+
await db.query(
|
|
31
|
+
`CREATE OR REPLACE VIEW "${db.sqlsanitize(table.name)}" AS ${ctx.sql}`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return ctx;
|
|
35
|
+
},
|
|
18
36
|
steps: [
|
|
19
37
|
{
|
|
20
38
|
name: "query",
|
|
@@ -46,12 +64,24 @@ const configuration_workflow = (req) =>
|
|
|
46
64
|
}
|
|
47
65
|
},
|
|
48
66
|
},
|
|
67
|
+
...(features.table_create_callback
|
|
68
|
+
? [
|
|
69
|
+
{
|
|
70
|
+
label: "Create SQL VIEW",
|
|
71
|
+
type: "Bool",
|
|
72
|
+
name: "sql_view",
|
|
73
|
+
},
|
|
74
|
+
]
|
|
75
|
+
: []),
|
|
49
76
|
{
|
|
50
77
|
label: "Ignore where/order",
|
|
51
78
|
sublabel:
|
|
52
79
|
"Always use this SQL directly without attempting to modify it",
|
|
53
80
|
type: "Bool",
|
|
54
81
|
name: "ignore_where",
|
|
82
|
+
showIf: features.table_create_callback
|
|
83
|
+
? { sql_view: false }
|
|
84
|
+
: undefined,
|
|
55
85
|
},
|
|
56
86
|
],
|
|
57
87
|
});
|
|
@@ -66,10 +96,10 @@ const configuration_workflow = (req) =>
|
|
|
66
96
|
label: field.name,
|
|
67
97
|
key: field.name,
|
|
68
98
|
})),
|
|
69
|
-
qres.rows?.slice?.(0, 5)
|
|
99
|
+
qres.rows?.slice?.(0, 5),
|
|
70
100
|
);
|
|
71
101
|
const pkey_options = getState().type_names.filter(
|
|
72
|
-
(tnm) => getState().types[tnm]?.primaryKey
|
|
102
|
+
(tnm) => getState().types[tnm]?.primaryKey,
|
|
73
103
|
);
|
|
74
104
|
const tables = await Table.find({});
|
|
75
105
|
|
|
@@ -197,7 +227,7 @@ const getSqlQuery = (sql, cfg, where, opts) => {
|
|
|
197
227
|
as: null,
|
|
198
228
|
}
|
|
199
229
|
: (ast[0].columns || []).find(
|
|
200
|
-
(c) => k === c.as || (!c.as && k === c.expr?.column)
|
|
230
|
+
(c) => k === c.as || (!c.as && k === c.expr?.column),
|
|
201
231
|
);
|
|
202
232
|
const sqlExprCol =
|
|
203
233
|
ast[0].columns == "*"
|
|
@@ -210,22 +240,22 @@ const getSqlQuery = (sql, cfg, where, opts) => {
|
|
|
210
240
|
const sqlAggrCol = (ast[0].columns || []).find(
|
|
211
241
|
(c) =>
|
|
212
242
|
c.expr?.type === "aggr_func" &&
|
|
213
|
-
c.expr?.name?.toUpperCase() === k.toUpperCase()
|
|
243
|
+
c.expr?.name?.toUpperCase() === k.toUpperCase(),
|
|
214
244
|
);
|
|
215
245
|
|
|
216
246
|
let left = sqlExprCol
|
|
217
247
|
? { ...sqlExprCol.expr, as: null }
|
|
218
248
|
: sqlAggrCol
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
249
|
+
? { ...sqlAggrCol.expr }
|
|
250
|
+
: {
|
|
251
|
+
type: "column_ref",
|
|
252
|
+
table: sqlCol?.expr?.table,
|
|
253
|
+
column: sqlCol?.expr?.column || db.sqlsanitize(k),
|
|
254
|
+
};
|
|
225
255
|
//console.log({ k, sqlCol, sqlExprCol });
|
|
226
256
|
if (!sqlCol) {
|
|
227
257
|
const starCol = (ast[0].columns || []).find(
|
|
228
|
-
(c) => c.type === "star_ref"
|
|
258
|
+
(c) => c.type === "star_ref",
|
|
229
259
|
);
|
|
230
260
|
if (starCol)
|
|
231
261
|
left = {
|
|
@@ -241,14 +271,14 @@ const getSqlQuery = (sql, cfg, where, opts) => {
|
|
|
241
271
|
wherek?.ilike && !sqlAggrCol
|
|
242
272
|
? "ILIKE"
|
|
243
273
|
: wherek?.gt && !sqlAggrCol
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
274
|
+
? wherek.equal
|
|
275
|
+
? ">="
|
|
276
|
+
: ">"
|
|
277
|
+
: wherek?.lt && !sqlAggrCol
|
|
278
|
+
? wherek.equal
|
|
279
|
+
? "<="
|
|
280
|
+
: "<"
|
|
281
|
+
: "=",
|
|
252
282
|
left,
|
|
253
283
|
right:
|
|
254
284
|
wherek?.ilike && !sqlAggrCol
|
|
@@ -469,33 +499,56 @@ const countRows = async (cfg, where, opts) => {
|
|
|
469
499
|
//console.trace({ sqlQ, phValues, opts });
|
|
470
500
|
db.sql_log(
|
|
471
501
|
`select count(*) from (${ensure_no_final_semicolon(sqlQ)})`,
|
|
472
|
-
phValues
|
|
502
|
+
phValues,
|
|
473
503
|
);
|
|
474
504
|
const qres = await client.query(
|
|
475
505
|
`select count(*) from (${ensure_no_final_semicolon(sqlQ)})`,
|
|
476
|
-
phValues
|
|
506
|
+
phValues,
|
|
477
507
|
);
|
|
478
508
|
qres.query = sqlQ;
|
|
479
509
|
db.sql_log("ROLLBACK;");
|
|
480
510
|
await client.query(`ROLLBACK;`);
|
|
481
511
|
|
|
482
512
|
if (!is_sqlite) client.release(true);
|
|
483
|
-
return qres.rows[0].count;
|
|
513
|
+
return +qres.rows[0].count;
|
|
484
514
|
};
|
|
485
515
|
|
|
486
516
|
module.exports = {
|
|
487
517
|
"SQL query": {
|
|
488
518
|
configuration_workflow,
|
|
489
519
|
fields: (cfg) => cfg?.columns || [],
|
|
490
|
-
|
|
520
|
+
on_create,
|
|
521
|
+
get_table: (cfg, table) => {
|
|
522
|
+
let syntheticTable;
|
|
523
|
+
if (cfg?.sql_view && table)
|
|
524
|
+
syntheticTable = new Table({
|
|
525
|
+
...table,
|
|
526
|
+
provider_name: undefined,
|
|
527
|
+
provider_cfg: undefined,
|
|
528
|
+
});
|
|
491
529
|
return {
|
|
530
|
+
disableFiltering: true,
|
|
492
531
|
getRows: async (where, opts) => {
|
|
493
|
-
|
|
532
|
+
if (syntheticTable) return await syntheticTable.getRows(where, opts);
|
|
533
|
+
const qres = await runQuery(cfg, where || {}, opts || {});
|
|
494
534
|
return qres.rows;
|
|
495
535
|
},
|
|
496
536
|
countRows: async (where, opts) => {
|
|
497
|
-
|
|
537
|
+
if (syntheticTable)
|
|
538
|
+
return await syntheticTable.countRows(where, opts);
|
|
539
|
+
|
|
540
|
+
return await countRows(cfg, where || {}, opts || {});
|
|
498
541
|
},
|
|
542
|
+
...(syntheticTable
|
|
543
|
+
? {
|
|
544
|
+
distinctValues: async (fldNm, opts) => {
|
|
545
|
+
return await syntheticTable.distinctValues(fldNm, opts);
|
|
546
|
+
},
|
|
547
|
+
getJoinedRows: async (opts) => {
|
|
548
|
+
return await syntheticTable.getJoinedRows(opts);
|
|
549
|
+
},
|
|
550
|
+
}
|
|
551
|
+
: {}),
|
|
499
552
|
};
|
|
500
553
|
},
|
|
501
554
|
},
|
package/tests/data.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const sqlusers = {
|
|
2
|
+
min_role_read: 1,
|
|
3
|
+
min_role_write: 1,
|
|
4
|
+
provider_name: "SQL query",
|
|
5
|
+
provider_cfg: {
|
|
6
|
+
sql: "select * from users;",
|
|
7
|
+
columns: [
|
|
8
|
+
{
|
|
9
|
+
name: "id",
|
|
10
|
+
type: "Integer",
|
|
11
|
+
label: "id",
|
|
12
|
+
primary_key: true,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "email",
|
|
16
|
+
type: "String",
|
|
17
|
+
label: "email",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "password",
|
|
21
|
+
type: "String",
|
|
22
|
+
label: "password",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "role_id",
|
|
26
|
+
type: "Integer",
|
|
27
|
+
label: "role id",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "reset_password_token",
|
|
31
|
+
type: "String",
|
|
32
|
+
label: "reset password token",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "reset_password_expiry",
|
|
36
|
+
type: "String",
|
|
37
|
+
label: "reset password expiry",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "language",
|
|
41
|
+
type: "String",
|
|
42
|
+
label: "language",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "disabled",
|
|
46
|
+
type: "Bool",
|
|
47
|
+
label: "disabled",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "api_token",
|
|
51
|
+
type: "String",
|
|
52
|
+
label: "api token",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "_attributes",
|
|
56
|
+
type: "JSON",
|
|
57
|
+
label: " attributes",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "verification_token",
|
|
61
|
+
type: "String",
|
|
62
|
+
label: "verification token",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "verified_on",
|
|
66
|
+
type: "String",
|
|
67
|
+
label: "verified on",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "last_mobile_login",
|
|
71
|
+
type: "String",
|
|
72
|
+
label: "last mobile login",
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
//ignore_where: true,
|
|
76
|
+
},
|
|
77
|
+
ownership_formula: null,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const sqlusers_view = {
|
|
81
|
+
...sqlusers,
|
|
82
|
+
provider_cfg: { ...sqlusers.provider_cfg, sql_view: true },
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
module.exports = { sqlusers, sqlusers_view };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
2
|
+
const View = require("@saltcorn/data/models/view");
|
|
3
|
+
const Table = require("@saltcorn/data/models/table");
|
|
4
|
+
const Plugin = require("@saltcorn/data/models/plugin");
|
|
5
|
+
|
|
6
|
+
const { mockReqRes } = require("@saltcorn/data/tests/mocks");
|
|
7
|
+
const { afterAll, beforeAll, describe, it, expect } = require("@jest/globals");
|
|
8
|
+
const db = require("@saltcorn/data/db");
|
|
9
|
+
|
|
10
|
+
afterAll(require("@saltcorn/data/db").close);
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
await require("@saltcorn/data/db/reset_schema")();
|
|
13
|
+
await require("@saltcorn/data/db/fixtures")();
|
|
14
|
+
|
|
15
|
+
getState().registerPlugin("base", require("@saltcorn/data/base-plugin"));
|
|
16
|
+
getState().registerPlugin("@saltcorn/sql", require(".."));
|
|
17
|
+
//db.set_sql_logging(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("sql view", () => {
|
|
21
|
+
it("runs", async () => {
|
|
22
|
+
const view = new View({
|
|
23
|
+
name: "BookSQLView",
|
|
24
|
+
description: "",
|
|
25
|
+
viewtemplate: "SQLView",
|
|
26
|
+
configuration: {
|
|
27
|
+
sql: "select id, author, pages from books order by id;",
|
|
28
|
+
html_code: `<script>const boooks = {{ JSON.stringify(rows) }}<script>`,
|
|
29
|
+
output_type: "HTML",
|
|
30
|
+
state_parameters: "",
|
|
31
|
+
},
|
|
32
|
+
min_role: 1,
|
|
33
|
+
table: null,
|
|
34
|
+
});
|
|
35
|
+
const result = await view.run({}, mockReqRes);
|
|
36
|
+
|
|
37
|
+
expect(result).toBe(
|
|
38
|
+
'<script>const boooks = [{"id":1,"author":"Herman Melville","pages":967},{"id":2,"author":"Leo Tolstoy","pages":728}]<script>',
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
it("run with state params", async () => {
|
|
42
|
+
const view = new View({
|
|
43
|
+
name: "BookSQLView",
|
|
44
|
+
description: "",
|
|
45
|
+
viewtemplate: "SQLView",
|
|
46
|
+
configuration: {
|
|
47
|
+
sql: "select id, author, pages from books where id = $1",
|
|
48
|
+
html_code: `<script>const boooks = {{ JSON.stringify(rows) }}<script>`,
|
|
49
|
+
output_type: "HTML",
|
|
50
|
+
state_parameters: "id",
|
|
51
|
+
},
|
|
52
|
+
min_role: 1,
|
|
53
|
+
table: null,
|
|
54
|
+
});
|
|
55
|
+
const result = await view.run({id:2}, mockReqRes);
|
|
56
|
+
|
|
57
|
+
expect(result).toBe(
|
|
58
|
+
'<script>const boooks = [{"id":2,"author":"Leo Tolstoy","pages":728}]<script>',
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
it("runs with handlebars", async () => {
|
|
62
|
+
const view = new View({
|
|
63
|
+
name: "BookSQLView",
|
|
64
|
+
description: "",
|
|
65
|
+
viewtemplate: "SQLView",
|
|
66
|
+
configuration: {
|
|
67
|
+
sql: "select id, author, pages from books order by id;",
|
|
68
|
+
html_code: `{{#each rows}}<h1>{{this.author}}</h1>{{/each}}`,
|
|
69
|
+
output_type: "HTML with handlebars",
|
|
70
|
+
state_parameters: "",
|
|
71
|
+
},
|
|
72
|
+
min_role: 1,
|
|
73
|
+
table: null,
|
|
74
|
+
});
|
|
75
|
+
const result = await view.run({}, mockReqRes);
|
|
76
|
+
|
|
77
|
+
expect(result).toBe(
|
|
78
|
+
'<h1>Herman Melville</h1><h1>Leo Tolstoy</h1>',
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
2
|
+
const View = require("@saltcorn/data/models/view");
|
|
3
|
+
const Table = require("@saltcorn/data/models/table");
|
|
4
|
+
const Plugin = require("@saltcorn/data/models/plugin");
|
|
5
|
+
|
|
6
|
+
const { mockReqRes } = require("@saltcorn/data/tests/mocks");
|
|
7
|
+
const { afterAll, beforeAll, describe, it, expect } = require("@jest/globals");
|
|
8
|
+
const db = require("@saltcorn/data/db");
|
|
9
|
+
|
|
10
|
+
afterAll(require("@saltcorn/data/db").close);
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
await require("@saltcorn/data/db/reset_schema")();
|
|
13
|
+
await require("@saltcorn/data/db/fixtures")();
|
|
14
|
+
|
|
15
|
+
getState().registerPlugin("base", require("@saltcorn/data/base-plugin"));
|
|
16
|
+
getState().registerPlugin("@saltcorn/sql", require(".."));
|
|
17
|
+
//db.set_sql_logging(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// run with:
|
|
21
|
+
// saltcorn dev:plugin-test -d ~/sql/
|
|
22
|
+
|
|
23
|
+
//jest.setTimeout(30000);
|
|
24
|
+
|
|
25
|
+
describe("sql table provider", () => {
|
|
26
|
+
it("creates table", async () => {
|
|
27
|
+
await Table.create("sqlusers", require("./data").sqlusers);
|
|
28
|
+
await getState().refresh_tables(false);
|
|
29
|
+
});
|
|
30
|
+
it("counts table", async () => {
|
|
31
|
+
const table = Table.findOne("sqlusers");
|
|
32
|
+
const nus = await table.countRows({});
|
|
33
|
+
expect(nus).toBe(3);
|
|
34
|
+
const nadmin = await table.countRows({ role_id: 1 });
|
|
35
|
+
expect(nadmin).toBe(1);
|
|
36
|
+
});
|
|
37
|
+
it("gets rows from table", async () => {
|
|
38
|
+
const table = Table.findOne("sqlusers");
|
|
39
|
+
const us = await table.getRows({}, { orderBy: "id" });
|
|
40
|
+
expect(us.length).toBe(3);
|
|
41
|
+
expect(us[0].id).toBe(1);
|
|
42
|
+
|
|
43
|
+
const twous = await table.getRows({}, { limit: 2 });
|
|
44
|
+
expect(twous.length).toBe(2);
|
|
45
|
+
const twous1 = await table.getRows(
|
|
46
|
+
{},
|
|
47
|
+
{ limit: 2, orderBy: "id", orderDesc: true },
|
|
48
|
+
);
|
|
49
|
+
expect(twous1.length).toBe(2);
|
|
50
|
+
expect(twous1[0].id).toBe(3);
|
|
51
|
+
|
|
52
|
+
const admins = await table.getRows({ role_id: 1 });
|
|
53
|
+
expect(admins.length).toBe(1);
|
|
54
|
+
expect(admins[0].email).toBe("admin@foo.com");
|
|
55
|
+
const admin = await table.getRow({ role_id: 1 });
|
|
56
|
+
expect(admin.email).toBe("admin@foo.com");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe("view-based sql table provider", () => {
|
|
60
|
+
it("creates table", async () => {
|
|
61
|
+
await Table.create("sqlusersv", require("./data").sqlusers_view);
|
|
62
|
+
await getState().refresh_tables(false);
|
|
63
|
+
});
|
|
64
|
+
it("counts table", async () => {
|
|
65
|
+
const table = Table.findOne("sqlusersv");
|
|
66
|
+
const nus = await table.countRows({});
|
|
67
|
+
expect(nus).toBe(3);
|
|
68
|
+
const nadmin = await table.countRows({ role_id: 1 });
|
|
69
|
+
expect(nadmin).toBe(1);
|
|
70
|
+
});
|
|
71
|
+
it("gets rows from table", async () => {
|
|
72
|
+
const table = Table.findOne("sqlusersv");
|
|
73
|
+
const us = await table.getRows({}, { orderBy: "id" });
|
|
74
|
+
expect(us.length).toBe(3);
|
|
75
|
+
expect(us[0].id).toBe(1);
|
|
76
|
+
|
|
77
|
+
const twous = await table.getRows({}, { limit: 2 });
|
|
78
|
+
expect(twous.length).toBe(2);
|
|
79
|
+
const twous1 = await table.getRows(
|
|
80
|
+
{},
|
|
81
|
+
{ limit: 2, orderBy: "id", orderDesc: true },
|
|
82
|
+
);
|
|
83
|
+
expect(twous1.length).toBe(2);
|
|
84
|
+
expect(twous1[0].id).toBe(3);
|
|
85
|
+
|
|
86
|
+
const admins = await table.getRows({ role_id: 1 });
|
|
87
|
+
expect(admins.length).toBe(1);
|
|
88
|
+
expect(admins[0].email).toBe("admin@foo.com");
|
|
89
|
+
const admin = await table.getRow({ role_id: 1 });
|
|
90
|
+
expect(admin.email).toBe("admin@foo.com");
|
|
91
|
+
});
|
|
92
|
+
it("has distinct values", async () => {
|
|
93
|
+
const table = Table.findOne("sqlusersv");
|
|
94
|
+
const vs = await table.distinctValues("role_id");
|
|
95
|
+
expect(vs.length).toBe(3);
|
|
96
|
+
expect(vs).toContain(80);
|
|
97
|
+
});
|
|
98
|
+
it("gets joined values", async () => {
|
|
99
|
+
const table = Table.findOne("sqlusersv");
|
|
100
|
+
const vs = await table.getJoinedRows({});
|
|
101
|
+
expect(vs.length).toBe(3);
|
|
102
|
+
const vs2 = await table.getJoinedRows({ limit: 2 });
|
|
103
|
+
expect(vs2.length).toBe(2);
|
|
104
|
+
const admins = await table.getJoinedRows({ where: { role_id: 1 } });
|
|
105
|
+
expect(admins.length).toBe(1);
|
|
106
|
+
});
|
|
107
|
+
});
|