Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion pkg/interactive/interactive_client_autocomplete.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,22 @@ func sanitiseTableName(strToEscape string) string {
for _, token := range tokens {
// if string contains spaces or special characters(-) or upper case characters, escape it,
// as Postgres by default converts to lower case
if strings.ContainsAny(token, " -") || utils.ContainsUpper(token) {
// Also escape unicode/emoji characters as PostgreSQL requires escaping for non-ASCII identifiers
if strings.ContainsAny(token, " -") || utils.ContainsUpper(token) || containsNonASCII(token) {
token = db_common.PgEscapeName(token)
}
escaped = append(escaped, token)
}
return strings.Join(escaped, ".")
}

// containsNonASCII checks if a string contains any non-ASCII characters
// (unicode, emoji, etc.) which require PostgreSQL identifier escaping
func containsNonASCII(s string) bool {
for _, r := range s {
if r > 127 {
return true
}
}
return false
}
88 changes: 88 additions & 0 deletions pkg/interactive/interactive_helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package interactive

import (
"testing"
)

// TestSanitiseTableName tests that sanitiseTableName properly escapes table names
// that require PostgreSQL identifier escaping.
//
// PostgreSQL requires escaping for:
// - Names with spaces
// - Names with hyphens
// - Names with uppercase letters (to preserve case)
// - Names with unicode/emoji characters
//
// Bug #4801: sanitiseTableName doesn't escape unicode/emoji
func TestSanitiseTableName(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "simple lowercase",
input: "users",
expected: "users",
},
{
name: "uppercase requires escaping",
input: "Users",
expected: `"Users"`,
},
{
name: "space requires escaping",
input: "user data",
expected: `"user data"`,
},
{
name: "hyphen requires escaping",
input: "user-data",
expected: `"user-data"`,
},
{
name: "qualified table with schema",
input: "public.Users",
expected: `public."Users"`,
},
{
name: "unicode table name",
input: "用户",
expected: `"用户"`,
},
{
name: "emoji in table name",
input: "table_😀_data",
expected: `"table_😀_data"`,
},
{
name: "qualified with unicode",
input: "schema.用户表",
expected: `schema."用户表"`,
},
{
name: "mixed unicode and ascii",
input: "données_utilisateur",
expected: `"données_utilisateur"`,
},
{
name: "cyrillic characters",
input: "таблица",
expected: `"таблица"`,
},
{
name: "arabic characters",
input: "جدول",
expected: `"جدول"`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := sanitiseTableName(tt.input)
if result != tt.expected {
t.Errorf("sanitiseTableName(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}