@comprehend/telemetry-node 0.1.4 → 0.2.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.
Files changed (38) hide show
  1. package/.claude/settings.local.json +2 -2
  2. package/.idea/telemetry-node.iml +0 -1
  3. package/README.md +73 -27
  4. package/dist/ComprehendDevSpanProcessor.d.ts +9 -6
  5. package/dist/ComprehendDevSpanProcessor.js +145 -87
  6. package/dist/ComprehendDevSpanProcessor.test.js +270 -449
  7. package/dist/ComprehendMetricsExporter.d.ts +18 -0
  8. package/dist/ComprehendMetricsExporter.js +178 -0
  9. package/dist/ComprehendMetricsExporter.test.d.ts +1 -0
  10. package/dist/ComprehendMetricsExporter.test.js +266 -0
  11. package/dist/ComprehendSDK.d.ts +18 -0
  12. package/dist/ComprehendSDK.js +56 -0
  13. package/dist/ComprehendSDK.test.d.ts +1 -0
  14. package/dist/ComprehendSDK.test.js +126 -0
  15. package/dist/WebSocketConnection.d.ts +23 -3
  16. package/dist/WebSocketConnection.js +106 -12
  17. package/dist/WebSocketConnection.test.js +236 -169
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.js +5 -1
  20. package/dist/sql-analyzer.js +2 -11
  21. package/dist/sql-analyzer.test.js +0 -12
  22. package/dist/util.d.ts +2 -0
  23. package/dist/util.js +7 -0
  24. package/dist/wire-protocol.d.ts +168 -28
  25. package/package.json +3 -1
  26. package/src/ComprehendDevSpanProcessor.test.ts +311 -507
  27. package/src/ComprehendDevSpanProcessor.ts +169 -105
  28. package/src/ComprehendMetricsExporter.test.ts +334 -0
  29. package/src/ComprehendMetricsExporter.ts +225 -0
  30. package/src/ComprehendSDK.test.ts +160 -0
  31. package/src/ComprehendSDK.ts +63 -0
  32. package/src/WebSocketConnection.test.ts +286 -205
  33. package/src/WebSocketConnection.ts +135 -13
  34. package/src/index.ts +3 -2
  35. package/src/util.ts +6 -0
  36. package/src/wire-protocol.ts +204 -29
  37. package/src/sql-analyzer.test.ts +0 -599
  38. package/src/sql-analyzer.ts +0 -439
