@saltcorn/sql 0.5.3 → 0.5.5
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/agent-skill.js +11 -3
- package/package.json +1 -1
- package/table-provider.js +133 -23
package/agent-skill.js
CHANGED
|
@@ -157,6 +157,12 @@ class SQLQuerySkill {
|
|
|
157
157
|
type: "String",
|
|
158
158
|
fieldview: "textarea",
|
|
159
159
|
},
|
|
160
|
+
{
|
|
161
|
+
name: "display_result",
|
|
162
|
+
label: "Display result",
|
|
163
|
+
type: "Bool",
|
|
164
|
+
sublabel: "Show rows from the query in JSON format",
|
|
165
|
+
},
|
|
160
166
|
{
|
|
161
167
|
name: "row_format",
|
|
162
168
|
label: "Row format",
|
|
@@ -187,9 +193,11 @@ class SQLQuerySkill {
|
|
|
187
193
|
/*renderToolCall({ phrase }, { req }) {
|
|
188
194
|
return div({ class: "border border-primary p-2 m-2" }, phrase);
|
|
189
195
|
},*/
|
|
190
|
-
renderToolResponse:
|
|
191
|
-
|
|
192
|
-
|
|
196
|
+
renderToolResponse: this.display_result
|
|
197
|
+
? async (response, { req }) => {
|
|
198
|
+
return div({ class: "border border-success p-2 m-2" }, response);
|
|
199
|
+
}
|
|
200
|
+
: undefined,
|
|
193
201
|
function: {
|
|
194
202
|
name: this.tool_name,
|
|
195
203
|
description: this.tool_description,
|
package/package.json
CHANGED
package/table-provider.js
CHANGED
|
@@ -163,22 +163,7 @@ const sqlEscapeObject = (o) => {
|
|
|
163
163
|
return r;
|
|
164
164
|
};
|
|
165
165
|
|
|
166
|
-
const
|
|
167
|
-
const sqlTmpl = cfg?.sql || "";
|
|
168
|
-
const template = _.template(sqlTmpl || "", {
|
|
169
|
-
evaluate: /\{\{#(.+?)\}\}/g,
|
|
170
|
-
interpolate: /\{\{([^#].+?)\}\}/g,
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
const qctx = {};
|
|
174
|
-
|
|
175
|
-
if (opts?.forUser) qctx.user = sqlEscapeObject(opts.forUser);
|
|
176
|
-
else if (where?.forUser)
|
|
177
|
-
qctx.user = sqlEscapeObject(where.forUser); //workaround legacy bug
|
|
178
|
-
else qctx.user = null;
|
|
179
|
-
|
|
180
|
-
const sql = template(qctx);
|
|
181
|
-
|
|
166
|
+
const getSqlQuery = (sql, cfg, where, opts) => {
|
|
182
167
|
const is_sqlite = db.isSQLite;
|
|
183
168
|
const opt = {
|
|
184
169
|
database: is_sqlite ? "SQLite" : "PostgreSQL",
|
|
@@ -189,12 +174,21 @@ const runQuery = async (cfg, where, opts) => {
|
|
|
189
174
|
sqlQ = sql;
|
|
190
175
|
} else {
|
|
191
176
|
const { ast } = parser.parse(sql, opt);
|
|
192
|
-
|
|
177
|
+
/*console.log(
|
|
178
|
+
JSON.stringify(
|
|
179
|
+
parser.parse(
|
|
180
|
+
`select * from "users" where "email" ILIKE concat('%',cast($1 as text),'%')`,
|
|
181
|
+
opt
|
|
182
|
+
).ast,
|
|
183
|
+
null,
|
|
184
|
+
2
|
|
185
|
+
)
|
|
186
|
+
);*/
|
|
193
187
|
const colNames = new Set((cfg?.columns || []).map((c) => c.name));
|
|
194
188
|
let phIndex = 1;
|
|
195
189
|
//console.log(ast[0].columns);
|
|
196
|
-
|
|
197
|
-
if (!colNames.has(k))
|
|
190
|
+
const proc_k_where = (k, wherek) => {
|
|
191
|
+
if (!colNames.has(k)) return;
|
|
198
192
|
const sqlCol =
|
|
199
193
|
ast[0].columns == "*"
|
|
200
194
|
? {
|
|
@@ -240,14 +234,66 @@ const runQuery = async (cfg, where, opts) => {
|
|
|
240
234
|
column: db.sqlsanitize(k),
|
|
241
235
|
};
|
|
242
236
|
}
|
|
237
|
+
|
|
243
238
|
const newClause = {
|
|
244
239
|
type: "binary_expr",
|
|
245
|
-
operator:
|
|
240
|
+
operator:
|
|
241
|
+
wherek?.ilike && !sqlAggrCol
|
|
242
|
+
? "ILIKE"
|
|
243
|
+
: wherek?.gt && !sqlAggrCol
|
|
244
|
+
? wherek.equal
|
|
245
|
+
? ">="
|
|
246
|
+
: ">"
|
|
247
|
+
: wherek?.lt && !sqlAggrCol
|
|
248
|
+
? wherek.equal
|
|
249
|
+
? "<="
|
|
250
|
+
: "<"
|
|
251
|
+
: "=",
|
|
246
252
|
left,
|
|
247
|
-
right:
|
|
253
|
+
right:
|
|
254
|
+
wherek?.ilike && !sqlAggrCol
|
|
255
|
+
? {
|
|
256
|
+
type: "function",
|
|
257
|
+
name: {
|
|
258
|
+
name: [
|
|
259
|
+
{
|
|
260
|
+
type: "default",
|
|
261
|
+
value: "concat",
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
args: {
|
|
266
|
+
type: "expr_list",
|
|
267
|
+
value: [
|
|
268
|
+
{
|
|
269
|
+
type: "single_quote_string",
|
|
270
|
+
value: "%",
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
type: "cast",
|
|
274
|
+
keyword: "cast",
|
|
275
|
+
expr: { type: "number", value: "$" + phIndex },
|
|
276
|
+
symbol: "as",
|
|
277
|
+
target: [
|
|
278
|
+
{
|
|
279
|
+
dataType: "TEXT",
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
{
|
|
285
|
+
type: "single_quote_string",
|
|
286
|
+
value: "%",
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
},
|
|
290
|
+
}
|
|
291
|
+
: { type: "number", value: "$" + phIndex },
|
|
248
292
|
};
|
|
249
293
|
phIndex += 1;
|
|
250
|
-
phValues.push(
|
|
294
|
+
phValues.push(
|
|
295
|
+
wherek?.ilike || wherek?.gt || wherek?.lt || wherek
|
|
296
|
+
);
|
|
251
297
|
if (!sqlAggrCol) {
|
|
252
298
|
if (!ast[0].where) ast[0].where = newClause;
|
|
253
299
|
else {
|
|
@@ -269,6 +315,10 @@ const runQuery = async (cfg, where, opts) => {
|
|
|
269
315
|
};
|
|
270
316
|
}
|
|
271
317
|
}
|
|
318
|
+
};
|
|
319
|
+
for (const k of Object.keys(where)) {
|
|
320
|
+
if (Array.isArray(where[k])) where[k].forEach((w) => proc_k_where(k, w));
|
|
321
|
+
else proc_k_where(k, where[k]);
|
|
272
322
|
}
|
|
273
323
|
if (where?.limit && where?.offset) {
|
|
274
324
|
ast[0].limit = {
|
|
@@ -346,6 +396,28 @@ const runQuery = async (cfg, where, opts) => {
|
|
|
346
396
|
}
|
|
347
397
|
sqlQ = parser.sqlify(ast, opt);
|
|
348
398
|
}
|
|
399
|
+
return { sqlQ, phValues };
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const runQuery = async (cfg, where, opts) => {
|
|
403
|
+
const sqlTmpl = cfg?.sql || "";
|
|
404
|
+
const template = _.template(sqlTmpl || "", {
|
|
405
|
+
evaluate: /\{\{#(.+?)\}\}/g,
|
|
406
|
+
interpolate: /\{\{([^#].+?)\}\}/g,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const qctx = {};
|
|
410
|
+
|
|
411
|
+
if (opts?.forUser) qctx.user = sqlEscapeObject(opts.forUser);
|
|
412
|
+
else if (where?.forUser)
|
|
413
|
+
qctx.user = sqlEscapeObject(where.forUser); //workaround legacy bug
|
|
414
|
+
else qctx.user = null;
|
|
415
|
+
|
|
416
|
+
const sql = template(qctx);
|
|
417
|
+
const is_sqlite = db.isSQLite;
|
|
418
|
+
|
|
419
|
+
const { sqlQ, phValues } = getSqlQuery(sql, cfg, where, opts);
|
|
420
|
+
|
|
349
421
|
const client = is_sqlite ? db : await db.getClient();
|
|
350
422
|
await client.query(`BEGIN;`);
|
|
351
423
|
if (!is_sqlite) {
|
|
@@ -353,7 +425,7 @@ const runQuery = async (cfg, where, opts) => {
|
|
|
353
425
|
await client.query(`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;`);
|
|
354
426
|
}
|
|
355
427
|
|
|
356
|
-
//console.
|
|
428
|
+
//console.trace({ sqlQ, phValues, opts });
|
|
357
429
|
const qres = await client.query(sqlQ, phValues);
|
|
358
430
|
qres.query = sqlQ;
|
|
359
431
|
await client.query(`ROLLBACK;`);
|
|
@@ -362,6 +434,41 @@ const runQuery = async (cfg, where, opts) => {
|
|
|
362
434
|
return qres;
|
|
363
435
|
};
|
|
364
436
|
|
|
437
|
+
const countRows = async (cfg, where, opts) => {
|
|
438
|
+
const sqlTmpl = cfg?.sql || "";
|
|
439
|
+
const template = _.template(sqlTmpl || "", {
|
|
440
|
+
evaluate: /\{\{#(.+?)\}\}/g,
|
|
441
|
+
interpolate: /\{\{([^#].+?)\}\}/g,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
const qctx = {};
|
|
445
|
+
|
|
446
|
+
if (opts?.forUser) qctx.user = sqlEscapeObject(opts.forUser);
|
|
447
|
+
else if (where?.forUser)
|
|
448
|
+
qctx.user = sqlEscapeObject(where.forUser); //workaround legacy bug
|
|
449
|
+
else qctx.user = null;
|
|
450
|
+
|
|
451
|
+
const sql = template(qctx);
|
|
452
|
+
const is_sqlite = db.isSQLite;
|
|
453
|
+
|
|
454
|
+
const { sqlQ, phValues } = getSqlQuery(sql, cfg, where, opts);
|
|
455
|
+
|
|
456
|
+
const client = is_sqlite ? db : await db.getClient();
|
|
457
|
+
await client.query(`BEGIN;`);
|
|
458
|
+
if (!is_sqlite) {
|
|
459
|
+
await client.query(`SET LOCAL search_path TO "${db.getTenantSchema()}";`);
|
|
460
|
+
await client.query(`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
//console.trace({ sqlQ, phValues, opts });
|
|
464
|
+
const qres = await client.query(`select count(*) from (${sqlQ})`, phValues);
|
|
465
|
+
qres.query = sqlQ;
|
|
466
|
+
await client.query(`ROLLBACK;`);
|
|
467
|
+
|
|
468
|
+
if (!is_sqlite) client.release(true);
|
|
469
|
+
return qres.rows[0].count;
|
|
470
|
+
};
|
|
471
|
+
|
|
365
472
|
module.exports = {
|
|
366
473
|
"SQL query": {
|
|
367
474
|
configuration_workflow,
|
|
@@ -372,6 +479,9 @@ module.exports = {
|
|
|
372
479
|
const qres = await runQuery(cfg, where, opts);
|
|
373
480
|
return qres.rows;
|
|
374
481
|
},
|
|
482
|
+
countRows: async (where, opts) => {
|
|
483
|
+
return await countRows(cfg, where, opts);
|
|
484
|
+
},
|
|
375
485
|
};
|
|
376
486
|
},
|
|
377
487
|
},
|