Skip to content

Commit 1b543c0

Browse files
author
tc6-01
committed
# This is a combination of 2 commits.
# This is the 1st commit message: feature(sqlite): egorm 支持 sqlite 文件模式与内存模式 # This is the commit message #2: # feature(sqlite): egorm 支持 sqlite 文件模式与内存模式
1 parent 198cb61 commit 1b543c0

File tree

4 files changed

+238
-0
lines changed

4 files changed

+238
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type Config struct {
6161
insecureSkipVerify=true
6262
```
6363

64+
6465
## 5.2 优雅的Debug
6566
通过开启``debug``配置和命令行的``export EGO_DEBUG=true``,我们就可以在测试环境里看到请求里的配置名、地址、耗时、请求数据、响应数据
6667
![image](./docs/images/ego_debug.png)
@@ -125,6 +126,7 @@ func testDB() error {
125126
return err
126127
}
127128
```
129+
128130
## 6 GORM的日志
129131
任何gorm的请求都会记录gorm的错误access日志,如果需要对gorm的日志做定制化处理,可参考以下使用方式。
130132

examples/sqlite/main.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package main
2+
3+
import (
4+
"context"
5+
6+
"github.com/ego-component/egorm"
7+
"github.com/gotomicro/ego"
8+
"github.com/gotomicro/ego/core/elog"
9+
)
10+
11+
// 运行方式:
12+
// export EGO_DEBUG=true && go run main.go --config=config.toml
13+
type KV struct {
14+
ID int `gorm:"primaryKey" json:"id"`
15+
K string `gorm:"uniqueIndex;not null" json:"k"`
16+
V string `gorm:"not null" json:"v"`
17+
}
18+
19+
func (KV) TableName() string { return "kv" }
20+
21+
var dbs []*egorm.Component
22+
23+
func main() {
24+
if err := ego.New().Invoker(
25+
openDB,
26+
run,
27+
).Run(); err != nil {
28+
elog.Error("startup", elog.Any("err", err))
29+
}
30+
}
31+
32+
func openDB() error {
33+
dbs = []*egorm.Component{
34+
egorm.Load("sqlite.test").Build(),
35+
egorm.Load("sqlite.share.memory").Build(),
36+
egorm.Load("sqlite.nonshared.memory").Build(),
37+
}
38+
for _, db := range dbs {
39+
if err := db.AutoMigrate(&KV{}); err != nil {
40+
return err
41+
}
42+
}
43+
return nil
44+
}
45+
46+
func run() error {
47+
ctx := context.Background()
48+
for _, db := range dbs {
49+
_ = db.WithContext(ctx).Create(&KV{K: "hello", V: "world"}).Error
50+
var out KV
51+
err := db.WithContext(ctx).Where("k = ?", "hello").First(&out).Error
52+
elog.Info("kv", elog.String("k", out.K), elog.String("v", out.V), elog.FieldErr(err))
53+
}
54+
return nil
55+
}

internal/dsn/sqlite.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package dsn
2+
3+
import (
4+
"net/url"
5+
"path/filepath"
6+
"strings"
7+
8+
"gorm.io/driver/sqlite"
9+
"gorm.io/gorm"
10+
"gorm.io/gorm/schema"
11+
12+
"github.com/ego-component/egorm/manager"
13+
)
14+
15+
var (
16+
_ manager.DSNParser = (*SqliteDSNParser)(nil)
17+
)
18+
19+
type SqliteDSNParser struct {
20+
}
21+
22+
func init() {
23+
manager.Register(&SqliteDSNParser{})
24+
}
25+
26+
func (p *SqliteDSNParser) Scheme() string {
27+
return "sqlite"
28+
}
29+
30+
func (p *SqliteDSNParser) NamingStrategy() schema.Namer {
31+
return nil
32+
}
33+
34+
func (p *SqliteDSNParser) GetDialector(dsn string) gorm.Dialector {
35+
return sqlite.Open(dsn)
36+
}
37+
38+
// ParseDSN supports typical gorm sqlite DSN strings:
39+
// - file based: "test.db", "/abs/path/to/test.db", "file:test.db?cache=shared&_fk=1"
40+
// - memory: ":memory:", "file::memory:?cache=shared"
41+
func (p *SqliteDSNParser) ParseDSN(dsn string) (cfg *manager.DSN, err error) {
42+
cfg = new(manager.DSN)
43+
cfg.Params = map[string]string{}
44+
45+
// Normalize for parsing query parameters
46+
raw := dsn
47+
if strings.HasPrefix(raw, "file:") {
48+
if idx := strings.IndexByte(raw, '?'); idx >= 0 {
49+
query := raw[idx+1:]
50+
for _, kv := range strings.Split(query, "&") {
51+
parts := strings.SplitN(kv, "=", 2)
52+
if len(parts) != 2 {
53+
continue
54+
}
55+
val, decodeErr := url.QueryUnescape(parts[1])
56+
if decodeErr != nil {
57+
continue
58+
}
59+
cfg.Params[parts[0]] = val
60+
}
61+
}
62+
}
63+
64+
// Determine DBName for logging/metrics only
65+
switch {
66+
// memory mode
67+
case dsn == ":memory:":
68+
case strings.Contains(dsn, "::memory:"):
69+
cfg.DBName = "memory"
70+
default:
71+
trimmed := dsn
72+
// strip "file:" prefix for name extraction
73+
if strings.HasPrefix(trimmed, "file:") {
74+
if idx := strings.IndexByte(trimmed, '?'); idx >= 0 {
75+
trimmed = trimmed[:idx]
76+
}
77+
trimmed = strings.TrimPrefix(trimmed, "file:")
78+
} else {
79+
// if path like "dir/db.sqlite" keep base
80+
if idx := strings.IndexByte(trimmed, '?'); idx >= 0 {
81+
trimmed = trimmed[:idx]
82+
}
83+
}
84+
base := filepath.Base(trimmed)
85+
if base == "" || base == "." || base == "/" {
86+
base = "sqlite"
87+
}
88+
cfg.DBName = base
89+
}
90+
return
91+
}

internal/dsn/sqlite_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package dsn
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestSqliteDSNParser_ParseDSN(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
dsn string
13+
expected *struct {
14+
DBName string
15+
Addr string
16+
Params map[string]string
17+
}
18+
}{
19+
{
20+
name: "memory database",
21+
dsn: ":memory:",
22+
expected: &struct {
23+
DBName string
24+
Addr string
25+
Params map[string]string
26+
}{
27+
DBName: "memory",
28+
Addr: "",
29+
Params: map[string]string{},
30+
},
31+
},
32+
{
33+
name: "file memory with cache",
34+
dsn: "file::memory:?cache=shared",
35+
expected: &struct {
36+
DBName string
37+
Addr string
38+
Params map[string]string
39+
}{
40+
DBName: "memory",
41+
Addr: "",
42+
Params: map[string]string{
43+
"cache": "shared",
44+
},
45+
},
46+
},
47+
{
48+
name: "file based database",
49+
dsn: "test.db",
50+
expected: &struct {
51+
DBName string
52+
Addr string
53+
Params map[string]string
54+
}{
55+
DBName: "test.db",
56+
Addr: "",
57+
Params: map[string]string{},
58+
},
59+
},
60+
{
61+
name: "file URI with parameters",
62+
dsn: "file:./data/app.db?cache=shared&_journal_mode=WAL&_busy_timeout=5000&_fk=1",
63+
expected: &struct {
64+
DBName string
65+
Addr string
66+
Params map[string]string
67+
}{
68+
DBName: "app.db",
69+
Addr: "",
70+
Params: map[string]string{
71+
"cache": "shared",
72+
"_journal_mode": "WAL",
73+
"_busy_timeout": "5000",
74+
"_fk": "1",
75+
},
76+
},
77+
},
78+
}
79+
80+
dsnParser := SqliteDSNParser{}
81+
for _, tt := range tests {
82+
t.Run(tt.name, func(t *testing.T) {
83+
cfg, err := dsnParser.ParseDSN(tt.dsn)
84+
assert.NoError(t, err)
85+
assert.Equal(t, tt.expected.DBName, cfg.DBName)
86+
assert.Equal(t, tt.expected.Addr, cfg.Addr)
87+
assert.Equal(t, tt.expected.Params, cfg.Params)
88+
})
89+
}
90+
}

0 commit comments

Comments
 (0)