Skip to content

Commit 85c7b61

Browse files
authored
feat: full functional support (#5)
Signed-off-by: Young Xu <xuthus5@gmail.com>
1 parent ce9e38e commit 85c7b61

File tree

15 files changed

+739
-85
lines changed

15 files changed

+739
-85
lines changed

app.go

Lines changed: 222 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@ import (
1818
"context"
1919
"encoding/json"
2020
"errors"
21+
"fmt"
22+
"strconv"
23+
"strings"
2124
"sync"
25+
"time"
2226

27+
"github.com/openGemini/opengemini-client-go/opengemini"
2328
"github.com/wailsapp/wails/v2/pkg/runtime"
2429
bolt "go.etcd.io/bbolt"
2530
)
@@ -59,11 +64,24 @@ func (app *App) startup(ctx context.Context) {
5964
}
6065

6166
func (app *App) shutdown(ctx context.Context) {
62-
if err := app.db.Close(); err != nil {
63-
app.logger.Error("close database failed", "reason", err)
67+
// Close all HTTP client connections first
68+
app.connects.Range(func(key, value interface{}) bool {
69+
app.connects.Delete(key)
70+
return true
71+
})
72+
73+
// Close database connection
74+
if app.db != nil {
75+
if err := app.db.Close(); err != nil {
76+
app.logger.Error("close database failed", "reason", err)
77+
}
78+
app.db = nil
6479
}
6580

66-
app.logger.Close()
81+
// Close logger last
82+
if app.logger != nil {
83+
app.logger.Close()
84+
}
6785
}
6886

6987
func (app *App) ListConnects() []*ConnectConfig {
@@ -194,6 +212,7 @@ func (app *App) OpenFileDialog() (string, error) {
194212

195213
func (app *App) DialConnect(name string) ([]string, error) {
196214
app.connects.Delete(name)
215+
app.logger.Info("dial connect", "name", name)
197216
cc, err := app.GetConnect(name)
198217
if err != nil {
199218
app.logger.Error("dial connect failed: find connect config failed", "reason", err)
@@ -246,3 +265,203 @@ func (app *App) GetDatabaseMetadata(connectName, databaseName string) (*Database
246265

247266
return &DatabaseMetadata{RetentionPolicy: policies, Measurements: measurements}, nil
248267
}
268+
269+
func (app *App) CloseConnect(connectName string) {
270+
app.logger.Info("close connect", "name", connectName)
271+
app.connects.Delete(connectName)
272+
}
273+
274+
func (app *App) ExecuteCommand(data *ExecuteRequest) (*ExecuteResponse, error) {
275+
if data.ConnectName == "" {
276+
return nil, errors.New("connect name required")
277+
}
278+
if data.Command == "" {
279+
return nil, errors.New("command required")
280+
}
281+
httpClient, err := app.getDialer(data.ConnectName)
282+
if err != nil {
283+
app.logger.Error("get opengemini client failed", "reason", err, "command", data.Command)
284+
return nil, err
285+
}
286+
287+
app.logger.Info("execute command data", "data", data)
288+
289+
var startTime = time.Now()
290+
291+
if strings.HasPrefix(strings.ToLower(data.Command), "insert") {
292+
if data.Database == "" {
293+
return nil, errors.New("database required")
294+
}
295+
if data.RetentionPolicy == "" {
296+
data.RetentionPolicy = "autogen"
297+
}
298+
if data.Precision == "" {
299+
data.Precision = "ns"
300+
}
301+
lineProtocol := strings.TrimSpace(data.Command[6:])
302+
err := httpClient.Write(app.ctx, data.Database, data.RetentionPolicy, lineProtocol, data.Precision)
303+
executionTime := time.Since(startTime).Milliseconds()
304+
305+
// Save history record
306+
history := &History{
307+
ID: strconv.FormatInt(time.Now().UnixMilli(), 10),
308+
Query: data.Command,
309+
Timestamp: time.Now().UnixMilli(),
310+
ExecutionTime: float64(executionTime),
311+
Database: data.Database,
312+
RetentionPolicy: data.RetentionPolicy,
313+
Success: err == nil,
314+
}
315+
if err != nil {
316+
history.Error = err.Error()
317+
app.logger.Error("execute command failed", "reason", err, "command", data.Command)
318+
_ = app.AddHistory(history)
319+
return nil, err
320+
}
321+
_ = app.AddHistory(history)
322+
return &ExecuteResponse{NoContent: true, Message: "write success", ExecutionTime: float64(executionTime)}, nil
323+
}
324+
response, err := httpClient.Query(app.ctx, &opengemini.Query{
325+
Database: data.Database,
326+
Command: data.Command,
327+
RetentionPolicy: data.RetentionPolicy,
328+
Precision: opengemini.ToPrecision(data.Precision),
329+
})
330+
executionTime := time.Since(startTime).Milliseconds()
331+
332+
// Save history record
333+
history := &History{
334+
ID: strconv.FormatInt(time.Now().UnixMilli(), 10),
335+
Query: data.Command,
336+
Timestamp: time.Now().UnixMilli(),
337+
ExecutionTime: float64(executionTime),
338+
Database: data.Database,
339+
RetentionPolicy: data.RetentionPolicy,
340+
Success: false,
341+
}
342+
343+
if err != nil {
344+
history.Error = err.Error()
345+
app.logger.Error("execute command failed", "reason", err, "name", data.ConnectName)
346+
_ = app.AddHistory(history)
347+
return nil, err
348+
}
349+
if response.Error != "" {
350+
history.Error = response.Error
351+
app.logger.Error("execute command failed", "reason", response.Error)
352+
_ = app.AddHistory(history)
353+
return nil, fmt.Errorf("execute command failed: %s", response.Error)
354+
}
355+
if len(response.Results) == 0 {
356+
history.Success = true
357+
_ = app.AddHistory(history)
358+
return &ExecuteResponse{NoContent: true, ExecutionTime: float64(executionTime)}, nil
359+
}
360+
361+
if response.Results[0].Error != "" {
362+
history.Error = response.Results[0].Error
363+
app.logger.Error("execute command failed", "reason", response.Results[0].Error)
364+
_ = app.AddHistory(history)
365+
return nil, fmt.Errorf("execute command failed: %s", response.Results[0].Error)
366+
}
367+
368+
if len(response.Results[0].Series) == 0 {
369+
history.Success = true
370+
_ = app.AddHistory(history)
371+
return &ExecuteResponse{NoContent: true, ExecutionTime: float64(executionTime)}, nil
372+
}
373+
374+
var (
375+
seriesValues = response.Results[0].Series[0].Values
376+
columns = response.Results[0].Series[0].Columns
377+
values = make([][]any, 0, len(seriesValues))
378+
)
379+
for _, v := range seriesValues {
380+
// Directly append the row data from seriesValues
381+
values = append(values, v)
382+
}
383+
384+
history.Success = true
385+
_ = app.AddHistory(history)
386+
return &ExecuteResponse{NoContent: false, Columns: columns, Values: values, ExecutionTime: float64(executionTime)}, nil
387+
}
388+
389+
func (app *App) GetHistories() ([]*History, error) {
390+
var histories []*History
391+
err := app.db.View(func(tx *bolt.Tx) error {
392+
bucket := tx.Bucket([]byte(BucketHistories))
393+
err := bucket.ForEach(func(k, v []byte) error {
394+
var history = &History{}
395+
err := json.Unmarshal(v, history)
396+
if err != nil {
397+
return err
398+
}
399+
histories = append(histories, history)
400+
return nil
401+
})
402+
return err
403+
})
404+
if err != nil {
405+
app.logger.Error("list histories failed", "reason", err)
406+
return nil, err
407+
}
408+
409+
// Sort by timestamp descending (newest first)
410+
for i := 0; i < len(histories)-1; i++ {
411+
for j := i + 1; j < len(histories); j++ {
412+
if histories[i].Timestamp < histories[j].Timestamp {
413+
histories[i], histories[j] = histories[j], histories[i]
414+
}
415+
}
416+
}
417+
418+
// Limit to maxHistoryCount from settings
419+
settings, err := app.GetSetting()
420+
if err == nil && settings.MaxHistoryCount > 0 && len(histories) > settings.MaxHistoryCount {
421+
histories = histories[:settings.MaxHistoryCount]
422+
}
423+
424+
return histories, nil
425+
}
426+
427+
func (app *App) AddHistory(history *History) error {
428+
data, err := json.Marshal(history)
429+
if err != nil {
430+
app.logger.Error("marshal history failed", "reason", err)
431+
return err
432+
}
433+
434+
err = app.db.Update(func(tx *bolt.Tx) error {
435+
bucket := tx.Bucket([]byte(BucketHistories))
436+
return bucket.Put([]byte(history.ID), data)
437+
})
438+
439+
if err != nil {
440+
app.logger.Error("save history failed", "reason", err)
441+
return err
442+
}
443+
444+
// Clean up old histories if exceeds max count
445+
settings, err := app.GetSetting()
446+
if err == nil && settings.MaxHistoryCount > 0 {
447+
histories, err := app.GetHistories()
448+
if err == nil && len(histories) > settings.MaxHistoryCount {
449+
// Delete oldest histories
450+
toDelete := histories[settings.MaxHistoryCount:]
451+
err = app.db.Update(func(tx *bolt.Tx) error {
452+
bucket := tx.Bucket([]byte(BucketHistories))
453+
for _, h := range toDelete {
454+
if err := bucket.Delete([]byte(h.ID)); err != nil {
455+
return err
456+
}
457+
}
458+
return nil
459+
})
460+
if err != nil {
461+
app.logger.Error("delete old histories failed", "reason", err)
462+
}
463+
}
464+
}
465+
466+
return nil
467+
}

data_struct.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,39 @@ type DatabaseMetadata struct {
6464
RetentionPolicy []*RetentionPolicy `json:"retention_policies"`
6565
Measurements []string `json:"measurements"`
6666
}
67+
68+
type ExecuteRequest struct {
69+
ConnectName string `json:"connect_name"`
70+
Database string `json:"database"`
71+
RetentionPolicy string `json:"retention_policy"`
72+
Measurement string `json:"measurement"`
73+
Precision string `json:"precision"`
74+
Command string `json:"command"`
75+
}
76+
77+
type ExecuteResponse struct {
78+
NoContent bool `json:"no_content"`
79+
Message string `json:"message"`
80+
ExecutionTime float64 `json:"execution_time"` // Execution time in milliseconds
81+
Columns []string `json:"columns"`
82+
Values [][]any `json:"values"`
83+
}
84+
85+
type History struct {
86+
ID string `json:"id"`
87+
Query string `json:"query"`
88+
Timestamp int64 `json:"timestamp"` // Unix timestamp in milliseconds
89+
ExecutionTime float64 `json:"execution_time"` // Execution time in milliseconds
90+
Database string `json:"database"`
91+
RetentionPolicy string `json:"retention_policy"`
92+
Success bool `json:"success"`
93+
Error string `json:"error"`
94+
}
95+
96+
func (h *History) Marshal() []byte {
97+
data, err := json.Marshal(h)
98+
if err != nil {
99+
return []byte("{}")
100+
}
101+
return data
102+
}

0 commit comments

Comments
 (0)