@@ -1463,6 +1463,64 @@ describe("column completion session integration", () => {
14631463 }
14641464 } ) ;
14651465
1466+ it ( "bounds provider-declared column loading to one automatic retry" , async ( ) => {
1467+ vi . useFakeTimers ( ) ;
1468+ try {
1469+ let calls = 0 ;
1470+ const service = serviceWithColumns ( async ( request ) => {
1471+ calls += 1 ;
1472+ return {
1473+ epoch : { generation : 1 , token : "epoch-1" } ,
1474+ relations : request . relations . map ( ( relation ) => ( {
1475+ requestKey : relation . requestKey ,
1476+ status : "loading" ,
1477+ } ) ) ,
1478+ } ;
1479+ } ) ;
1480+ const text = "SELECT u. FROM users u" ;
1481+ const session = service . openDocument ( {
1482+ context : {
1483+ catalog : { scope : "connection:columns-loading" } ,
1484+ dialect : "duckdb" ,
1485+ engine : "local" ,
1486+ } ,
1487+ text,
1488+ } ) ;
1489+ const events : SqlSessionChangeEvent [ ] = [ ] ;
1490+ session . onDidChange ( ( event ) => events . push ( event ) ) ;
1491+ const result = await session . complete ( {
1492+ position : "SELECT u." . length ,
1493+ trigger : { kind : "invoked" } ,
1494+ } ) ;
1495+ if ( result . status !== "ready" || result . refreshToken === null ) {
1496+ throw new Error ( "Expected retained column loading" ) ;
1497+ }
1498+
1499+ await vi . advanceTimersByTimeAsync ( 100 ) ;
1500+ expect ( events ) . toMatchObject ( [ {
1501+ reason : "catalog-availability" ,
1502+ refreshToken : result . refreshToken ,
1503+ } ] ) ;
1504+ const retry = await session . complete ( {
1505+ position : "SELECT u." . length ,
1506+ trigger : { kind : "invoked" } ,
1507+ } ) ;
1508+ expect ( retry ) . toMatchObject ( {
1509+ refreshToken : null ,
1510+ sources : [ {
1511+ feature : "column-catalog" ,
1512+ outcome : "loading" ,
1513+ } ] ,
1514+ } ) ;
1515+ await vi . advanceTimersByTimeAsync ( 500 ) ;
1516+ expect ( calls ) . toBe ( 2 ) ;
1517+ expect ( events ) . toHaveLength ( 1 ) ;
1518+ service . dispose ( ) ;
1519+ } finally {
1520+ vi . useRealTimers ( ) ;
1521+ }
1522+ } ) ;
1523+
14661524 it ( "emits readiness when a timed-out column batch settles" , async ( ) => {
14671525 vi . useFakeTimers ( ) ;
14681526 try {
@@ -1812,10 +1870,14 @@ describe("namespace completion session integration", () => {
18121870 it ( "polls a provider-declared loading namespace with token correlation" , async ( ) => {
18131871 vi . useFakeTimers ( ) ;
18141872 try {
1815- const service = serviceWithNamespaces ( async ( ) => ( {
1816- epoch : { generation : 1 , token : "epoch-1" } ,
1817- status : "loading" ,
1818- } ) ) ;
1873+ let calls = 0 ;
1874+ const service = serviceWithNamespaces ( async ( ) => {
1875+ calls += 1 ;
1876+ return {
1877+ epoch : { generation : 1 , token : "epoch-1" } ,
1878+ status : "loading" ,
1879+ } ;
1880+ } ) ;
18191881 const text = "SELECT * FROM ma" ;
18201882 const session = service . openDocument ( {
18211883 context : {
@@ -1846,6 +1908,24 @@ describe("namespace completion session integration", () => {
18461908 reason : "catalog-availability" ,
18471909 refreshToken : result . refreshToken ,
18481910 } ] ) ;
1911+
1912+ const retry = await session . complete ( {
1913+ position : text . length ,
1914+ trigger : { kind : "invoked" } ,
1915+ } ) ;
1916+ expect ( retry ) . toMatchObject ( {
1917+ refreshToken : null ,
1918+ sources : [
1919+ { feature : "relation-catalog" } ,
1920+ {
1921+ feature : "namespace-catalog" ,
1922+ outcome : "loading" ,
1923+ } ,
1924+ ] ,
1925+ } ) ;
1926+ await vi . advanceTimersByTimeAsync ( 500 ) ;
1927+ expect ( calls ) . toBe ( 2 ) ;
1928+ expect ( events ) . toHaveLength ( 1 ) ;
18491929 service . dispose ( ) ;
18501930 } finally {
18511931 vi . useRealTimers ( ) ;
@@ -1987,6 +2067,97 @@ describe("namespace completion session integration", () => {
19872067 } ) ;
19882068 service . dispose ( ) ;
19892069 } ) ;
2070+
2071+ it ( "manually invalidates auxiliary-only catalog authority" , async ( ) => {
2072+ let generation = 1 ;
2073+ let columnCalls = 0 ;
2074+ let namespaceCalls = 0 ;
2075+ const service = createSqlLanguageService < TestContext > ( {
2076+ columns : {
2077+ id : "columns" ,
2078+ loadColumns : async ( request ) => {
2079+ columnCalls += 1 ;
2080+ return {
2081+ epoch : { generation, token : `epoch-${ generation } ` } ,
2082+ relations : request . relations . map ( ( relation ) => ( {
2083+ columns : [ ] ,
2084+ coverage : "complete" ,
2085+ relationEntityId : relation . requestKey ,
2086+ requestKey : relation . requestKey ,
2087+ status : "ready" ,
2088+ } ) ) ,
2089+ } ;
2090+ } ,
2091+ } ,
2092+ dialects : [ duckdb ] ,
2093+ namespaces : {
2094+ id : "namespaces" ,
2095+ search : async ( ) => {
2096+ namespaceCalls += 1 ;
2097+ return {
2098+ containers : [ ] ,
2099+ coverage : "complete" ,
2100+ epoch : { generation, token : `epoch-${ generation } ` } ,
2101+ status : "ready" ,
2102+ } ;
2103+ } ,
2104+ } ,
2105+ } ) ;
2106+ const columns = service . openDocument ( {
2107+ context : {
2108+ catalog : { scope : "connection:manual-invalidate" } ,
2109+ dialect : "duckdb" ,
2110+ engine : "local" ,
2111+ } ,
2112+ text : "SELECT u. FROM users u" ,
2113+ } ) ;
2114+ const namespaces = service . openDocument ( {
2115+ context : {
2116+ catalog : { scope : "connection:manual-invalidate" } ,
2117+ dialect : "duckdb" ,
2118+ engine : "local" ,
2119+ } ,
2120+ text : "SELECT * FROM ma" ,
2121+ } ) ;
2122+ const completeColumns = ( ) =>
2123+ columns . complete ( {
2124+ position : "SELECT u." . length ,
2125+ trigger : { kind : "invoked" } ,
2126+ } ) ;
2127+ const completeNamespaces = ( ) =>
2128+ namespaces . complete ( {
2129+ position : "SELECT * FROM ma" . length ,
2130+ trigger : { kind : "invoked" } ,
2131+ } ) ;
2132+
2133+ await completeColumns ( ) ;
2134+ await completeNamespaces ( ) ;
2135+ await completeColumns ( ) ;
2136+ await completeNamespaces ( ) ;
2137+ expect ( { columnCalls, namespaceCalls } ) . toEqual ( {
2138+ columnCalls : 1 ,
2139+ namespaceCalls : 1 ,
2140+ } ) ;
2141+
2142+ generation = 2 ;
2143+ const columnRevision = columns . invalidateCatalog ( ) ;
2144+ const namespaceRevision = namespaces . invalidateCatalog ( ) ;
2145+ expect ( columns . revision ) . toBe ( columnRevision ) ;
2146+ expect ( namespaces . revision ) . toBe ( namespaceRevision ) ;
2147+ await completeColumns ( ) ;
2148+ await completeNamespaces ( ) ;
2149+ expect ( { columnCalls, namespaceCalls } ) . toEqual ( {
2150+ columnCalls : 2 ,
2151+ namespaceCalls : 2 ,
2152+ } ) ;
2153+
2154+ columns . dispose ( ) ;
2155+ expect ( ( ) => columns . invalidateCatalog ( ) ) . toThrow (
2156+ "SQL document session is disposed" ,
2157+ ) ;
2158+ namespaces . dispose ( ) ;
2159+ service . dispose ( ) ;
2160+ } ) ;
19902161} ) ;
19912162
19922163describe ( "statement-index session cache" , ( ) => {
0 commit comments