Fix getQueryInfo() 'from ' detection closes #4810 (rebased) (#4884)

* Add test for #4810: getQueryInfo() fails to detect 'from ' correctly

This test demonstrates that getQueryInfo("from ") incorrectly returns
EditingTable = false when it should return true. This prevents autocomplete
from suggesting tables after users type "from ".

The test currently fails as expected, proving the bug exists.

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix #4810: Correct getQueryInfo() 'from ' detection for autocomplete

This commit fixes a bug where getQueryInfo("from ") incorrectly returned
EditingTable = false, preventing autocomplete from suggesting tables after
users type "from ".

The fix involves two changes:

1. Modified getPreviousWord() to correctly return "from" when the input is
   "from " (single word followed by space). Previously, it returned an empty
   string because it couldn't find a space before "from".

2. Modified isEditingTable() to check that the text ends with a space. This
   ensures we only enable table suggestions when the user has typed "from "
   (ready for a table name), not when they're in the middle of typing "from"
   or after they've already started typing a table name like "from my_table".

The combination of these changes ensures:
- "from " → EditingTable = true (autocomplete shows tables)
- "from my_table" → EditingTable = false (autocomplete doesn't interfere)
- "from" → EditingTable = false (no space yet, not ready for table name)

All existing tests pass, and the new test from the previous commit now passes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Nathan Wallace
2025-11-16 08:50:25 -05:00
committed by GitHub
parent ed5bc425aa
commit 152420d278
3 changed files with 76 additions and 9 deletions

View File

@@ -580,3 +580,53 @@ func TestInitialisationComplete_RaceCondition(t *testing.T) {
wg.Wait() wg.Wait()
} }
// TestGetQueryInfo_FromDetection tests that getQueryInfo correctly detects
// when the user is editing a table name after typing "from ".
//
// This is important for autocomplete - when a user types "from " (with a space),
// the system should recognize they are about to enter a table name and enable
// table suggestions.
//
// Bug: #4810
func TestGetQueryInfo_FromDetection(t *testing.T) {
tests := []struct {
name string
input string
expectedTable string
expectedEditTable bool
}{
{
name: "just_from",
input: "from ",
expectedTable: "",
expectedEditTable: true, // Should be true - user is about to enter table name
},
{
name: "from_with_table",
input: "from my_table",
expectedTable: "my_table",
expectedEditTable: false, // Not editing, already entered
},
{
name: "from_keyword_only",
input: "from",
expectedTable: "",
expectedEditTable: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getQueryInfo(tt.input)
if result.Table != tt.expectedTable {
t.Errorf("getQueryInfo(%q).Table = %q, expected %q", tt.input, result.Table, tt.expectedTable)
}
if result.EditingTable != tt.expectedEditTable {
t.Errorf("getQueryInfo(%q).EditingTable = %v, expected %v", tt.input, result.EditingTable, tt.expectedEditTable)
}
})
}
}

View File

@@ -17,12 +17,16 @@ func getQueryInfo(text string) *queryCompletionInfo {
return &queryCompletionInfo{ return &queryCompletionInfo{
Table: table, Table: table,
EditingTable: isEditingTable(prevWord), EditingTable: isEditingTable(text, prevWord),
} }
} }
func isEditingTable(prevWord string) bool { func isEditingTable(text string, prevWord string) bool {
var editingTable = prevWord == "from" // Only consider it editing table if:
// 1. The previous word is "from"
// 2. The text ends with a space (meaning cursor is after "from ", not in the middle of typing a table name)
endsWithSpace := len(text) > 0 && text[len(text)-1] == ' '
var editingTable = prevWord == "from" && endsWithSpace
return editingTable return editingTable
} }
@@ -52,7 +56,8 @@ func getPreviousWord(text string) string {
} }
prevSpace := strings.LastIndex(text[:lastNotSpace], " ") prevSpace := strings.LastIndex(text[:lastNotSpace], " ")
if prevSpace == -1 { if prevSpace == -1 {
return "" // No space before the word, so return from the beginning to lastNotSpace
return text[0 : lastNotSpace+1]
} }
return text[prevSpace+1 : lastNotSpace+1] return text[prevSpace+1 : lastNotSpace+1]
} }

View File

@@ -200,9 +200,9 @@ func TestGetPreviousWord(t *testing.T) {
expected: "from", expected: "from",
}, },
{ {
name: "no previous word", name: "single word with trailing space",
input: "select ", input: "select ",
expected: "", expected: "select",
}, },
{ {
name: "single word", name: "single word",
@@ -324,31 +324,43 @@ func TestGetTable(t *testing.T) {
func TestIsEditingTable(t *testing.T) { func TestIsEditingTable(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
text string
prevWord string prevWord string
expected bool expected bool
}{ }{
{ {
name: "from keyword", name: "from keyword with trailing space",
text: "from ",
prevWord: "from", prevWord: "from",
expected: true, expected: true,
}, },
{
name: "from keyword without trailing space",
text: "from",
prevWord: "from",
expected: false,
},
{ {
name: "not from keyword", name: "not from keyword",
text: "select ",
prevWord: "select", prevWord: "select",
expected: false, expected: false,
}, },
{ {
name: "empty string", name: "empty string",
text: "",
prevWord: "", prevWord: "",
expected: false, expected: false,
}, },
{ {
name: "FROM uppercase", name: "FROM uppercase",
text: "FROM ",
prevWord: "FROM", prevWord: "FROM",
expected: false, expected: false,
}, },
{ {
name: "whitespace", name: "whitespace",
text: " from ",
prevWord: " from ", prevWord: " from ",
expected: false, expected: false,
}, },
@@ -356,9 +368,9 @@ func TestIsEditingTable(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
result := isEditingTable(tt.prevWord) result := isEditingTable(tt.text, tt.prevWord)
if result != tt.expected { if result != tt.expected {
t.Errorf("isEditingTable(%q) = %v, want %v", tt.prevWord, result, tt.expected) t.Errorf("isEditingTable(%q, %q) = %v, want %v", tt.text, tt.prevWord, result, tt.expected)
} }
}) })
} }