@carbonorm/carbonnode 6.0.13 → 6.0.17
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/dist/executors/SqlExecutor.d.ts +17 -0
- package/dist/index.cjs.js +413 -245
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +413 -245
- package/dist/index.esm.js.map +1 -1
- package/dist/utils/cacheManager.d.ts +2 -1
- package/dist/utils/logLevel.d.ts +3 -3
- package/dist/utils/logSql.d.ts +10 -1
- package/package.json +1 -1
- package/scripts/assets/handlebars/C6.ts.handlebars +1 -1
- package/src/__tests__/fixtures/sqlResponses/sqlAllowList.json +1 -1
- package/src/__tests__/httpExecutor.multiRowUpsert.test.ts +50 -0
- package/src/__tests__/logSql.test.ts +54 -2
- package/src/__tests__/sakila-db/C6.js +1 -1
- package/src/__tests__/sakila-db/C6.mysqldump.json +1 -1
- package/src/__tests__/sakila-db/C6.mysqldump.sql +1 -1
- package/src/__tests__/sakila-db/C6.sqlAllowList.json +59 -70
- package/src/__tests__/sakila-db/C6.ts +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.json +3 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.latest.json +3 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.lookup.json +3 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.address.post.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.address.post.latest.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.address.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.address.put.lookup.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.category.post.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.category.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.category.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.category.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.city.post.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.city.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.city.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.city.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.country.post.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.country.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.country.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.country.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.latest.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.lookup.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.film.post.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.film.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.film.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.film.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.latest.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.lookup.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.language.post.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.language.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.language.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.language.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.join.json +10 -10
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.json +3 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.latest.json +3 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.lookup.json +3 -3
- package/src/__tests__/sqlAllowList.test.ts +100 -0
- package/src/__tests__/sqlBuilders.test.ts +3 -4
- package/src/executors/HttpExecutor.ts +7 -2
- package/src/executors/SqlExecutor.ts +108 -7
- package/src/orm/queries/DeleteQueryBuilder.ts +0 -4
- package/src/orm/queries/PostQueryBuilder.ts +0 -4
- package/src/orm/queries/SelectQueryBuilder.ts +0 -4
- package/src/orm/queries/UpdateQueryBuilder.ts +0 -4
- package/src/utils/cacheManager.ts +17 -9
- package/src/utils/logLevel.ts +3 -4
- package/src/utils/logSql.ts +51 -6
- package/src/utils/sqlAllowList.ts +111 -9
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"rest": [
|
|
3
3
|
{
|
|
4
|
-
"rental_id":
|
|
5
|
-
"rental_date": "2005-05-
|
|
6
|
-
"inventory_id":
|
|
7
|
-
"customer_id":
|
|
8
|
-
"return_date": "2005-
|
|
9
|
-
"staff_id":
|
|
4
|
+
"rental_id": 76,
|
|
5
|
+
"rental_date": "2005-05-25T11:30:37.000Z",
|
|
6
|
+
"inventory_id": 3021,
|
|
7
|
+
"customer_id": 1,
|
|
8
|
+
"return_date": "2005-06-03T12:00:37.000Z",
|
|
9
|
+
"staff_id": 2,
|
|
10
10
|
"last_update": "2006-02-15T04:57:20.000Z",
|
|
11
11
|
"store_id": 1,
|
|
12
|
-
"first_name": "
|
|
13
|
-
"last_name": "
|
|
14
|
-
"email": "
|
|
15
|
-
"address_id":
|
|
12
|
+
"first_name": "MARY",
|
|
13
|
+
"last_name": "SMITH",
|
|
14
|
+
"email": "MARY.SMITH@sakilacustomer.org",
|
|
15
|
+
"address_id": 5,
|
|
16
16
|
"active": 1,
|
|
17
17
|
"create_date": "2006-02-14T22:04:36.000Z"
|
|
18
18
|
}
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
"sql": {
|
|
6
6
|
"sql": "INSERT INTO `rental` (\n `rental_date`, `inventory_id`, `customer_id`, `return_date`, `staff_id`, `last_update`\n ) VALUES\n (?, ?, ?, ?, ?, ?)",
|
|
7
7
|
"values": [
|
|
8
|
-
"2026-02-
|
|
8
|
+
"2026-02-12 19:14:08",
|
|
9
9
|
1,
|
|
10
10
|
1,
|
|
11
|
-
"2026-02-
|
|
11
|
+
"2026-02-12 19:14:08",
|
|
12
12
|
1,
|
|
13
|
-
"2026-02-
|
|
13
|
+
"2026-02-12 19:14:08"
|
|
14
14
|
]
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"rest": [
|
|
3
3
|
{
|
|
4
4
|
"rental_id": 16050,
|
|
5
|
-
"rental_date": "2026-02-
|
|
5
|
+
"rental_date": "2026-02-12T19:14:08.000Z",
|
|
6
6
|
"inventory_id": 1,
|
|
7
7
|
"customer_id": 1,
|
|
8
|
-
"return_date": "2026-02-
|
|
8
|
+
"return_date": "2026-02-12T19:14:08.000Z",
|
|
9
9
|
"staff_id": 1,
|
|
10
|
-
"last_update": "2026-02-
|
|
10
|
+
"last_update": "2026-02-12T19:14:08.000Z"
|
|
11
11
|
}
|
|
12
12
|
],
|
|
13
13
|
"sql": {
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"rest": [
|
|
3
3
|
{
|
|
4
4
|
"rental_id": 16050,
|
|
5
|
-
"rental_date": "2026-02-
|
|
5
|
+
"rental_date": "2026-02-12T19:14:08.000Z",
|
|
6
6
|
"inventory_id": 1,
|
|
7
7
|
"customer_id": 1,
|
|
8
|
-
"return_date": "2026-02-
|
|
8
|
+
"return_date": "2026-02-12T19:14:08.000Z",
|
|
9
9
|
"staff_id": 1,
|
|
10
|
-
"last_update": "2026-02-
|
|
10
|
+
"last_update": "2026-02-12T19:14:08.000Z"
|
|
11
11
|
}
|
|
12
12
|
],
|
|
13
13
|
"sql": {
|
|
@@ -119,10 +119,110 @@ describe("SQL allowlist", () => {
|
|
|
119
119
|
await expect(
|
|
120
120
|
Actor.Get({
|
|
121
121
|
[C6.PAGINATION]: {[C6.LIMIT]: 1},
|
|
122
|
+
cacheResults: false,
|
|
122
123
|
} as any)
|
|
123
124
|
).rejects.toThrow("SQL statement is not permitted");
|
|
124
125
|
} finally {
|
|
125
126
|
restoreGlobals(originalGlobals);
|
|
126
127
|
}
|
|
127
128
|
});
|
|
129
|
+
|
|
130
|
+
it("normalizes multi-row VALUES with variable row counts", () => {
|
|
131
|
+
const oneRow = `
|
|
132
|
+
INSERT INTO \`valuation_report_comparables\` (\`report_id\`, \`unit_id\`, \`subject_unit_id\`)
|
|
133
|
+
VALUES (?, ?, ?)
|
|
134
|
+
ON DUPLICATE KEY UPDATE \`subject_unit_id\` = VALUES(\`subject_unit_id\`)
|
|
135
|
+
`;
|
|
136
|
+
const manyRows = `
|
|
137
|
+
INSERT INTO \`valuation_report_comparables\` (\`report_id\`, \`unit_id\`, \`subject_unit_id\`)
|
|
138
|
+
VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?)
|
|
139
|
+
ON DUPLICATE KEY UPDATE \`subject_unit_id\` = VALUES(\`subject_unit_id\`)
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
expect(normalizeSql(oneRow)).toContain("VALUES (? ×3) ×*");
|
|
143
|
+
expect(normalizeSql(manyRows)).toContain("VALUES (? ×3) ×*");
|
|
144
|
+
expect(normalizeSql(oneRow)).toBe(normalizeSql(manyRows));
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("normalizes IN bind list cardinality", () => {
|
|
148
|
+
const smallIn = "SELECT * FROM `geometries` WHERE ( geometries.geometry_id IN (?, ?, ?) ) LIMIT 100";
|
|
149
|
+
const largeIn = "SELECT * FROM `geometries` WHERE ( geometries.geometry_id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) LIMIT 250";
|
|
150
|
+
|
|
151
|
+
const normalizedSmall = normalizeSql(smallIn);
|
|
152
|
+
const normalizedLarge = normalizeSql(largeIn);
|
|
153
|
+
|
|
154
|
+
expect(normalizedSmall).toContain("IN (? ×*)");
|
|
155
|
+
expect(normalizedLarge).toContain("IN (? ×*)");
|
|
156
|
+
expect(normalizedSmall).toContain("LIMIT ?");
|
|
157
|
+
expect(normalizedLarge).toContain("LIMIT ?");
|
|
158
|
+
expect(normalizedSmall).toBe(normalizedLarge);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("normalizes LIMIT and OFFSET numeric literals", () => {
|
|
162
|
+
expect(normalizeSql("SELECT * FROM `actor` LIMIT 100")).toBe(
|
|
163
|
+
normalizeSql("SELECT * FROM `actor` LIMIT 25"),
|
|
164
|
+
);
|
|
165
|
+
expect(normalizeSql("SELECT * FROM `actor` LIMIT 10, 50")).toBe(
|
|
166
|
+
"SELECT * FROM `actor` LIMIT ?, ?",
|
|
167
|
+
);
|
|
168
|
+
expect(normalizeSql("SELECT * FROM `actor` LIMIT 50 OFFSET 100")).toBe(
|
|
169
|
+
"SELECT * FROM `actor` LIMIT ? OFFSET ?",
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("normalizes variable ST_GEOMFROMTEXT POINT and POLYGON literals", () => {
|
|
174
|
+
const q1 = `
|
|
175
|
+
SELECT * FROM \`property_units\`
|
|
176
|
+
WHERE MBRCONTAINS(
|
|
177
|
+
ST_GEOMFROMTEXT('POLYGON((39.1 -105.1, 39.2 -105.1, 39.2 -105.0, 39.1 -105.0, 39.1 -105.1))', 4326),
|
|
178
|
+
property_units.location
|
|
179
|
+
)
|
|
180
|
+
ORDER BY ST_DISTANCE_SPHERE(
|
|
181
|
+
property_units.location,
|
|
182
|
+
ST_GEOMFROMTEXT('POINT(39.15 -105.05)', 4326)
|
|
183
|
+
)
|
|
184
|
+
LIMIT 100
|
|
185
|
+
`;
|
|
186
|
+
const q2 = `
|
|
187
|
+
SELECT * FROM \`property_units\`
|
|
188
|
+
WHERE MBRCONTAINS(
|
|
189
|
+
ST_GEOMFROMTEXT('POLYGON((39.3 -105.3, 39.7 -105.3, 39.7 -104.8, 39.3 -104.8, 39.3 -105.3))', 4326),
|
|
190
|
+
property_units.location
|
|
191
|
+
)
|
|
192
|
+
ORDER BY ST_DISTANCE_SPHERE(
|
|
193
|
+
property_units.location,
|
|
194
|
+
ST_GEOMFROMTEXT('POINT(39.5321821 -105.0035613)', 4326)
|
|
195
|
+
)
|
|
196
|
+
LIMIT 250
|
|
197
|
+
`;
|
|
198
|
+
|
|
199
|
+
const normalized1 = normalizeSql(q1);
|
|
200
|
+
const normalized2 = normalizeSql(q2);
|
|
201
|
+
|
|
202
|
+
expect(normalized1).toContain("ST_GEOMFROMTEXT('POLYGON((?))', ?)");
|
|
203
|
+
expect(normalized1).toContain("ST_GEOMFROMTEXT('POINT(? ?)', ?)");
|
|
204
|
+
expect(normalized1).toContain("ST_DISTANCE_SPHERE(");
|
|
205
|
+
expect(normalized1).toContain("LIMIT ?");
|
|
206
|
+
expect(normalized1).toBe(normalized2);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("normalizes geo function casing and FORCE INDEX spacing", () => {
|
|
210
|
+
const a =
|
|
211
|
+
"SELECT * FROM `property_units` FORCE INDEX (`idx_county_id`,`idx_property_units_location`) WHERE ST_Distance_Sphere(property_units.location, ST_GeomFromText('POINT(39.5 -105.0)', 4326)) <= 50 LIMIT 100";
|
|
212
|
+
const b =
|
|
213
|
+
"SELECT * FROM `property_units` FORCE INDEX (`idx_county_id`, `idx_property_units_location`) WHERE st_distance_sphere(property_units.location, st_geomfromtext('POINT(39.7 -104.9)', 4326)) <= 25 LIMIT 25";
|
|
214
|
+
|
|
215
|
+
const normalizedA = normalizeSql(a);
|
|
216
|
+
const normalizedB = normalizeSql(b);
|
|
217
|
+
|
|
218
|
+
expect(normalizedA).toContain("FORCE INDEX (`idx_county_id`, `idx_property_units_location`)");
|
|
219
|
+
expect(normalizedA).toContain("ST_DISTANCE_SPHERE");
|
|
220
|
+
expect(normalizedA).toContain("ST_GEOMFROMTEXT('POINT(? ?)', ?)");
|
|
221
|
+
expect(normalizedA).toContain("LIMIT ?");
|
|
222
|
+
expect(normalizedB).toContain("FORCE INDEX (`idx_county_id`, `idx_property_units_location`)");
|
|
223
|
+
expect(normalizedB).toContain("ST_DISTANCE_SPHERE");
|
|
224
|
+
expect(normalizedB).toContain("ST_GEOMFROMTEXT('POINT(? ?)', ?)");
|
|
225
|
+
expect(normalizedB).toContain("LIMIT ?");
|
|
226
|
+
});
|
|
227
|
+
|
|
128
228
|
});
|
|
@@ -5,7 +5,6 @@ import { PostQueryBuilder } from '../orm/queries/PostQueryBuilder';
|
|
|
5
5
|
import { UpdateQueryBuilder } from '../orm/queries/UpdateQueryBuilder';
|
|
6
6
|
import { DeleteQueryBuilder } from '../orm/queries/DeleteQueryBuilder';
|
|
7
7
|
import { buildTestConfig, buildBinaryTestConfig, buildBinaryTestConfigFqn } from './fixtures/c6.fixture';
|
|
8
|
-
import { version } from '../../package.json';
|
|
9
8
|
|
|
10
9
|
describe('SQL Builders', () => {
|
|
11
10
|
it('builds SELECT with JOIN, WHERE, GROUP BY, HAVING and default LIMIT', () => {
|
|
@@ -44,11 +43,11 @@ describe('SQL Builders', () => {
|
|
|
44
43
|
expect(params).toEqual(['%A%', 10, 1]);
|
|
45
44
|
});
|
|
46
45
|
|
|
47
|
-
it('logs SELECT
|
|
46
|
+
it('logs SELECT aggregate expressions at DEBUG level', () => {
|
|
48
47
|
const config = buildTestConfig();
|
|
49
48
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
50
49
|
const qb = new SelectQueryBuilder(config as any, {
|
|
51
|
-
SELECT: ['actor.
|
|
50
|
+
SELECT: [[C6C.COUNT, 'actor.actor_id', C6C.AS, 'cnt']],
|
|
52
51
|
} as any, false);
|
|
53
52
|
|
|
54
53
|
qb.build('actor');
|
|
@@ -59,7 +58,7 @@ describe('SQL Builders', () => {
|
|
|
59
58
|
const selectLine = logLines.find((line) => line.includes('[SELECT]'));
|
|
60
59
|
|
|
61
60
|
expect(selectLine).toBeDefined();
|
|
62
|
-
expect(selectLine).toContain(
|
|
61
|
+
expect(selectLine).toContain('COUNT(actor.actor_id) AS cnt');
|
|
63
62
|
logSpy.mockRestore();
|
|
64
63
|
});
|
|
65
64
|
|
|
@@ -260,7 +260,7 @@ export class HttpExecutor<
|
|
|
260
260
|
let cachedRequest: Promise<{ data: ResponseDataType }> | false = false;
|
|
261
261
|
|
|
262
262
|
if (cacheResults) {
|
|
263
|
-
cachedRequest = checkCache<ResponseDataType>(requestMethod, tableName, cacheRequestData);
|
|
263
|
+
cachedRequest = checkCache<ResponseDataType>(requestMethod, tableName, cacheRequestData, logContext);
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
if (cachedRequest) {
|
|
@@ -420,8 +420,13 @@ export class HttpExecutor<
|
|
|
420
420
|
|
|
421
421
|
case POST:
|
|
422
422
|
if (dataInsertMultipleRows !== undefined) {
|
|
423
|
+
const convertedRows = dataInsertMultipleRows.map(convert);
|
|
424
|
+
const convertedQuery = convert(query);
|
|
423
425
|
return [
|
|
424
|
-
|
|
426
|
+
{
|
|
427
|
+
...convertedQuery,
|
|
428
|
+
dataInsertMultipleRows: convertedRows,
|
|
429
|
+
},
|
|
425
430
|
baseConfig
|
|
426
431
|
];
|
|
427
432
|
}
|
|
@@ -17,14 +17,75 @@ import type { PoolConnection } from 'mysql2/promise';
|
|
|
17
17
|
import { Buffer } from 'buffer';
|
|
18
18
|
import { Executor } from "./Executor";
|
|
19
19
|
import {checkCache, setCache} from "../utils/cacheManager";
|
|
20
|
+
import logSql, {
|
|
21
|
+
SqlAllowListStatus,
|
|
22
|
+
} from "../utils/logSql";
|
|
20
23
|
import { normalizeSingularRequest } from "../utils/normalizeSingularRequest";
|
|
21
24
|
import {sortAndSerializeQueryObject} from "../utils/sortAndSerializeQueryObject";
|
|
22
25
|
import { loadSqlAllowList, normalizeSql } from "../utils/sqlAllowList";
|
|
23
26
|
import { getLogContext, LogLevel, logWithLevel } from "../utils/logLevel";
|
|
24
27
|
|
|
28
|
+
const SQL_ALLOWLIST_BLOCKED_CODE = "SQL_ALLOWLIST_BLOCKED";
|
|
29
|
+
|
|
30
|
+
export type SqlAllowListBlockedError = Error & {
|
|
31
|
+
code: typeof SQL_ALLOWLIST_BLOCKED_CODE;
|
|
32
|
+
tableName?: string;
|
|
33
|
+
method?: string;
|
|
34
|
+
normalizedSql: string;
|
|
35
|
+
allowListPath: string;
|
|
36
|
+
sqlAllowList: {
|
|
37
|
+
sql: string;
|
|
38
|
+
table: string | null;
|
|
39
|
+
method: string | null;
|
|
40
|
+
allowListPath: string;
|
|
41
|
+
canAdd: boolean;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const createSqlAllowListBlockedError = (args: {
|
|
46
|
+
tableName?: string;
|
|
47
|
+
method?: string;
|
|
48
|
+
normalizedSql: string;
|
|
49
|
+
allowListPath: string;
|
|
50
|
+
}): SqlAllowListBlockedError => {
|
|
51
|
+
const error = new Error(
|
|
52
|
+
`SQL statement is not permitted by allowlist (${args.allowListPath}).`,
|
|
53
|
+
) as SqlAllowListBlockedError;
|
|
54
|
+
|
|
55
|
+
error.name = "SqlAllowListBlockedError";
|
|
56
|
+
error.code = SQL_ALLOWLIST_BLOCKED_CODE;
|
|
57
|
+
error.tableName = args.tableName;
|
|
58
|
+
error.method = args.method;
|
|
59
|
+
error.normalizedSql = args.normalizedSql;
|
|
60
|
+
error.allowListPath = args.allowListPath;
|
|
61
|
+
error.sqlAllowList = {
|
|
62
|
+
sql: args.normalizedSql,
|
|
63
|
+
table: args.tableName ?? null,
|
|
64
|
+
method: args.method ?? null,
|
|
65
|
+
allowListPath: args.allowListPath,
|
|
66
|
+
canAdd: true,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return error;
|
|
70
|
+
};
|
|
71
|
+
|
|
25
72
|
export class SqlExecutor<
|
|
26
73
|
G extends OrmGenerics
|
|
27
74
|
> extends Executor<G> {
|
|
75
|
+
private resolveSqlLogMethod(method: iRestMethods, sql: string): string {
|
|
76
|
+
const token = sql.trim().split(/\s+/, 1)[0]?.toUpperCase();
|
|
77
|
+
if (token) return token;
|
|
78
|
+
switch (method) {
|
|
79
|
+
case C6C.GET:
|
|
80
|
+
return "SELECT";
|
|
81
|
+
case C6C.POST:
|
|
82
|
+
return "INSERT";
|
|
83
|
+
case C6C.PUT:
|
|
84
|
+
return "UPDATE";
|
|
85
|
+
default:
|
|
86
|
+
return "DELETE";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
28
89
|
|
|
29
90
|
async execute(): Promise<DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>> {
|
|
30
91
|
const { TABLE_NAME } = this.config.restModel;
|
|
@@ -487,8 +548,7 @@ export class SqlExecutor<
|
|
|
487
548
|
const tableName = this.config.restModel.TABLE_NAME;
|
|
488
549
|
const logContext = getLogContext(this.config, this.request);
|
|
489
550
|
const cacheResults = method === C6C.GET
|
|
490
|
-
&&
|
|
491
|
-
&& (this.request as { cacheResults?: boolean })?.cacheResults !== false;
|
|
551
|
+
&& (this.request.cacheResults ?? true);
|
|
492
552
|
|
|
493
553
|
const cacheRequestData = cacheResults
|
|
494
554
|
? JSON.parse(JSON.stringify(this.request ?? {}))
|
|
@@ -503,6 +563,7 @@ export class SqlExecutor<
|
|
|
503
563
|
method,
|
|
504
564
|
tableName,
|
|
505
565
|
cacheRequestData,
|
|
566
|
+
logContext
|
|
506
567
|
);
|
|
507
568
|
if (cachedRequest) {
|
|
508
569
|
return (await cachedRequest).data;
|
|
@@ -510,8 +571,15 @@ export class SqlExecutor<
|
|
|
510
571
|
}
|
|
511
572
|
|
|
512
573
|
const sqlExecution = this.buildSqlExecutionContext(method, tableName, logContext);
|
|
574
|
+
const sqlMethod = this.resolveSqlLogMethod(method, sqlExecution.sql);
|
|
513
575
|
const queryPromise = this.withConnection(async (conn) =>
|
|
514
|
-
this.executeQueryWithLifecycle(
|
|
576
|
+
this.executeQueryWithLifecycle(
|
|
577
|
+
conn,
|
|
578
|
+
method,
|
|
579
|
+
sqlExecution,
|
|
580
|
+
logContext,
|
|
581
|
+
sqlMethod
|
|
582
|
+
),
|
|
515
583
|
);
|
|
516
584
|
|
|
517
585
|
if (!cacheResults || !cacheRequestData || !requestArgumentsSerialized) {
|
|
@@ -640,6 +708,7 @@ export class SqlExecutor<
|
|
|
640
708
|
method: iRestMethods,
|
|
641
709
|
sqlExecution: iRestSqlExecutionContext,
|
|
642
710
|
logContext: ReturnType<typeof getLogContext>,
|
|
711
|
+
sqlMethod: string
|
|
643
712
|
): Promise<DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>> {
|
|
644
713
|
const useTransaction = method !== C6C.GET;
|
|
645
714
|
let committed = false;
|
|
@@ -655,7 +724,27 @@ export class SqlExecutor<
|
|
|
655
724
|
await conn.beginTransaction();
|
|
656
725
|
}
|
|
657
726
|
|
|
658
|
-
|
|
727
|
+
let allowListStatus: SqlAllowListStatus = "not verified";
|
|
728
|
+
try {
|
|
729
|
+
allowListStatus = await this.validateSqlAllowList(sqlExecution.sql);
|
|
730
|
+
} catch (error) {
|
|
731
|
+
logSql({
|
|
732
|
+
method: sqlMethod,
|
|
733
|
+
sql: sqlExecution.sql,
|
|
734
|
+
context: logContext,
|
|
735
|
+
cacheStatus: this.request.cacheResults === false ? "ignored" : "miss",
|
|
736
|
+
allowListStatus: "denied",
|
|
737
|
+
});
|
|
738
|
+
throw error;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
logSql({
|
|
742
|
+
method: sqlMethod,
|
|
743
|
+
sql: sqlExecution.sql,
|
|
744
|
+
context: logContext,
|
|
745
|
+
cacheStatus: this.request.cacheResults === false ? "ignored" : "miss",
|
|
746
|
+
allowListStatus,
|
|
747
|
+
});
|
|
659
748
|
|
|
660
749
|
await this.runLifecycleHooks<"beforeExecution">(
|
|
661
750
|
"beforeExecution",
|
|
@@ -729,17 +818,29 @@ export class SqlExecutor<
|
|
|
729
818
|
}
|
|
730
819
|
}
|
|
731
820
|
|
|
732
|
-
private async validateSqlAllowList(sql: string): Promise<
|
|
821
|
+
private async validateSqlAllowList(sql: string): Promise<SqlAllowListStatus> {
|
|
733
822
|
const allowListPath = this.config.sqlAllowListPath;
|
|
734
823
|
if (!allowListPath) {
|
|
735
|
-
return;
|
|
824
|
+
return "not verified";
|
|
736
825
|
}
|
|
737
826
|
|
|
738
827
|
const allowList = await loadSqlAllowList(allowListPath);
|
|
739
828
|
const normalized = normalizeSql(sql);
|
|
740
829
|
if (!allowList.has(normalized)) {
|
|
741
|
-
throw
|
|
830
|
+
throw createSqlAllowListBlockedError({
|
|
831
|
+
tableName:
|
|
832
|
+
typeof this.config.restModel?.TABLE_NAME === "string"
|
|
833
|
+
? this.config.restModel.TABLE_NAME
|
|
834
|
+
: undefined,
|
|
835
|
+
method:
|
|
836
|
+
typeof this.config.requestMethod === "string"
|
|
837
|
+
? this.config.requestMethod
|
|
838
|
+
: undefined,
|
|
839
|
+
normalizedSql: normalized,
|
|
840
|
+
allowListPath,
|
|
841
|
+
});
|
|
742
842
|
}
|
|
843
|
+
return "allowed";
|
|
743
844
|
}
|
|
744
845
|
|
|
745
846
|
|
|
@@ -2,8 +2,6 @@ import { OrmGenerics } from "../../types/ormGenerics";
|
|
|
2
2
|
import { SqlBuilderResult } from "../utils/sqlUtils";
|
|
3
3
|
import { JoinBuilder } from "../builders/JoinBuilder";
|
|
4
4
|
import { SelectQueryBuilder } from "./SelectQueryBuilder";
|
|
5
|
-
import logSql from "../../utils/logSql";
|
|
6
|
-
import {getLogContext} from "../../utils/logLevel";
|
|
7
5
|
|
|
8
6
|
export class DeleteQueryBuilder<G extends OrmGenerics> extends JoinBuilder<G> {
|
|
9
7
|
protected createSelectBuilder(request: any) {
|
|
@@ -27,8 +25,6 @@ export class DeleteQueryBuilder<G extends OrmGenerics> extends JoinBuilder<G> {
|
|
|
27
25
|
sql += this.buildWhereClause(this.request.WHERE, params);
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
logSql("DELETE", sql, getLogContext(this.config, this.request));
|
|
31
|
-
|
|
32
28
|
return { sql, params };
|
|
33
29
|
}
|
|
34
30
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import {C6C} from "../../constants/C6Constants";
|
|
2
2
|
import {ConditionBuilder} from "../builders/ConditionBuilder";
|
|
3
3
|
import {OrmGenerics} from "../../types/ormGenerics";
|
|
4
|
-
import logSql from "../../utils/logSql";
|
|
5
|
-
import {getLogContext} from "../../utils/logLevel";
|
|
6
4
|
|
|
7
5
|
export class PostQueryBuilder<G extends OrmGenerics> extends ConditionBuilder<G>{
|
|
8
6
|
|
|
@@ -61,8 +59,6 @@ export class PostQueryBuilder<G extends OrmGenerics> extends ConditionBuilder<G>
|
|
|
61
59
|
sql += ` ON DUPLICATE KEY UPDATE ${updateClause}`;
|
|
62
60
|
}
|
|
63
61
|
|
|
64
|
-
logSql(verb, sql, getLogContext(this.config, this.request));
|
|
65
|
-
|
|
66
62
|
return {sql, params};
|
|
67
63
|
}
|
|
68
64
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import {OrmGenerics} from "../../types/ormGenerics";
|
|
2
2
|
import {PaginationBuilder} from "../builders/PaginationBuilder";
|
|
3
3
|
import {SqlBuilderResult} from "../utils/sqlUtils";
|
|
4
|
-
import logSql from "../../utils/logSql";
|
|
5
|
-
import {getLogContext} from "../../utils/logLevel";
|
|
6
4
|
|
|
7
5
|
export class SelectQueryBuilder<G extends OrmGenerics> extends PaginationBuilder<G>{
|
|
8
6
|
|
|
@@ -58,8 +56,6 @@ export class SelectQueryBuilder<G extends OrmGenerics> extends PaginationBuilder
|
|
|
58
56
|
sql += ` LIMIT 100`;
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
logSql("SELECT", sql, getLogContext(this.config, this.request));
|
|
62
|
-
|
|
63
59
|
return { sql, params };
|
|
64
60
|
}
|
|
65
61
|
}
|
|
@@ -3,8 +3,6 @@ import {OrmGenerics} from "../../types/ormGenerics";
|
|
|
3
3
|
import { PaginationBuilder } from '../builders/PaginationBuilder';
|
|
4
4
|
import {SqlBuilderResult} from "../utils/sqlUtils";
|
|
5
5
|
import {SelectQueryBuilder} from "./SelectQueryBuilder";
|
|
6
|
-
import logSql from "../../utils/logSql";
|
|
7
|
-
import {getLogContext} from "../../utils/logLevel";
|
|
8
6
|
|
|
9
7
|
export class UpdateQueryBuilder<G extends OrmGenerics> extends PaginationBuilder<G>{
|
|
10
8
|
protected createSelectBuilder(request: any) {
|
|
@@ -56,8 +54,6 @@ export class UpdateQueryBuilder<G extends OrmGenerics> extends PaginationBuilder
|
|
|
56
54
|
sql += this.buildPaginationClause(args.PAGINATION, params);
|
|
57
55
|
}
|
|
58
56
|
|
|
59
|
-
logSql("UPDATE", sql, getLogContext(this.config, this.request));
|
|
60
|
-
|
|
61
57
|
return { sql, params };
|
|
62
58
|
}
|
|
63
59
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {iCacheAPI, iCacheResponse} from "../types/ormInterfaces";
|
|
2
|
-
import {LogLevel, logWithLevel, shouldLog} from "./logLevel";
|
|
2
|
+
import {LogContext, LogLevel, logWithLevel, shouldLog} from "./logLevel";
|
|
3
|
+
import logSql from "./logSql";
|
|
3
4
|
|
|
4
5
|
// -----------------------------------------------------------------------------
|
|
5
6
|
// Cache Storage
|
|
@@ -60,19 +61,26 @@ export function checkCache<ResponseDataType = any>(
|
|
|
60
61
|
method: string,
|
|
61
62
|
tableName: string | string[],
|
|
62
63
|
requestData: any,
|
|
64
|
+
logContext: LogContext,
|
|
63
65
|
): Promise<iCacheResponse<ResponseDataType>> | false {
|
|
64
66
|
const key = makeCacheKey(method, tableName, requestData);
|
|
65
67
|
const cached = apiRequestCache.get(key);
|
|
66
68
|
|
|
67
|
-
if (!cached)
|
|
69
|
+
if (!cached) {
|
|
70
|
+
console.log('apiRequestCache.size', apiRequestCache.size)
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
68
73
|
|
|
69
|
-
if (shouldLog(LogLevel.INFO,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
if (shouldLog(LogLevel.INFO, logContext)) {
|
|
75
|
+
const sql = cached.response?.data?.sql?.sql ?? "";
|
|
76
|
+
const sqlMethod = sql.trim().split(/\s+/, 1)[0]?.toUpperCase() || method;
|
|
77
|
+
logSql({
|
|
78
|
+
allowListStatus: "not verified",
|
|
79
|
+
cacheStatus: "hit",
|
|
80
|
+
context: logContext,
|
|
81
|
+
method: sqlMethod,
|
|
82
|
+
sql
|
|
83
|
+
});
|
|
76
84
|
}
|
|
77
85
|
|
|
78
86
|
return cached.request;
|
package/src/utils/logLevel.ts
CHANGED
|
@@ -135,10 +135,9 @@ export const applyLogLevelDefaults = (
|
|
|
135
135
|
};
|
|
136
136
|
|
|
137
137
|
export const getLogContext = (
|
|
138
|
-
config
|
|
139
|
-
request
|
|
140
|
-
): LogContext
|
|
141
|
-
if (!config && !request) return undefined;
|
|
138
|
+
config: { logLevel?: number | null; verbose?: boolean | null },
|
|
139
|
+
request: { debug?: boolean } | null,
|
|
140
|
+
): LogContext => {
|
|
142
141
|
return {
|
|
143
142
|
logLevel: config?.logLevel ?? undefined,
|
|
144
143
|
verbose: config?.verbose ?? undefined,
|