postgresai 0.14.0-dev.85 → 0.14.0-dev.87

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/test/init.test.ts CHANGED
@@ -281,7 +281,7 @@ describe("init module", () => {
281
281
  return { rowCount: 1, rows: [] };
282
282
  }
283
283
  if (String(sql).includes("select rolconfig")) {
284
- return { rowCount: 1, rows: [{ rolconfig: ['search_path=postgres_ai, "$user", public, pg_catalog'] }] };
284
+ return { rowCount: 1, rows: [{ rolconfig: ['search_path=postgres_ai, extensions, "$user", public, pg_catalog'] }] };
285
285
  }
286
286
  if (String(sql).includes("from pg_catalog.pg_roles")) {
287
287
  return { rowCount: 1, rows: [] };
@@ -307,6 +307,10 @@ describe("init module", () => {
307
307
  if (String(sql).includes("has_schema_privilege")) {
308
308
  return { rowCount: 1, rows: [{ ok: true }] };
309
309
  }
310
+ // Query for pg_stat_statements extension schema location
311
+ if (String(sql).includes("pg_extension e") && String(sql).includes("pg_stat_statements")) {
312
+ return { rowCount: 1, rows: [{ schema: "pg_catalog" }] };
313
+ }
310
314
 
311
315
  throw new Error(`unexpected sql: ${sql} params=${JSON.stringify(params)}`);
312
316
  },
@@ -363,6 +367,10 @@ describe("init module", () => {
363
367
  if (String(sql).includes("has_schema_privilege")) {
364
368
  return { rowCount: 1, rows: [{ ok: true }] };
365
369
  }
370
+ // Query for pg_stat_statements extension schema location (Supabase uses 'extensions' schema)
371
+ if (String(sql).includes("pg_extension e") && String(sql).includes("pg_stat_statements")) {
372
+ return { rowCount: 1, rows: [{ schema: "extensions" }] };
373
+ }
366
374
 
367
375
  throw new Error(`unexpected sql: ${sql} params=${JSON.stringify(params)}`);
368
376
  },
@@ -382,6 +390,335 @@ describe("init module", () => {
382
390
  expect(calls.some((c) => c.includes("select rolconfig"))).toBe(false);
383
391
  });
384
392
 
393
+ test("verifyInitSetup checks extensions schema when pg_stat_statements is there", async () => {
394
+ const calls: string[] = [];
395
+ const client = {
396
+ query: async (sql: string, params?: any) => {
397
+ calls.push(String(sql));
398
+
399
+ if (String(sql).toLowerCase().startsWith("begin isolation level repeatable read")) {
400
+ return { rowCount: 1, rows: [] };
401
+ }
402
+ if (String(sql).toLowerCase() === "rollback;") {
403
+ return { rowCount: 1, rows: [] };
404
+ }
405
+ if (String(sql).includes("select rolconfig")) {
406
+ return { rowCount: 1, rows: [{ rolconfig: ['search_path=postgres_ai, extensions, "$user", public, pg_catalog'] }] };
407
+ }
408
+ if (String(sql).includes("from pg_catalog.pg_roles")) {
409
+ return { rowCount: 1, rows: [{ rolname: DEFAULT_MONITORING_USER }] };
410
+ }
411
+ if (String(sql).includes("has_database_privilege")) {
412
+ return { rowCount: 1, rows: [{ ok: true }] };
413
+ }
414
+ if (String(sql).includes("pg_has_role")) {
415
+ return { rowCount: 1, rows: [{ ok: true }] };
416
+ }
417
+ if (String(sql).includes("has_table_privilege")) {
418
+ return { rowCount: 1, rows: [{ ok: true }] };
419
+ }
420
+ if (String(sql).includes("to_regclass")) {
421
+ return { rowCount: 1, rows: [{ ok: true }] };
422
+ }
423
+ if (String(sql).includes("has_function_privilege")) {
424
+ return { rowCount: 1, rows: [{ ok: true }] };
425
+ }
426
+ // pg_stat_statements is in 'extensions' schema
427
+ if (String(sql).includes("pg_extension e") && String(sql).includes("pg_stat_statements")) {
428
+ return { rowCount: 1, rows: [{ schema: "extensions" }] };
429
+ }
430
+ // Check for USAGE on extensions schema
431
+ if (String(sql).includes("has_schema_privilege") && params?.[1] === "extensions") {
432
+ return { rowCount: 1, rows: [{ ok: true }] };
433
+ }
434
+ if (String(sql).includes("has_schema_privilege")) {
435
+ return { rowCount: 1, rows: [{ ok: true }] };
436
+ }
437
+
438
+ throw new Error(`unexpected sql: ${sql} params=${JSON.stringify(params)}`);
439
+ },
440
+ };
441
+
442
+ const r = await init.verifyInitSetup({
443
+ client: client as any,
444
+ database: "mydb",
445
+ monitoringUser: DEFAULT_MONITORING_USER,
446
+ includeOptionalPermissions: false,
447
+ });
448
+ expect(r.ok).toBe(true);
449
+ expect(r.missingRequired.length).toBe(0);
450
+ // Should have queried for pg_stat_statements schema location
451
+ expect(calls.some((c) => c.includes("pg_extension e") && c.includes("pg_stat_statements"))).toBe(true);
452
+ });
453
+
454
+ test("verifyInitSetup reports missing extensions schema access", async () => {
455
+ const client = {
456
+ query: async (sql: string, params?: any) => {
457
+ if (String(sql).toLowerCase().startsWith("begin isolation level repeatable read")) {
458
+ return { rowCount: 1, rows: [] };
459
+ }
460
+ if (String(sql).toLowerCase() === "rollback;") {
461
+ return { rowCount: 1, rows: [] };
462
+ }
463
+ if (String(sql).includes("select rolconfig")) {
464
+ return { rowCount: 1, rows: [{ rolconfig: ['search_path=postgres_ai, "$user", public, pg_catalog'] }] };
465
+ }
466
+ if (String(sql).includes("from pg_catalog.pg_roles")) {
467
+ return { rowCount: 1, rows: [{ rolname: DEFAULT_MONITORING_USER }] };
468
+ }
469
+ if (String(sql).includes("has_database_privilege")) {
470
+ return { rowCount: 1, rows: [{ ok: true }] };
471
+ }
472
+ if (String(sql).includes("pg_has_role")) {
473
+ return { rowCount: 1, rows: [{ ok: true }] };
474
+ }
475
+ if (String(sql).includes("has_table_privilege")) {
476
+ return { rowCount: 1, rows: [{ ok: true }] };
477
+ }
478
+ if (String(sql).includes("to_regclass")) {
479
+ return { rowCount: 1, rows: [{ ok: true }] };
480
+ }
481
+ if (String(sql).includes("has_function_privilege")) {
482
+ return { rowCount: 1, rows: [{ ok: true }] };
483
+ }
484
+ // pg_stat_statements is in 'extensions' schema
485
+ if (String(sql).includes("pg_extension e") && String(sql).includes("pg_stat_statements")) {
486
+ return { rowCount: 1, rows: [{ schema: "extensions" }] };
487
+ }
488
+ // No USAGE on extensions schema
489
+ if (String(sql).includes("has_schema_privilege") && params?.[1] === "extensions") {
490
+ return { rowCount: 1, rows: [{ ok: false }] };
491
+ }
492
+ if (String(sql).includes("has_schema_privilege")) {
493
+ return { rowCount: 1, rows: [{ ok: true }] };
494
+ }
495
+
496
+ throw new Error(`unexpected sql: ${sql} params=${JSON.stringify(params)}`);
497
+ },
498
+ };
499
+
500
+ const r = await init.verifyInitSetup({
501
+ client: client as any,
502
+ database: "mydb",
503
+ monitoringUser: DEFAULT_MONITORING_USER,
504
+ includeOptionalPermissions: false,
505
+ });
506
+ expect(r.ok).toBe(false);
507
+ // Should report missing USAGE on extensions schema
508
+ expect(r.missingRequired.some((m) => m.includes("extensions") && m.includes("pg_stat_statements"))).toBe(true);
509
+ // Should also report missing extensions in search_path
510
+ expect(r.missingRequired.some((m) => m.includes("search_path") && m.includes("extensions"))).toBe(true);
511
+ });
512
+
513
+ test("buildInitPlan includes dynamic search_path with extension schema detection", async () => {
514
+ const plan = await init.buildInitPlan({
515
+ database: "mydb",
516
+ monitoringUser: DEFAULT_MONITORING_USER,
517
+ monitoringPassword: "pw",
518
+ includeOptionalPermissions: false,
519
+ });
520
+
521
+ const permStep = plan.steps.find((s) => s.name === "03.permissions");
522
+ expect(permStep).toBeTruthy();
523
+ // Should use dynamic DO block to set search_path based on detected extension schema
524
+ expect(permStep!.sql).toMatch(/alter\s+user.*set\s+search_path\s*=/i);
525
+ // Should detect pg_stat_statements extension schema dynamically
526
+ expect(permStep!.sql).toMatch(/quote_ident\(ext_schema\)/i);
527
+ });
528
+
529
+ test("buildInitPlan includes dynamic extension schema grant", async () => {
530
+ const plan = await init.buildInitPlan({
531
+ database: "mydb",
532
+ monitoringUser: DEFAULT_MONITORING_USER,
533
+ monitoringPassword: "pw",
534
+ includeOptionalPermissions: false,
535
+ });
536
+
537
+ const permStep = plan.steps.find((s) => s.name === "03.permissions");
538
+ expect(permStep).toBeTruthy();
539
+ // Should include DO block that grants USAGE on extension schema
540
+ expect(permStep!.sql).toMatch(/do\s+\$\$/i);
541
+ expect(permStep!.sql).toMatch(/pg_stat_statements/);
542
+ expect(permStep!.sql).toMatch(/grant usage on schema/i);
543
+ });
544
+
545
+ test("verifyInitSetup handles pg_stat_statements not installed", async () => {
546
+ const calls: string[] = [];
547
+ const client = {
548
+ query: async (sql: string, params?: any) => {
549
+ calls.push(String(sql));
550
+
551
+ if (String(sql).toLowerCase().startsWith("begin isolation level repeatable read")) {
552
+ return { rowCount: 1, rows: [] };
553
+ }
554
+ if (String(sql).toLowerCase() === "rollback;") {
555
+ return { rowCount: 1, rows: [] };
556
+ }
557
+ if (String(sql).includes("select rolconfig")) {
558
+ return { rowCount: 1, rows: [{ rolconfig: ['search_path=postgres_ai, extensions, "$user", public, pg_catalog'] }] };
559
+ }
560
+ if (String(sql).includes("from pg_catalog.pg_roles")) {
561
+ return { rowCount: 1, rows: [{ rolname: DEFAULT_MONITORING_USER }] };
562
+ }
563
+ if (String(sql).includes("has_database_privilege")) {
564
+ return { rowCount: 1, rows: [{ ok: true }] };
565
+ }
566
+ if (String(sql).includes("pg_has_role")) {
567
+ return { rowCount: 1, rows: [{ ok: true }] };
568
+ }
569
+ if (String(sql).includes("has_table_privilege")) {
570
+ return { rowCount: 1, rows: [{ ok: true }] };
571
+ }
572
+ if (String(sql).includes("to_regclass")) {
573
+ return { rowCount: 1, rows: [{ ok: true }] };
574
+ }
575
+ if (String(sql).includes("has_function_privilege")) {
576
+ return { rowCount: 1, rows: [{ ok: true }] };
577
+ }
578
+ // pg_stat_statements is NOT installed - empty result
579
+ if (String(sql).includes("pg_extension e") && String(sql).includes("pg_stat_statements")) {
580
+ return { rowCount: 0, rows: [] };
581
+ }
582
+ if (String(sql).includes("has_schema_privilege")) {
583
+ return { rowCount: 1, rows: [{ ok: true }] };
584
+ }
585
+
586
+ throw new Error(`unexpected sql: ${sql} params=${JSON.stringify(params)}`);
587
+ },
588
+ };
589
+
590
+ const r = await init.verifyInitSetup({
591
+ client: client as any,
592
+ database: "mydb",
593
+ monitoringUser: DEFAULT_MONITORING_USER,
594
+ includeOptionalPermissions: false,
595
+ });
596
+ // Should pass without errors - missing extension shouldn't cause failure
597
+ expect(r.ok).toBe(true);
598
+ expect(r.missingRequired.length).toBe(0);
599
+ // Should have queried for pg_stat_statements schema location
600
+ expect(calls.some((c) => c.includes("pg_extension e") && c.includes("pg_stat_statements"))).toBe(true);
601
+ });
602
+
603
+ test("verifyInitSetup skips extension schema check when in pg_catalog", async () => {
604
+ const calls: string[] = [];
605
+ const client = {
606
+ query: async (sql: string, params?: any) => {
607
+ calls.push(String(sql));
608
+
609
+ if (String(sql).toLowerCase().startsWith("begin isolation level repeatable read")) {
610
+ return { rowCount: 1, rows: [] };
611
+ }
612
+ if (String(sql).toLowerCase() === "rollback;") {
613
+ return { rowCount: 1, rows: [] };
614
+ }
615
+ if (String(sql).includes("select rolconfig")) {
616
+ return { rowCount: 1, rows: [{ rolconfig: ['search_path=postgres_ai, "$user", public, pg_catalog'] }] };
617
+ }
618
+ if (String(sql).includes("from pg_catalog.pg_roles")) {
619
+ return { rowCount: 1, rows: [{ rolname: DEFAULT_MONITORING_USER }] };
620
+ }
621
+ if (String(sql).includes("has_database_privilege")) {
622
+ return { rowCount: 1, rows: [{ ok: true }] };
623
+ }
624
+ if (String(sql).includes("pg_has_role")) {
625
+ return { rowCount: 1, rows: [{ ok: true }] };
626
+ }
627
+ if (String(sql).includes("has_table_privilege")) {
628
+ return { rowCount: 1, rows: [{ ok: true }] };
629
+ }
630
+ if (String(sql).includes("to_regclass")) {
631
+ return { rowCount: 1, rows: [{ ok: true }] };
632
+ }
633
+ if (String(sql).includes("has_function_privilege")) {
634
+ return { rowCount: 1, rows: [{ ok: true }] };
635
+ }
636
+ // pg_stat_statements is in pg_catalog (standard location)
637
+ if (String(sql).includes("pg_extension e") && String(sql).includes("pg_stat_statements")) {
638
+ return { rowCount: 1, rows: [{ schema: "pg_catalog" }] };
639
+ }
640
+ if (String(sql).includes("has_schema_privilege")) {
641
+ return { rowCount: 1, rows: [{ ok: true }] };
642
+ }
643
+
644
+ throw new Error(`unexpected sql: ${sql} params=${JSON.stringify(params)}`);
645
+ },
646
+ };
647
+
648
+ const r = await init.verifyInitSetup({
649
+ client: client as any,
650
+ database: "mydb",
651
+ monitoringUser: DEFAULT_MONITORING_USER,
652
+ includeOptionalPermissions: false,
653
+ });
654
+ // Should pass - pg_catalog doesn't need extra USAGE grant
655
+ expect(r.ok).toBe(true);
656
+ expect(r.missingRequired.length).toBe(0);
657
+ // Should NOT have queried for has_schema_privilege on pg_catalog specifically
658
+ // (the code skips the check for pg_catalog and public schemas)
659
+ const pgCatalogPrivCheck = calls.filter(
660
+ (c) => c.includes("has_schema_privilege") && c.includes("pg_catalog")
661
+ );
662
+ // Should only have the standard public schema check, not a pg_catalog check for extension
663
+ expect(pgCatalogPrivCheck.length).toBe(0);
664
+ });
665
+
666
+ test("verifyInitSetup skips extension schema check when in public", async () => {
667
+ const calls: string[] = [];
668
+ const client = {
669
+ query: async (sql: string, params?: any) => {
670
+ calls.push(String(sql));
671
+
672
+ if (String(sql).toLowerCase().startsWith("begin isolation level repeatable read")) {
673
+ return { rowCount: 1, rows: [] };
674
+ }
675
+ if (String(sql).toLowerCase() === "rollback;") {
676
+ return { rowCount: 1, rows: [] };
677
+ }
678
+ if (String(sql).includes("select rolconfig")) {
679
+ return { rowCount: 1, rows: [{ rolconfig: ['search_path=postgres_ai, "$user", public, pg_catalog'] }] };
680
+ }
681
+ if (String(sql).includes("from pg_catalog.pg_roles")) {
682
+ return { rowCount: 1, rows: [{ rolname: DEFAULT_MONITORING_USER }] };
683
+ }
684
+ if (String(sql).includes("has_database_privilege")) {
685
+ return { rowCount: 1, rows: [{ ok: true }] };
686
+ }
687
+ if (String(sql).includes("pg_has_role")) {
688
+ return { rowCount: 1, rows: [{ ok: true }] };
689
+ }
690
+ if (String(sql).includes("has_table_privilege")) {
691
+ return { rowCount: 1, rows: [{ ok: true }] };
692
+ }
693
+ if (String(sql).includes("to_regclass")) {
694
+ return { rowCount: 1, rows: [{ ok: true }] };
695
+ }
696
+ if (String(sql).includes("has_function_privilege")) {
697
+ return { rowCount: 1, rows: [{ ok: true }] };
698
+ }
699
+ // pg_stat_statements is in public schema
700
+ if (String(sql).includes("pg_extension e") && String(sql).includes("pg_stat_statements")) {
701
+ return { rowCount: 1, rows: [{ schema: "public" }] };
702
+ }
703
+ if (String(sql).includes("has_schema_privilege")) {
704
+ return { rowCount: 1, rows: [{ ok: true }] };
705
+ }
706
+
707
+ throw new Error(`unexpected sql: ${sql} params=${JSON.stringify(params)}`);
708
+ },
709
+ };
710
+
711
+ const r = await init.verifyInitSetup({
712
+ client: client as any,
713
+ database: "mydb",
714
+ monitoringUser: DEFAULT_MONITORING_USER,
715
+ includeOptionalPermissions: false,
716
+ });
717
+ // Should pass - public doesn't need extra USAGE grant for extension
718
+ expect(r.ok).toBe(true);
719
+ expect(r.missingRequired.length).toBe(0);
720
+ });
721
+
385
722
  test("buildInitPlan preserves comments when filtering ALTER USER", async () => {
386
723
  const plan = await init.buildInitPlan({
387
724
  database: "mydb",