Skip to content

Commit e59423c

Browse files
committed
feat(vnext): integrate namespace completion
1 parent 345ea3b commit e59423c

11 files changed

Lines changed: 591 additions & 56 deletions

docs/vnext/marimo-sql-migration.md

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Marimo SQL Completion Migration
22

3-
Status: implementation fixture; relation and column completion
3+
Status: implementation fixture; relation, column, and namespace completion
44

55
The compile-only fixture
66
[`marimo-sql-migration.test-d.ts`](../../test/vnext-types/marimo-sql-migration.test-d.ts)
@@ -121,20 +121,17 @@ explicit, tested generic-dialect policy.
121121

122122
Marimo's current `tablesCompletionSource()` is broader than its name. The
123123
CodeMirror SQL schema source provides relations, namespace navigation, and
124-
columns. vNext now provides the relation and lazy batched column portions,
124+
columns. vNext now provides all three through separate bounded providers,
125125
including qualified and unqualified query-site completion, ambiguity handling,
126-
stable provenance, cancellation, and bounded provider work.
126+
stable provenance, cancellation, and batched column work.
127127

128128
The remaining feature gaps are:
129129

130-
- namespace/container completion for database, schema, project, and dataset
131-
navigation; and
132130
- the dialect coverage described above.
133131

134-
The fixture defines marimo's immutable namespace projection—stable entity ID,
135-
scope, canonical identifier path, and namespace kind—but deliberately does
136-
not invent a provider import. Once a public namespace provider lands, that
137-
projection should feed one scoped provider on the shared service.
132+
The fixture feeds marimo's immutable namespace projection—stable entity ID,
133+
scope, canonical identifier path, and namespace kind—through one public,
134+
scoped namespace provider on the shared service.
138135

139136
## Migration sequence
140137

@@ -148,20 +145,18 @@ projection should feed one scoped provider on the shared service.
148145
4. Compare relation and column results with the golden corpus, including
149146
quoted insert text, aliases, ambiguity, partial/loading/failure states, and
150147
cold epoch behavior.
151-
5. Add the public namespace provider and connect the prepared marimo namespace
152-
projection.
153-
6. Cut over the four supported dialects as one source replacement, preserving
148+
5. Cut over the four supported dialects as one source replacement, preserving
154149
variable and keyword external sources.
155-
7. Add dialect coverage, expand the router, and remove completion-only legacy
150+
6. Add dialect coverage, expand the router, and remove completion-only legacy
156151
schema code. Keep legacy schema data while hover or diagnostics still use
157152
it.
158153

159154
## Acceptance tests
160155

161156
The compile-only marimo fixture proves:
162157

163-
- one shared service configured with one relation provider and one batched
164-
column provider;
158+
- one shared service configured with one relation provider, one batched
159+
column provider, and one namespace provider;
165160
- a two-relation cold column request with `expectedEpoch: null`;
166161
- stable relation and column IDs, canonical identifiers, and distinct
167162
provider-rendered insert text;

docs/vnext/namespace-catalog-authority.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# vNext Namespace Catalog Authority
22

3-
Status: integration-ready internal vertical slice
3+
Status: public provider and session integration
44

55
Namespace completion searches catalog, schema, project, and dataset containers.
66
One query site produces one bounded provider request containing its qualifier,
@@ -30,8 +30,9 @@ A newer request supersedes and aborts prior work for the same owner. Explicit
3030
cancellation and owner/coordinator disposal abort pending work. Provider throws,
3131
rejections, malformed data, and late settlements are contained.
3232

33-
Session integration should prepare one owner for each live scope/dialect and
34-
dispose it when catalog authority changes. Query-site integration calls
33+
Session integration prepares one owner for each live scope/dialect and
34+
disposes it when catalog authority changes. Query-site integration calls
3535
`prepareSqlNamespaceCatalogSearch`, submits the result through the owner, and
3636
passes the outcome plus the site's replacement range and dialect prefix matcher
37-
to `composeSqlNamespaceCompletion`.
37+
to `composeSqlNamespaceCompletion`. Namespace items are merged with local and
38+
relation-catalog items under the same bounded completion response budget.

