|
7 | 7 | postgresDialect, |
8 | 8 | SqlSessionError, |
9 | 9 | type SqlColumnCatalogProvider, |
| 10 | + type SqlNamespaceCatalogProvider, |
10 | 11 | } from "../index.js"; |
11 | 12 | import { |
12 | 13 | DefaultSqlLanguageService, |
@@ -1428,6 +1429,149 @@ describe("column completion session integration", () => { |
1428 | 1429 | }); |
1429 | 1430 | }); |
1430 | 1431 |
|
| 1432 | +describe("namespace completion session integration", () => { |
| 1433 | + const relationCatalog: SqlRelationCatalogProvider = { |
| 1434 | + id: "relations", |
| 1435 | + search: async () => ({ |
| 1436 | + coverage: { kind: "complete" }, |
| 1437 | + epoch: { generation: 1, token: "epoch-1" }, |
| 1438 | + relations: [], |
| 1439 | + status: "ready", |
| 1440 | + }), |
| 1441 | + }; |
| 1442 | + |
| 1443 | + function serviceWithNamespaces( |
| 1444 | + search: SqlNamespaceCatalogProvider["search"], |
| 1445 | + budget = 40, |
| 1446 | + ) { |
| 1447 | + return createSqlLanguageService<TestContext>({ |
| 1448 | + catalog: relationCatalog, |
| 1449 | + completion: { catalogResponseBudgetMs: budget }, |
| 1450 | + dialects: [duckdb], |
| 1451 | + namespaces: { id: "namespaces", search }, |
| 1452 | + }); |
| 1453 | + } |
| 1454 | + |
| 1455 | + it("merges namespace containers into relation-site completion", async () => { |
| 1456 | + const requests: Parameters< |
| 1457 | + SqlNamespaceCatalogProvider["search"] |
| 1458 | + >[0][] = []; |
| 1459 | + const service = serviceWithNamespaces(async (request) => { |
| 1460 | + requests.push(request); |
| 1461 | + return { |
| 1462 | + containers: [{ |
| 1463 | + canonicalPath: [{ |
| 1464 | + quoted: false, |
| 1465 | + role: "schema", |
| 1466 | + value: "main", |
| 1467 | + }], |
| 1468 | + containerEntityId: "schema:main", |
| 1469 | + detail: "DuckDB schema", |
| 1470 | + insertText: "main", |
| 1471 | + matchQuality: "exact", |
| 1472 | + }], |
| 1473 | + coverage: "complete", |
| 1474 | + epoch: { generation: 1, token: "epoch-1" }, |
| 1475 | + status: "ready", |
| 1476 | + }; |
| 1477 | + }); |
| 1478 | + const text = "SELECT * FROM ma"; |
| 1479 | + const session = service.openDocument({ |
| 1480 | + context: { |
| 1481 | + catalog: { |
| 1482 | + scope: "connection:1", |
| 1483 | + searchPath: [[{ quoted: false, value: "main" }]], |
| 1484 | + }, |
| 1485 | + dialect: "duckdb", |
| 1486 | + engine: "local", |
| 1487 | + }, |
| 1488 | + text, |
| 1489 | + }); |
| 1490 | + |
| 1491 | + await expect(session.complete({ |
| 1492 | + position: text.length, |
| 1493 | + trigger: { kind: "invoked" }, |
| 1494 | + })).resolves.toMatchObject({ |
| 1495 | + sources: [ |
| 1496 | + { feature: "relation-catalog" }, |
| 1497 | + { |
| 1498 | + feature: "namespace-catalog", |
| 1499 | + outcome: "ready", |
| 1500 | + providerId: "namespaces", |
| 1501 | + }, |
| 1502 | + ], |
| 1503 | + status: "ready", |
| 1504 | + value: { |
| 1505 | + isIncomplete: false, |
| 1506 | + items: [{ |
| 1507 | + detail: "DuckDB schema", |
| 1508 | + edit: { from: 14, insert: "main", to: 16 }, |
| 1509 | + kind: "namespace", |
| 1510 | + label: "main", |
| 1511 | + provenance: { |
| 1512 | + containerEntityId: "schema:main", |
| 1513 | + kind: "namespace-catalog", |
| 1514 | + providerId: "namespaces", |
| 1515 | + scope: "connection:1", |
| 1516 | + }, |
| 1517 | + role: "schema", |
| 1518 | + }], |
| 1519 | + }, |
| 1520 | + }); |
| 1521 | + expect(requests).toHaveLength(1); |
| 1522 | + expect(requests[0]).toMatchObject({ |
| 1523 | + dialectId: "duckdb", |
| 1524 | + expectedEpoch: null, |
| 1525 | + limit: 128, |
| 1526 | + prefix: { quoted: false, value: "ma" }, |
| 1527 | + qualifier: [], |
| 1528 | + scope: "connection:1", |
| 1529 | + }); |
| 1530 | + service.dispose(); |
| 1531 | + }); |
| 1532 | + |
| 1533 | + it("keeps slow namespace providers inside the shared budget", async () => { |
| 1534 | + vi.useFakeTimers(); |
| 1535 | + try { |
| 1536 | + const service = serviceWithNamespaces( |
| 1537 | + () => new Promise(() => {}), |
| 1538 | + 0, |
| 1539 | + ); |
| 1540 | + const text = "SELECT * FROM "; |
| 1541 | + const session = service.openDocument({ |
| 1542 | + context: { |
| 1543 | + catalog: { scope: "connection:1" }, |
| 1544 | + dialect: "duckdb", |
| 1545 | + engine: "local", |
| 1546 | + }, |
| 1547 | + text, |
| 1548 | + }); |
| 1549 | + const completion = session.complete({ |
| 1550 | + position: text.length, |
| 1551 | + trigger: { kind: "invoked" }, |
| 1552 | + }); |
| 1553 | + await vi.advanceTimersByTimeAsync(0); |
| 1554 | + await expect(completion).resolves.toMatchObject({ |
| 1555 | + sources: [ |
| 1556 | + { feature: "relation-catalog" }, |
| 1557 | + { |
| 1558 | + feature: "namespace-catalog", |
| 1559 | + outcome: "loading", |
| 1560 | + }, |
| 1561 | + ], |
| 1562 | + status: "ready", |
| 1563 | + value: { |
| 1564 | + isIncomplete: true, |
| 1565 | + issues: [{ reason: "namespace-catalog-loading" }], |
| 1566 | + }, |
| 1567 | + }); |
| 1568 | + service.dispose(); |
| 1569 | + } finally { |
| 1570 | + vi.useRealTimers(); |
| 1571 | + } |
| 1572 | + }); |
| 1573 | +}); |
| 1574 | + |
1431 | 1575 | describe("statement-index session cache", () => { |
1432 | 1576 | it("binds lexical behavior through authentic dialect handles", () => { |
1433 | 1577 | const service = new DefaultSqlLanguageService<TestContext>({ |
|
0 commit comments