fix(xugu): return SHOW statement result sets

This commit is contained in:
Elias 2026-07-27 17:46:03 +08:00 committed by GitHub
parent e3361ad928
commit 7e024b8cef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 203 additions and 2 deletions

View File

@ -16,6 +16,8 @@ import (
"strings"
"sync"
"time"
"unicode"
"unicode/utf8"
_ "gitee.com/XuguDB/go-xugu-driver"
)
@ -3953,8 +3955,28 @@ func stripLeadingSQLComments(sqlText string) string {
}
func isQuerySQL(sqlText string) bool {
lower := strings.ToLower(strings.TrimSpace(sqlText))
return strings.HasPrefix(lower, "select") || strings.HasPrefix(lower, "with")
sqlText = stripLeadingSQLComments(sqlText)
for _, keyword := range []string{"select", "with", "show"} {
if hasLeadingSQLKeyword(sqlText, keyword) {
return true
}
}
return false
}
func hasLeadingSQLKeyword(sqlText, keyword string) bool {
if len(sqlText) < len(keyword) || !strings.EqualFold(sqlText[:len(keyword)], keyword) {
return false
}
if len(sqlText) == len(keyword) {
return true
}
next, _ := utf8.DecodeRuneInString(sqlText[len(keyword):])
return !isSQLIdentifierContinuation(next)
}
func isSQLIdentifierContinuation(value rune) bool {
return value == '_' || value == '$' || value == '#' || unicode.IsLetter(value) || unicode.IsDigit(value) || unicode.IsMark(value)
}
func quoteIdentifier(value string) string {

View File

@ -1472,6 +1472,116 @@ func TestExecuteQueryPreservesXuguTypeBodyTerminator(t *testing.T) {
}
}
func TestXuguShowStatementsUseResultSetQueryPath(t *testing.T) {
resetXuguShowResultDriver()
db, err := sql.Open("xugu-test-show-result", "")
if err != nil {
t.Fatal(err)
}
defer db.Close()
s := newServer()
s.db = db
result, err := s.executeQuery(queryOptions{SQL: "SHOW DB_INFO;"})
if err != nil {
t.Fatalf("executeQuery(SHOW DB_INFO): %v", err)
}
if got, want := result.Columns, []string{"DB_NAME", "DB_ID", "DB_OWNER", "DB_CHARSET", "DB_TIMEZ"}; !equalStrings(got, want) {
t.Fatalf("SHOW DB_INFO columns = %v, want %v", got, want)
}
if len(result.Rows) != 1 || result.Rows[0][0] != "SYSTEM" {
t.Fatalf("SHOW DB_INFO rows = %#v, want database row", result.Rows)
}
page, err := s.executeQueryPage(queryOptions{SQL: "SHOW DB_INFO"}, 10)
if err != nil {
t.Fatalf("executeQueryPage(SHOW DB_INFO): %v", err)
}
if len(page.Rows) != 1 || page.Rows[0][0] != "SYSTEM" {
t.Fatalf("SHOW DB_INFO page rows = %#v, want database row", page.Rows)
}
queries, execs := recordedXuguShowStatements()
if got, want := queries, []string{"SHOW DB_INFO", "SHOW DB_INFO"}; !equalStrings(got, want) {
t.Fatalf("SHOW statements queried = %v, want %v", got, want)
}
if len(execs) != 0 {
t.Fatalf("SHOW statements must not use ExecContext, got %v", execs)
}
}
func TestXuguQueryKeywordBoundariesUseResultSetPath(t *testing.T) {
for _, test := range []struct {
name string
sqlText string
wantQuery string
wantColumns []string
wantValue any
}{
{name: "parenthesized select", sqlText: "SELECT(1);", wantQuery: "SELECT(1)", wantColumns: []string{"VALUE"}, wantValue: int64(1)},
{name: "select hint", sqlText: "SELECT/*+ index */1;", wantQuery: "SELECT/*+ index */1", wantColumns: []string{"VALUE"}, wantValue: int64(1)},
{name: "show comment", sqlText: "SHOW/* metadata */ DB_INFO;", wantQuery: "SHOW/* metadata */ DB_INFO", wantColumns: []string{"DB_NAME", "DB_ID", "DB_OWNER", "DB_CHARSET", "DB_TIMEZ"}, wantValue: "SYSTEM"},
} {
t.Run(test.name, func(t *testing.T) {
resetXuguShowResultDriver()
db, err := sql.Open("xugu-test-show-result", "")
if err != nil {
t.Fatal(err)
}
defer db.Close()
s := newServer()
s.db = db
result, err := s.executeQuery(queryOptions{SQL: test.sqlText})
if err != nil {
t.Fatalf("executeQuery(%q): %v", test.sqlText, err)
}
if !equalStrings(result.Columns, test.wantColumns) {
t.Fatalf("columns = %v, want %v", result.Columns, test.wantColumns)
}
if len(result.Rows) != 1 || len(result.Rows[0]) == 0 || result.Rows[0][0] != test.wantValue {
t.Fatalf("rows = %#v, want first value %#v", result.Rows, test.wantValue)
}
queries, execs := recordedXuguShowStatements()
if !equalStrings(queries, []string{test.wantQuery}) {
t.Fatalf("queries = %v, want %v", queries, []string{test.wantQuery})
}
if len(execs) != 0 {
t.Fatalf("query statements must not use ExecContext, got %v", execs)
}
})
}
}
func TestIsQuerySQLRecognizesQueryKeywordBoundaries(t *testing.T) {
for _, test := range []struct {
sqlText string
want bool
}{
{sqlText: "SELECT 1", want: true},
{sqlText: "SELECT(1)", want: true},
{sqlText: "SELECT/*+ index */1", want: true},
{sqlText: "WITH value AS (SELECT 1) SELECT * FROM value", want: true},
{sqlText: "SHOW DB_INFO", want: true},
{sqlText: " show current_schema", want: true},
{sqlText: "/* Xugu metadata */ SHOW CHARSETS", want: true},
{sqlText: "SHOW/* metadata */ DB_INFO", want: true},
{sqlText: "-- leading comment\nSELECT(1)", want: true},
{sqlText: "SELECTIVE settings", want: false},
{sqlText: "SHOWCASE settings", want: false},
{sqlText: "SHOW_CURRENT_SCHEMA", want: false},
{sqlText: "CREATE TABLE items (id INTEGER)", want: false},
} {
t.Run(test.sqlText, func(t *testing.T) {
if got := isQuerySQL(test.sqlText); got != test.want {
t.Fatalf("isQuerySQL(%q) = %t, want %t", test.sqlText, got, test.want)
}
})
}
}
func contains(values []string, target string) bool {
for _, value := range values {
if value == target {
@ -1481,6 +1591,18 @@ func contains(values []string, target string) bool {
return false
}
func equalStrings(got, want []string) bool {
if len(got) != len(want) {
return false
}
for index := range got {
if got[index] != want[index] {
return false
}
}
return true
}
// -- fake drivers for agent tests --
func init() {
@ -1490,6 +1612,63 @@ func init() {
sql.Register("xugu-test-legacy-columns", &xuguLegacyColumnsDriver{})
sql.Register("xugu-test-table-objects", &xuguTableObjectsDriver{})
sql.Register("xugu-test-table-ddl", &xuguTableDDLDriver{})
sql.Register("xugu-test-show-result", &xuguShowResultDriver{})
}
type xuguShowResultDriver struct{}
var xuguShowResultState struct {
sync.Mutex
queries []string
execs []string
}
func resetXuguShowResultDriver() {
xuguShowResultState.Lock()
xuguShowResultState.queries = nil
xuguShowResultState.execs = nil
xuguShowResultState.Unlock()
}
func recordedXuguShowStatements() (queries []string, execs []string) {
xuguShowResultState.Lock()
defer xuguShowResultState.Unlock()
return append([]string(nil), xuguShowResultState.queries...), append([]string(nil), xuguShowResultState.execs...)
}
func (d *xuguShowResultDriver) Open(name string) (driver.Conn, error) {
return &xuguShowResultConn{}, nil
}
type xuguShowResultConn struct{}
func (c *xuguShowResultConn) Prepare(query string) (driver.Stmt, error) {
return nil, errors.New("not supported")
}
func (c *xuguShowResultConn) Close() error { return nil }
func (c *xuguShowResultConn) Begin() (driver.Tx, error) { return nil, errors.New("not supported") }
func (c *xuguShowResultConn) QueryContext(_ context.Context, query string, _ []driver.NamedValue) (driver.Rows, error) {
xuguShowResultState.Lock()
xuguShowResultState.queries = append(xuguShowResultState.queries, query)
xuguShowResultState.Unlock()
switch query {
case "SELECT(1)", "SELECT/*+ index */1":
return &xuguStaticRows{columns: []string{"VALUE"}, values: [][]driver.Value{{int64(1)}}}, nil
case "SHOW DB_INFO", "SHOW/* metadata */ DB_INFO":
return &xuguStaticRows{
columns: []string{"DB_NAME", "DB_ID", "DB_OWNER", "DB_CHARSET", "DB_TIMEZ"},
values: [][]driver.Value{{"SYSTEM", int64(1), "SYS", "UTF8.UTF8_GENERAL_CI", "GMT+08:00"}},
}, nil
default:
return nil, fmt.Errorf("unexpected query: %s", query)
}
}
func (c *xuguShowResultConn) ExecContext(_ context.Context, query string, _ []driver.NamedValue) (driver.Result, error) {
xuguShowResultState.Lock()
xuguShowResultState.execs = append(xuguShowResultState.execs, query)
xuguShowResultState.Unlock()
return nil, fmt.Errorf("SHOW statement was incorrectly sent to ExecContext: %s", query)
}
type xuguTableDDLDriver struct{}