Skip to content

Commit a6c55aa

Browse files
authored
add tdengine support (#25)
* add tdengine support --------- Co-authored-by: Rick <[email protected]>
1 parent b20fd6d commit a6c55aa

File tree

8 files changed

+205
-82
lines changed

8 files changed

+205
-82
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
# atest-ext-store-orm
44
ORM database Store Extension for API Testing
55

6-
This project provides an ORM-based database store extension for API testing, simplifying data storage and retrieval operations. It supports various databases including SQLite, MySQL, and others, making it versatile for different testing environments.
6+
This project provides an ORM-based database store extension for API testing, simplifying data storage and retrieval operations. It supports various databases including SQLite, MySQL, PostgreSQL, TDengine, and others, making it versatile for different testing environments.
77

88
## Features
99
- Simplified database operations using ORM.
1010
- Integration with API testing frameworks.
11-
- Support for multiple databases (SQLite, MySQL, etc.).
11+
- Support for multiple databases (SQLite, MySQL, PostgreSQL, TDengine, etc.).
1212

1313
## Usage
1414
To use this extension in your API testing project, follow these steps:

cmd/root.go

+6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ func NewRootCommand() (c *cobra.Command) {
3838
}
3939

4040
func (o *option) runE(c *cobra.Command, args []string) (err error) {
41+
defer func() {
42+
if r := recover(); r != nil {
43+
c.Println(r)
44+
}
45+
}()
46+
4147
if o.version {
4248
c.Println(version.GetVersion())
4349
c.Println(version.GetDate())

go.mod

+5-10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/linuxsuren/api-testing v0.0.19-0.20250220092001-ed58adc30f20
99
github.com/spf13/cobra v1.8.1
1010
github.com/stretchr/testify v1.9.0
11+
github.com/taosdata/driver-go/v3 v3.6.0
1112
google.golang.org/protobuf v1.33.0
1213
gorm.io/driver/mysql v1.5.2
1314
gorm.io/driver/postgres v1.5.4
@@ -29,9 +30,6 @@ require (
2930
github.com/blang/semver/v4 v4.0.0 // indirect
3031
github.com/bufbuild/protocompile v0.6.0 // indirect
3132
github.com/cespare/xxhash/v2 v2.2.0 // indirect
32-
github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect
33-
github.com/cucumber/godog v0.12.6 // indirect
34-
github.com/cucumber/messages-go/v16 v16.0.1 // indirect
3533
github.com/davecgh/go-spew v1.1.1 // indirect
3634
github.com/expr-lang/expr v1.15.6 // indirect
3735
github.com/flopp/go-findfont v0.1.0 // indirect
@@ -43,33 +41,30 @@ require (
4341
github.com/go-openapi/spec v0.21.0 // indirect
4442
github.com/go-openapi/swag v0.23.0 // indirect
4543
github.com/go-sql-driver/mysql v1.7.0 // indirect
46-
github.com/gofrs/uuid v4.2.0+incompatible // indirect
4744
github.com/golang/protobuf v1.5.4 // indirect
4845
github.com/google/uuid v1.6.0 // indirect
4946
github.com/gorilla/mux v1.8.1 // indirect
47+
github.com/gorilla/websocket v1.5.0 // indirect
5048
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
51-
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
52-
github.com/hashicorp/go-memdb v1.3.2 // indirect
53-
github.com/hashicorp/golang-lru v0.5.4 // indirect
5449
github.com/huandu/xstrings v1.4.0 // indirect
55-
github.com/iancoleman/orderedmap v0.3.0 // indirect
5650
github.com/imdario/mergo v0.3.16 // indirect
5751
github.com/inconshreveable/mousetrap v1.1.0 // indirect
58-
github.com/invopop/jsonschema v0.7.0 // indirect
5952
github.com/jackc/pgpassfile v1.0.0 // indirect
6053
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
6154
github.com/jackc/pgx/v5 v5.4.3 // indirect
6255
github.com/jinzhu/inflection v1.0.0 // indirect
6356
github.com/jinzhu/now v1.1.5 // indirect
6457
github.com/josharian/intern v1.0.0 // indirect
58+
github.com/json-iterator/go v1.1.12 // indirect
6559
github.com/linuxsuren/go-fake-runtime v0.0.4 // indirect
66-
github.com/linuxsuren/go-service v0.0.0-20231225060426-efabcd3a5161 // indirect
6760
github.com/linuxsuren/oauth-hub v0.0.0-20240809060240-e78c21b5d8d4 // indirect
6861
github.com/linuxsuren/unstructured v0.0.1 // indirect
6962
github.com/mailru/easyjson v0.7.7 // indirect
7063
github.com/mattn/go-sqlite3 v1.14.22 // indirect
7164
github.com/mitchellh/copystructure v1.2.0 // indirect
7265
github.com/mitchellh/reflectwalk v1.0.2 // indirect
66+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
67+
github.com/modern-go/reflect2 v1.0.2 // indirect
7368
github.com/phpdave11/gofpdi v1.0.14-0.20211212211723-1f10f9844311 // indirect
7469
github.com/pkg/errors v0.9.1 // indirect
7570
github.com/pmezard/go-difflib v1.0.0 // indirect

go.sum

+13-56
Large diffs are not rendered by default.

pkg/data_query.go

+37-11
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ package pkg
1717

1818
import (
1919
"context"
20-
20+
"fmt"
2121
"github.com/linuxsuren/api-testing/pkg/server"
2222
"gorm.io/gorm"
23+
"reflect"
24+
"time"
2325
)
2426

2527
func (s *dbserver) Query(ctx context.Context, query *server.DataQuery) (result *server.DataQueryResult, err error) {
@@ -32,18 +34,31 @@ func (s *dbserver) Query(ctx context.Context, query *server.DataQuery) (result *
3234
if err != nil {
3335
return
3436
}
35-
defer rows.Close()
36-
37-
columns, err := rows.Columns()
38-
if err != nil {
39-
return
40-
}
37+
defer func() {
38+
if rows != nil {
39+
rows.Close()
40+
}
41+
}()
4142

4243
result = &server.DataQueryResult{
4344
Data: []*server.Pair{},
4445
Items: make([]*server.Pairs, 0),
4546
}
4647

48+
if rows == nil {
49+
if rows, err = db.ConnPool.QueryContext(ctx, query.Sql); err != nil {
50+
return
51+
} else if rows == nil {
52+
fmt.Println("no rows found")
53+
return
54+
}
55+
}
56+
57+
columns, err := rows.Columns()
58+
if err != nil {
59+
return
60+
}
61+
4762
for rows.Next() {
4863
// Create a slice of interface{}'s to represent each column,
4964
// and a second slice to contain pointers to each item in the columns slice.
@@ -63,10 +78,21 @@ func (s *dbserver) Query(ctx context.Context, query *server.DataQuery) (result *
6378
for i, colName := range columns {
6479
rowData := &server.Pair{}
6580
val := columnsData[i]
66-
b, ok := val.([]byte)
67-
if ok {
68-
rowData.Key = colName
69-
rowData.Value = string(b)
81+
82+
rowData.Key = colName
83+
switch v := val.(type) {
84+
case []byte:
85+
rowData.Value = string(v)
86+
case string:
87+
rowData.Value = v
88+
case int, uint64, uint32, int32, int64:
89+
rowData.Value = fmt.Sprintf("%d", v)
90+
case float32, float64:
91+
rowData.Value = fmt.Sprintf("%f", v)
92+
case time.Time:
93+
rowData.Value = v.String()
94+
default:
95+
fmt.Println("column", colName, "type", reflect.TypeOf(v))
7096
}
7197

7298
// Append the map to our slice of maps.

pkg/server.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ func createDB(user, password, address, database, driver string) (db *gorm.DB, er
6868
}
6969
dsn = fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Shanghai", host, user, password, database, port)
7070
dialector = postgres.Open(dsn)
71+
case "tdengine":
72+
dsn = fmt.Sprintf("%s:%s@ws(%s)/", user, password, address)
73+
dialector = NewTDengineDialector(dsn)
7174
default:
7275
err = fmt.Errorf("invalid database driver %q", driver)
7376
return
@@ -82,9 +85,11 @@ func createDB(user, password, address, database, driver string) (db *gorm.DB, er
8285
return
8386
}
8487

85-
err = errors.Join(err, db.AutoMigrate(&TestCase{}))
86-
err = errors.Join(err, db.AutoMigrate(&TestSuite{}))
87-
err = errors.Join(err, db.AutoMigrate(&HistoryTestResult{}))
88+
if driver != "tdengine" {
89+
err = errors.Join(err, db.AutoMigrate(&TestCase{}))
90+
err = errors.Join(err, db.AutoMigrate(&TestSuite{}))
91+
err = errors.Join(err, db.AutoMigrate(&HistoryTestResult{}))
92+
}
8893
return
8994
}
9095

pkg/tdengine_dialector.go

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
Copyright 2025 API Testing Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package pkg
17+
18+
import (
19+
"database/sql"
20+
"fmt"
21+
22+
_ "github.com/taosdata/driver-go/v3/taosWS"
23+
"gorm.io/driver/mysql"
24+
"gorm.io/gorm"
25+
"gorm.io/gorm/clause"
26+
"gorm.io/gorm/logger"
27+
"gorm.io/gorm/schema"
28+
)
29+
30+
var _ gorm.Dialector = &tdengineDialector{}
31+
32+
type tdengineDialector struct {
33+
DSN string
34+
}
35+
36+
func (d tdengineDialector) Name() string {
37+
return "tdengine"
38+
}
39+
40+
func (d tdengineDialector) Initialize(db *gorm.DB) (err error) {
41+
// Initialize the TDengine connection here
42+
if db.ConnPool == nil {
43+
db.ConnPool, err = sql.Open("taosWS", d.DSN)
44+
}
45+
return
46+
}
47+
48+
func (d tdengineDialector) Migrator(db *gorm.DB) gorm.Migrator {
49+
// Return the TDengine migrator here
50+
return &mysql.Migrator{}
51+
}
52+
53+
func (d tdengineDialector) DataTypeOf(field *schema.Field) string {
54+
// Return the TDengine data type for the given field
55+
switch field.DataType {
56+
case schema.Bool:
57+
return "bool"
58+
case schema.Int, schema.Uint:
59+
sqlType := "bigint"
60+
switch {
61+
case field.Size <= 8:
62+
sqlType = "tinyint"
63+
case field.Size <= 16:
64+
sqlType = "smallint"
65+
case field.Size <= 32:
66+
sqlType = "int"
67+
}
68+
return sqlType
69+
case schema.Float:
70+
if field.Size <= 32 {
71+
return "float"
72+
}
73+
return "double"
74+
case schema.String:
75+
size := field.Size
76+
if size == 0 {
77+
size = 64
78+
}
79+
return fmt.Sprintf("NCHAR(%d)", size)
80+
case schema.Time:
81+
return "TIMESTAMP"
82+
case schema.Bytes:
83+
size := field.Size
84+
if size == 0 {
85+
size = 64
86+
}
87+
return fmt.Sprintf("BINARY(%d)", size)
88+
}
89+
90+
return string(field.DataType)
91+
}
92+
93+
func (d tdengineDialector) DefaultValueOf(field *schema.Field) clause.Expression {
94+
return clause.Expr{SQL: "NULL"}
95+
}
96+
97+
func (d tdengineDialector) BindVarTo(writer clause.Writer, stmt *gorm.Statement, v interface{}) {
98+
// Bind variables for TDengine
99+
switch v.(type) {
100+
case string:
101+
writer.WriteString("'?'")
102+
default:
103+
writer.WriteByte('?')
104+
}
105+
}
106+
107+
func (d tdengineDialector) QuoteTo(writer clause.Writer, str string) {
108+
// Quote identifiers for TDengine
109+
writer.WriteString(str)
110+
}
111+
112+
func (d tdengineDialector) Explain(sql string, vars ...interface{}) string {
113+
// Explain the SQL query for TDengine
114+
return logger.ExplainSQL(sql, nil, "'", vars...)
115+
}
116+
117+
func NewTDengineDialector(dsn string) gorm.Dialector {
118+
return tdengineDialector{DSN: dsn}
119+
}

pkg/types.go

+15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
/*
2+
Copyright 2025 API Testing Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
116
package pkg
217

318
type TestCase struct {

0 commit comments

Comments
 (0)