Skip to content

範圍與建議清單重開 ​

本頁包含括號與語句開頭怎麼切開範圍、詞元結束後重開清單的三個步驟,以及一次詞法分析的兩個答案。 提交進去的文字長什麼樣見插入文字。

只有開啟查詢的括號才切開範圍 ​

範圍以括號界定,但不是每一個括號都算。判斷的依據是左括號後面接什麼:

寫法是不是新範圍
IN (SELECT … FROM Child c)是,子查詢自己帶 FROM 子句
FROM (SELECT …) d是
COUNT(a.…)、ISNULL(a.…, 0)否,只是函式引數
WHERE (a.… = 1)否,只是運算優先權
IN (1, 2, 3)否,只是清單
INSERT INTO t (…)否,只是資料行清單
FROM (a JOIN b ON …)、USING ((SELECT …) AS s JOIN c ON …)否,括起一段聯結;裡面的來源屬於外層

((SELECT …)) 的外層只在裡面那一組就是整個查詢時才算——關上之後接右括號或集合運算子; 接別名或 JOIN 的是聯結分組(SqlTokenNavigator.OpensQuery)。當成衍生資料表的症狀是 ON | 一個別名都不列。

一開始每個括號都當成子查詢,症狀是彙總函式裡完全沒有建議:

text
SELECT COUNT(a.| FROM dbo.PUBLISHER a

範圍只剩括號裡那一段,看不到 FROM dbo.PUBLISHER a,別名 a 解析不出來就退回 「a 是結構描述」的解讀——而沒有任何物件屬於名為 a 的結構描述, 於是清單是空的。同一個原因也讓 WHERE (a.、IN (a.、ISNULL(a. 全都沒有欄位。

規則的兩半必須一起成立:分不出「開啟查詢的括號」與「運算式的括號」的話, 修好彙總函式就會弄壞子查詢,內層的別名會解析到外層的資料表去。

順帶的好處是 INSERT INTO t (|) 也列得出 t 的欄位——那個括號同樣不是子查詢, 而那正是使用者在那個位置要的東西。

子查詢切開的是未限定的那一半:* 與沒有限定字的欄位只屬於這一層。限定字則由內往外 解析(SqlStatementScope.Outer),內層同名的別名遮住外層。只看這一層的症狀是相互關聯 子查詢 NOT EXISTS (SELECT … WHERE c.x = a.|) 的 a. 一個欄位都沒有,滑鼠停留與 F12 也找不到那張表。

語句的開頭也切開範圍 ​

括號之外,範圍停在分號、GO 與語句開頭。語句開頭的判準與位置分析同一條 (SqlStatementBoundaries,見語句的界線),不另列關鍵字名單。 名單的症狀是名單外的語句(BACKUP、RESTORE、THROW、BEGIN)不切:ON source.| 的範圍 延伸進下一行的 RESTORE,RESTORE 那一行又接回上一句,WITH | 列出上一句資料表的欄位。

  • 每個 SELECT 另開一個範圍,即使它不是一句的開頭:INSERT INTO t (|) SELECT … FROM s 的資料行清單只屬於 t,UNION SELECT 的 FROM 只屬於後面那一個。
  • 游標那一格當成寫完的運算元:隱含的界線要子句寫完,游標前的子句卻幾乎總停在半途 (WHERE |、ON source.|)。不補的話下一行的 UPDATE 併進來,source. 後面的 RESTORE 還被讀成名稱。游標貼著名稱、變數或常值時那個詞元自己就是,不補。
  • 換行只有原文有:SqlScopeAnalyzer.Analyze 與 SqlColumnSourceResolver 都要拿到整份文字。

詞元一結束就把清單重開 ​

平台的規則是「沒有 session 就問建議來源要不要開,已經有 session 就只重新篩選」。 這對識別字是對的:多打一個字母只是把候選變少。但結束詞元的字元不是—— 它會讓上下文整個換掉,而還開著的那份清單是照舊上下文組出來的:

text
SELECT a       → 清單開著,裡面是關鍵字與資料庫物件
SELECT a.      → 平台拿 a. 去比對同一份清單,一個都比不中,清單默默關掉
SELECT a.N     → 這時才重新問來源,欄位清單終於出現

因此輸入這類字元時自己把 session 收掉再開一次,但只在舊清單還開著或字元被自己吞掉時—— 其餘情形平台自己會開,再重開就是「開、關、開」(FROM [ 的自動配對先收掉了清單)。 這一道在按鍵當下問(SqlCompletionReopen.AfterTypedCharacter),上下文換不換等字元 進了緩衝區再由 SqlCompletionTriggers 判斷:

  • 前一個字元還能構成識別字(SELECT CUST|)→ 不重開,平台自己的篩選是對的。
  • 小老鼠除外:它構得成識別字(@@ROW 的詞元起點必須落在第一個小老鼠上), 但打出來的那一刻目標會整個換掉。INSERT INTO 開著的是資料表清單, INSERT INTO @ 要的是使用者自己宣告的變數,兩份沒有一項重疊——症狀是 「單打一個 @ 什麼都沒有,@S 才有提示」。@@ 同理,那又是另一份封閉清單。 判斷與呼叫端共用 SqlCompletionTriggers.MayChangeContext:一邊放行、 另一邊擋掉,等於沒改。
  • 候選集合封閉(SqlCompletionPolicy.IsClosed:下一個詞元一定是清單上的某一項)→ 重開。 線索有四種:限定字(a.、[dbo].)與左方括號;目標收斂(FROM 、EXEC 、DATEADD(、 封閉片語;可能是名字的那一格不算,它寫得出清單外的新名字);文法指定了資料行的所屬資料表(UPDATE t SET ,見欄位); 只接那幾個字的關鍵字位置(ORDER 、MERGE 的 THEN ,SqlKeywordPositionExtensions.IsClosed)。 少了任何一種,那個位置就要多打一個字母才有清單——與點號完全同一個病。
  • 其餘(SELECT 、COUNT(、ORDER BY 、WHERE a )→ 不重開:接得了運算式、常值、 逗號或下一句,按一下空白鍵就開的話是整個資料庫。

這幾條與建議來源是同一條參與規則(SqlCompletionPolicy.Participates),不另寫一份: 最後一條是空前綴沒到觸發字元數,12. 的點號是數值常值(Inert),名字那一格不開。 自己開出來的清單還沒打字,一律軟選,見補全。

重開清單的三個步驟 ​

片段接續與分隔字元走的是同一段程式(SqlCompletionReopen),三個步驟一個都不能少:

csharp
broker.GetSession(view)?.Dismiss();                  // 1
var session = broker.TriggerCompletion(view, trigger, caret, token);   // 2
session?.OpenOrUpdate(trigger, caret, token);        // 3
  1. 先收掉舊的。 TriggerCompletion 一開頭就先問 GetSession,只要還有 session 就原封不動把它交回來——不先收掉,整個呼叫沒有任何作用。Dismiss 是同步的, 回傳之前就已經把自己從 broker 的紀錄裡拿掉。
  2. TriggerCompletion 只是建立 session:問過各個來源要不要參與、算出適用範圍, 然後就結束了。
  3. OpenOrUpdate 才會去要清單並把 UI 畫出來。 少了這一行,前面每一步都算對了, 畫面上仍然什麼都不會出現。平台自己的命令處理常式在同一個位置也是這樣接著寫的。

整段排在派送佇列的 Background 優先權上執行,不在原地直接呼叫:提交當下平台正要 把 session 收掉,而輸入字元當下那個字元還沒進緩衝區——在原地開出來的清單, 看到的是上一個狀態。

緊接在 FROM、JOIN、EXEC 之後的限定字一律當結構描述: FROM dbo. 要列出 dbo 的物件,而 FROM u. 這種寫法並不存在。

沒有限定字的位置(SELECT |、WHERE |、ON |)也會列出敘述看得到的欄位, 而且排在資料庫物件之前——在這些位置要的幾乎都是欄位。這裡走的是同一個 解析器,所以子查詢、CTE、暫存資料表與資料表變數的欄位一樣列得出來;解析不出來的 那一個來源跳過,其他來源照列——與限定字的位置不同,這裡少列一個來源的欄位 不影響其他來源的正確性。

同一批位置也列出看得到的別名(含外層查詢的,SqlScopeAliasSuggestions),排在欄位之前: 多個來源的 ON |、WHERE | 先打的是 target.、a.,而別名只寫在這一句裡,中繼資料沒有它。 提交只寫別名本身,不套「一律加方括號」——那是資料庫物件名稱的偏好。沒寫別名的來源列的是 名稱,但只列 CTE 與暫存資料表:資料庫資料表的名稱清單裡本來就有,這兩種只存在於指令碼裡。

敘述裡有兩個以上相異的限定字時,插入的文字會自動補上別名,否則 SELECT Name FROM A a JOIN B b 會因為欄位名稱模稜兩可而執行失敗。 數的是相異的限定字而不是來源數量:FROM (SELECT Id, * FROM T t) d 攤平出兩個 來源,但它們都叫 d。

這條路徑只使用已經在快取裡的欄位,不會為了列清單去等一次查詢;沒命中就 這一輪不顯示,背景預先載入補上之後下一次按鍵就有了。指令碼裡讀得出來的欄位 不必等任何東西,一律當場就有。

一次詞法分析,兩個答案 ​

「敘述看得到哪些欄位來源」與「限定字指向哪些欄位」由同一次分析算出來, 一起掛在上下文上。呼叫端各自再掃一次同一份文字的話,每按一鍵就要多剖析 整份指令碼一遍。

同一個理由,InitializeCompletion 與提交路徑改用只吃游標前文的多載: 前者要的是適用範圍與要不要參與,後者要的是限定字與 ALTER 的關鍵字起點, 沒有一項需要看游標後方。全文分析只留在真的需要解析別名的那一次。

以 Apache-2.0 授權條款發布 · 專為 SSMS 22 設計