@@ -1,599 +0,0 @@
1
- import { analyzeSQL } from './sql-analyzer';
2
-
3
- describe('SQL Analyzer - basic SQL operations', () => {
4
- it('detects a simple SELECT from one table', () => {
5
- const sql = `SELECT * FROM users`;
6
- const result = analyzeSQL(sql);
7
-
8
- expect(result.tableOperations).toEqual({ users: ['SELECT'] });
9
- expect(result.presentableQuery).toEqual(sql);
10
- });
11
-
12
- it('detects an INSERT INTO ... VALUES', () => {
13
- const sql = `INSERT INTO logs (message, level) VALUES ('hi', 'info')`;
14
- const result = analyzeSQL(sql);
15
-
16
- expect(result.tableOperations).toEqual({ logs: ['INSERT'] });
17
- expect(result.presentableQuery).toEqual(`INSERT INTO logs (message, level) VALUES (...)`);
18
- });
19
-
20
- it('detects an INSERT INTO ... SELECT', () => {
21
- const sql = `INSERT INTO archive SELECT * FROM logs`;
22
- const result = analyzeSQL(sql);
23
-
24
- expect(result.tableOperations).toEqual({
25
- archive: ['INSERT'],
26
- logs: ['SELECT']
27
- });
28
- expect(result.presentableQuery).toEqual(sql);
29
- });
30
-
31
- it('detects a simple UPDATE', () => {
32
- const sql = `UPDATE users SET last_login = NOW() WHERE id = 1`;
33
- const result = analyzeSQL(sql);
34
-
35
- expect(result.tableOperations).toEqual({ users: ['UPDATE'] });
36
- expect(result.presentableQuery).toEqual(sql);
37
- });
38
-
39
- it('detects a simple DELETE', () => {
40
- const sql = `DELETE FROM sessions WHERE expired = true`;
41
- const result = analyzeSQL(sql);
42
-
43
- expect(result.tableOperations).toEqual({ sessions: ['DELETE'] });
44
- expect(result.presentableQuery).toEqual(sql);
45
- });
46
-
47
- it('detects tables in DELETE ... USING clause', () => {
48
- const sql = `
49
- DELETE FROM sessions
50
- USING users
51
- WHERE sessions.user_id = users.id
52
- `;
53
- const result = analyzeSQL(sql);
54
-
55
- expect(result.tableOperations).toEqual({
56
- sessions: ['DELETE'],
57
- users: ['SELECT'],
58
- });
59
-
60
- expect(result.normalizedQuery).toMatch(/delete\s+from\s+sessions/i);
61
- expect(result.normalizedQuery).toMatch(/using\s+users/i);
62
- expect(result.presentableQuery).toEqual(sql);
63
- });
64
-
65
- it('detects tables with aliases in FROM clause', () => {
66
- const sql = `
67
- SELECT u.id, u.name
68
- FROM users u
69
- WHERE u.active = true
70
- `;
71
- const result = analyzeSQL(sql);
72
-
73
- expect(result.tableOperations).toEqual({
74
- users: ['SELECT'],
75
- });
76
-
77
- expect(result.normalizedQuery).toMatch(/from\s+users/i);
78
- expect(result.presentableQuery).toEqual(sql);
79
- });
80
-
81
- it('handles multiple operations on the same table', () => {
82
- const sql = `
83
- INSERT INTO stats (user_id, value)
84
- SELECT id, 42 FROM users;
85
- UPDATE stats SET value = 99 WHERE value < 50;
86
- `;
87
- const result = analyzeSQL(sql);
88
-
89
- expect(result.tableOperations).toEqual({
90
- stats: ['INSERT', 'UPDATE'],
91
- users: ['SELECT']
92
- });
93
- expect(result.presentableQuery).toEqual(sql);
94
- });
95
-
96
- it('detects tables in REPLACE INTO statements', () => {
97
- const sql = `
98
- REPLACE INTO users (id, name) VALUES (1, 'Alice');
99
- `;
100
- const result = analyzeSQL(sql);
101
-
102
- expect(result.tableOperations).toEqual({
103
- users: ['INSERT', 'UPDATE'],
104
- });
105
-
106
- expect(result.normalizedQuery).toMatch(/replace\s+into\s+users/i);
107
- expect(result.presentableQuery).toEqual(`
108
- REPLACE INTO users (id, name) VALUES (...);
109
- `);
110
- });
111
-
112
- it('detects operation type from MERGE WHEN clause', () => {
113
- const sql = `
114
- MERGE INTO inventory AS t
115
- USING incoming AS s
116
- ON t.sku = s.sku
117
- WHEN MATCHED THEN
118
- UPDATE SET t.qty = t.qty + s.qty
119
- WHEN NOT MATCHED THEN
120
- INSERT (sku, qty) VALUES (s.sku, s.qty);
121
- `;
122
- const result = analyzeSQL(sql);
123
-
124
- expect(result.tableOperations).toEqual({
125
- inventory: ['INSERT', 'UPDATE'],
126
- incoming: ['SELECT'],
127
- });
128
-
129
- expect(result.normalizedQuery).toMatch(/merge\s+into\s+inventory/i);
130
- expect(result.presentableQuery).toEqual(`
131
- MERGE INTO inventory AS t
132
- USING incoming AS s
133
- ON t.sku = s.sku
134
- WHEN MATCHED THEN
135
- UPDATE SET t.qty = t.qty + s.qty
136
- WHEN NOT MATCHED THEN
137
- INSERT (sku, qty) VALUES (...);
138
- `);
139
- });
140
-
141
- it('handles double-quoted identifiers', () => {
142
- const sql = `SELECT * FROM "Users" WHERE "Users"."Id" = 42`;
143
- const result = analyzeSQL(sql);
144
-
145
- expect(result.tableOperations).toEqual({ users: ['SELECT'] });
146
- expect(result.normalizedQuery).toMatch(/from\s+users/i);
147
- expect(result.presentableQuery).toEqual(sql);
148
- });
149
-
150
- it('handles backtick-quoted identifiers (MySQL style)', () => {
151
- const sql = 'SELECT * FROM `auditLogs` WHERE `eventType` = "login"';
152
- const result = analyzeSQL(sql);
153
-
154
- expect(result.tableOperations).toEqual({ auditlogs: ['SELECT'] });
155
- expect(result.normalizedQuery).toMatch(/from\s+auditlogs/i);
156
- expect(result.presentableQuery).toEqual(sql);
157
- });
158
-
159
- it('handles bracket-quoted identifiers (SQL Server style)', () => {
160
- const sql = `SELECT [userId], [userName] FROM [Accounts]`;
161
- const result = analyzeSQL(sql);
162
-
163
- expect(result.tableOperations).toEqual({ accounts: ['SELECT'] });
164
- expect(result.normalizedQuery).toMatch(/from\s+accounts/i);
165
- expect(result.presentableQuery).toEqual(sql);
166
- });
167
-
168
- it('normalizes quoted table names and columns to lowercase', () => {
169
- const sql = `SELECT "ID", "Email" FROM "Customer"`;
170
- const result = analyzeSQL(sql);
171
-
172
- expect(result.tableOperations).toEqual({ customer: ['SELECT'] });
173
- expect(result.normalizedQuery).toMatch(/from\s+customer/i);
174
- expect(result.presentableQuery).toEqual(sql);
175
- });
176
-
177
- it('detects real tables from subqueries in the FROM clause', () => {
178
- const sql = `
179
- SELECT sq.name
180
- FROM (
181
- SELECT name FROM employees WHERE active = true
182
- ) sq
183
- `;
184
- const result = analyzeSQL(sql);
185
-
186
- expect(result.tableOperations).toEqual({
187
- employees: ['SELECT'],
188
- });
189
-
190
- expect(result.normalizedQuery).toMatch(/from\s+employees/i);
191
- expect(result.presentableQuery).toEqual(sql);
192
- });
193
-
194
- it('does not treat FROM function() as a table', () => {
195
- const sql = `
196
- SELECT * FROM get_active_users();
197
- `;
198
- const result = analyzeSQL(sql);
199
-
200
- expect(result.tableOperations).toEqual({});
201
- expect(result.normalizedQuery).toMatch(/from\s+get_active_users\s*\(\)/i);
202
- expect(result.presentableQuery).toEqual(sql);
203
- });
204
-
205
- it('detects simple inner join', () => {
206
- const sql = `
207
- SELECT * FROM users
208
- JOIN orders ON users.id = orders.user_id
209
- `;
210
- const result = analyzeSQL(sql);
211
-
212
- expect(result.tableOperations).toEqual({
213
- users: ['SELECT'],
214
- orders: ['SELECT'],
215
- });
216
- });
217
-
218
- it('detects LEFT JOIN', () => {
219
- const sql = `
220
- SELECT u.name, o.total
221
- FROM users u
222
- LEFT JOIN orders o ON u.id = o.user_id
223
- `;
224
- const result = analyzeSQL(sql);
225
-
226
- expect(result.tableOperations).toEqual({
227
- users: ['SELECT'],
228
- orders: ['SELECT'],
229
- });
230
- });
231
-
232
- it('detects RIGHT JOIN', () => {
233
- const sql = `
234
- SELECT * FROM payments
235
- RIGHT JOIN invoices ON payments.invoice_id = invoices.id
236
- `;
237
- const result = analyzeSQL(sql);
238
-
239
- expect(result.tableOperations).toEqual({
240
- payments: ['SELECT'],
241
- invoices: ['SELECT'],
242
- });
243
- });
244
-
245
- it('detects FULL OUTER JOIN', () => {
246
- const sql = `
247
- SELECT * FROM logs l
248
- FULL OUTER JOIN metrics m ON l.time = m.time
249
- `;
250
- const result = analyzeSQL(sql);
251
-
252
- expect(result.tableOperations).toEqual({
253
- logs: ['SELECT'],
254
- metrics: ['SELECT'],
255
- });
256
- });
257
-
258
- it('detects JOIN with subquery alias', () => {
259
- const sql = `
260
- SELECT * FROM users u
261
- JOIN (SELECT * FROM events WHERE type = 'login') e ON u.id = e.user_id
262
- `;
263
- const result = analyzeSQL(sql);
264
-
265
- expect(result.tableOperations).toEqual({
266
- users: ['SELECT'],
267
- events: ['SELECT'],
268
- });
269
- });
270
-
271
- it('ignores subquery alias after JOIN', () => {
272
- const sql = `
273
- SELECT * FROM users
274
- JOIN (SELECT * FROM sessions) AS s ON users.id = s.user_id
275
- `;
276
- const result = analyzeSQL(sql);
277
-
278
- expect(result.tableOperations).toEqual({
279
- users: ['SELECT'],
280
- sessions: ['SELECT'],
281
- });
282
- });
283
-
284
- it('handles JOIN with quoted identifiers', () => {
285
- const sql = `
286
- SELECT * FROM "userData"
287
- JOIN "auditLogs" ON "userData"."id" = "auditLogs"."userId"
288
- `;
289
- const result = analyzeSQL(sql);
290
-
291
- expect(result.tableOperations).toEqual({
292
- userdata: ['SELECT'],
293
- auditlogs: ['SELECT'],
294
- });
295
- });
296
-
297
- it('detects tables involved in LATERAL JOINs', () => {
298
- const sql = `
299
- SELECT u.id, r.*
300
- FROM users u
301
- LEFT JOIN LATERAL (
302
- SELECT * FROM reports WHERE reports.user_id = u.id ORDER BY created_at DESC LIMIT 1
303
- ) r ON true
304
- `;
305
- const result = analyzeSQL(sql);
306
-
307
- expect(result.tableOperations).toEqual({
308
- users: ['SELECT'],
309
- reports: ['SELECT'],
310
- });
311
-
312
- expect(result.normalizedQuery).toMatch(/join\s+lateral/i);
313
- expect(result.normalizedQuery).toMatch(/from\s+reports/i);
314
- });
315
-
316
- it('collapses IN clauses with values to avoid cardinality explosion', () => {
317
- const sql = `SELECT name FROM users WHERE id IN (1, 2, 3)`;
318
- const result = analyzeSQL(sql);
319
-
320
- expect(result.tableOperations).toEqual({ users: ['SELECT'] });
321
- expect(result.normalizedQuery).toContain('IN(...)');
322
- expect(result.presentableQuery).toEqual(`SELECT name FROM users WHERE id IN (...)`);
323
- });
324
-
325
- it('preserves and analyzes subquery in IN clause', () => {
326
- const sql = `SELECT * FROM users WHERE id IN (SELECT user_id FROM events)`;
327
- const result = analyzeSQL(sql);
328
-
329
- expect(result.tableOperations).toEqual({ users: ['SELECT'], events: ['SELECT'] });
330
- expect(result.normalizedQuery).toMatch(/IN\s*\(.*SELECT.*\)/i);
331
- expect(result.presentableQuery).toEqual(sql);
332
- });
333
-
334
- it('ignores CTEs in table detection', () => {
335
- const sql = `
336
- WITH recent_orders AS (
337
- SELECT * FROM orders
338
- )
339
- SELECT * FROM recent_orders JOIN users ON users.id = recent_orders.user_id
340
- `;
341
- const result = analyzeSQL(sql);
342
-
343
- expect(result.tableOperations).not.toHaveProperty('recent_orders');
344
- expect(result.tableOperations).toEqual({ orders: ['SELECT'], users: ['SELECT'] });
345
- });
346
-
347
- it('handles multiple CTEs and ignores them as tables', () => {
348
- const sql = `
349
- WITH active_users AS (
350
- SELECT * FROM users WHERE active = true
351
- ),
352
- recent_logins AS (
353
- SELECT * FROM logins WHERE login_time > NOW() - INTERVAL '7 days'
354
- )
355
- SELECT au.id, rl.login_time
356
- FROM active_users au
357
- JOIN recent_logins rl ON au.id = rl.user_id
358
- JOIN sessions s ON s.user_id = au.id
359
- `;
360
- const result = analyzeSQL(sql);
361
-
362
- expect(result.tableOperations).toEqual({
363
- users: ['SELECT'],
364
- logins: ['SELECT'],
365
- sessions: ['SELECT'],
366
- });
367
-
368
- expect(result.tableOperations).not.toHaveProperty('active_users');
369
- expect(result.tableOperations).not.toHaveProperty('recent_logins');
370
-
371
- // Also check normalization keeps the CTEs in the output
372
- expect(result.normalizedQuery).toMatch(/with\s+active_users/i);
373
- expect(result.normalizedQuery).toMatch(/recent_logins/i);
374
- });
375
-
376
- it('handles multiple quoted CTEs and real quoted table names', () => {
377
- const sql = `
378
- WITH "ActiveUsers" AS (
379
- SELECT * FROM "Users" WHERE "Active" = true
380
- ),
381
- [RecentLogins] AS (
382
- SELECT * FROM [Logins] WHERE [LoginTime] > NOW() - INTERVAL '7 days'
383
- )
384
- SELECT au."Id", rl."LoginTime"
385
- FROM "ActiveUsers" au
386
- JOIN [RecentLogins] rl ON au."Id" = rl."UserId"
387
- JOIN \`Sessions\` s ON s.\`UserId\` = au."Id"
388
- `;
389
- const result = analyzeSQL(sql);
390
-
391
- expect(result.tableOperations).toEqual({
392
- users: ['SELECT'],
393
- logins: ['SELECT'],
394
- sessions: ['SELECT'],
395
- });
396
-
397
- expect(result.tableOperations).not.toHaveProperty('activeusers');
398
- expect(result.tableOperations).not.toHaveProperty('recentlogins');
399
-
400
- expect(result.normalizedQuery).toMatch(/with\s+activeusers/i);
401
- expect(result.normalizedQuery).toMatch(/recentlogins/i);
402
- expect(result.normalizedQuery).toMatch(/from\s+activeusers/i);
403
- expect(result.normalizedQuery).toMatch(/join\s+sessions/i);
404
- });
405
-
406
- it('handles recursive CTEs and ignores the CTE alias as a table', () => {
407
- const sql = `
408
- WITH RECURSIVE descendants AS (
409
- SELECT id, parent_id FROM categories WHERE parent_id IS NULL
410
- UNION ALL
411
- SELECT c.id, c.parent_id
412
- FROM categories c
413
- JOIN descendants d ON c.parent_id = d.id
414
- )
415
- SELECT * FROM descendants;
416
- `;
417
- const result = analyzeSQL(sql);
418
-
419
- expect(result.tableOperations).toEqual({
420
- categories: ['SELECT'],
421
- });
422
-
423
- expect(result.tableOperations).not.toHaveProperty('descendants');
424
-
425
- // Normalization check (ensure RECURSIVE appears)
426
- expect(result.normalizedQuery).toMatch(/with\s+recursive\s+descendants/i);
427
- expect(result.normalizedQuery).toMatch(/join\s+descendants/i);
428
- });
429
-
430
- it('ignores function argument in EXTRACT() when detecting tables', () => {
431
- const sql = `
432
- SELECT id, extract('epoch' FROM created) AS time, actor, changes
433
- FROM transactions
434
- WHERE transactions.id = $1
435
- `;
436
- const result = analyzeSQL(sql);
437
-
438
- expect(result.tableOperations).toEqual({
439
- transactions: ['SELECT']
440
- });
441
-
442
- // Bonus assertion: normalized query includes EXTRACT(...) intact
443
- expect(result.normalizedQuery).toMatch(/extract\s*\(.*from.*created.*\)/i);
444
- expect(result.presentableQuery).toEqual(sql);
445
- });
446
- });
447
-
448
- describe('SQL Analyzer - bulk INSERT VALUES cardinality reduction', () => {
449
- it('collapses single VALUES tuple to maintain consistency', () => {
450
- const sql = `INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')`;
451
- const result = analyzeSQL(sql);
452
-
453
- expect(result.tableOperations).toEqual({ users: ['INSERT'] });
454
- expect(result.normalizedQuery).toContain("VALUES(...)");
455
- expect(result.presentableQuery).toEqual(`INSERT INTO users (name, email) VALUES (...)`);
456
- });
457
-
458
- it('collapses multiple VALUES tuples to reduce cardinality', () => {
459
- const sql = `INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'), ('Bob', 'bob@example.com'), ('Charlie', 'charlie@example.com')`;
460
- const result = analyzeSQL(sql);
461
-
462
- expect(result.tableOperations).toEqual({ users: ['INSERT'] });
463
- expect(result.normalizedQuery).toContain("VALUES(...)");
464
- expect(result.presentableQuery).toEqual(`INSERT INTO users (name, email) VALUES (...)`);
465
- });
466
-
467
- it('collapses multi-line bulk INSERT VALUES', () => {
468
- const sql = `INSERT INTO products (name, price, category_id) VALUES
469
- ('Laptop', 999.99, 1),
470
- ('Mouse', 29.99, 2),
471
- ('Keyboard', 79.99, 2),
472
- ('Monitor', 299.99, 3)`;
473
- const result = analyzeSQL(sql);
474
-
475
- expect(result.tableOperations).toEqual({ products: ['INSERT'] });
476
- expect(result.normalizedQuery).toContain("VALUES(...)");
477
- expect(result.presentableQuery).toEqual(`INSERT INTO products (name, price, category_id) VALUES
478
- (...)`);
479
- });
480
-
481
- it('handles bulk INSERT with different spacing and formatting', () => {
482
- const sql = `INSERT INTO logs(timestamp,level,message)VALUES('2023-01-01','info','start'),('2023-01-02','error','failed'),('2023-01-03','info','end')`;
483
- const result = analyzeSQL(sql);
484
-
485
- expect(result.tableOperations).toEqual({ logs: ['INSERT'] });
486
- expect(result.normalizedQuery).toContain("VALUES(...)");
487
- expect(result.presentableQuery).toEqual(`INSERT INTO logs(timestamp,level,message)VALUES(...)`);
488
- });
489
-
490
- it('collapses REPLACE INTO with multiple VALUES tuples', () => {
491
- const sql = `REPLACE INTO cache (key, value, expires) VALUES ('user:1', 'data1', 3600), ('user:2', 'data2', 3600)`;
492
- const result = analyzeSQL(sql);
493
-
494
- expect(result.tableOperations).toEqual({ cache: ['INSERT', 'UPDATE'] });
495
- expect(result.normalizedQuery).toContain("VALUES(...)");
496
- expect(result.presentableQuery).toEqual(`REPLACE INTO cache (key, value, expires) VALUES (...)`);
497
- });
498
-
499
- it('handles bulk INSERT with complex nested values', () => {
500
- const sql = `INSERT INTO events (data, metadata) VALUES
501
- ('{"type":"login"}', '{"source":"web","ip":"192.168.1.1"}'),
502
- ('{"type":"logout"}', '{"source":"mobile","ip":"10.0.0.1"}')`;
503
- const result = analyzeSQL(sql);
504
-
505
- expect(result.tableOperations).toEqual({ events: ['INSERT'] });
506
- expect(result.normalizedQuery).toContain("VALUES(...)");
507
- expect(result.presentableQuery).toEqual(`INSERT INTO events (data, metadata) VALUES
508
- (...)`);
509
- });
510
-
511
- it('preserves INSERT with subquery (not VALUES)', () => {
512
- const sql = `INSERT INTO archive SELECT * FROM logs WHERE created < '2023-01-01'`;
513
- const result = analyzeSQL(sql);
514
-
515
- expect(result.tableOperations).toEqual({
516
- archive: ['INSERT'],
517
- logs: ['SELECT']
518
- });
519
- expect(result.presentableQuery).toEqual(sql);
520
- expect(result.normalizedQuery).not.toContain("VALUES(...)");
521
- });
522
-
523
- it('handles bulk INSERT with quoted identifiers', () => {
524
- const sql = `INSERT INTO "UserProfiles" ("firstName", "lastName") VALUES ('John', 'Doe'), ('Jane', 'Smith')`;
525
- const result = analyzeSQL(sql);
526
-
527
- expect(result.tableOperations).toEqual({ userprofiles: ['INSERT'] });
528
- expect(result.normalizedQuery).toContain("VALUES(...)");
529
- expect(result.presentableQuery).toEqual(`INSERT INTO "UserProfiles" ("firstName", "lastName") VALUES (...)`);
530
- });
531
-
532
- it('handles bulk INSERT with mixed value types including NULL', () => {
533
- const sql = `INSERT INTO metrics (name, value, tags) VALUES
534
- ('cpu_usage', 85.5, NULL),
535
- ('memory_usage', 67.2, 'production'),
536
- ('disk_usage', NULL, 'staging')`;
537
- const result = analyzeSQL(sql);
538
-
539
- expect(result.tableOperations).toEqual({ metrics: ['INSERT'] });
540
- expect(result.normalizedQuery).toContain("VALUES(...)");
541
- expect(result.presentableQuery).toEqual(`INSERT INTO metrics (name, value, tags) VALUES
542
- (...)`);
543
- });
544
-
545
- it('handles very large bulk INSERT (cardinality explosion scenario)', () => {
546
- // Generate a bulk insert with many VALUES tuples to simulate real cardinality issues
547
- const valueTuples = Array.from({length: 100}, (_, i) => `('user${i}', 'user${i}@example.com')`);
548
- const sql = `INSERT INTO users (name, email) VALUES ${valueTuples.join(', ')}`;
549
- const result = analyzeSQL(sql);
550
-
551
- expect(result.tableOperations).toEqual({ users: ['INSERT'] });
552
- expect(result.normalizedQuery).toContain("VALUES(...)");
553
- expect(result.presentableQuery).toEqual(`INSERT INTO users (name, email) VALUES (...)`);
554
-
555
- // Ensure the normalized query is much shorter than the original
556
- expect(result.normalizedQuery.length).toBeLessThan(sql.length / 2);
557
- });
558
-
559
- it('handles bulk INSERT with functions and expressions in VALUES', () => {
560
- const sql = `INSERT INTO audit_log (event_time, user_id, action) VALUES
561
- (NOW(), 1, 'login'),
562
- (CURRENT_TIMESTAMP, 2, 'logout'),
563
- (DATE('2023-01-01'), 3, 'update')`;
564
- const result = analyzeSQL(sql);
565
-
566
- expect(result.tableOperations).toEqual({ audit_log: ['INSERT'] });
567
- expect(result.normalizedQuery).toContain("VALUES(...)");
568
- expect(result.presentableQuery).toEqual(`INSERT INTO audit_log (event_time, user_id, action) VALUES
569
- (...)`);
570
- });
571
-
572
- it('handles bulk INSERT with parentheses in string values', () => {
573
- const sql = `INSERT INTO comments (text, author) VALUES
574
- ('This is a comment (with parentheses)', 'user1'),
575
- ('Another comment (also with parens)', 'user2')`;
576
- const result = analyzeSQL(sql);
577
-
578
- expect(result.tableOperations).toEqual({ comments: ['INSERT'] });
579
- expect(result.normalizedQuery).toContain("VALUES(...)");
580
- expect(result.presentableQuery).toEqual(`INSERT INTO comments (text, author) VALUES
581
- (...)`);
582
- });
583
-
584
- it('preserves whitespace before ON CONFLICT after VALUES clause', () => {
585
- const sql = `INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com') ON CONFLICT (email) DO NOTHING`;
586
- const result = analyzeSQL(sql);
587
-
588
- expect(result.tableOperations).toEqual({ users: ['INSERT'] });
589
- expect(result.presentableQuery).toEqual(`INSERT INTO users (name, email) VALUES (...) ON CONFLICT (email) DO NOTHING`);
590
- });
591
-
592
- it('preserves whitespace before ON CONFLICT with multiple VALUES tuples', () => {
593
- const sql = `INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'), ('Bob', 'bob@example.com') ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name`;
594
- const result = analyzeSQL(sql);
595
-
596
- expect(result.tableOperations).toEqual({ users: ['INSERT'] });
597
- expect(result.presentableQuery).toEqual(`INSERT INTO users (name, email) VALUES (...) ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name`);
598
- });
599
- });