1 Star 0 Fork 0

github_repo/trino-golang-client

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
trino_test.go 20.34 KB
一键复制 编辑 原始数据 按行查看 历史
painsOnline 提交于 2022-01-06 17:51 +08:00 . Add files via upload
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package trino
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"time"
)
func TestConfig(t *testing.T) {
c := &Config{
ServerURI: "http://foobar@localhost:8080",
SessionProperties: map[string]string{"query_priority": "1"},
}
dsn, err := c.FormatDSN()
if err != nil {
t.Fatal(err)
}
want := "http://foobar@localhost:8080?session_properties=query_priority%3D1&source=trino-go-client"
if dsn != want {
t.Fatal("unexpected dsn:", dsn)
}
}
func TestConfigSSLCertPath(t *testing.T) {
c := &Config{
ServerURI: "https://foobar@localhost:8080",
SessionProperties: map[string]string{"query_priority": "1"},
SSLCertPath: "cert.pem",
}
dsn, err := c.FormatDSN()
if err != nil {
t.Fatal(err)
}
want := "https://foobar@localhost:8080?SSLCertPath=cert.pem&session_properties=query_priority%3D1&source=trino-go-client"
if dsn != want {
t.Fatal("unexpected dsn:", dsn)
}
}
func TestConfigWithoutSSLCertPath(t *testing.T) {
c := &Config{
ServerURI: "https://foobar@localhost:8080",
SessionProperties: map[string]string{"query_priority": "1"},
}
dsn, err := c.FormatDSN()
if err != nil {
t.Fatal(err)
}
want := "https://foobar@localhost:8080?session_properties=query_priority%3D1&source=trino-go-client"
if dsn != want {
t.Fatal("unexpected dsn:", dsn)
}
}
func TestKerberosConfig(t *testing.T) {
c := &Config{
ServerURI: "https://foobar@localhost:8090",
SessionProperties: map[string]string{"query_priority": "1"},
KerberosEnabled: "true",
KerberosKeytabPath: "/opt/test.keytab",
KerberosPrincipal: "trino/testhost",
KerberosRealm: "example.com",
KerberosConfigPath: "/etc/krb5.conf",
SSLCertPath: "/tmp/test.cert",
}
dsn, err := c.FormatDSN()
if err != nil {
t.Fatal(err)
}
want := "https://foobar@localhost:8090?KerberosConfigPath=%2Fetc%2Fkrb5.conf&KerberosEnabled=true&KerberosKeytabPath=%2Fopt%2Ftest.keytab&KerberosPrincipal=trino%2Ftesthost&KerberosRealm=example.com&SSLCertPath=%2Ftmp%2Ftest.cert&session_properties=query_priority%3D1&source=trino-go-client"
if dsn != want {
t.Fatal("unexpected dsn:", dsn)
}
}
func TestInvalidKerberosConfig(t *testing.T) {
c := &Config{
ServerURI: "http://foobar@localhost:8090",
KerberosEnabled: "true",
}
_, err := c.FormatDSN()
if err == nil {
t.Fatal("dsn generated from invalid secure url, since kerberos enabled must has SSL enabled")
}
}
func TestConfigWithMalformedURL(t *testing.T) {
_, err := (&Config{ServerURI: ":("}).FormatDSN()
if err == nil {
t.Fatal("dsn generated from malformed url")
}
}
func TestConnErrorDSN(t *testing.T) {
testcases := []struct {
Name string
DSN string
}{
{Name: "malformed", DSN: "://"},
{Name: "unknown_client", DSN: "http://localhost?custom_client=unknown"},
}
for _, tc := range testcases {
t.Run(tc.Name, func(t *testing.T) {
db, err := sql.Open("trino", tc.DSN)
if err != nil {
t.Fatal(err)
}
if _, err = db.Query("SELECT 1"); err == nil {
db.Close()
t.Fatal("test dsn is supposed to fail:", tc.DSN)
}
})
}
}
func TestRegisterCustomClientReserved(t *testing.T) {
for _, tc := range []string{"true", "false"} {
t.Run(fmt.Sprintf("%v", tc), func(t *testing.T) {
err := RegisterCustomClient(tc, &http.Client{})
if err == nil {
t.Fatal("client key name supposed to fail:", tc)
}
})
}
}
func TestRoundTripRetryQueryError(t *testing.T) {
count := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if count == 0 {
count++
w.WriteHeader(http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(&stmtResponse{
Error: stmtError{
ErrorName: "TEST",
},
})
}))
defer ts.Close()
db, err := sql.Open("trino", ts.URL)
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.Query("SELECT 1")
if _, ok := err.(*ErrQueryFailed); !ok {
t.Fatal("unexpected error:", err)
}
}
func TestRoundTripCancellation(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusServiceUnavailable)
}))
defer ts.Close()
db, err := sql.Open("trino", ts.URL)
if err != nil {
t.Fatal(err)
}
defer db.Close()
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
_, err = db.QueryContext(ctx, "SELECT 1")
if err == nil {
t.Fatal("unexpected query with cancelled context succeeded")
}
}
func TestAuthFailure(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
}))
defer ts.Close()
db, err := sql.Open("trino", ts.URL)
if err != nil {
t.Fatal(err)
}
defer db.Close()
}
func TestQueryForUsername(t *testing.T) {
c := &Config{
ServerURI: "http://foobar@localhost:8080",
SessionProperties: map[string]string{"query_priority": "1"},
}
dsn, err := c.FormatDSN()
if err != nil {
t.Fatal(err)
}
db, err := sql.Open("trino", dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()
rows, err := db.Query("SELECT current_user", sql.Named("X-Trino-User", string("TestUser")))
if err != nil {
t.Fatal("Failed executing query", err.Error())
}
if rows != nil {
for rows.Next() {
var ts string
err = rows.Scan(&ts)
if err != nil {
t.Fatal("Failed scanning query result", err.Error())
}
want := "TestUser"
if ts != want {
t.Fatal("Expected value does not equal result value : ", ts, " != ", want)
}
}
}
}
func TestQueryCancellation(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(&stmtResponse{
Error: stmtError{
ErrorName: "USER_CANCELLED",
},
})
}))
defer ts.Close()
db, err := sql.Open("trino", ts.URL)
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.Query("SELECT 1")
if err != ErrQueryCancelled {
t.Fatal("unexpected error:", err)
}
}
func TestQueryFailure(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer ts.Close()
db, err := sql.Open("trino", ts.URL)
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.Query("SELECT 1")
if _, ok := err.(*ErrQueryFailed); !ok {
t.Fatal("unexpected error:", err)
}
}
func TestUnsupportedHeader(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(trinoSetRoleHeader, "foo")
w.WriteHeader(http.StatusOK)
}))
defer ts.Close()
db, err := sql.Open("trino", ts.URL)
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.Query("SELECT 1")
if err != ErrUnsupportedHeader {
t.Fatal("unexpected error:", err)
}
}
func TestSSLCertPath(t *testing.T) {
db, err := sql.Open("trino", "https://localhost:9?SSLCertPath=/tmp/invalid_test.cert")
if err != nil {
t.Fatal(err)
}
defer db.Close()
want := "Error loading SSL Cert File"
if err := db.Ping(); err == nil {
t.Fatal(err)
} else if !strings.Contains(err.Error(), want) {
t.Fatalf("want: %q, got: %v", want, err)
}
}
func TestWithoutSSLCertPath(t *testing.T) {
db, err := sql.Open("trino", "https://localhost:9")
if err != nil {
t.Fatal(err)
}
defer db.Close()
if err := db.Ping(); err != nil {
t.Fatal(err)
}
}
func TestUnsupportedTransaction(t *testing.T) {
db, err := sql.Open("trino", "http://localhost:9")
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.Begin()
if err == nil {
t.Fatal("unsupported transaction succeeded with no error")
}
expected := "operation not supported"
if !strings.Contains(err.Error(), expected) {
t.Fatalf("expected begin to fail with %s but got %v", expected, err)
}
}
func TestTypeConversion(t *testing.T) {
utc, err := time.LoadLocation("UTC")
if err != nil {
t.Fatal(err)
}
testcases := []struct {
DataType string
ResponseUnmarshalledSample interface{}
ExpectedGoValue interface{}
}{
{
DataType: "boolean",
ResponseUnmarshalledSample: true,
ExpectedGoValue: true,
},
{
DataType: "varchar(1)",
ResponseUnmarshalledSample: "hello",
ExpectedGoValue: "hello",
},
{
DataType: "bigint",
ResponseUnmarshalledSample: json.Number("1234516165077230279"),
ExpectedGoValue: int64(1234516165077230279),
},
{
DataType: "double",
ResponseUnmarshalledSample: json.Number("1.0"),
ExpectedGoValue: float64(1),
},
{
DataType: "date",
ResponseUnmarshalledSample: "2017-07-10",
ExpectedGoValue: time.Date(2017, 7, 10, 0, 0, 0, 0, time.Local),
},
{
DataType: "time",
ResponseUnmarshalledSample: "01:02:03.000",
ExpectedGoValue: time.Date(0, 1, 1, 1, 2, 3, 0, time.Local),
},
{
DataType: "time with time zone",
ResponseUnmarshalledSample: "01:02:03.000 UTC",
ExpectedGoValue: time.Date(0, 1, 1, 1, 2, 3, 0, utc),
},
{
DataType: "timestamp",
ResponseUnmarshalledSample: "2017-07-10 01:02:03.000",
ExpectedGoValue: time.Date(2017, 7, 10, 1, 2, 3, 0, time.Local),
},
{
DataType: "timestamp with time zone",
ResponseUnmarshalledSample: "2017-07-10 01:02:03.000 UTC",
ExpectedGoValue: time.Date(2017, 7, 10, 1, 2, 3, 0, utc),
},
{
DataType: "map",
ResponseUnmarshalledSample: nil,
ExpectedGoValue: nil,
},
{
// arrays return data as-is for slice scanners
DataType: "array",
ResponseUnmarshalledSample: nil,
ExpectedGoValue: nil,
},
}
for _, tc := range testcases {
converter := newTypeConverter(tc.DataType)
t.Run(tc.DataType+":nil", func(t *testing.T) {
if _, err := converter.ConvertValue(nil); err != nil {
t.Fatal(err)
}
})
t.Run(tc.DataType+":bogus", func(t *testing.T) {
if _, err := converter.ConvertValue(struct{}{}); err == nil {
t.Fatal("bogus data scanned with no error")
}
})
t.Run(tc.DataType+":sample", func(t *testing.T) {
v, err := converter.ConvertValue(tc.ResponseUnmarshalledSample)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(v, tc.ExpectedGoValue) {
t.Fatalf("unexpected data from sample:\nhave %+v\nwant %+v", v, tc.ExpectedGoValue)
}
})
}
}
func TestSliceTypeConversion(t *testing.T) {
testcases := []struct {
GoType string
Scanner sql.Scanner
TrinoResponseUnmarshalledSample interface{}
TestScanner func(t *testing.T, s sql.Scanner)
}{
{
GoType: "[]bool",
Scanner: &NullSliceBool{},
TrinoResponseUnmarshalledSample: []interface{}{true},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSliceBool)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[]string",
Scanner: &NullSliceString{},
TrinoResponseUnmarshalledSample: []interface{}{"hello"},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSliceString)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[]int64",
Scanner: &NullSliceInt64{},
TrinoResponseUnmarshalledSample: []interface{}{json.Number("1")},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSliceInt64)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[]float64",
Scanner: &NullSliceFloat64{},
TrinoResponseUnmarshalledSample: []interface{}{json.Number("1.0")},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSliceFloat64)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[]time.Time",
Scanner: &NullSliceTime{},
TrinoResponseUnmarshalledSample: []interface{}{"2017-07-01"},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSliceTime)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[]map[string]interface{}",
Scanner: &NullSliceMap{},
TrinoResponseUnmarshalledSample: []interface{}{map[string]interface{}{"hello": "world"}},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSliceMap)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
}
for _, tc := range testcases {
t.Run(tc.GoType+":nil", func(t *testing.T) {
if err := tc.Scanner.Scan(nil); err != nil {
t.Error(err)
}
})
t.Run(tc.GoType+":bogus", func(t *testing.T) {
if err := tc.Scanner.Scan(struct{}{}); err == nil {
t.Error("bogus data scanned with no error")
}
if err := tc.Scanner.Scan([]interface{}{struct{}{}}); err == nil {
t.Error("bogus data scanned with no error")
}
})
t.Run(tc.GoType+":sample", func(t *testing.T) {
if err := tc.Scanner.Scan(tc.TrinoResponseUnmarshalledSample); err != nil {
t.Error(err)
}
tc.TestScanner(t, tc.Scanner)
})
}
}
func TestSlice2TypeConversion(t *testing.T) {
testcases := []struct {
GoType string
Scanner sql.Scanner
TrinoResponseUnmarshalledSample interface{}
TestScanner func(t *testing.T, s sql.Scanner)
}{
{
GoType: "[][]bool",
Scanner: &NullSlice2Bool{},
TrinoResponseUnmarshalledSample: []interface{}{[]interface{}{true}},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSlice2Bool)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[][]string",
Scanner: &NullSlice2String{},
TrinoResponseUnmarshalledSample: []interface{}{[]interface{}{"hello"}},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSlice2String)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[][]int64",
Scanner: &NullSlice2Int64{},
TrinoResponseUnmarshalledSample: []interface{}{[]interface{}{json.Number("1")}},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSlice2Int64)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[][]float64",
Scanner: &NullSlice2Float64{},
TrinoResponseUnmarshalledSample: []interface{}{[]interface{}{json.Number("1.0")}},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSlice2Float64)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[][]time.Time",
Scanner: &NullSlice2Time{},
TrinoResponseUnmarshalledSample: []interface{}{[]interface{}{"2017-07-01"}},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSlice2Time)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[][]map[string]interface{}",
Scanner: &NullSlice2Map{},
TrinoResponseUnmarshalledSample: []interface{}{[]interface{}{map[string]interface{}{"hello": "world"}}},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSlice2Map)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
}
for _, tc := range testcases {
t.Run(tc.GoType+":nil", func(t *testing.T) {
if err := tc.Scanner.Scan(nil); err != nil {
t.Error(err)
}
if err := tc.Scanner.Scan([]interface{}{nil}); err != nil {
t.Error(err)
}
})
t.Run(tc.GoType+":bogus", func(t *testing.T) {
if err := tc.Scanner.Scan(struct{}{}); err == nil {
t.Error("bogus data scanned with no error")
}
if err := tc.Scanner.Scan([]interface{}{struct{}{}}); err == nil {
t.Error("bogus data scanned with no error")
}
if err := tc.Scanner.Scan([]interface{}{[]interface{}{struct{}{}}}); err == nil {
t.Error("bogus data scanned with no error")
}
})
t.Run(tc.GoType+":sample", func(t *testing.T) {
if err := tc.Scanner.Scan(tc.TrinoResponseUnmarshalledSample); err != nil {
t.Error(err)
}
tc.TestScanner(t, tc.Scanner)
})
}
}
func TestSlice3TypeConversion(t *testing.T) {
testcases := []struct {
GoType string
Scanner sql.Scanner
TrinoResponseUnmarshalledSample interface{}
TestScanner func(t *testing.T, s sql.Scanner)
}{
{
GoType: "[][][]bool",
Scanner: &NullSlice3Bool{},
TrinoResponseUnmarshalledSample: []interface{}{[]interface{}{[]interface{}{true}}},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSlice3Bool)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[][][]string",
Scanner: &NullSlice3String{},
TrinoResponseUnmarshalledSample: []interface{}{[]interface{}{[]interface{}{"hello"}}},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSlice3String)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[][][]int64",
Scanner: &NullSlice3Int64{},
TrinoResponseUnmarshalledSample: []interface{}{[]interface{}{[]interface{}{json.Number("1")}}},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSlice3Int64)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[][][]float64",
Scanner: &NullSlice3Float64{},
TrinoResponseUnmarshalledSample: []interface{}{[]interface{}{[]interface{}{json.Number("1.0")}}},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSlice3Float64)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[][][]time.Time",
Scanner: &NullSlice3Time{},
TrinoResponseUnmarshalledSample: []interface{}{[]interface{}{[]interface{}{"2017-07-01"}}},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSlice3Time)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
{
GoType: "[][][]map[string]interface{}",
Scanner: &NullSlice3Map{},
TrinoResponseUnmarshalledSample: []interface{}{[]interface{}{[]interface{}{map[string]interface{}{"hello": "world"}}}},
TestScanner: func(t *testing.T, s sql.Scanner) {
v, _ := s.(*NullSlice3Map)
if !v.Valid {
t.Fatal("scanner failed")
}
},
},
}
for _, tc := range testcases {
t.Run(tc.GoType+":nil", func(t *testing.T) {
if err := tc.Scanner.Scan(nil); err != nil {
t.Fatal(err)
}
if err := tc.Scanner.Scan([]interface{}{[]interface{}{nil}}); err != nil {
t.Fatal(err)
}
})
t.Run(tc.GoType+":bogus", func(t *testing.T) {
if err := tc.Scanner.Scan(struct{}{}); err == nil {
t.Error("bogus data scanned with no error")
}
if err := tc.Scanner.Scan([]interface{}{[]interface{}{struct{}{}}}); err == nil {
t.Error("bogus data scanned with no error")
}
if err := tc.Scanner.Scan([]interface{}{[]interface{}{[]interface{}{struct{}{}}}}); err == nil {
t.Error("bogus data scanned with no error")
}
})
t.Run(tc.GoType+":sample", func(t *testing.T) {
if err := tc.Scanner.Scan(tc.TrinoResponseUnmarshalledSample); err != nil {
t.Error(err)
}
tc.TestScanner(t, tc.Scanner)
})
}
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/github_repo/trino-golang-client.git
git@gitee.com:github_repo/trino-golang-client.git
github_repo
trino-golang-client
trino-golang-client
main

搜索帮助