Skip to content

Commit c5e3eb9

Browse files
committed
Enhance error handling and add tests for connection methods; improve log table and query assertions
1 parent 485929b commit c5e3eb9

9 files changed

Lines changed: 309 additions & 66 deletions

File tree

api/machsvr/mach_grpc.go

Lines changed: 0 additions & 65 deletions
This file was deleted.

api/machsvr/machsvr.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,9 @@ func (db *Database) ConnectSync(ctx context.Context, opts ...api.ConnectOption)
535535

536536
// Close closes connection
537537
func (conn *Conn) Close() (err error) {
538+
if conn == nil || conn.db == nil || conn.handle == nil {
539+
return api.ErrDatabaseNoConnection
540+
}
538541
if conn.db.enableWorkerPool {
539542
return conn.CloseAsync()
540543
}
@@ -568,6 +571,9 @@ func (conn *Conn) CloseSync() (err error) {
568571
}
569572

570573
func (conn *Conn) Cancel() error {
574+
if conn == nil || conn.handle == nil {
575+
return api.ErrDatabaseNoConnection
576+
}
571577
if err := mach.EngCancel(conn.handle); err != nil {
572578
return err
573579
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package machsvr
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/machbase/neo-client/api"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestConnCancelNilHandle(t *testing.T) {
12+
conn := &Conn{db: &Database{}}
13+
require.Error(t, conn.Cancel())
14+
}
15+
16+
func TestConnCloseNilHandle(t *testing.T) {
17+
conn := &Conn{db: &Database{}}
18+
require.ErrorIs(t, conn.Close(), api.ErrDatabaseNoConnection)
19+
}
20+
21+
func TestConnCloseSignalsReturnChan(t *testing.T) {
22+
conn, err := _env.database.Connect(context.Background(), api.WithPassword("sys", "manager"))
23+
require.NoError(t, err)
24+
25+
machConn, ok := conn.(*Conn)
26+
require.True(t, ok)
27+
28+
machConn.returnChan = make(chan struct{}, 1)
29+
require.NoError(t, machConn.Close())
30+
31+
select {
32+
case <-machConn.returnChan:
33+
default:
34+
t.Fatal("expected Close to signal returnChan")
35+
}
36+
37+
require.NoError(t, machConn.Close())
38+
}

api/machsvr/machsvr_test.go

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package machsvr_test
22

33
import (
4+
"context"
45
"os"
6+
"runtime"
7+
"strings"
58
"testing"
69

710
"github.com/machbase/neo-client/api"
@@ -31,6 +34,11 @@ func TestAll(t *testing.T) {
3134
testsuite.CreateTestTables(machsvrDB)
3235
testsuite.TestAll(t, machsvrDB,
3336
tcSetMaxConn,
37+
tcSetMaxQuery,
38+
tcDatabaseError,
39+
tcWatcherRegistry,
40+
tcKillConnection,
41+
tcCancelConnection,
3442
)
3543
testsuite.DropTestTables(machsvrDB)
3644
}
@@ -41,9 +49,170 @@ func tcSetMaxConn(t *testing.T) {
4149
require.NotZero(t, expectLimit)
4250
require.LessOrEqual(t, -1, open)
4351

52+
engine.SetMaxOpenConn(-1)
53+
limit, open := engine.MaxOpenConn()
54+
require.Equal(t, -1, limit)
55+
require.Equal(t, -1, open)
56+
57+
engine.SetMaxOpenConn(0)
58+
limit, open = engine.MaxOpenConn()
59+
require.Equal(t, runtime.NumCPU()*2, limit)
60+
require.Equal(t, limit, open)
61+
4462
expectLimit = 1000
4563
engine.SetMaxOpenConn(expectLimit)
46-
limit, open := engine.MaxOpenConn()
64+
limit, open = engine.MaxOpenConn()
4765
require.Equal(t, expectLimit, limit)
66+
require.Equal(t, expectLimit, open)
67+
}
68+
69+
func tcSetMaxQuery(t *testing.T) {
70+
engine := machsvrDB.(*machsvr.Database)
71+
expectLimit, open := engine.MaxOpenQuery()
72+
require.NotZero(t, expectLimit)
4873
require.LessOrEqual(t, -1, open)
74+
75+
engine.SetMaxOpenQuery(-1)
76+
limit, open := engine.MaxOpenQuery()
77+
require.Equal(t, -1, limit)
78+
require.Equal(t, -1, open)
79+
80+
engine.SetMaxOpenQuery(0)
81+
limit, open = engine.MaxOpenQuery()
82+
require.Equal(t, runtime.NumCPU()*2, limit)
83+
require.Equal(t, limit, open)
84+
85+
expectLimit = 1000
86+
engine.SetMaxOpenQuery(expectLimit)
87+
limit, open = engine.MaxOpenQuery()
88+
require.Equal(t, expectLimit, limit)
89+
require.Equal(t, expectLimit, open)
90+
}
91+
92+
func tcDatabaseError(t *testing.T) {
93+
engine := machsvrDB.(*machsvr.Database)
94+
_, err := machsvrDB.Connect(context.Background(), api.WithPassword("sys", "wrong-password"))
95+
require.Error(t, err)
96+
97+
lastErr := engine.Error()
98+
require.Error(t, lastErr)
99+
require.True(t, strings.Contains(lastErr.Error(), "Invalid username/password") || strings.Contains(lastErr.Error(), "invalid username/password"))
100+
}
101+
102+
func tcWatcherRegistry(t *testing.T) {
103+
engine := machsvrDB.(*machsvr.Database)
104+
watcherKey := "registry-test"
105+
engine.RemoveWatcher(watcherKey)
106+
t.Cleanup(func() {
107+
engine.RemoveWatcher(watcherKey)
108+
})
109+
110+
engine.RegisterWatcher(watcherKey, nil)
111+
state, ok := engine.GetWatcher(watcherKey)
112+
require.True(t, ok)
113+
require.NotNil(t, state)
114+
require.False(t, state.CreatedTime.IsZero())
115+
require.Empty(t, state.Id)
116+
require.Empty(t, state.LatestSql)
117+
require.True(t, state.LatestTime.IsZero())
118+
expectCreatedTime := state.CreatedTime
119+
120+
engine.ListWatcher(nil)
121+
122+
found := false
123+
engine.ListWatcher(func(state *machsvr.ConnState) bool {
124+
if state.Id == "" && state.CreatedTime.Equal(expectCreatedTime) {
125+
found = true
126+
return false
127+
}
128+
return true
129+
})
130+
require.True(t, found)
131+
132+
engine.RemoveWatcher(watcherKey)
133+
_, ok = engine.GetWatcher(watcherKey)
134+
require.False(t, ok)
135+
}
136+
137+
func tcKillConnection(t *testing.T) {
138+
engine := machsvrDB.(*machsvr.Database)
139+
require.EqualError(t, engine.KillConnection("missing-watcher", true), "connection 'missing-watcher' not found")
140+
141+
invalidKey := "invalid-watcher"
142+
engine.SetWatcher(invalidKey, &machsvr.ConnWatcher{})
143+
t.Cleanup(func() {
144+
engine.RemoveWatcher(invalidKey)
145+
})
146+
require.EqualError(t, engine.KillConnection(invalidKey, true), "invalid connection 'invalid-watcher'")
147+
engine.RemoveWatcher(invalidKey)
148+
149+
before := watcherStates(engine)
150+
conn, err := machsvrDB.Connect(context.Background(), api.WithPassword("sys", "manager"))
151+
require.NoError(t, err)
152+
153+
after := watcherStates(engine)
154+
watcherID := newWatcherID(before, after)
155+
require.NotEmpty(t, watcherID)
156+
157+
require.NoError(t, engine.KillConnection(watcherID, true))
158+
require.EqualError(t, engine.KillConnection(watcherID, true), "connection '"+watcherID+"' not found")
159+
require.NoError(t, conn.Close())
160+
}
161+
162+
func tcCancelConnection(t *testing.T) {
163+
engine := machsvrDB.(*machsvr.Database)
164+
165+
before := watcherStates(engine)
166+
conn, err := machsvrDB.Connect(context.Background(), api.WithPassword("sys", "manager"))
167+
require.NoError(t, err)
168+
169+
after := watcherStates(engine)
170+
watcherID := newWatcherID(before, after)
171+
require.NotEmpty(t, watcherID)
172+
173+
require.NoError(t, engine.KillConnection(watcherID, false))
174+
require.EqualError(t, engine.KillConnection(watcherID, false), "connection '"+watcherID+"' not found")
175+
require.NoError(t, conn.Close())
176+
177+
before = watcherStates(engine)
178+
conn, err = machsvrDB.Connect(context.Background(), api.WithPassword("sys", "manager"))
179+
require.NoError(t, err)
180+
181+
machConn, ok := conn.(*machsvr.Conn)
182+
require.True(t, ok)
183+
184+
after = watcherStates(engine)
185+
watcherID = newWatcherID(before, after)
186+
require.NotEmpty(t, watcherID)
187+
188+
require.NoError(t, machConn.Cancel())
189+
require.EqualError(t, engine.KillConnection(watcherID, false), "connection '"+watcherID+"' not found")
190+
require.NoError(t, conn.Close())
191+
}
192+
193+
func watcherStates(engine *machsvr.Database) []*machsvr.ConnState {
194+
states := []*machsvr.ConnState{}
195+
engine.ListWatcher(func(state *machsvr.ConnState) bool {
196+
states = append(states, state)
197+
return true
198+
})
199+
return states
200+
}
201+
202+
func newWatcherID(before []*machsvr.ConnState, after []*machsvr.ConnState) string {
203+
known := map[string]struct{}{}
204+
for _, state := range before {
205+
if state != nil && state.Id != "" {
206+
known[state.Id] = struct{}{}
207+
}
208+
}
209+
for _, state := range after {
210+
if state == nil || state.Id == "" {
211+
continue
212+
}
213+
if _, ok := known[state.Id]; !ok {
214+
return state.Id
215+
}
216+
}
217+
return ""
49218
}

api/testsuite/logtable.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func LogTableExec(t *testing.T, db api.Database, ctx context.Context) {
4343
if err := result.Err(); err != nil {
4444
t.Fatal(err)
4545
}
46+
require.Equal(t, int64(1), result.RowsAffected())
4647
result = conn.Exec(ctx, "insert into log_data values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
4748
tick.Add(1), // time
4849
0, one, // short, ushort
@@ -58,6 +59,7 @@ func LogTableExec(t *testing.T, db api.Database, ctx context.Context) {
5859
if err := result.Err(); err != nil {
5960
t.Fatal(err)
6061
}
62+
require.Equal(t, int64(1), result.RowsAffected())
6163
}
6264

6365
func LogTableAppend(t *testing.T, db api.Database, ctx context.Context) {
@@ -67,6 +69,9 @@ func LogTableAppend(t *testing.T, db api.Database, ctx context.Context) {
6769

6870
appender, err := conn.Appender(ctx, "log_data")
6971
require.NoError(t, err)
72+
require.Equal(t, "LOG_DATA", appender.TableName())
73+
require.Equal(t, api.TableTypeLog, appender.TableType())
74+
appender = appender.WithInputFormats()
7075

7176
expectCols := []*api.Column{
7277
{Name: "_ARRIVAL_TIME", Type: api.ColumnTypeDatetime, Length: 8, DataType: api.DataTypeDatetime},
@@ -124,6 +129,10 @@ func LogTableAppend(t *testing.T, db api.Database, ctx context.Context) {
124129
require.NoError(t, err)
125130
require.Equal(t, int64(expectCount), sc)
126131
require.Equal(t, int64(0), fc)
132+
sc, fc, err = appender.Close()
133+
require.NoError(t, err)
134+
require.Equal(t, int64(expectCount), sc)
135+
require.Equal(t, int64(0), fc)
127136
}
128137

129138
var seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))

api/testsuite/query.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ func QueryRow(t *testing.T, db api.Database, ctx context.Context) {
1515

1616
row := conn.QueryRow(ctx, "SELECT * from tag_data WHERE name='_not_exist_'")
1717
require.EqualError(t, row.Err(), "sql: no rows in result set")
18+
require.Equal(t, int64(0), row.RowsAffected())
19+
require.Equal(t, "sql: no rows in result set", row.Message())
1820
var result int
1921
err = row.Scan(&result)
2022
require.EqualError(t, err, "sql: no rows in result set")
@@ -31,4 +33,14 @@ func QueryRow(t *testing.T, db api.Database, ctx context.Context) {
3133
for i, col := range columns {
3234
require.Equal(t, expectedColumns[i], col.Name)
3335
}
36+
37+
row = conn.QueryRow(ctx, "SELECT count(*) from tag_data")
38+
require.NoError(t, row.Err())
39+
require.Equal(t, int64(1), row.RowsAffected())
40+
require.Equal(t, "a row selected.", row.Message())
41+
42+
var count int64
43+
err = row.Scan(&count)
44+
require.NoError(t, err)
45+
require.GreaterOrEqual(t, count, int64(0))
3446
}

api/testsuite/tables.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,12 @@ func InsertAndQuery(t *testing.T, db api.Database, ctx context.Context) {
149149
result := conn.Exec(ctx, `insert into tag_data (name, time, value, short_value, int_value, long_value, str_value, json_value) `+
150150
`values('insert-once', '`+nowStrInLocal+`', 1.23, 1, 2, 3, 'str1', '{"key1": "value1"}')`)
151151
require.NoError(t, result.Err(), "insert fail")
152+
require.Equal(t, int64(1), result.RowsAffected())
152153

153154
sysConn, _ := db.Connect(ctx, api.WithPassword("sys", "manager"), api.WithStatementCache(api.StatementCacheAuto))
154155
result = sysConn.Exec(ctx, `EXEC table_flush(tag_data)`)
155156
require.NoError(t, result.Err(), "table_flush fail")
157+
require.Equal(t, int64(0), result.RowsAffected())
156158
sysConn.Close()
157159

158160
// prepare and query

0 commit comments

Comments
 (0)