@saltcorn/sql 0.5.8 → 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.
- package/index.js +39 -14
- package/package.json +10 -2
- package/table-provider.js +82 -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
|
-
|
|
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:
|
|
67
74
|
"Use handlebars to access query result in the <code>rows</code> variable. Example: <code>{{#each rows}}<h1>{{this.name}}</h1>{{/each}}</code>",
|
|
68
|
-
),
|
|
69
|
-
showIf: { row_count: "Many" },
|
|
70
75
|
},
|
|
71
76
|
],
|
|
72
77
|
});
|
|
@@ -114,11 +119,6 @@ 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 },
|
|
@@ -126,7 +126,9 @@ const run = async (
|
|
|
126
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
|
|
|
@@ -184,6 +186,29 @@ module.exports = {
|
|
|
184
186
|
name: "SQLView",
|
|
185
187
|
display_state_form: false,
|
|
186
188
|
tableless: true,
|
|
189
|
+
copilot_generate_view_prompt: async ({ table }) => {
|
|
190
|
+
const tableLines = [];
|
|
191
|
+
const tables = await Table.find({});
|
|
192
|
+
tables.forEach((table) => {
|
|
193
|
+
const fieldLines = table.fields.map(
|
|
194
|
+
(f) =>
|
|
195
|
+
` * ${f.name} with type: ${f.pretty_type.replace(
|
|
196
|
+
"Key to",
|
|
197
|
+
"ForeignKey referencing",
|
|
198
|
+
)}.${f.description ? ` ${f.description}` : ""}`,
|
|
199
|
+
);
|
|
200
|
+
tableLines.push(
|
|
201
|
+
`${table.name}${
|
|
202
|
+
table.description ? `: ${table.description}.` : "."
|
|
203
|
+
} Contains the following fields:\n${fieldLines.join("\n")}`,
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
return `The database already contains the following tables:
|
|
207
|
+
|
|
208
|
+
${tableLines.join("\n\n")}
|
|
209
|
+
|
|
210
|
+
You can reference these tables when generating SQL`;
|
|
211
|
+
},
|
|
187
212
|
get_state_fields,
|
|
188
213
|
configuration_workflow,
|
|
189
214
|
run,
|
package/package.json
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/sql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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,25 @@ 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
|
+
default: true
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
: []),
|
|
49
77
|
{
|
|
50
78
|
label: "Ignore where/order",
|
|
51
79
|
sublabel:
|
|
52
80
|
"Always use this SQL directly without attempting to modify it",
|
|
53
81
|
type: "Bool",
|
|
54
82
|
name: "ignore_where",
|
|
83
|
+
showIf: features.table_create_callback
|
|
84
|
+
? { sql_view: false }
|
|
85
|
+
: undefined,
|
|
55
86
|
},
|
|
56
87
|
],
|
|
57
88
|
});
|
|
@@ -66,10 +97,10 @@ const configuration_workflow = (req) =>
|
|
|
66
97
|
label: field.name,
|
|
67
98
|
key: field.name,
|
|
68
99
|
})),
|
|
69
|
-
qres.rows?.slice?.(0, 5)
|
|
100
|
+
qres.rows?.slice?.(0, 5),
|
|
70
101
|
);
|
|
71
102
|
const pkey_options = getState().type_names.filter(
|
|
72
|
-
(tnm) => getState().types[tnm]?.primaryKey
|
|
103
|
+
(tnm) => getState().types[tnm]?.primaryKey,
|
|
73
104
|
);
|
|
74
105
|
const tables = await Table.find({});
|
|
75
106
|
|
|
@@ -197,7 +228,7 @@ const getSqlQuery = (sql, cfg, where, opts) => {
|
|
|
197
228
|
as: null,
|
|
198
229
|
}
|
|
199
230
|
: (ast[0].columns || []).find(
|
|
200
|
-
(c) => k === c.as || (!c.as && k === c.expr?.column)
|
|
231
|
+
(c) => k === c.as || (!c.as && k === c.expr?.column),
|
|
201
232
|
);
|
|
202
233
|
const sqlExprCol =
|
|
203
234
|
ast[0].columns == "*"
|
|
@@ -210,22 +241,22 @@ const getSqlQuery = (sql, cfg, where, opts) => {
|
|
|
210
241
|
const sqlAggrCol = (ast[0].columns || []).find(
|
|
211
242
|
(c) =>
|
|
212
243
|
c.expr?.type === "aggr_func" &&
|
|
213
|
-
c.expr?.name?.toUpperCase() === k.toUpperCase()
|
|
244
|
+
c.expr?.name?.toUpperCase() === k.toUpperCase(),
|
|
214
245
|
);
|
|
215
246
|
|
|
216
247
|
let left = sqlExprCol
|
|
217
248
|
? { ...sqlExprCol.expr, as: null }
|
|
218
249
|
: sqlAggrCol
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
250
|
+
? { ...sqlAggrCol.expr }
|
|
251
|
+
: {
|
|
252
|
+
type: "column_ref",
|
|
253
|
+
table: sqlCol?.expr?.table,
|
|
254
|
+
column: sqlCol?.expr?.column || db.sqlsanitize(k),
|
|
255
|
+
};
|
|
225
256
|
//console.log({ k, sqlCol, sqlExprCol });
|
|
226
257
|
if (!sqlCol) {
|
|
227
258
|
const starCol = (ast[0].columns || []).find(
|
|
228
|
-
(c) => c.type === "star_ref"
|
|
259
|
+
(c) => c.type === "star_ref",
|
|
229
260
|
);
|
|
230
261
|
if (starCol)
|
|
231
262
|
left = {
|
|
@@ -241,14 +272,14 @@ const getSqlQuery = (sql, cfg, where, opts) => {
|
|
|
241
272
|
wherek?.ilike && !sqlAggrCol
|
|
242
273
|
? "ILIKE"
|
|
243
274
|
: wherek?.gt && !sqlAggrCol
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
275
|
+
? wherek.equal
|
|
276
|
+
? ">="
|
|
277
|
+
: ">"
|
|
278
|
+
: wherek?.lt && !sqlAggrCol
|
|
279
|
+
? wherek.equal
|
|
280
|
+
? "<="
|
|
281
|
+
: "<"
|
|
282
|
+
: "=",
|
|
252
283
|
left,
|
|
253
284
|
right:
|
|
254
285
|
wherek?.ilike && !sqlAggrCol
|
|
@@ -469,33 +500,59 @@ const countRows = async (cfg, where, opts) => {
|
|
|
469
500
|
//console.trace({ sqlQ, phValues, opts });
|
|
470
501
|
db.sql_log(
|
|
471
502
|
`select count(*) from (${ensure_no_final_semicolon(sqlQ)})`,
|
|
472
|
-
phValues
|
|
503
|
+
phValues,
|
|
473
504
|
);
|
|
474
505
|
const qres = await client.query(
|
|
475
506
|
`select count(*) from (${ensure_no_final_semicolon(sqlQ)})`,
|
|
476
|
-
phValues
|
|
507
|
+
phValues,
|
|
477
508
|
);
|
|
478
509
|
qres.query = sqlQ;
|
|
479
510
|
db.sql_log("ROLLBACK;");
|
|
480
511
|
await client.query(`ROLLBACK;`);
|
|
481
512
|
|
|
482
513
|
if (!is_sqlite) client.release(true);
|
|
483
|
-
return qres.rows[0].count;
|
|
514
|
+
return +qres.rows[0].count;
|
|
484
515
|
};
|
|
485
516
|
|
|
486
517
|
module.exports = {
|
|
487
518
|
"SQL query": {
|
|
488
519
|
configuration_workflow,
|
|
489
520
|
fields: (cfg) => cfg?.columns || [],
|
|
490
|
-
|
|
521
|
+
on_create,
|
|
522
|
+
get_table: (cfg, table) => {
|
|
523
|
+
let syntheticTable;
|
|
524
|
+
if (cfg?.sql_view && table)
|
|
525
|
+
syntheticTable = new Table({
|
|
526
|
+
...table,
|
|
527
|
+
provider_name: undefined,
|
|
528
|
+
provider_cfg: undefined,
|
|
529
|
+
});
|
|
491
530
|
return {
|
|
531
|
+
disableFiltering: true,
|
|
492
532
|
getRows: async (where, opts) => {
|
|
493
|
-
|
|
533
|
+
if (syntheticTable) return await syntheticTable.getRows(where, opts);
|
|
534
|
+
const qres = await runQuery(cfg, where || {}, opts || {});
|
|
494
535
|
return qres.rows;
|
|
495
536
|
},
|
|
496
537
|
countRows: async (where, opts) => {
|
|
497
|
-
|
|
538
|
+
if (syntheticTable)
|
|
539
|
+
return await syntheticTable.countRows(where, opts);
|
|
540
|
+
|
|
541
|
+
return await countRows(cfg, where || {}, opts || {});
|
|
498
542
|
},
|
|
543
|
+
...(syntheticTable
|
|
544
|
+
? {
|
|
545
|
+
distinctValues: async (fldNm, opts) => {
|
|
546
|
+
return await syntheticTable.distinctValues(fldNm, opts);
|
|
547
|
+
},
|
|
548
|
+
getJoinedRows: async (opts) => {
|
|
549
|
+
return await syntheticTable.getJoinedRows(opts);
|
|
550
|
+
},
|
|
551
|
+
aggregationQuery: async (aggs, opts) => {
|
|
552
|
+
return await syntheticTable.aggregationQuery(aggs, opts);
|
|
553
|
+
},
|
|
554
|
+
}
|
|
555
|
+
: {}),
|
|
499
556
|
};
|
|
500
557
|
},
|
|
501
558
|
},
|
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
|
+
});
|