src/vnext/__tests__/session.test.ts

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
postgresDialect,
88
SqlSessionError,
99
type SqlColumnCatalogProvider,
10+
type SqlNamespaceCatalogProvider,
1011
} from "../index.js";
1112
import {
1213
DefaultSqlLanguageService,
@@ -1428,6 +1429,149 @@ describe("column completion session integration", () => {
14281429
});
14291430
});
14301431

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+
14311575
describe("statement-index session cache", () => {
14321576
it("binds lexical behavior through authentic dialect handles", () => {
14331577
const service = new DefaultSqlLanguageService<TestContext>({

src/vnext/codemirror/browser_tests/sql-editor-completion-gate.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,60 @@ test("vNext editor applies a batched column completion", async () => {
218218
service.dispose();
219219
parent.remove();
220220
});
221+
222+
test("vNext editor exposes namespace containers at relation sites", async () => {
223+
const parent = document.createElement("div");
224+
document.body.append(parent);
225+
let namespaceCalls = 0;
226+
const service = createSqlLanguageService({
227+
dialects: [duckdbDialect()],
228+
namespaces: {
229+
id: "browser-namespaces",
230+
search: async () => {
231+
namespaceCalls += 1;
232+
return {
233+
containers: [{
234+
canonicalPath: [{
235+
quoted: false,
236+
role: "schema",
237+
value: "main",
238+
}],
239+
containerEntityId: "schema:main",
240+
insertText: "main",
241+
matchQuality: "exact",
242+
}],
243+
coverage: "complete",
244+
epoch: { generation: 1, token: "epoch-1" },
245+
status: "ready",
246+
};
247+
},
248+
},
249+
});
250+
const support = sqlEditor({
251+
initialContext: {
252+
catalog: { scope: "browser-namespaces" },
253+
dialect: "duckdb",
254+
},
255+
service,
256+
});
257+
const documentText = "SELECT * FROM ma";
258+
const view = new EditorView({
259+
doc: documentText,
260+
extensions: support.extension,
261+
parent,
262+
selection: { anchor: documentText.length },
263+
});
264+
265+
expect(startCompletion(view)).toBe(true);
266+
await expect.poll(() =>
267+
currentCompletions(view.state).map((item) => ({
268+
label: item.label,
269+
type: item.type,
270+
}))
271+
).toEqual([{ label: "main", type: "namespace" }]);
272+
expect(namespaceCalls).toBe(1);
273+
274+
view.destroy();
275+
service.dispose();
276+
parent.remove();
277+
});

src/vnext/codemirror/sql-editor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ function haveOneEditRange(items: readonly SqlCompletionItem[]): boolean {
204204

205205
function completionType(item: SqlCompletionItem): string {
206206
if (item.kind === "column") return "property";
207+
if (item.kind === "namespace") return "namespace";
207208
return item.relationKind === "cte" ? "type" : "table";
208209
}
209210

src/vnext/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ export type {
1616
SqlColumnCatalogRelationResult,
1717
SqlColumnCatalogResolvedColumn,
1818
} from "./column-catalog-types.js";
19+
export type {
20+
SqlCanonicalNamespacePath,
21+
SqlNamespaceCatalogContainer,
22+
SqlNamespaceCatalogProvider,
23+
SqlNamespaceCatalogProvenance,
24+
SqlNamespaceCatalogResolvedContainer,
25+
SqlNamespaceCatalogSearchRequest,
26+
SqlNamespaceCatalogSearchResponse,
27+
SqlNamespaceContainerRole,
28+
SqlNamespacePathComponent,
29+
SqlNamespaceQuerySite,
30+
} from "./namespace-catalog-types.js";
1931
export type {
2032
OpenSqlDocument,
2133
SqlCatalogContext,
@@ -70,6 +82,8 @@ export type {
7082
SqlCompletionIssue,
7183
SqlCompletionRequest,
7284
SqlCompletionRefreshToken,
85+
SqlNamespaceCatalogProviderReport,
86+
SqlNamespaceCompletionProvenance,
7387
SqlCompletionTrigger,
7488
SqlDisposable,
7589
SqlRelationCatalogProvider,

0 commit comments

Comments
 (0)