Compare commits

...

43 Commits

Author SHA1 Message Date
b040654574 enhance: add ORM tag to the entity result of command gen dao to make entity assignment more faster (#3454) 2024-04-07 19:19:07 +08:00
505fc25a64 fix: fsnotify watcher panics when closing (#3399) 2024-04-07 14:12:03 +08:00
83ba887df7 fix: #3449 accept slice parameter as json.RawMessage for http request (#3452) 2024-04-07 14:09:52 +08:00
911f1cb1de enhance: use map iter to iterate the map instead of map keys and values (#3457) 2024-04-07 10:00:04 +08:00
1b7aea0ced feat: add metrics option and prometheus handler wraps (#3445) 2024-04-07 09:57:13 +08:00
x
db5eed17b1 fix: invalid cache key using pointer address of variable for soft time fields (#3448) 2024-04-02 20:10:16 +08:00
1ffb510a77 feat: add custom go module configuration support when initialize go project using command gf init (#3425) 2024-04-02 14:05:22 +08:00
f039393b2b fix: map converting in loop logic of validation for package gvalid (#3423) 2024-04-01 19:23:12 +08:00
00ba053ce6 enhance: use in-memory cache for soft field name and type (#3420) 2024-04-01 19:22:23 +08:00
dcf1f19ff7 enhance: Save operation support for mssql (#3365) 2024-04-01 19:18:54 +08:00
06826750ed enhance: improve Save feature for drivers oracle and dm (#3426) 2024-04-01 19:08:26 +08:00
e0a2645f4a fix: #3390 name&shor tag mapping failed to command input object for package gcmd (#3429) 2024-04-01 19:07:08 +08:00
509fdf45c6 feat: add auth support in address configuration for etcd registry (#3439) 2024-03-29 19:19:45 +08:00
1e7d897b66 fix: lost trace content in occasion when shutting down trace (#3418) 2024-03-29 10:08:58 +08:00
6df0a9ca66 fix: the cron job with precise second time pattern might be executed twice in the same time (#3437) 2024-03-28 22:09:11 +08:00
1c12f3a30d fix: http superfluous response.WriteHeader call in some scenario (#3428) 2024-03-28 22:08:54 +08:00
3a9e0e34ca fix: #3432 Add Access-Control-Expose-Headers: Content-Disposition header in ServeFileDownload (#3433) 2024-03-28 20:07:56 +08:00
8669512f42 feat: add metric feature support in goframe (#3138) 2024-03-24 21:18:30 +08:00
313d9d138f fix: unit testing cases of contrib/drivers occasionally failed by using now time assertion (#3410) 2024-03-21 21:57:33 +08:00
040118b7c6 fix: #3370 fixed process parameter parsing failed on Windows for package gproc (#3386) 2024-03-20 19:58:42 +08:00
b3f48212f1 fix: #3362 IsEmpty panics when some interface implement panics with nil receiver (#3367) 2024-03-20 19:52:12 +08:00
cade0775e8 enhance: Save operation support for contrib/drivers/dm (#3404) 2024-03-20 19:18:25 +08:00
164aad48c3 fix: unit test of dm failed occasionally (#3382) 2024-03-19 19:40:52 +08:00
04756d05a7 fix: fix lost log directory path for grapc log (#3387) 2024-03-18 19:14:31 +08:00
409041b965 enhance: support save for Oracle (#3364) 2024-03-13 20:11:45 +08:00
11f7187367 fix: unit test of dm failed occasionally (#3369) 2024-03-13 19:29:41 +08:00
4feda4c395 enhance: do not ignore error from gdb.FormatMultiLineSqlToSingle func (#3368) 2024-03-13 19:22:17 +08:00
a8713da97f enhance: cut tracing content as unicode for safety (#3342) 2024-03-13 19:21:16 +08:00
199737cd0f fix: for typo in comments for package contrib/drivers and gdb (#3366) 2024-03-12 20:40:20 +08:00
3a8f246569 fix: unit test error in PgSQL and SQLite; Unified t.Assert(err, nil) to t.AssertNil(err) (#3356) 2024-03-12 20:05:03 +08:00
607f079b23 fix: cache value assertion panic if the cache adapter is not in-memory for soft time feature of package gdb; improve converting performance for gconv.Scan (#3351) 2024-03-07 11:36:42 +08:00
cab6b89446 enhance: update contrib/drivers/README.MD (#3355) 2024-03-07 11:34:49 +08:00
6ed3038312 feat: version v2.6.4 (#3352) 2024-03-07 10:12:58 +08:00
9b48da459e enhance: add sentinel auth config for redis (#3348) 2024-03-06 19:27:52 +08:00
fbd266fad0 enhance: add Save operation support for SQLite #2764 (#3315) 2024-03-06 19:22:58 +08:00
240dadff92 fix: WherePri function wrong in pgsql #3330 (#3339) 2024-03-06 19:19:07 +08:00
290f4a3e65 fix: recognize json_valid constraint as json field type for database mariadb #2746 (#3309) 2024-03-06 19:07:31 +08:00
97fcd9d726 enhance: improve FormatUpsert implements for pgsql (#3349) 2024-03-06 19:05:13 +08:00
df15d70466 enhance: change tracing span name from request uri to router uri for http request (#3338) 2024-03-05 21:11:27 +08:00
680ae8616b fix: generated dao go files forcely cleared by command gf gen dao if clear set true (#3337) 2024-03-05 20:49:19 +08:00
4feb8219fa feat: upgrade set-go version and add go 1.22 version (#3316) 2024-03-04 20:18:31 +08:00
509cc47d3f enhance: add Save operation support for pgsql #3053 (#3324) 2024-03-04 20:17:43 +08:00
849b104c31 enhance: gproc.signal enhance #3325 (#3326) 2024-03-04 20:10:05 +08:00
251 changed files with 9939 additions and 2396 deletions

View File

@ -70,6 +70,14 @@ for file in `find . -name go.mod`; do
fi
fi
# package otelmetric needs golang >= v1.20
if [ "otelmetric" = $(basename $dirpath) ]; then
if ! go version|grep -qE "go1.[2-9][0-9]"; then
echo "ignore otelmetric as go version: $(go version)"
continue 1
fi
fi
cd $dirpath
go mod tidy
go build ./...

View File

@ -66,6 +66,15 @@ jobs:
ports:
- 3306:3306
# MariaDb backend server.
mariadb:
image: loads/mariadb:10.4
env:
MARIADB_DATABASE: test
MARIADB_ROOT_PASSWORD: 12345678
ports:
- 3307:3306
# PostgreSQL backend server.
# docker run -d --name postgres \
# -p 5432:5432 \
@ -176,7 +185,7 @@ jobs:
strategy:
matrix:
go-version: [ "1.18", "1.19", "1.20", "1.21" ]
go-version: [ "1.18", "1.19", "1.20", "1.21", "1.22" ]
goarch: [ "386", "amd64" ]
steps:
@ -190,19 +199,19 @@ jobs:
uses: actions/checkout@v4
- name: Start Apollo Containers
run: docker-compose -f ".github/workflows/apollo/docker-compose.yml" up -d --build
run: docker compose -f ".github/workflows/apollo/docker-compose.yml" up -d --build
- name: Start Nacos Containers
run: docker-compose -f ".github/workflows/nacos/docker-compose.yml" up -d --build
run: docker compose -f ".github/workflows/nacos/docker-compose.yml" up -d --build
- name: Start Redis Cluster Containers
run: docker-compose -f ".github/workflows/redis/docker-compose.yml" up -d --build
run: docker compose -f ".github/workflows/redis/docker-compose.yml" up -d --build
- name: Start Consul Containers
run: docker-compose -f ".github/workflows/consul/docker-compose.yml" up -d --build
run: docker compose -f ".github/workflows/consul/docker-compose.yml" up -d --build
- name: Setup Golang ${{ matrix.go-version }}
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache-dependency-path: '**/go.sum'
@ -219,19 +228,19 @@ jobs:
run: bash .github/workflows/ci-main.sh coverage
- name: Stop Redis Cluster Containers
run: docker-compose -f ".github/workflows/redis/docker-compose.yml" down
run: docker compose -f ".github/workflows/redis/docker-compose.yml" down
- name: Stop Apollo Containers
run: docker-compose -f ".github/workflows/apollo/docker-compose.yml" down
run: docker compose -f ".github/workflows/apollo/docker-compose.yml" down
- name: Stop Nacos Containers
run: docker-compose -f ".github/workflows/nacos/docker-compose.yml" down
run: docker compose -f ".github/workflows/nacos/docker-compose.yml" down
- name: Stop Consul Containers
run: docker-compose -f ".github/workflows/consul/docker-compose.yml" down
run: docker compose -f ".github/workflows/consul/docker-compose.yml" down
- name: Report Coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
with:
flags: go-${{ matrix.go-version }}-${{ matrix.goarch }}

View File

@ -37,7 +37,7 @@ jobs:
strategy:
matrix:
go-version: [ "1.18", "1.19", "1.20", "1.21" ]
go-version: [ "1.18", "1.19", "1.20", "1.22" ]
goarch: [ "386", "amd64" ]
steps:
@ -53,7 +53,7 @@ jobs:
uses: medyagh/setup-minikube@master
- name: Setup Golang ${{ matrix.go-version }}
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache-dependency-path: '**/go.sum'

View File

@ -36,19 +36,19 @@ jobs:
golangci:
strategy:
matrix:
go-version: [ '1.18','1.19','1.20','1.21.4' ]
go-version: [ '1.18','1.19','1.20','1.21.4','1.22' ]
name: golangci-lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Golang ${{ matrix.go-version }}
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v4
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.52.2
version: v1.56.2
args: --timeout 3m0s

View File

@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@v4
- name: Set Up Golang Environment
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: 1.20.8

View File

@ -262,7 +262,7 @@ linters-settings:
# Select the Go version to target.
# Default: "1.13"
# Deprecated: use the global `run.go` instead.
go: "1.15"
go: "1.20"
# SAxxxx checks in https://staticcheck.io/docs/configuration/options/#checks
# Default: ["*"]
checks: [ "all","-SA1019","-SA4015","-SA1029","-SA1016","-SA9003","-SA4006","-SA6003" ]

View File

@ -3,13 +3,13 @@ module github.com/gogf/gf/cmd/gf/v2
go 1.18
require (
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.6.3
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.6.3
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.6.3
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.6.3
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.6.3
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.6.3
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.7.0
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.7.0
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.7.0
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.7.0
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.7.0
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.7.0
github.com/gogf/gf/v2 v2.7.0
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f
github.com/olekukonko/tablewriter v0.0.5
golang.org/x/mod v0.9.0

View File

@ -25,6 +25,7 @@ import (
)
var (
// Init .
Init = cInit{}
)
@ -64,14 +65,13 @@ type cInitInput struct {
Name string `name:"NAME" arg:"true" v:"required" brief:"{cInitNameBrief}"`
Mono bool `name:"mono" short:"m" brief:"initialize a mono-repo instead a single-repo" orphan:"true"`
Update bool `name:"update" short:"u" brief:"update to the latest goframe version" orphan:"true"`
Module string `name:"module" short:"g" brief:"custom go module"`
}
type cInitOutput struct{}
func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err error) {
var (
overwrote = false
)
var overwrote = false
if !gfile.IsEmpty(in.Name) && !allyes.Check() {
s := gcmd.Scanf(`the folder "%s" is not empty, files might be overwrote, continue? [y/n]: `, in.Name)
if strings.EqualFold(s, "n") {
@ -105,7 +105,7 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err
err = gfile.ReadLines(gitignoreFile, func(line string) error {
// Add only hidden files or directories
// If other directories are added, it may cause the entire directory to be ignored
// such as 'main' in the .gitignore file, but the path is 'D:\main\my-project'
// such as 'main' in the .gitignore file, but the path is ' D:\main\my-project '
if line != "" && strings.HasPrefix(line, ".") {
ignoreFiles = append(ignoreFiles, line)
}
@ -118,6 +118,11 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err
}
}
// Replace module name.
if in.Module == "" {
in.Module = gfile.Basename(gfile.RealPath(in.Name))
}
// Replace template name to project name.
err = gfile.ReplaceDirFunc(func(path, content string) string {
for _, ignoreFile := range ignoreFiles {
@ -125,7 +130,7 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err
return content
}
}
return gstr.Replace(gfile.GetContents(path), cInitRepoPrefix+templateRepoName, gfile.Basename(gfile.RealPath(in.Name)))
return gstr.Replace(gfile.GetContents(path), cInitRepoPrefix+templateRepoName, in.Module)
}, in.Name, "*", true)
if err != nil {
return

View File

@ -393,3 +393,78 @@ func Test_Gen_Dao_Issue2616(t *testing.T) {
t.Assert(gstr.Contains(daoUser2Content, keyStr), false)
})
}
// https://github.com/gogf/gf/issues/2746
func Test_Gen_Dao_Issue2746(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
err error
mdb gdb.DB
link2746 = "mariadb:root:12345678@tcp(127.0.0.1:3307)/test?loc=Local&parseTime=true"
table = "issue2746"
sqlContent = fmt.Sprintf(
gtest.DataContent(`issue`, `2746`, `sql.sql`),
table,
)
)
mdb, err = gdb.New(gdb.ConfigNode{
Link: link2746,
})
t.AssertNil(err)
array := gstr.SplitAndTrim(sqlContent, ";")
for _, v := range array {
if _, err = mdb.Exec(ctx, v); err != nil {
t.AssertNil(err)
}
}
defer dropTableWithDb(mdb, table)
var (
path = gfile.Temp(guid.S())
group = "test"
in = gendao.CGenDaoInput{
Path: path,
Link: link2746,
Tables: "",
TablesEx: "",
Group: group,
Prefix: "",
RemovePrefix: "",
JsonCase: "SnakeScreaming",
ImportPrefix: "",
DaoPath: "",
DoPath: "",
EntityPath: "",
TplDaoIndexPath: "",
TplDaoInternalPath: "",
TplDaoDoPath: "",
TplDaoEntityPath: "",
StdTime: false,
WithTime: false,
GJsonSupport: true,
OverwriteDao: false,
DescriptionTag: false,
NoJsonTag: false,
NoModelComment: false,
Clear: false,
TypeMapping: nil,
}
)
err = gutil.FillStructWithDefault(&in)
t.AssertNil(err)
err = gfile.Mkdir(path)
t.AssertNil(err)
_, err = gendao.CGenDao{}.Dao(ctx, in)
t.AssertNil(err)
defer gfile.Remove(path)
var (
file = filepath.FromSlash(path + "/model/entity/issue_2746.go")
expectContent = gtest.DataContent(`issue`, `2746`, `issue_2746.go`)
)
t.Assert(expectContent, gfile.GetContents(file))
})
}

View File

@ -201,8 +201,8 @@ type (
NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"`
TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
generatedFilePaths *CGenDaoInternalGeneratedFilePaths
TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
genItems *CGenDaoInternalGenItems
}
CGenDaoOutput struct{}
@ -213,13 +213,6 @@ type (
NewTableNames []string
}
CGenDaoInternalGeneratedFilePaths struct {
DaoFilePaths []string
DaoInternalFilePaths []string
DoFilePaths []string
EntityFilePaths []string
}
DBFieldTypeName = string
CustomAttributeType struct {
Type string `brief:"custom attribute type name"`
@ -228,12 +221,7 @@ type (
)
func (c CGenDao) Dao(ctx context.Context, in CGenDaoInput) (out *CGenDaoOutput, err error) {
in.generatedFilePaths = &CGenDaoInternalGeneratedFilePaths{
DaoFilePaths: make([]string, 0),
DaoInternalFilePaths: make([]string, 0),
DoFilePaths: make([]string, 0),
EntityFilePaths: make([]string, 0),
}
in.genItems = newCGenDaoInternalGenItems()
if g.Cfg().Available(ctx) {
v := g.Cfg().MustGet(ctx, CGenDaoConfig)
if v.IsSlice() {
@ -246,6 +234,7 @@ func (c CGenDao) Dao(ctx context.Context, in CGenDaoInput) (out *CGenDaoOutput,
} else {
doGenDaoForArray(ctx, -1, in)
}
doClear(in.genItems)
mlog.Print("done!")
return
}
@ -326,6 +315,8 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
newTableNames[i] = newTableName
}
in.genItems.Scale()
// Dao: index and internal.
generateDao(ctx, CGenDaoInternalInput{
CGenDaoInput: in,
@ -348,9 +339,7 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) {
NewTableNames: newTableNames,
})
if in.Clear {
doClear(ctx, in)
}
in.genItems.SetClear(in.Clear)
}
func getImportPartContent(ctx context.Context, source string, isDo bool, appendImports []string) string {

View File

@ -7,27 +7,40 @@
package gendao
import (
"context"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
)
func doClear(ctx context.Context, in CGenDaoInput) {
filePaths, err := gfile.ScanDirFile(in.Path, "*.go", true)
if err != nil {
mlog.Fatal(err)
}
func doClear(items *CGenDaoInternalGenItems) {
var allGeneratedFilePaths = make([]string, 0)
allGeneratedFilePaths = append(allGeneratedFilePaths, in.generatedFilePaths.DaoFilePaths...)
allGeneratedFilePaths = append(allGeneratedFilePaths, in.generatedFilePaths.DaoInternalFilePaths...)
allGeneratedFilePaths = append(allGeneratedFilePaths, in.generatedFilePaths.EntityFilePaths...)
allGeneratedFilePaths = append(allGeneratedFilePaths, in.generatedFilePaths.DoFilePaths...)
for _, filePath := range filePaths {
for _, item := range items.Items {
allGeneratedFilePaths = append(allGeneratedFilePaths, item.GeneratedFilePaths...)
}
for i, v := range allGeneratedFilePaths {
allGeneratedFilePaths[i] = gfile.RealPath(v)
}
for _, item := range items.Items {
if !item.Clear {
continue
}
doClearItem(item, allGeneratedFilePaths)
}
}
func doClearItem(item CGenDaoInternalGenItem, allGeneratedFilePaths []string) {
var generatedFilePaths = make([]string, 0)
for _, dirPath := range item.StorageDirPaths {
filePaths, err := gfile.ScanDirFile(dirPath, "*.go", true)
if err != nil {
mlog.Fatal(err)
}
generatedFilePaths = append(generatedFilePaths, filePaths...)
}
for _, filePath := range generatedFilePaths {
if !gstr.InArray(allGeneratedFilePaths, filePath) {
if err = gfile.Remove(filePath); err != nil {
if err := gfile.Remove(filePath); err != nil {
mlog.Print(err)
}
}

View File

@ -30,6 +30,7 @@ func generateDao(ctx context.Context, in CGenDaoInternalInput) {
dirPathDao = gfile.Join(in.Path, in.DaoPath)
dirPathDaoInternal = gfile.Join(dirPathDao, "internal")
)
in.genItems.AppendDirPath(dirPathDao)
for i := 0; i < len(in.TableNames); i++ {
generateDaoSingle(ctx, generateDaoSingleInput{
CGenDaoInternalInput: in,
@ -106,10 +107,7 @@ type generateDaoIndexInput struct {
func generateDaoIndex(in generateDaoIndexInput) {
path := filepath.FromSlash(gfile.Join(in.DirPathDao, in.FileName+".go"))
// It should add path to result slice whenever it would generate the path file or not.
in.generatedFilePaths.DaoFilePaths = append(
in.generatedFilePaths.DaoFilePaths,
path,
)
in.genItems.AppendGeneratedFilePath(path)
if in.OverwriteDao || !gfile.Exists(path) {
indexContent := gstr.ReplaceByMap(
getTemplateFromPathOrDefault(in.TplDaoIndexPath, consts.TemplateGenDaoIndexContent),
@ -153,10 +151,7 @@ func generateDaoInternal(in generateDaoInternalInput) {
tplVarColumnNames: gstr.Trim(generateColumnNamesForDao(in.FieldMap, removeFieldPrefixArray)),
})
modelContent = replaceDefaultVar(in.CGenDaoInternalInput, modelContent)
in.generatedFilePaths.DaoInternalFilePaths = append(
in.generatedFilePaths.DaoInternalFilePaths,
path,
)
in.genItems.AppendGeneratedFilePath(path)
if err := gfile.PutContents(path, strings.TrimSpace(modelContent)); err != nil {
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
} else {

View File

@ -24,6 +24,7 @@ import (
func generateDo(ctx context.Context, in CGenDaoInternalInput) {
var dirPathDo = filepath.FromSlash(gfile.Join(in.Path, in.DoPath))
in.genItems.AppendDirPath(dirPathDo)
in.NoJsonTag = true
in.DescriptionTag = false
in.NoModelComment = false
@ -63,10 +64,7 @@ func generateDo(ctx context.Context, in CGenDaoInternalInput) {
gstr.CaseCamel(newTableName),
structDefinition,
)
in.generatedFilePaths.DoFilePaths = append(
in.generatedFilePaths.DoFilePaths,
doFilePath,
)
in.genItems.AppendGeneratedFilePath(doFilePath)
err = gfile.PutContents(doFilePath, strings.TrimSpace(modelContent))
if err != nil {
mlog.Fatalf(`writing content to "%s" failed: %v`, doFilePath, err)

View File

@ -22,6 +22,7 @@ import (
func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
var dirPathEntity = gfile.Join(in.Path, in.EntityPath)
in.genItems.AppendDirPath(dirPathEntity)
// Model content.
for i, tableName := range in.TableNames {
fieldMap, err := in.DB.TableFields(ctx, tableName)
@ -48,10 +49,7 @@ func generateEntity(ctx context.Context, in CGenDaoInternalInput) {
appendImports,
)
)
in.generatedFilePaths.EntityFilePaths = append(
in.generatedFilePaths.EntityFilePaths,
entityFilePath,
)
in.genItems.AppendGeneratedFilePath(entityFilePath)
err = gfile.PutContents(entityFilePath, strings.TrimSpace(entityContent))
if err != nil {
mlog.Fatalf("writing content to '%s' failed: %v", entityFilePath, err)

View File

@ -0,0 +1,53 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gendao
type (
CGenDaoInternalGenItems struct {
index int
Items []CGenDaoInternalGenItem
}
CGenDaoInternalGenItem struct {
Clear bool
StorageDirPaths []string
GeneratedFilePaths []string
}
)
func newCGenDaoInternalGenItems() *CGenDaoInternalGenItems {
return &CGenDaoInternalGenItems{
index: -1,
Items: make([]CGenDaoInternalGenItem, 0),
}
}
func (i *CGenDaoInternalGenItems) Scale() {
i.Items = append(i.Items, CGenDaoInternalGenItem{
StorageDirPaths: make([]string, 0),
GeneratedFilePaths: make([]string, 0),
Clear: false,
})
i.index++
}
func (i *CGenDaoInternalGenItems) SetClear(clear bool) {
i.Items[i.index].Clear = clear
}
func (i CGenDaoInternalGenItems) AppendDirPath(storageDirPath string) {
i.Items[i.index].StorageDirPaths = append(
i.Items[i.index].StorageDirPaths,
storageDirPath,
)
}
func (i CGenDaoInternalGenItems) AppendGeneratedFilePath(generatedFilePath string) {
i.Items[i.index].GeneratedFilePaths = append(
i.Items[i.index].GeneratedFilePaths,
generatedFilePath,
)
}

View File

@ -135,9 +135,14 @@ func generateStructFieldDefinition(
" #" + gstr.CaseCamel(newFiledName),
" #" + localTypeNameStr,
}
attrLines = append(attrLines, " #"+fmt.Sprintf(tagKey+`json:"%s"`, jsonTag))
attrLines = append(attrLines, " #"+fmt.Sprintf(`description:"%s"`+tagKey, descriptionTag))
attrLines = append(attrLines, " #"+fmt.Sprintf(`// %s`, formatComment(field.Comment)))
attrLines = append(attrLines, fmt.Sprintf(` #%sjson:"%s"`, tagKey, jsonTag))
// orm tag
if !in.IsDo {
// entity
attrLines = append(attrLines, fmt.Sprintf(` #orm:"%s"`, field.Name))
}
attrLines = append(attrLines, fmt.Sprintf(` #description:"%s"%s`, descriptionTag, tagKey))
attrLines = append(attrLines, fmt.Sprintf(` #// %s`, formatComment(field.Comment)))
for k, v := range attrLines {
if in.NoJsonTag {

View File

@ -3,18 +3,17 @@ github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyM
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=

View File

@ -10,11 +10,11 @@ import (
// TableUser is the golang structure for table table_user.
type TableUser struct {
Id uint `json:"ID" ` // User ID
Passport string `json:"PASSPORT" ` // User Passport
Password string `json:"PASSWORD" ` // User Password
Nickname string `json:"NICKNAME" ` // User Nickname
Score float64 `json:"SCORE" ` // Total score amount.
CreateAt *gtime.Time `json:"CREATE_AT" ` // Created Time
UpdateAt *gtime.Time `json:"UPDATE_AT" ` // Updated Time
Id uint `json:"ID" orm:"id" ` // User ID
Passport string `json:"PASSPORT" orm:"passport" ` // User Passport
Password string `json:"PASSWORD" orm:"password" ` // User Password
Nickname string `json:"NICKNAME" orm:"nickname" ` // User Nickname
Score float64 `json:"SCORE" orm:"score" ` // Total score amount.
CreateAt *gtime.Time `json:"CREATE_AT" orm:"create_at" ` // Created Time
UpdateAt *gtime.Time `json:"UPDATE_AT" orm:"update_at" ` // Updated Time
}

View File

@ -11,11 +11,11 @@ import (
// TableUser is the golang structure for table table_user.
type TableUser struct {
Id int64 `json:"id" ` // User ID
Passport string `json:"passport" ` // User Passport
Password string `json:"password" ` // User Password
Nickname string `json:"nickname" ` // User Nickname
Score decimal.Decimal `json:"score" ` // Total score amount.
CreateAt *gtime.Time `json:"createAt" ` // Created Time
UpdateAt *gtime.Time `json:"updateAt" ` // Updated Time
Id int64 `json:"id" orm:"id" ` // User ID
Passport string `json:"passport" orm:"passport" ` // User Passport
Password string `json:"password" orm:"password" ` // User Password
Nickname string `json:"nickname" orm:"nickname" ` // User Nickname
Score decimal.Decimal `json:"score" orm:"score" ` // Total score amount.
CreateAt *gtime.Time `json:"createAt" orm:"create_at" ` // Created Time
UpdateAt *gtime.Time `json:"updateAt" orm:"update_at" ` // Updated Time
}

View File

@ -0,0 +1,18 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package entity
import (
"github.com/gogf/gf/v2/encoding/gjson"
)
// Issue2746 is the golang structure for table issue2746.
type Issue2746 struct {
Id uint `json:"ID" orm:"id" ` // User ID
Nickname string `json:"NICKNAME" orm:"nickname" ` // User Nickname
Tag *gjson.Json `json:"TAG" orm:"tag" ` //
Info string `json:"INFO" orm:"info" ` //
Tag2 *gjson.Json `json:"TAG_2" orm:"tag2" ` // Tag2
}

View File

@ -0,0 +1,9 @@
CREATE TABLE %s (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID',
`nickname` varchar(45) NOT NULL COMMENT 'User Nickname',
`tag` json NOT NULL,
`info` longtext DEFAULT NULL,
`tag2` json COMMENT 'Tag2',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

View File

@ -7,8 +7,8 @@
// Package gtype provides high performance and concurrent-safe basic variable types.
package gtype
// New is alias of NewInterface.
// See NewInterface.
func New(value ...interface{}) *Interface {
return NewInterface(value...)
// New is alias of NewAny.
// See NewAny, NewInterface.
func New(value ...interface{}) *Any {
return NewAny(value...)
}

View File

@ -0,0 +1,20 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gtype
// Any is a struct for concurrent-safe operation for type any.
type Any = Interface
// NewAny creates and returns a concurrent-safe object for any type,
// with given initial value `value`.
func NewAny(value ...any) *Any {
t := &Any{}
if len(value) > 0 && value[0] != nil {
t.value.Store(value[0])
}
return t
}

View File

@ -0,0 +1,74 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gtype_test
import (
"testing"
"github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/gconv"
)
func Test_Any(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t1 := Temp{Name: "gf", Age: 18}
t2 := Temp{Name: "gf", Age: 19}
i := gtype.New(t1)
iClone := i.Clone()
t.AssertEQ(iClone.Set(t2), t1)
t.AssertEQ(iClone.Val().(Temp), t2)
// empty param test
i1 := gtype.New()
t.AssertEQ(i1.Val(), nil)
i2 := gtype.New("gf")
t.AssertEQ(i2.String(), "gf")
copyVal := i2.DeepCopy()
i2.Set("goframe")
t.AssertNE(copyVal, iClone.Val())
i2 = nil
copyVal = i2.DeepCopy()
t.AssertNil(copyVal)
})
}
func Test_Any_JSON(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := "i love gf"
i := gtype.New(s)
b1, err1 := json.Marshal(i)
b2, err2 := json.Marshal(i.Val())
t.Assert(err1, nil)
t.Assert(err2, nil)
t.Assert(b1, b2)
i2 := gtype.New()
err := json.UnmarshalUseNumber(b2, &i2)
t.AssertNil(err)
t.Assert(i2.Val(), s)
})
}
func Test_Any_UnmarshalValue(t *testing.T) {
type V struct {
Name string
Var *gtype.Any
}
gtest.C(t, func(t *gtest.T) {
var v *V
err := gconv.Struct(map[string]any{
"name": "john",
"var": "123",
}, &v)
t.AssertNil(err)
t.Assert(v.Name, "john")
t.Assert(v.Var.Val(), "123")
})
}

View File

@ -4,7 +4,7 @@ go 1.18
require (
github.com/apolloconfig/agollo/v4 v4.3.1
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/v2 v2.7.0
)
require (

View File

@ -1,9 +1,9 @@
module github.com/gogf/gf/contrib/config/consul/v2
go 1.19
go 1.18
require (
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/v2 v2.7.0
github.com/hashicorp/consul/api v1.24.0
github.com/hashicorp/go-cleanhttp v0.5.2
)

View File

@ -1,9 +1,9 @@
module github.com/gogf/gf/contrib/config/kubecm/v2
go 1.19
go 1.18
require (
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/v2 v2.7.0
k8s.io/api v0.27.4
k8s.io/apimachinery v0.27.4
k8s.io/client-go v0.27.4

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/nacos/v2
go 1.18
require (
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/v2 v2.7.0
github.com/nacos-group/nacos-sdk-go/v2 v2.2.5
)

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/config/polaris/v2
go 1.18
require (
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/v2 v2.7.0
github.com/polarismesh/polaris-go v1.5.5
)

View File

@ -39,23 +39,20 @@ import _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
```
import _ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
```
Note:
- It does not support `Save` features.
## PostgreSQL
```
import _ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
```
Note:
- It does not support `Save/Replace` features.
- It does not support `LastInsertId`.
- It does not support `Replace` features.
## SQL Server
```
import _ "github.com/gogf/gf/contrib/drivers/mssql/v2"
```
Note:
- It does not support `Save/Replace` features.
- It does not support `Replace` features.
- It does not support `LastInsertId`.
- It supports server version >= `SQL Server2005`
- It ONLY supports datetime2 and datetimeoffset types for auto handling created_at/updated_at/deleted_at columns, because datetime type does not support microseconds precision when column value is passed as string.
@ -65,7 +62,7 @@ Note:
import _ "github.com/gogf/gf/contrib/drivers/oracle/v2"
```
Note:
- It does not support `Save/Replace` features.
- It does not support `Replace` features.
- It does not support `LastInsertId`.
## ClickHouse

View File

@ -15,7 +15,7 @@ import (
"github.com/gogf/gf/v2/database/gdb"
)
// DoInsert inserts or updates data forF given table.
// DoInsert inserts or updates data for given table.
func (d *Driver) DoInsert(
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
) (result sql.Result, err error) {

View File

@ -4,7 +4,7 @@ go 1.18
require (
github.com/ClickHouse/clickhouse-go/v2 v2.0.15
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/v2 v2.7.0
github.com/google/uuid v1.3.0
github.com/shopspring/decimal v1.3.1
)

View File

@ -10,198 +10,142 @@ import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
// DoInsert inserts or updates data forF given table.
// DoInsert inserts or updates data for given table.
func (d *Driver) DoInsert(
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
) (result sql.Result, err error) {
switch option.InsertOption {
case gdb.InsertOptionSave:
return d.doSave(ctx, link, table, list, option)
case gdb.InsertOptionReplace:
// TODO:: Should be Supported
return nil, gerror.NewCode(
gcode.CodeNotSupported, `Replace operation is not supported by dm driver`,
)
case gdb.InsertOptionSave:
// This syntax currently only supports design tables whose primary key is ID.
listLength := len(list)
if listLength == 0 {
return nil, gerror.NewCode(
gcode.CodeInvalidRequest, `Save operation list is empty by dm driver`,
)
}
var (
keysSort []string
charL, charR = d.GetChars()
)
// Column names need to be aligned in the syntax
for k := range list[0] {
keysSort = append(keysSort, k)
}
var char = struct {
charL string
charR string
valueCharL string
valueCharR string
duplicateKey string
keys []string
}{
charL: charL,
charR: charR,
valueCharL: "'",
valueCharR: "'",
// TODO:: Need to dynamically set the primary key of the table
duplicateKey: "ID",
keys: keysSort,
}
// insertKeys: Handle valid keys that need to be inserted and updated
// insertValues: Handle values that need to be inserted
// updateValues: Handle values that need to be updated
// queryValues: Handle only one insert with column name
insertKeys, insertValues, updateValues, queryValues := parseValue(list[0], char)
// unionValues: Handling values that need to be inserted and updated
unionValues := parseUnion(list[1:], char)
batchResult := new(gdb.SqlResult)
// parseSql():
// MERGE INTO {{table}} T1
// USING ( SELECT {{queryValues}} FROM DUAL
// {{unionValues}} ) T2
// ON (T1.{{duplicateKey}} = T2.{{duplicateKey}})
// WHEN NOT MATCHED THEN
// INSERT {{insertKeys}} VALUES {{insertValues}}
// WHEN MATCHED THEN
// UPDATE SET {{updateValues}}
sqlStr := parseSql(
insertKeys, insertValues, updateValues, queryValues, unionValues, table, char.duplicateKey,
)
r, err := d.DoExec(ctx, link, sqlStr)
if err != nil {
return r, err
}
if n, err := r.RowsAffected(); err != nil {
return r, err
} else {
batchResult.Result = r
batchResult.Affected += n
}
return batchResult, nil
}
return d.Core.DoInsert(ctx, link, table, list, option)
}
func parseValue(listOne gdb.Map, char struct {
charL string
charR string
valueCharL string
valueCharR string
duplicateKey string
keys []string
}) (insertKeys []string, insertValues []string, updateValues []string, queryValues []string) {
for _, column := range char.keys {
if listOne[column] == nil {
// remove unassigned struct object
continue
}
insertKeys = append(insertKeys, char.charL+column+char.charR)
insertValues = append(insertValues, "T2."+char.charL+column+char.charR)
if column != char.duplicateKey {
// doSave support upsert for dm
func (d *Driver) doSave(ctx context.Context,
link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
) (result sql.Result, err error) {
if len(option.OnConflict) == 0 {
return nil, gerror.NewCode(
gcode.CodeMissingParameter, `Please specify conflict columns`,
)
}
if len(list) == 0 {
return nil, gerror.NewCode(
gcode.CodeInvalidRequest, `Save operation list is empty by oracle driver`,
)
}
var (
one = list[0]
oneLen = len(one)
charL, charR = d.GetChars()
conflictKeys = option.OnConflict
conflictKeySet = gset.New(false)
// queryHolders: Handle data with Holder that need to be upsert
// queryValues: Handle data that need to be upsert
// insertKeys: Handle valid keys that need to be inserted
// insertValues: Handle values that need to be inserted
// updateValues: Handle values that need to be updated
queryHolders = make([]string, oneLen)
queryValues = make([]interface{}, oneLen)
insertKeys = make([]string, oneLen)
insertValues = make([]string, oneLen)
updateValues []string
)
// conflictKeys slice type conv to set type
for _, conflictKey := range conflictKeys {
conflictKeySet.Add(gstr.ToUpper(conflictKey))
}
index := 0
for key, value := range one {
keyWithChar := charL + key + charR
queryHolders[index] = fmt.Sprintf("? AS %s", keyWithChar)
queryValues[index] = value
insertKeys[index] = keyWithChar
insertValues[index] = fmt.Sprintf("T2.%s", keyWithChar)
// filter conflict keys in updateValues.
// And the key is not a soft created field.
if !(conflictKeySet.Contains(key) || d.Core.IsSoftCreatedFieldName(key)) {
updateValues = append(
updateValues,
fmt.Sprintf(`T1.%s = T2.%s`, char.charL+column+char.charR, char.charL+column+char.charR),
fmt.Sprintf(`T1.%s = T2.%s`, keyWithChar, keyWithChar),
)
}
saveValue := gconv.String(listOne[column])
queryValues = append(
queryValues,
fmt.Sprintf(
char.valueCharL+"%s"+char.valueCharR+" AS "+char.charL+"%s"+char.charR,
saveValue, column,
),
)
index++
}
return
batchResult := new(gdb.SqlResult)
sqlStr := parseSqlForUpsert(table, queryHolders, insertKeys, insertValues, updateValues, conflictKeys)
r, err := d.DoExec(ctx, link, sqlStr, queryValues...)
if err != nil {
return r, err
}
if n, err := r.RowsAffected(); err != nil {
return r, err
} else {
batchResult.Result = r
batchResult.Affected += n
}
return batchResult, nil
}
func parseUnion(list gdb.List, char struct {
charL string
charR string
valueCharL string
valueCharR string
duplicateKey string
keys []string
}) (unionValues []string) {
for _, mapper := range list {
var saveValue []string
for _, column := range char.keys {
if mapper[column] == nil {
continue
}
// va := reflect.ValueOf(mapper[column])
// ty := reflect.TypeOf(mapper[column])
// switch ty.Kind() {
// case reflect.String:
// saveValue = append(saveValue, char.valueCharL+va.String()+char.valueCharR)
// case reflect.Int:
// saveValue = append(saveValue, strconv.FormatInt(va.Int(), 10))
// case reflect.Int64:
// saveValue = append(saveValue, strconv.FormatInt(va.Int(), 10))
// default:
// // The fish has no chance getting here.
// // Nothing to do.
// }
saveValue = append(saveValue,
fmt.Sprintf(
char.valueCharL+"%s"+char.valueCharR,
gconv.String(mapper[column]),
))
}
unionValues = append(
unionValues,
fmt.Sprintf(`UNION ALL SELECT %s FROM DUAL`, strings.Join(saveValue, ",")),
)
}
return
}
func parseSql(
insertKeys, insertValues, updateValues, queryValues, unionValues []string, table, duplicateKey string,
// parseSqlForUpsert
// MERGE INTO {{table}} T1
// USING ( SELECT {{queryHolders}} FROM DUAL T2
// ON (T1.{{duplicateKey}} = T2.{{duplicateKey}} AND ...)
// WHEN NOT MATCHED THEN
// INSERT {{insertKeys}} VALUES {{insertValues}}
// WHEN MATCHED THEN
// UPDATE SET {{updateValues}}
func parseSqlForUpsert(table string,
queryHolders, insertKeys, insertValues, updateValues, duplicateKey []string,
) (sqlStr string) {
var (
queryValueStr = strings.Join(queryValues, ",")
unionValueStr = strings.Join(unionValues, " ")
insertKeyStr = strings.Join(insertKeys, ",")
insertValueStr = strings.Join(insertValues, ",")
updateValueStr = strings.Join(updateValues, ",")
pattern = gstr.Trim(`
MERGE INTO %s T1 USING (SELECT %s FROM DUAL %s) T2 ON %s
WHEN NOT MATCHED
THEN
INSERT(%s) VALUES (%s)
WHEN MATCHED
THEN
UPDATE SET %s;
COMMIT;
`)
queryHolderStr = strings.Join(queryHolders, ",")
insertKeyStr = strings.Join(insertKeys, ",")
insertValueStr = strings.Join(insertValues, ",")
updateValueStr = strings.Join(updateValues, ",")
duplicateKeyStr string
pattern = gstr.Trim(`MERGE INTO %s T1 USING (SELECT %s FROM DUAL) T2 ON (%s) WHEN NOT MATCHED THEN INSERT(%s) VALUES (%s) WHEN MATCHED THEN UPDATE SET %s;`)
)
return fmt.Sprintf(
pattern,
table, queryValueStr, unionValueStr,
fmt.Sprintf("(T1.%s = T2.%s)", duplicateKey, duplicateKey),
insertKeyStr, insertValueStr, updateValueStr,
for index, keys := range duplicateKey {
if index != 0 {
duplicateKeyStr += " AND "
}
duplicateTmp := fmt.Sprintf("T1.%s = T2.%s", keys, keys)
duplicateKeyStr += duplicateTmp
}
return fmt.Sprintf(pattern,
table,
queryHolderStr,
duplicateKeyStr,
insertKeyStr,
insertValueStr,
updateValueStr,
)
}

View File

@ -7,6 +7,7 @@
package dm_test
import (
"database/sql"
"fmt"
"strings"
"testing"
@ -138,52 +139,53 @@ func Test_DB_Query(t *testing.T) {
}
func TestModelSave(t *testing.T) {
table := "A_tables"
createInitTable(table)
table := createTable("test")
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
data := []User{
{
ID: 100,
AccountName: "user_100",
AttrIndex: 100,
CreatedTime: time.Now(),
},
type User struct {
Id int
AccountName string
AttrIndex int
}
_, err := db.Model(table).Data(data).Save()
gtest.Assert(err, nil)
var (
user User
count int
result sql.Result
err error
)
db.SetDebug(true)
data2 := []User{
{
ID: 101,
AccountName: "user_101",
},
}
_, err = db.Model(table).Data(&data2).Save()
gtest.Assert(err, nil)
result, err = db.Model(table).Data(g.Map{
"id": 1,
"accountName": "ac1",
"attrIndex": 100,
}).OnConflict("id").Save()
data3 := []User{
{
ID: 10,
AccountName: "user_10",
PwdReset: 10,
},
}
_, err = db.Model(table).Save(data3)
gtest.Assert(err, nil)
t.AssertNil(err)
n, _ := result.RowsAffected()
t.Assert(n, 1)
data4 := []User{
{
ID: 9,
AccountName: "user_9",
CreatedTime: time.Now(),
},
}
_, err = db.Model(table).Save(&data4)
gtest.Assert(err, nil)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 1)
t.Assert(user.AccountName, "ac1")
t.Assert(user.AttrIndex, 100)
// TODO:: Should be Supported 'Replace' Operation
// _, err = db.Schema(TestDBName).Replace(ctx, "DoInsert", data, 10)
// gtest.Assert(err, nil)
_, err = db.Model(table).Data(g.Map{
"id": 1,
"accountName": "ac2",
"attrIndex": 200,
}).OnConflict("id").Save()
t.AssertNil(err)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.AccountName, "ac2")
t.Assert(user.AttrIndex, 200)
count, err = db.Model(table).Count()
t.AssertNil(err)
t.Assert(count, 1)
})
}
@ -198,7 +200,7 @@ func TestModelInsert(t *testing.T) {
AccountName: fmt.Sprintf(`A%dtwo`, i),
PwdReset: 0,
AttrIndex: 99,
// CreatedTime: time.Now(),
CreatedTime: time.Now(),
UpdatedTime: time.Now(),
}
// _, err := db.Schema(TestDBName).Model(table).Data(data).Insert()
@ -214,7 +216,7 @@ func TestModelInsert(t *testing.T) {
PwdReset: 1,
CreatedTime: time.Now(),
AttrIndex: 98,
// UpdatedTime: time.Now(),
UpdatedTime: time.Now(),
}
// _, err := db.Schema(TestDBName).Model(table).Data(data).Insert()
_, err := db.Model(table).Data(&data).Insert()
@ -232,6 +234,8 @@ func TestDBInsert(t *testing.T) {
"ACCOUNT_NAME": fmt.Sprintf(`A%dthress`, i),
"PWD_RESET": 3,
"ATTR_INDEX": 98,
"CREATED_TIME": gtime.Now(),
"UPDATED_TIME": gtime.Now(),
}
_, err := db.Insert(ctx, table, &data)
gtest.Assert(err, nil)
@ -253,18 +257,21 @@ func Test_DB_Insert(t *testing.T) {
table := "A_tables"
createInitTable(table)
gtest.C(t, func(t *gtest.T) {
timeNow := time.Now()
// normal map
_, err := db.Insert(ctx, table, g.Map{
"ID": 1000,
"ACCOUNT_NAME": "map1",
"CREATED_TIME": gtime.Now(),
"CREATED_TIME": timeNow,
"UPDATED_TIME": timeNow,
})
t.AssertNil(err)
result, err := db.Insert(ctx, table, g.Map{
"ID": "2000",
"ACCOUNT_NAME": "map2",
"CREATED_TIME": gtime.Now(),
"CREATED_TIME": timeNow,
"UPDATED_TIME": timeNow,
})
t.AssertNil(err)
n, _ := result.RowsAffected()
@ -273,7 +280,8 @@ func Test_DB_Insert(t *testing.T) {
result, err = db.Insert(ctx, table, g.Map{
"ID": 3000,
"ACCOUNT_NAME": "map3",
// "CREATED_TIME": gtime.Now(),
"CREATED_TIME": timeNow,
"UPDATED_TIME": timeNow,
})
t.AssertNil(err)
n, _ = result.RowsAffected()
@ -283,8 +291,8 @@ func Test_DB_Insert(t *testing.T) {
result, err = db.Insert(ctx, table, User{
ID: 4000,
AccountName: "struct_4",
// CreatedTime: timeStr,
// UpdatedTime: timeStr,
CreatedTime: timeNow,
UpdatedTime: timeNow,
})
t.AssertNil(err)
n, _ = result.RowsAffected()
@ -299,12 +307,11 @@ func Test_DB_Insert(t *testing.T) {
// t.Assert(one["CREATED_TIME"].GTime().String(), timeStr)
// *struct
timeStr := time.Now()
result, err = db.Insert(ctx, table, &User{
ID: 5000,
AccountName: "struct_5",
CreatedTime: timeStr,
// UpdatedTime: timeStr,
CreatedTime: timeNow,
UpdatedTime: timeNow,
})
t.AssertNil(err)
n, _ = result.RowsAffected()
@ -320,10 +327,14 @@ func Test_DB_Insert(t *testing.T) {
g.Map{
"ID": 6000,
"ACCOUNT_NAME": "t6000",
"CREATED_TIME": timeNow,
"UPDATED_TIME": timeNow,
},
g.Map{
"ID": 6001,
"ACCOUNT_NAME": "t6001",
"CREATED_TIME": timeNow,
"UPDATED_TIME": timeNow,
},
})
t.AssertNil(err)
@ -345,12 +356,14 @@ func Test_DB_BatchInsert(t *testing.T) {
{
"ID": 400,
"ACCOUNT_NAME": "list_400",
// "CREATE_TIME": gtime.Now(),
"CREATE_TIME": gtime.Now(),
"UPDATED_TIME": gtime.Now(),
},
{
"ID": 401,
"ACCOUNT_NAME": "list_401",
"CREATE_TIME": gtime.Now(),
"UPDATED_TIME": gtime.Now(),
},
}, 1)
t.AssertNil(err)
@ -365,11 +378,13 @@ func Test_DB_BatchInsert(t *testing.T) {
"ID": 500,
"ACCOUNT_NAME": "500_batch_500",
"CREATE_TIME": gtime.Now(),
"UPDATED_TIME": gtime.Now(),
},
g.Map{
"ID": 501,
"ACCOUNT_NAME": "501_batch_501",
// "CREATE_TIME": gtime.Now(),
"CREATE_TIME": gtime.Now(),
"UPDATED_TIME": gtime.Now(),
},
}, 1)
t.AssertNil(err)
@ -383,6 +398,7 @@ func Test_DB_BatchInsert(t *testing.T) {
"ID": 600,
"ACCOUNT_NAME": "600_batch_600",
"CREATE_TIME": gtime.Now(),
"UPDATED_TIME": gtime.Now(),
})
t.AssertNil(err)
n, _ := result.RowsAffected()
@ -394,11 +410,13 @@ func Test_DB_BatchInsert_Struct(t *testing.T) {
// batch insert struct
table := "A_tables"
createInitTable(table)
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
user := &User{
ID: 700,
AccountName: "BatchInsert_Struct_700",
// CreatedTime: time.Now(),
CreatedTime: time.Now(),
UpdatedTime: time.Now(),
}
result, err := db.Model(table).Insert(user)
t.AssertNil(err)

View File

@ -6,7 +6,7 @@ replace github.com/gogf/gf/v2 => ../../../
require (
gitee.com/chunanyong/dm v1.8.12
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/v2 v2.7.0
)
require (

View File

@ -4,7 +4,7 @@ go 1.18
require (
github.com/denisenkom/go-mssqldb v0.12.3
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/v2 v2.7.0
)
require (

View File

@ -7,7 +7,7 @@
// Package mssql implements gdb.Driver, which supports operations for database MSSql.
//
// Note:
// 1. It does not support Save/Replace features.
// 1. It does not support Replace features.
// 2. It does not support LastInsertId.
package mssql
@ -15,8 +15,6 @@ import (
_ "github.com/denisenkom/go-mssqldb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
// Driver is the driver for SQL server database.
@ -34,21 +32,6 @@ func init() {
}
}
// formatSqlTmp formats sql template string into one line.
func formatSqlTmp(sqlTmp string) string {
var err error
// format sql template string.
sqlTmp, err = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(sqlTmp))
if err != nil {
panic(err)
}
sqlTmp, err = gregex.ReplaceString(`\s{2,}`, " ", gstr.Trim(sqlTmp))
if err != nil {
panic(err)
}
return sqlTmp
}
// New create and returns a driver that implements gdb.Driver, which supports operations for Mssql.
func New() gdb.Driver {
return &Driver{}

View File

@ -9,7 +9,6 @@ package mssql
import (
"context"
"fmt"
"strconv"
"strings"
@ -27,7 +26,11 @@ WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d
)
func init() {
selectWithOrderSqlTmp = formatSqlTmp(selectWithOrderSqlTmp)
var err error
selectWithOrderSqlTmp, err = gdb.FormatMultiLineSqlToSingle(selectWithOrderSqlTmp)
if err != nil {
panic(err)
}
}
// DoFilter deals with the sql string before commits it to underlying sql driver.

View File

@ -9,20 +9,21 @@ package mssql
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/text/gstr"
)
// DoInsert inserts or updates data forF given table.
// DoInsert inserts or updates data for given table.
func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) {
switch option.InsertOption {
case gdb.InsertOptionSave:
return nil, gerror.NewCode(
gcode.CodeNotSupported,
`Save operation is not supported by mssql driver`,
)
return d.doSave(ctx, link, table, list, option)
case gdb.InsertOptionReplace:
return nil, gerror.NewCode(
@ -34,3 +35,116 @@ func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list
return d.Core.DoInsert(ctx, link, table, list, option)
}
}
// doSave support upsert for SQL server
func (d *Driver) doSave(ctx context.Context,
link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
) (result sql.Result, err error) {
if len(option.OnConflict) == 0 {
return nil, gerror.NewCode(
gcode.CodeMissingParameter, `Please specify conflict columns`,
)
}
if len(list) == 0 {
return nil, gerror.NewCode(
gcode.CodeInvalidRequest, `Save operation list is empty by mssql driver`,
)
}
var (
one = list[0]
oneLen = len(one)
charL, charR = d.GetChars()
conflictKeys = option.OnConflict
conflictKeySet = gset.New(false)
// queryHolders: Handle data with Holder that need to be upsert
// queryValues: Handle data that need to be upsert
// insertKeys: Handle valid keys that need to be inserted
// insertValues: Handle values that need to be inserted
// updateValues: Handle values that need to be updated
queryHolders = make([]string, oneLen)
queryValues = make([]interface{}, oneLen)
insertKeys = make([]string, oneLen)
insertValues = make([]string, oneLen)
updateValues []string
)
// conflictKeys slice type conv to set type
for _, conflictKey := range conflictKeys {
conflictKeySet.Add(gstr.ToUpper(conflictKey))
}
index := 0
for key, value := range one {
queryHolders[index] = "?"
queryValues[index] = value
insertKeys[index] = charL + key + charR
insertValues[index] = "T2." + charL + key + charR
// filter conflict keys in updateValues.
// And the key is not a soft created field.
if !(conflictKeySet.Contains(key) || d.Core.IsSoftCreatedFieldName(key)) {
updateValues = append(
updateValues,
fmt.Sprintf(`T1.%s = T2.%s`, charL+key+charR, charL+key+charR),
)
}
index++
}
batchResult := new(gdb.SqlResult)
sqlStr := parseSqlForUpsert(table, queryHolders, insertKeys, insertValues, updateValues, conflictKeys)
r, err := d.DoExec(ctx, link, sqlStr, queryValues...)
if err != nil {
return r, err
}
if n, err := r.RowsAffected(); err != nil {
return r, err
} else {
batchResult.Result = r
batchResult.Affected += n
}
return batchResult, nil
}
// parseSqlForUpsert
// MERGE INTO {{table}} T1
// USING ( VALUES( {{queryHolders}}) T2 ({{insertKeyStr}})
// ON (T1.{{duplicateKey}} = T2.{{duplicateKey}} AND ...)
// WHEN NOT MATCHED THEN
// INSERT {{insertKeys}} VALUES {{insertValues}}
// WHEN MATCHED THEN
// UPDATE SET {{updateValues}}
func parseSqlForUpsert(table string,
queryHolders, insertKeys, insertValues, updateValues, duplicateKey []string,
) (sqlStr string) {
var (
queryHolderStr = strings.Join(queryHolders, ",")
insertKeyStr = strings.Join(insertKeys, ",")
insertValueStr = strings.Join(insertValues, ",")
updateValueStr = strings.Join(updateValues, ",")
duplicateKeyStr string
pattern = gstr.Trim(`MERGE INTO %s T1 USING (VALUES(%s)) T2 (%s) ON (%s) WHEN NOT MATCHED THEN INSERT(%s) VALUES (%s) WHEN MATCHED THEN UPDATE SET %s;`)
)
for index, keys := range duplicateKey {
if index != 0 {
duplicateKeyStr += " AND "
}
duplicateTmp := fmt.Sprintf("T1.%s = T2.%s", keys, keys)
duplicateKeyStr += duplicateTmp
}
return fmt.Sprintf(pattern,
table,
queryHolderStr,
insertKeyStr,
duplicateKeyStr,
insertKeyStr,
insertValueStr,
updateValueStr,
)
}

View File

@ -47,7 +47,11 @@ ORDER BY a.id,a.colorder
)
func init() {
tableFieldsSqlTmp = formatSqlTmp(tableFieldsSqlTmp)
var err error
tableFieldsSqlTmp, err = gdb.FormatMultiLineSqlToSingle(tableFieldsSqlTmp)
if err != nil {
panic(err)
}
}
// TableFields retrieves and returns the fields' information of specified table of current schema.

View File

@ -217,7 +217,7 @@ func Test_DB_Insert(t *testing.T) {
Nickname string `gconv:"nickname"`
CreateTime *gtime.Time `json:"create_time"`
}
timeNow := gtime.Now()
timeNow := gtime.New("2024-10-01 12:01:01")
result, err = db.Insert(ctx, table, User{
Id: 3,
Passport: "user_3",
@ -236,7 +236,8 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["PASSPORT"].String(), "user_3")
t.Assert(one["PASSWORD"].String(), "25d55ad283aa400af464c76d713c07ad")
t.Assert(one["NICKNAME"].String(), "name_3")
t.Assert(one["CREATE_TIME"].GTime(), timeNow)
t.AssertNE(one["CREATE_TIME"].GTime(), nil)
t.AssertLT(timeNow.Sub(one["CREATE_TIME"].GTime()), 3)
// *struct
timeNow = gtime.Now()

View File

@ -24,10 +24,10 @@ import (
"github.com/gogf/gf/v2/util/gutil"
)
func TestPage(t *testing.T) {
func Test_Page(t *testing.T) {
table := createInitTable()
defer dropTable(table)
//db.SetDebug(true)
// db.SetDebug(true)
result, err := db.Model(table).Page(1, 2).Order("id").All()
gtest.Assert(err, nil)
fmt.Println("page:1--------", result)
@ -2558,7 +2558,7 @@ func Test_Model_AllAndCount(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
result, total, err := db.Model(table).Order("id").Limit(0, 3).AllAndCount(false)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(len(result), 3)
t.Assert(total, TableSize)
@ -2582,9 +2582,86 @@ func Test_Model_ScanAndCount(t *testing.T) {
total := 0
err := db.Model(table).Order("id").Limit(0, 3).ScanAndCount(&users, &total, false)
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(len(users), 3)
t.Assert(total, TableSize)
})
}
func Test_Model_Save(t *testing.T) {
table := createTable("test")
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Passport string
Password string
NickName string
CreatedAt *gtime.Time
UpdatedAt *gtime.Time
}
var (
user User
count int
result sql.Result
err error
)
result, err = db.Model(table).Data(g.Map{
"id": 1,
"passport": "p1",
"password": "15d55ad283aa400af464c76d713c07ad",
"nickname": "n1",
}).OnConflict("id").Save()
t.AssertNil(err)
n, _ := result.RowsAffected()
t.Assert(n, 1)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 1)
t.Assert(user.Passport, "p1")
t.Assert(user.Password, "15d55ad283aa400af464c76d713c07ad")
t.Assert(user.NickName, "n1")
// Sleep 1 second to make sure the updated time is different.
time.Sleep(1 * time.Second)
_, err = db.Model(table).Data(g.Map{
"id": 1,
"passport": "p1",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "n2",
}).OnConflict("id").Save()
t.AssertNil(err)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.Passport, "p1")
t.Assert(user.Password, "25d55ad283aa400af464c76d713c07ad")
t.Assert(user.NickName, "n2")
// check created_at not equal to updated_at
t.AssertNE(user.CreatedAt, user.UpdatedAt)
count, err = db.Model(table).Count()
t.AssertNil(err)
t.Assert(count, 1)
})
}
func Test_Model_Replace(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
_, err := db.Model(table).Data(g.Map{
"id": 1,
"passport": "t11",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T11",
"create_time": "2018-10-24 10:00:00",
}).Replace()
t.Assert(err, "Replace operation is not supported by mssql driver")
})
}

View File

@ -4,7 +4,7 @@ go 1.18
require (
github.com/go-sql-driver/mysql v1.7.1
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/v2 v2.7.0
)
require (

View File

@ -14,6 +14,36 @@ import (
"github.com/gogf/gf/v2/util/gutil"
)
var (
tableFieldsSqlByMariadb = `
SELECT
c.COLUMN_NAME AS 'Field',
( CASE WHEN ch.CHECK_CLAUSE LIKE 'json_valid%%' THEN 'json' ELSE c.COLUMN_TYPE END ) AS 'Type',
c.COLLATION_NAME AS 'Collation',
c.IS_NULLABLE AS 'Null',
c.COLUMN_KEY AS 'Key',
( CASE WHEN c.COLUMN_DEFAULT = 'NULL' OR c.COLUMN_DEFAULT IS NULL THEN NULL ELSE c.COLUMN_DEFAULT END) AS 'Default',
c.EXTRA AS 'Extra',
c.PRIVILEGES AS 'Privileges',
c.COLUMN_COMMENT AS 'Comment'
FROM
information_schema.COLUMNS AS c
LEFT JOIN information_schema.CHECK_CONSTRAINTS AS ch ON c.TABLE_NAME = ch.TABLE_NAME
AND c.COLUMN_NAME = ch.CONSTRAINT_NAME
WHERE
c.TABLE_SCHEMA = '%s'
AND c.TABLE_NAME = '%s'
ORDER BY c.ORDINAL_POSITION`
)
func init() {
var err error
tableFieldsSqlByMariadb, err = gdb.FormatMultiLineSqlToSingle(tableFieldsSqlByMariadb)
if err != nil {
panic(err)
}
}
// TableFields retrieves and returns the fields' information of specified table of current
// schema.
//
@ -28,16 +58,25 @@ import (
// process restarts.
func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) {
var (
result gdb.Result
link gdb.Link
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
result gdb.Result
link gdb.Link
usedSchema = gutil.GetOrDefaultStr(d.GetSchema(), schema...)
tableFieldsSql string
)
if link, err = d.SlaveLink(usedSchema); err != nil {
return nil, err
}
dbType := d.GetConfig().Type
switch dbType {
case "mariadb":
tableFieldsSql = fmt.Sprintf(tableFieldsSqlByMariadb, usedSchema, table)
default:
tableFieldsSql = fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table))
}
result, err = d.DoSelect(
ctx, link,
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.QuoteWord(table)),
tableFieldsSql,
)
if err != nil {
return nil, err

View File

@ -127,7 +127,7 @@ func Test_DB_Insert(t *testing.T) {
Nickname string `gconv:"nickname"`
CreateTime string `json:"create_time"`
}
timeStr := gtime.Now().String()
timeStr := gtime.New("2024-10-01 12:01:01").String()
result, err = db.Insert(ctx, table, User{
Id: 3,
Passport: "user_3",
@ -149,7 +149,7 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["create_time"].GTime().String(), timeStr)
// *struct
timeStr = gtime.Now().String()
timeStr = gtime.New("2024-10-01 12:01:01").String()
result, err = db.Insert(ctx, table, &User{
Id: 4,
Passport: "t4",
@ -170,7 +170,7 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["create_time"].GTime().String(), timeStr)
// batch with Insert
timeStr = gtime.Now().String()
timeStr = gtime.New("2024-10-01 12:01:01").String()
r, err := db.Insert(ctx, table, g.Slice{
g.Map{
"id": 200,
@ -487,7 +487,7 @@ func Test_DB_Save(t *testing.T) {
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
timeStr := gtime.Now().String()
timeStr := gtime.New("2024-10-01 12:01:01").String()
_, err := db.Save(ctx, table, g.Map{
"id": 1,
"passport": "t1",
@ -512,7 +512,7 @@ func Test_DB_Replace(t *testing.T) {
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
timeStr := gtime.Now().String()
timeStr := gtime.New("2024-10-01 12:01:01").String()
_, err := db.Replace(ctx, table, g.Map{
"id": 1,
"passport": "t1",

View File

@ -4669,13 +4669,13 @@ func TestResult_Structs1(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
r := gdb.Result{
gdb.Record{"id": gvar.New(nil), "name": gvar.New("john")},
gdb.Record{"id": gvar.New(nil), "name": gvar.New("smith")},
gdb.Record{"id": gvar.New(1), "name": gvar.New("smith")},
}
array := make([]*B, 2)
err := r.Structs(&array)
t.AssertNil(err)
t.Assert(array[0].Id, 0)
t.Assert(array[1].Id, 0)
t.Assert(array[1].Id, 1)
t.Assert(array[0].Name, "john")
t.Assert(array[1].Name, "smith")
})

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/oracle/v2
go 1.18
require (
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/v2 v2.7.0
github.com/sijms/go-ora/v2 v2.7.10
)

View File

@ -13,8 +13,6 @@ package oracle
import (
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
// Driver is the driver for oracle database.
@ -32,21 +30,6 @@ func init() {
}
}
// formatSqlTmp formats sql template string into one line.
func formatSqlTmp(sqlTmp string) string {
var err error
// format sql template string.
sqlTmp, err = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(sqlTmp))
if err != nil {
panic(err)
}
sqlTmp, err = gregex.ReplaceString(`\s{2,}`, " ", gstr.Trim(sqlTmp))
if err != nil {
panic(err)
}
return sqlTmp
}
// New create and returns a driver that implements gdb.Driver, which supports operations for Oracle.
func New() gdb.Driver {
return &Driver{}

View File

@ -27,7 +27,11 @@ SELECT * FROM (
)
func init() {
newSqlReplacementTmp = formatSqlTmp(newSqlReplacementTmp)
var err error
newSqlReplacementTmp, err = gdb.FormatMultiLineSqlToSingle(newSqlReplacementTmp)
if err != nil {
panic(err)
}
}
// DoFilter deals with the sql string before commits it to underlying sql driver.

View File

@ -12,6 +12,9 @@ import (
"fmt"
"strings"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
@ -24,10 +27,7 @@ func (d *Driver) DoInsert(
) (result sql.Result, err error) {
switch option.InsertOption {
case gdb.InsertOptionSave:
return nil, gerror.NewCode(
gcode.CodeNotSupported,
`Save operation is not supported by oracle driver`,
)
return d.doSave(ctx, link, table, list, option)
case gdb.InsertOptionReplace:
return nil, gerror.NewCode(
@ -93,3 +93,116 @@ func (d *Driver) DoInsert(
}
return batchResult, nil
}
// doSave support upsert for Oracle
func (d *Driver) doSave(ctx context.Context,
link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
) (result sql.Result, err error) {
if len(option.OnConflict) == 0 {
return nil, gerror.NewCode(
gcode.CodeMissingParameter, `Please specify conflict columns`,
)
}
if len(list) == 0 {
return nil, gerror.NewCode(
gcode.CodeInvalidRequest, `Save operation list is empty by oracle driver`,
)
}
var (
one = list[0]
oneLen = len(one)
charL, charR = d.GetChars()
conflictKeys = option.OnConflict
conflictKeySet = gset.New(false)
// queryHolders: Handle data with Holder that need to be upsert
// queryValues: Handle data that need to be upsert
// insertKeys: Handle valid keys that need to be inserted
// insertValues: Handle values that need to be inserted
// updateValues: Handle values that need to be updated
queryHolders = make([]string, oneLen)
queryValues = make([]interface{}, oneLen)
insertKeys = make([]string, oneLen)
insertValues = make([]string, oneLen)
updateValues []string
)
// conflictKeys slice type conv to set type
for _, conflictKey := range conflictKeys {
conflictKeySet.Add(gstr.ToUpper(conflictKey))
}
index := 0
for key, value := range one {
keyWithChar := charL + key + charR
queryHolders[index] = fmt.Sprintf("? AS %s", keyWithChar)
queryValues[index] = value
insertKeys[index] = keyWithChar
insertValues[index] = fmt.Sprintf("T2.%s", keyWithChar)
// filter conflict keys in updateValues.
// And the key is not a soft created field.
if !(conflictKeySet.Contains(key) || d.Core.IsSoftCreatedFieldName(key)) {
updateValues = append(
updateValues,
fmt.Sprintf(`T1.%s = T2.%s`, keyWithChar, keyWithChar),
)
}
index++
}
batchResult := new(gdb.SqlResult)
sqlStr := parseSqlForUpsert(table, queryHolders, insertKeys, insertValues, updateValues, conflictKeys)
r, err := d.DoExec(ctx, link, sqlStr, queryValues...)
if err != nil {
return r, err
}
if n, err := r.RowsAffected(); err != nil {
return r, err
} else {
batchResult.Result = r
batchResult.Affected += n
}
return batchResult, nil
}
// parseSqlForUpsert
// MERGE INTO {{table}} T1
// USING ( SELECT {{queryHolders}} FROM DUAL T2
// ON (T1.{{duplicateKey}} = T2.{{duplicateKey}} AND ...)
// WHEN NOT MATCHED THEN
// INSERT {{insertKeys}} VALUES {{insertValues}}
// WHEN MATCHED THEN
// UPDATE SET {{updateValues}}
func parseSqlForUpsert(table string,
queryHolders, insertKeys, insertValues, updateValues, duplicateKey []string,
) (sqlStr string) {
var (
queryHolderStr = strings.Join(queryHolders, ",")
insertKeyStr = strings.Join(insertKeys, ",")
insertValueStr = strings.Join(insertValues, ",")
updateValueStr = strings.Join(updateValues, ",")
duplicateKeyStr string
pattern = gstr.Trim(`MERGE INTO %s T1 USING (SELECT %s FROM DUAL) T2 ON (%s) WHEN NOT MATCHED THEN INSERT(%s) VALUES (%s) WHEN MATCHED THEN UPDATE SET %s`)
)
for index, keys := range duplicateKey {
if index != 0 {
duplicateKeyStr += " AND "
}
duplicateTmp := fmt.Sprintf("T1.%s = T2.%s", keys, keys)
duplicateKeyStr += duplicateTmp
}
return fmt.Sprintf(pattern,
table,
queryHolderStr,
duplicateKeyStr,
insertKeyStr,
insertValueStr,
updateValueStr,
)
}

View File

@ -29,7 +29,11 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID
)
func init() {
tableFieldsSqlTmp = formatSqlTmp(tableFieldsSqlTmp)
var err error
tableFieldsSqlTmp, err = gdb.FormatMultiLineSqlToSingle(tableFieldsSqlTmp)
if err != nil {
panic(err)
}
}
// TableFields retrieves and returns the fields' information of specified table of current schema.

View File

@ -19,7 +19,7 @@ import (
"github.com/gogf/gf/v2/test/gtest"
)
func TestTables(t *testing.T) {
func Test_Tables(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
tables := []string{"t_user1", "pop", "haha"}
@ -60,7 +60,7 @@ func TestTables(t *testing.T) {
})
}
func TestTableFields(t *testing.T) {
func Test_Table_Fields(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
createTable("t_user")
defer dropTable("t_user")
@ -107,7 +107,7 @@ func TestTableFields(t *testing.T) {
})
}
func TestDoInsert(t *testing.T) {
func Test_Do_Insert(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
createTable("t_user")
defer dropTable("t_user")
@ -219,7 +219,7 @@ func Test_DB_Insert(t *testing.T) {
Salary float64 `gconv:"SALARY"`
CreateTime string `json:"CREATE_TIME"`
}
timeStr := gtime.Now().String()
timeStr := gtime.New("2024-10-01 12:01:01").String()
result, err = db.Insert(ctx, table, User{
Id: 3,
Passport: "user_3",
@ -243,7 +243,7 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["CREATE_TIME"].GTime().String(), timeStr)
// *struct
timeStr = gtime.Now().String()
timeStr = gtime.New("2024-10-01 12:01:01").String()
result, err = db.Insert(ctx, table, &User{
Id: 4,
Passport: "t4",
@ -266,7 +266,7 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["CREATE_TIME"].GTime().String(), timeStr)
// batch with Insert
timeStr = gtime.Now().String()
timeStr = gtime.New("2024-10-01 12:01:01").String()
r, err := db.Insert(ctx, table, g.Slice{
g.Map{
"ID": 200,

View File

@ -126,7 +126,7 @@ func createTable(table ...string) (name string) {
gtest.Fatal(err)
}
//db.Schema("test")
// db.Schema("test")
return
}

View File

@ -128,7 +128,7 @@ func Test_Model_RightJoin(t *testing.T) {
})
}
func TestPage(t *testing.T) {
func Test_Page(t *testing.T) {
table := createInitTable()
defer dropTable(table)
result, err := db.Model(table).Page(1, 2).Order("ID").All()
@ -162,7 +162,6 @@ func TestPage(t *testing.T) {
func Test_Model_Insert(t *testing.T) {
table := createTable()
defer dropTable(table)
// db.SetDebug(true)
gtest.C(t, func(t *gtest.T) {
user := db.Model(table)
result, err := user.Data(g.Map{
@ -1101,6 +1100,83 @@ func Test_Model_WhereOrNotLike(t *testing.T) {
})
}
func Test_Model_Save(t *testing.T) {
table := createTable("test")
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var (
user User
count int
result sql.Result
createTime = gtime.Now().Format("Y-m-d")
err error
)
result, err = db.Model(table).Data(g.Map{
"id": 1,
"passport": "p1",
"password": "15d55ad283aa400af464c76d713c07ad",
"nickname": "n1",
"create_time": createTime,
}).OnConflict("id").Save()
t.AssertNil(err)
n, _ := result.RowsAffected()
t.Assert(n, 1)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 1)
t.Assert(user.Passport, "p1")
t.Assert(user.Password, "15d55ad283aa400af464c76d713c07ad")
t.Assert(user.NickName, "n1")
t.Assert(user.CreateTime.Format("Y-m-d"), createTime)
_, err = db.Model(table).Data(g.Map{
"id": 1,
"passport": "p1",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "n2",
"create_time": createTime,
}).OnConflict("id").Save()
t.AssertNil(err)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.Passport, "p1")
t.Assert(user.Password, "25d55ad283aa400af464c76d713c07ad")
t.Assert(user.NickName, "n2")
t.Assert(user.CreateTime.Format("Y-m-d"), createTime)
count, err = db.Model(table).Count()
t.AssertNil(err)
t.Assert(count, 1)
})
}
func Test_Model_Replace(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
_, err := db.Model(table).Data(g.Map{
"id": 1,
"passport": "t11",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T11",
"create_time": "2018-10-24 10:00:00",
}).Replace()
t.Assert(err, "Replace operation is not supported by oracle driver")
})
}
/* not support the "AS"
func Test_Model_Raw(t *testing.T) {
table := createInitTable()

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/drivers/pgsql/v2
go 1.18
require (
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/v2 v2.7.0
github.com/lib/pq v1.10.9
)

View File

@ -7,7 +7,7 @@
// Package pgsql implements gdb.Driver, which supports operations for database PostgreSQL.
//
// Note:
// 1. It does not support Save/Replace features.
// 1. It does not support Replace features.
// 2. It does not support Insert Ignore features.
package pgsql
@ -16,8 +16,6 @@ import (
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
// Driver is the driver for postgresql database.
@ -37,21 +35,6 @@ func init() {
}
}
// formatSqlTmp formats sql template string into one line.
func formatSqlTmp(sqlTmp string) string {
var err error
// format sql template string.
sqlTmp, err = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(sqlTmp))
if err != nil {
panic(err)
}
sqlTmp, err = gregex.ReplaceString(`\s{2,}`, " ", gstr.Trim(sqlTmp))
if err != nil {
panic(err)
}
return sqlTmp
}
// New create and returns a driver that implements gdb.Driver, which supports operations for PostgreSql.
func New() gdb.Driver {
return &Driver{}

View File

@ -15,15 +15,9 @@ import (
"github.com/gogf/gf/v2/errors/gerror"
)
// DoInsert inserts or updates data forF given table.
// DoInsert inserts or updates data for given table.
func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) {
switch option.InsertOption {
case gdb.InsertOptionSave:
return nil, gerror.NewCode(
gcode.CodeNotSupported,
`Save operation is not supported by pgsql driver`,
)
case gdb.InsertOptionReplace:
return nil, gerror.NewCode(
gcode.CodeNotSupported,

View File

@ -0,0 +1,71 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package pgsql
import (
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
// FormatUpsert returns SQL clause of type upsert for PgSQL.
// For example: ON CONFLICT (id) DO UPDATE SET ...
func (d *Driver) FormatUpsert(columns []string, list gdb.List, option gdb.DoInsertOption) (string, error) {
if len(option.OnConflict) == 0 {
return "", gerror.NewCode(
gcode.CodeMissingParameter, `Please specify conflict columns`,
)
}
var onDuplicateStr string
if option.OnDuplicateStr != "" {
onDuplicateStr = option.OnDuplicateStr
} else if len(option.OnDuplicateMap) > 0 {
for k, v := range option.OnDuplicateMap {
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
switch v.(type) {
case gdb.Raw, *gdb.Raw:
onDuplicateStr += fmt.Sprintf(
"%s=%s",
d.Core.QuoteWord(k),
v,
)
default:
onDuplicateStr += fmt.Sprintf(
"%s=EXCLUDED.%s",
d.Core.QuoteWord(k),
d.Core.QuoteWord(gconv.String(v)),
)
}
}
} else {
for _, column := range columns {
// If it's SAVE operation, do not automatically update the creating time.
if d.Core.IsSoftCreatedFieldName(column) {
continue
}
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
onDuplicateStr += fmt.Sprintf(
"%s=EXCLUDED.%s",
d.Core.QuoteWord(column),
d.Core.QuoteWord(column),
)
}
}
conflictKeys := gstr.Join(option.OnConflict, ",")
return fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET ", conflictKeys) + onDuplicateStr, nil
}

View File

@ -17,7 +17,7 @@ import (
var (
tableFieldsSqlTmp = `
SELECT a.attname AS field, t.typname AS type,a.attnotnull as null,
(case when d.contype is not null then 'pri' else '' end) as key
(case when d.contype = 'p' then 'pri' when d.contype = 'u' then 'uni' else '' end) as key
,ic.column_default as default_value,b.description as comment
,coalesce(character_maximum_length, numeric_precision, -1) as length
,numeric_scale as scale
@ -32,7 +32,11 @@ ORDER BY a.attnum`
)
func init() {
tableFieldsSqlTmp = formatSqlTmp(tableFieldsSqlTmp)
var err error
tableFieldsSqlTmp, err = gdb.FormatMultiLineSqlToSingle(tableFieldsSqlTmp)
if err != nil {
panic(err)
}
}
// TableFields retrieves and returns the fields' information of specified table of current schema.

View File

@ -35,7 +35,11 @@ ORDER BY
)
func init() {
tablesSqlTmp = formatSqlTmp(tablesSqlTmp)
var err error
tablesSqlTmp, err = gdb.FormatMultiLineSqlToSingle(tablesSqlTmp)
if err != nil {
panic(err)
}
}
// Tables retrieves and returns the tables of current schema.

View File

@ -78,12 +78,12 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
if _, err := db.Exec(ctx, fmt.Sprintf(`
CREATE TABLE %s (
id bigserial NOT NULL,
passport varchar(45) NOT NULL,
password varchar(32) NOT NULL,
nickname varchar(45) NOT NULL,
create_time timestamp NOT NULL,
PRIMARY KEY (id)
id bigserial NOT NULL,
passport varchar(45) NOT NULL,
password varchar(32) NOT NULL,
nickname varchar(45) NOT NULL,
create_time timestamp NOT NULL,
PRIMARY KEY (id)
) ;`, name,
)); err != nil {
gtest.Fatal(err)

View File

@ -0,0 +1,75 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package pgsql_test
import (
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"testing"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/test/gtest"
)
// https://github.com/gogf/gf/issues/3330
func Test_Issue3330(t *testing.T) {
var (
table = fmt.Sprintf(`%s_%d`, TablePrefix+"test", gtime.TimestampNano())
uniqueName = fmt.Sprintf(`%s_%d`, TablePrefix+"test_unique", gtime.TimestampNano())
)
if _, err := db.Exec(ctx, fmt.Sprintf(`
CREATE TABLE %s (
id bigserial NOT NULL,
passport varchar(45) NOT NULL,
password varchar(32) NOT NULL,
nickname varchar(45) NOT NULL,
create_time timestamp NOT NULL,
PRIMARY KEY (id),
CONSTRAINT %s unique ("password")
) ;`, table, uniqueName,
)); err != nil {
gtest.Fatal(err)
}
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
var (
list []map[string]interface{}
one gdb.Record
err error
)
fields, err := db.TableFields(ctx, table)
t.AssertNil(err)
t.Assert(fields["id"].Key, "pri")
t.Assert(fields["password"].Key, "uni")
for i := 1; i <= 10; i++ {
list = append(list, g.Map{
"id": i,
"passport": fmt.Sprintf("p%d", i),
"password": fmt.Sprintf("pw%d", i),
"nickname": fmt.Sprintf("n%d", i),
"create_time": "2016-06-01 00:00:00",
})
}
_, err = db.Model(table).Data(list).Insert()
t.AssertNil(err)
for i := 1; i <= 10; i++ {
one, err = db.Model(table).WherePri(i).One()
t.AssertNil(err)
t.Assert(one["id"], list[i-1]["id"])
t.Assert(one["passport"], list[i-1]["passport"])
t.Assert(one["password"], list[i-1]["password"])
t.Assert(one["nickname"], list[i-1]["nickname"])
}
})
}

View File

@ -7,8 +7,11 @@
package pgsql_test
import (
"database/sql"
"fmt"
"testing"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/test/gtest"
@ -258,14 +261,58 @@ func Test_Model_Save(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
_, err := db.Model(table).Data(g.Map{
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var (
user User
count int
result sql.Result
err error
)
result, err = db.Model(table).Data(g.Map{
"id": 1,
"passport": "t111",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T111",
"create_time": "2018-10-24 10:00:00",
}).Save()
t.Assert(err, "Save operation is not supported by pgsql driver")
"passport": "p1",
"password": "pw1",
"nickname": "n1",
"create_time": CreateTime,
}).OnConflict("id").Save()
t.AssertNil(err)
n, _ := result.RowsAffected()
t.Assert(n, 1)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 1)
t.Assert(user.Passport, "p1")
t.Assert(user.Password, "pw1")
t.Assert(user.NickName, "n1")
t.Assert(user.CreateTime.String(), CreateTime)
_, err = db.Model(table).Data(g.Map{
"id": 1,
"passport": "p1",
"password": "pw2",
"nickname": "n2",
"create_time": CreateTime,
}).OnConflict("id").Save()
t.AssertNil(err)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.Passport, "p1")
t.Assert(user.Password, "pw2")
t.Assert(user.NickName, "n2")
t.Assert(user.CreateTime.String(), CreateTime)
count, err = db.Model(table).Count()
t.AssertNil(err)
t.Assert(count, 1)
})
}
@ -284,3 +331,259 @@ func Test_Model_Replace(t *testing.T) {
t.Assert(err, "Replace operation is not supported by pgsql driver")
})
}
func Test_Model_OnConflict(t *testing.T) {
var (
table = fmt.Sprintf(`%s_%d`, TablePrefix+"test", gtime.TimestampNano())
uniqueName = fmt.Sprintf(`%s_%d`, TablePrefix+"test_unique", gtime.TimestampNano())
)
if _, err := db.Exec(ctx, fmt.Sprintf(`
CREATE TABLE %s (
id bigserial NOT NULL,
passport varchar(45) NOT NULL,
password varchar(32) NOT NULL,
nickname varchar(45) NOT NULL,
create_time timestamp NOT NULL,
PRIMARY KEY (id),
CONSTRAINT %s UNIQUE ("passport", "password")
) ;`, table, uniqueName,
)); err != nil {
gtest.Fatal(err)
}
defer dropTable(table)
// string type 1.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnConflict("passport,password").Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).Where("id", 1).One()
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "n1")
})
// string type 2.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnConflict("passport", "password").Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).Where("id", 1).One()
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "n1")
})
// slice.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnConflict(g.Slice{"passport", "password"}).Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).Where("id", 1).One()
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "n1")
})
}
func Test_Model_OnDuplicate(t *testing.T) {
table := createInitTable()
defer dropTable(table)
// string type 1.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnConflict("id").OnDuplicate("passport,password").Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "name_1")
})
// string type 2.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnConflict("id").OnDuplicate("passport", "password").Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "name_1")
})
// slice.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnConflict("id").OnDuplicate(g.Slice{"passport", "password"}).Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "name_1")
})
// map.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnConflict("id").OnDuplicate(g.Map{
"passport": "nickname",
"password": "nickname",
}).Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(one["passport"], data["nickname"])
t.Assert(one["password"], data["nickname"])
t.Assert(one["nickname"], "name_1")
})
// map+raw.
gtest.C(t, func(t *gtest.T) {
data := g.MapStrStr{
"id": "1",
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnConflict("id").OnDuplicate(g.Map{
"passport": gdb.Raw("CONCAT(EXCLUDED.passport, '1')"),
"password": gdb.Raw("CONCAT(EXCLUDED.password, '2')"),
}).Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(one["passport"], data["passport"]+"1")
t.Assert(one["password"], data["password"]+"2")
t.Assert(one["nickname"], "name_1")
})
}
func Test_Model_OnDuplicateEx(t *testing.T) {
table := createInitTable()
defer dropTable(table)
// string type 1.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnConflict("id").OnDuplicateEx("nickname,create_time").Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "name_1")
})
// string type 2.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnConflict("id").OnDuplicateEx("nickname", "create_time").Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "name_1")
})
// slice.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnConflict("id").OnDuplicateEx(g.Slice{"nickname", "create_time"}).Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "name_1")
})
// map.
gtest.C(t, func(t *gtest.T) {
data := g.Map{
"id": 1,
"passport": "pp1",
"password": "pw1",
"nickname": "n1",
"create_time": "2016-06-06",
}
_, err := db.Model(table).OnConflict("id").OnDuplicateEx(g.Map{
"nickname": "nickname",
"create_time": "nickname",
}).Data(data).Save()
t.AssertNil(err)
one, err := db.Model(table).WherePri(1).One()
t.AssertNil(err)
t.Assert(one["passport"], data["passport"])
t.Assert(one["password"], data["password"])
t.Assert(one["nickname"], "name_1")
})
}

View File

@ -37,12 +37,12 @@ func Test_LastInsertId(t *testing.T) {
{"passport": "user2", "password": "pwd", "nickname": "nickname", "create_time": CreateTime},
{"passport": "user3", "password": "pwd", "nickname": "nickname", "create_time": CreateTime},
})
t.Assert(err, nil)
t.AssertNil(err)
lastInsertId, err := res.LastInsertId()
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(lastInsertId, int64(3))
rowsAffected, err := res.RowsAffected()
t.Assert(err, nil)
t.AssertNil(err)
t.Assert(rowsAffected, int64(3))
})
}
@ -58,29 +58,29 @@ func Test_TxLastInsertId(t *testing.T) {
{"passport": "user2", "password": "pwd", "nickname": "nickname", "create_time": CreateTime},
{"passport": "user3", "password": "pwd", "nickname": "nickname", "create_time": CreateTime},
})
t.Assert(err, nil)
t.AssertNil(err)
lastInsertId, err := res.LastInsertId()
t.Assert(err, nil)
t.AssertNil(err)
t.AssertEQ(lastInsertId, int64(3))
rowsAffected, err := res.RowsAffected()
t.Assert(err, nil)
t.AssertNil(err)
t.AssertEQ(rowsAffected, int64(3))
res1, err := tx.Model(tableName).Insert(g.List{
{"passport": "user4", "password": "pwd", "nickname": "nickname", "create_time": CreateTime},
{"passport": "user5", "password": "pwd", "nickname": "nickname", "create_time": CreateTime},
})
t.Assert(err, nil)
t.AssertNil(err)
lastInsertId1, err := res1.LastInsertId()
t.Assert(err, nil)
t.AssertNil(err)
t.AssertEQ(lastInsertId1, int64(5))
rowsAffected1, err := res1.RowsAffected()
t.Assert(err, nil)
t.AssertNil(err)
t.AssertEQ(rowsAffected1, int64(2))
return nil
})
t.Assert(err, nil)
t.AssertNil(err)
})
}

View File

@ -4,7 +4,7 @@ go 1.18
require (
github.com/glebarez/go-sqlite v1.21.2
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/v2 v2.7.0
)
require (

View File

@ -5,9 +5,6 @@
// You can obtain one at https://github.com/gogf/gf.
// Package sqlite implements gdb.Driver, which supports operations for database SQLite.
//
// Note:
// 1. It does not support Save features.
package sqlite
import (

View File

@ -10,8 +10,6 @@ import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/text/gstr"
)
@ -24,14 +22,6 @@ func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args [
case gstr.HasPrefix(sql, gdb.InsertOperationReplace):
sql = "INSERT OR REPLACE" + sql[len(gdb.InsertOperationReplace):]
default:
if gstr.Contains(sql, gdb.InsertOnDuplicateKeyUpdate) {
return sql, args, gerror.NewCode(
gcode.CodeNotSupported,
`Save operation is not supported by sqlite driver`,
)
}
}
return d.Core.DoFilter(ctx, link, sql, args)
}

View File

@ -0,0 +1,71 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package sqlite
import (
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
// FormatUpsert returns SQL clause of type upsert for SQLite.
// For example: ON CONFLICT (id) DO UPDATE SET ...
func (d *Driver) FormatUpsert(columns []string, list gdb.List, option gdb.DoInsertOption) (string, error) {
if len(option.OnConflict) == 0 {
return "", gerror.NewCode(
gcode.CodeMissingParameter, `Please specify conflict columns`,
)
}
var onDuplicateStr string
if option.OnDuplicateStr != "" {
onDuplicateStr = option.OnDuplicateStr
} else if len(option.OnDuplicateMap) > 0 {
for k, v := range option.OnDuplicateMap {
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
switch v.(type) {
case gdb.Raw, *gdb.Raw:
onDuplicateStr += fmt.Sprintf(
"%s=%s",
d.Core.QuoteWord(k),
v,
)
default:
onDuplicateStr += fmt.Sprintf(
"%s=EXCLUDED.%s",
d.Core.QuoteWord(k),
d.Core.QuoteWord(gconv.String(v)),
)
}
}
} else {
for _, column := range columns {
// If it's SAVE operation, do not automatically update the creating time.
if d.Core.IsSoftCreatedFieldName(column) {
continue
}
if len(onDuplicateStr) > 0 {
onDuplicateStr += ","
}
onDuplicateStr += fmt.Sprintf(
"%s=EXCLUDED.%s",
d.Core.QuoteWord(column),
d.Core.QuoteWord(column),
)
}
}
conflictKeys := gstr.Join(option.OnConflict, ",")
return fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET ", conflictKeys) + onDuplicateStr, nil
}

View File

@ -140,7 +140,7 @@ func Test_DB_Insert(t *testing.T) {
Nickname string `gconv:"nickname"`
CreateTime string `json:"create_time"`
}
timeStr := gtime.Now().String()
timeStr := gtime.New("2024-10-01 12:01:01").String()
result, err = db.Insert(ctx, table, User{
Id: 3,
Passport: "user_3",
@ -162,7 +162,7 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["create_time"].GTime().String(), timeStr)
// *struct
timeStr = gtime.Now().String()
timeStr = gtime.New("2024-10-01 12:01:01").String()
result, err = db.Insert(ctx, table, &User{
Id: 4,
Passport: "t4",
@ -183,7 +183,7 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["create_time"].GTime().String(), timeStr)
// batch with Insert
timeStr = gtime.Now().String()
timeStr = gtime.New("2024-10-01 12:01:01").String()
r, err := db.Insert(ctx, table, g.Slice{
g.Map{
"id": 200,
@ -425,19 +425,22 @@ func Test_DB_BatchInsert_Struct(t *testing.T) {
}
func Test_DB_Save(t *testing.T) {
table := createInitTable()
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
timeStr := gtime.Now().String()
_, err := db.Save(ctx, table, g.Map{
"id": 1,
"passport": "t1",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T11",
"create_time": timeStr,
})
t.Assert(err, ErrorSave)
createTable("t_user")
defer dropTable("t_user")
i := 10
data := g.Map{
"id": i,
"passport": fmt.Sprintf(`t%d`, i),
"password": fmt.Sprintf(`p%d`, i),
"nickname": fmt.Sprintf(`T%d`, i),
"create_time": gtime.Now().String(),
}
_, err := db.Save(ctx, "t_user", data, 10)
gtest.AssertNE(err, nil)
})
}
@ -446,7 +449,7 @@ func Test_DB_Replace(t *testing.T) {
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
timeStr := gtime.Now().String()
timeStr := gtime.New("2024-10-01 12:01:01").String()
_, err := db.Replace(ctx, table, g.Map{
"id": 1,
"passport": "t1",

View File

@ -11,8 +11,6 @@ import (
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gfile"
@ -27,9 +25,6 @@ var (
configNode gdb.ConfigNode
dbDir = gfile.Temp("sqlite")
ctx = gctx.New()
// Error
ErrorSave = gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by sqlite driver`)
)
const (

View File

@ -364,14 +364,58 @@ func Test_Model_Save(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
_, err := db.Model(table).Data(g.Map{
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var (
user User
count int
result sql.Result
err error
)
result, err = db.Model(table).Data(g.Map{
"id": 1,
"passport": "t111",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T111",
"passport": "CN",
"password": "12345678",
"nickname": "oldme",
"create_time": CreateTime,
}).Save()
t.Assert(err, ErrorSave)
}).OnConflict("id").Save()
t.AssertNil(err)
n, _ := result.RowsAffected()
t.Assert(n, 1)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 1)
t.Assert(user.Passport, "CN")
t.Assert(user.Password, "12345678")
t.Assert(user.NickName, "oldme")
t.Assert(user.CreateTime.String(), CreateTime)
_, err = db.Model(table).Data(g.Map{
"id": 1,
"passport": "CN",
"password": "abc123456",
"nickname": "to be not to be",
"create_time": CreateTime,
}).OnConflict("id").Save()
t.AssertNil(err)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.Passport, "CN")
t.Assert(user.Password, "abc123456")
t.Assert(user.NickName, "to be not to be")
t.Assert(user.CreateTime.String(), CreateTime)
count, err = db.Model(table).Count()
t.AssertNil(err)
t.Assert(count, 1)
})
}

View File

@ -3,8 +3,8 @@ module github.com/gogf/gf/contrib/drivers/sqlitecgo/v2
go 1.18
require (
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.6.3
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.7.0
github.com/gogf/gf/v2 v2.7.0
github.com/mattn/go-sqlite3 v1.14.17
)

View File

@ -140,7 +140,7 @@ func Test_DB_Insert(t *testing.T) {
Nickname string `gconv:"nickname"`
CreateTime string `json:"create_time"`
}
timeStr := gtime.Now().String()
timeStr := gtime.New("2024-10-01 12:01:01").String()
result, err = db.Insert(ctx, table, User{
Id: 3,
Passport: "user_3",
@ -162,7 +162,7 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["create_time"].GTime().String(), timeStr)
// *struct
timeStr = gtime.Now().String()
timeStr = gtime.New("2024-10-01 12:01:01").String()
result, err = db.Insert(ctx, table, &User{
Id: 4,
Passport: "t4",
@ -183,7 +183,7 @@ func Test_DB_Insert(t *testing.T) {
t.Assert(one["create_time"].GTime().String(), timeStr)
// batch with Insert
timeStr = gtime.Now().String()
timeStr = gtime.New("2024-10-01 12:01:01").String()
r, err := db.Insert(ctx, table, g.Slice{
g.Map{
"id": 200,
@ -425,19 +425,22 @@ func Test_DB_BatchInsert_Struct(t *testing.T) {
}
func Test_DB_Save(t *testing.T) {
table := createInitTable()
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
timeStr := gtime.Now().String()
_, err := db.Save(ctx, table, g.Map{
"id": 1,
"passport": "t1",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T11",
"create_time": timeStr,
})
t.Assert(err, ErrorSave)
createTable("t_user")
defer dropTable("t_user")
i := 10
data := g.Map{
"id": i,
"passport": fmt.Sprintf(`t%d`, i),
"password": fmt.Sprintf(`p%d`, i),
"nickname": fmt.Sprintf(`T%d`, i),
"create_time": gtime.Now().String(),
}
_, err := db.Save(ctx, "t_user", data, 10)
gtest.AssertNE(err, nil)
})
}
@ -446,7 +449,7 @@ func Test_DB_Replace(t *testing.T) {
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
timeStr := gtime.Now().String()
timeStr := gtime.New("2024-10-01 12:01:01").String()
_, err := db.Replace(ctx, table, g.Map{
"id": 1,
"passport": "t1",

View File

@ -364,14 +364,58 @@ func Test_Model_Save(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
_, err := db.Model(table).Data(g.Map{
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var (
user User
count int
result sql.Result
err error
)
result, err = db.Model(table).Data(g.Map{
"id": 1,
"passport": "t111",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T111",
"passport": "CN",
"password": "12345678",
"nickname": "oldme",
"create_time": CreateTime,
}).Save()
t.Assert(err, ErrorSave)
}).OnConflict("id").Save()
t.AssertNil(err)
n, _ := result.RowsAffected()
t.Assert(n, 1)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.Id, 1)
t.Assert(user.Passport, "CN")
t.Assert(user.Password, "12345678")
t.Assert(user.NickName, "oldme")
t.Assert(user.CreateTime.String(), CreateTime)
_, err = db.Model(table).Data(g.Map{
"id": 1,
"passport": "CN",
"password": "abc123456",
"nickname": "to be not to be",
"create_time": CreateTime,
}).OnConflict("id").Save()
t.AssertNil(err)
err = db.Model(table).Scan(&user)
t.AssertNil(err)
t.Assert(user.Passport, "CN")
t.Assert(user.Password, "abc123456")
t.Assert(user.NickName, "to be not to be")
t.Assert(user.CreateTime.String(), CreateTime)
count, err = db.Model(table).Count()
t.AssertNil(err)
t.Assert(count, 1)
})
}

View File

@ -0,0 +1,160 @@
# GoFrame Metric In OpenTelemetry
## Installation
```
go get -u -v github.com/gogf/gf/contrib/metric/otelmetric/v2
```
suggested using `go.mod`:
```
require github.com/gogf/gf/contrib/metric/otelmetric/v2 latest
```
## Example
### [basic](../../../example/metric/basic/main.go)
```go
package main
import (
"context"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/otel/exporters/prometheus"
"go.opentelemetry.io/otel/sdk/metric"
"github.com/gogf/gf/contrib/metric/otelmetric/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gmetric"
)
var (
meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{
Instrument: "github.com/gogf/gf/example/metric/basic",
InstrumentVersion: "v1.0",
})
counter = meter.MustCounter(
"goframe.metric.demo.counter",
gmetric.MetricOption{
Help: "This is a simple demo for Counter usage",
Unit: "bytes",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_1", 1),
},
},
)
upDownCounter = meter.MustUpDownCounter(
"goframe.metric.demo.updown_counter",
gmetric.MetricOption{
Help: "This is a simple demo for UpDownCounter usage",
Unit: "%",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_2", 2),
},
},
)
histogram = meter.MustHistogram(
"goframe.metric.demo.histogram",
gmetric.MetricOption{
Help: "This is a simple demo for histogram usage",
Unit: "ms",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_3", 3),
},
Buckets: []float64{0, 10, 20, 50, 100, 500, 1000, 2000, 5000, 10000},
},
)
observableCounter = meter.MustObservableCounter(
"goframe.metric.demo.observable_counter",
gmetric.MetricOption{
Help: "This is a simple demo for ObservableCounter usage",
Unit: "%",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_4", 4),
},
},
)
observableUpDownCounter = meter.MustObservableUpDownCounter(
"goframe.metric.demo.observable_updown_counter",
gmetric.MetricOption{
Help: "This is a simple demo for ObservableUpDownCounter usage",
Unit: "%",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_5", 5),
},
},
)
observableGauge = meter.MustObservableGauge(
"goframe.metric.demo.observable_gauge",
gmetric.MetricOption{
Help: "This is a simple demo for ObservableGauge usage",
Unit: "%",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_6", 6),
},
},
)
)
func main() {
var ctx = gctx.New()
// Callback for observable metrics.
meter.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error {
obs.Observe(observableCounter, 10)
obs.Observe(observableUpDownCounter, 20)
obs.Observe(observableGauge, 30)
return nil
}, observableCounter, observableUpDownCounter, observableGauge)
// Prometheus exporter to export metrics as Prometheus format.
exporter, err := prometheus.New(
prometheus.WithoutCounterSuffixes(),
prometheus.WithoutUnits(),
)
if err != nil {
g.Log().Fatal(ctx, err)
}
// OpenTelemetry provider.
provider := otelmetric.MustProvider(metric.WithReader(exporter))
provider.SetAsGlobal()
defer provider.Shutdown(ctx)
// Counter.
counter.Inc(ctx)
counter.Add(ctx, 10)
// UpDownCounter.
upDownCounter.Inc(ctx)
upDownCounter.Add(ctx, 10)
upDownCounter.Dec(ctx)
// Record values for histogram.
histogram.Record(1)
histogram.Record(20)
histogram.Record(30)
histogram.Record(101)
histogram.Record(2000)
histogram.Record(9000)
histogram.Record(20000)
// HTTP Server for metrics exporting.
s := g.Server()
s.BindHandler("/metrics", ghttp.WrapH(promhttp.Handler()))
s.SetPort(8000)
s.Run()
}
```
### [more examples](../../../example/metric/)
## License
`GoFrame Polaris` is licensed under the [MIT License](../../../LICENSE), 100% free and open-source, forever.

View File

@ -0,0 +1,44 @@
module github.com/gogf/gf/contrib/metric/otelmetric/v2
go 1.18
require (
github.com/gogf/gf/v2 v2.7.0
github.com/prometheus/client_golang v1.19.0
go.opentelemetry.io/contrib/instrumentation/runtime v0.49.0
go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/exporters/prometheus v0.46.0
go.opentelemetry.io/otel/metric v1.24.0
go.opentelemetry.io/otel/sdk v1.24.0
go.opentelemetry.io/otel/sdk/metric v1.24.0
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grokify/html-strip-tags-go v0.0.1 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace github.com/gogf/gf/v2 => ../../../

View File

@ -0,0 +1,75 @@
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
go.opentelemetry.io/contrib/instrumentation/runtime v0.49.0 h1:dg9y+7ArpumB6zwImJv47RHfdgOGQ1EMkzP5vLkEnTU=
go.opentelemetry.io/contrib/instrumentation/runtime v0.49.0/go.mod h1:Ul4MtXqu/hJBM+v7a6dCF0nHwckPMLpIpLeCi4+zfdw=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/prometheus v0.46.0 h1:I8WIFXR351FoLJYuloU4EgXbtNX2URfU/85pUPheIEQ=
go.opentelemetry.io/otel/exporters/prometheus v0.46.0/go.mod h1:ztwVUHe5DTR/1v7PeuGRnU5Bbd4QKYwApWmuutKsJSs=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8=
go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -0,0 +1,31 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package otelmetric provides metric functionalities using OpenTelemetry metric.
package otelmetric
import (
"github.com/gogf/gf/v2/os/gmetric"
)
// NewProvider creates and returns a metrics provider.
func NewProvider(option ...Option) (gmetric.Provider, error) {
provider, err := newProvider(option...)
if err != nil {
return nil, err
}
return provider, nil
}
// MustProvider creates and returns a metrics provider.
// It panics if any error occurs.
func MustProvider(option ...Option) gmetric.Provider {
provider, err := NewProvider(option...)
if err != nil {
panic(err)
}
return provider
}

View File

@ -0,0 +1,53 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric
import (
"go.opentelemetry.io/otel/metric"
"github.com/gogf/gf/v2/os/gmetric"
)
// localObserver implements interface gmetric.Observer.
type localObserver struct {
metric.Observer
gmetric.MeterOption
}
// newObserver creates and returns gmetric.Observer.
func newObserver(observer metric.Observer, meterOption gmetric.MeterOption) gmetric.Observer {
return &localObserver{
Observer: observer,
MeterOption: meterOption,
}
}
// Observe observes the value for certain initialized Metric.
// It adds the value to total result if the observed Metrics is type of Counter.
// It sets the value as the result if the observed Metrics is type of Gauge.
func (l *localObserver) Observe(om gmetric.ObservableMetric, value float64, option ...gmetric.Option) {
var (
m = om.(gmetric.Metric)
constOption = getConstOptionByMetric(l.MeterOption, m)
dynamicOption = getDynamicOptionByMetricOption(option...)
globalAttributesOption = getGlobalAttributesOption(gmetric.GetGlobalAttributesOption{
Instrument: m.Info().Instrument().Name(),
InstrumentVersion: m.Info().Instrument().Version(),
})
observeOptions = make([]metric.ObserveOption, 0)
)
if globalAttributesOption != nil {
observeOptions = append(observeOptions, globalAttributesOption)
}
if constOption != nil {
observeOptions = append(observeOptions, constOption)
}
if dynamicOption != nil {
observeOptions = append(observeOptions, dynamicOption)
}
l.Observer.ObserveFloat64(metricToFloat64Observable(m), value, observeOptions...)
}

View File

@ -0,0 +1,65 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric
import (
"context"
"go.opentelemetry.io/otel/metric"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gmetric"
)
// localCounterPerformer is an implementer for interface gmetric.CounterPerformer.
type localCounterPerformer struct {
gmetric.MeterOption
gmetric.MetricOption
metric.Float64Counter
constOption metric.MeasurementOption
}
// newCounterPerformer creates and returns a CounterPerformer that truly takes action to implement Counter.
func (l *localMeterPerformer) newCounterPerformer(
meter metric.Meter,
metricName string,
metricOption gmetric.MetricOption,
) (gmetric.CounterPerformer, error) {
var (
options = []metric.Float64CounterOption{
metric.WithDescription(metricOption.Help),
metric.WithUnit(metricOption.Unit),
}
)
counter, err := meter.Float64Counter(metricName, options...)
if err != nil {
return nil, gerror.WrapCodef(
gcode.CodeInternalError,
err,
`create Float64Counter "%s" failed with option: %s`,
metricName, gjson.MustEncodeString(metricOption),
)
}
return &localCounterPerformer{
MetricOption: metricOption,
MeterOption: l.MeterOption,
Float64Counter: counter,
constOption: genConstOptionForMetric(l.MeterOption, metricOption),
}, nil
}
// Inc increments the counter by 1.
func (l *localCounterPerformer) Inc(ctx context.Context, option ...gmetric.Option) {
l.Add(ctx, 1, option...)
}
// Add adds the given value to the counter. It panics if the value is < 0.
func (l *localCounterPerformer) Add(ctx context.Context, increment float64, option ...gmetric.Option) {
l.Float64Counter.Add(ctx, increment, generateAddOptions(l.MeterOption, l.constOption, option...)...)
}

View File

@ -0,0 +1,84 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric
import (
"context"
"go.opentelemetry.io/otel/metric"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gmetric"
)
// localHistogramPerformer is an implementer for interface HistogramPerformer.
type localHistogramPerformer struct {
gmetric.MeterOption
gmetric.MetricOption
metric.Float64Histogram
constOption metric.MeasurementOption
}
// newHistogramPerformer creates and returns a HistogramPerformer that truly takes action to implement Histogram.
func (l *localMeterPerformer) newHistogramPerformer(
meter metric.Meter,
metricName string,
metricOption gmetric.MetricOption,
) (gmetric.HistogramPerformer, error) {
histogram, err := meter.Float64Histogram(
metricName,
metric.WithDescription(metricOption.Help),
metric.WithUnit(metricOption.Unit),
metric.WithExplicitBucketBoundaries(metricOption.Buckets...),
)
if err != nil {
return nil, gerror.WrapCodef(
gcode.CodeInternalError,
err,
`create Float64Histogram "%s" failed with option: %s`,
metricName, gjson.MustEncodeString(metricOption),
)
}
return &localHistogramPerformer{
MeterOption: l.MeterOption,
MetricOption: metricOption,
Float64Histogram: histogram,
constOption: genConstOptionForMetric(l.MeterOption, metricOption),
}, nil
}
// Record adds a single value to the histogram. The value is usually positive or zero.
func (l *localHistogramPerformer) Record(increment float64, option ...gmetric.Option) {
l.Float64Histogram.Record(
context.Background(),
increment,
l.generateRecordOptions(option...)...,
)
}
func (l *localHistogramPerformer) generateRecordOptions(option ...gmetric.Option) []metric.RecordOption {
var (
dynamicOption = getDynamicOptionByMetricOption(option...)
recordOptions = make([]metric.RecordOption, 0)
globalAttributesOption = getGlobalAttributesOption(gmetric.GetGlobalAttributesOption{
Instrument: l.MeterOption.Instrument,
InstrumentVersion: l.MeterOption.InstrumentVersion,
})
)
if globalAttributesOption != nil {
recordOptions = append(recordOptions, globalAttributesOption)
}
if l.constOption != nil {
recordOptions = append(recordOptions, l.constOption)
}
if dynamicOption != nil {
recordOptions = append(recordOptions, dynamicOption)
}
return recordOptions
}

View File

@ -0,0 +1,56 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric
import (
"context"
"go.opentelemetry.io/otel/metric"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gmetric"
)
// localCounterPerformer is an implementer for interface CounterPerformer.
type localObservableCounterPerformer struct {
gmetric.ObservableMetric
metric.Float64ObservableCounter
}
// newCounterPerformer creates and returns a CounterPerformer that truly takes action to implement Counter.
func (l *localMeterPerformer) newObservableCounterPerformer(
meter metric.Meter,
metricName string,
metricOption gmetric.MetricOption,
) (gmetric.ObservableCounterPerformer, error) {
var (
options = []metric.Float64ObservableCounterOption{
metric.WithDescription(metricOption.Help),
metric.WithUnit(metricOption.Unit),
}
)
if metricOption.Callback != nil {
callback := metric.WithFloat64Callback(func(ctx context.Context, observer metric.Float64Observer) error {
return metricOption.Callback(ctx, l.newMetricObserver(metricOption, observer))
})
options = append(options, callback)
}
counter, err := meter.Float64ObservableCounter(metricName, options...)
if err != nil {
return nil, gerror.WrapCodef(
gcode.CodeInternalError,
err,
`create Float64ObservableCounter "%s" failed with option: %s`,
metricName, gjson.MustEncodeString(metricOption),
)
}
return &localObservableCounterPerformer{
Float64ObservableCounter: counter,
}, nil
}

View File

@ -0,0 +1,56 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric
import (
"context"
"go.opentelemetry.io/otel/metric"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gmetric"
)
// localGaugePerformer is an implementer for interface GaugePerformer.
type localObservableGaugePerformer struct {
gmetric.ObservableMetric
metric.Float64ObservableGauge
}
// newGaugePerformer creates and returns a GaugePerformer that truly takes action to implement Gauge.
func (l *localMeterPerformer) newObservableGaugePerformer(
meter metric.Meter,
metricName string,
metricOption gmetric.MetricOption,
) (gmetric.ObservableGaugePerformer, error) {
var (
options = []metric.Float64ObservableGaugeOption{
metric.WithDescription(metricOption.Help),
metric.WithUnit(metricOption.Unit),
}
)
if metricOption.Callback != nil {
callback := metric.WithFloat64Callback(func(ctx context.Context, observer metric.Float64Observer) error {
return metricOption.Callback(ctx, l.newMetricObserver(metricOption, observer))
})
options = append(options, callback)
}
gauge, err := meter.Float64ObservableGauge(metricName, options...)
if err != nil {
return nil, gerror.WrapCodef(
gcode.CodeInternalError,
err,
`create Float64ObservableGauge "%s" failed with option: %s`,
metricName, gjson.MustEncodeString(metricOption),
)
}
return &localObservableGaugePerformer{
Float64ObservableGauge: gauge,
}, nil
}

View File

@ -0,0 +1,57 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric
import (
"context"
"go.opentelemetry.io/otel/metric"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gmetric"
)
// localObservableUpDownCounterPerformer is an implementer for interface CounterPerformer.
type localObservableUpDownCounterPerformer struct {
gmetric.ObservableMetric
metric.Float64ObservableUpDownCounter
}
// newObservableUpDownCounterPerformer creates and returns a UpDownCounterPerformer that truly takes action to
// implement ObservableUpDownCounter.
func (l *localMeterPerformer) newObservableUpDownCounterPerformer(
meter metric.Meter,
metricName string,
metricOption gmetric.MetricOption,
) (gmetric.ObservableUpDownCounterPerformer, error) {
var (
options = []metric.Float64ObservableUpDownCounterOption{
metric.WithDescription(metricOption.Help),
metric.WithUnit(metricOption.Unit),
}
)
if metricOption.Callback != nil {
callback := metric.WithFloat64Callback(func(ctx context.Context, observer metric.Float64Observer) error {
return metricOption.Callback(ctx, l.newMetricObserver(metricOption, observer))
})
options = append(options, callback)
}
counter, err := meter.Float64ObservableUpDownCounter(metricName, options...)
if err != nil {
return nil, gerror.WrapCodef(
gcode.CodeInternalError,
err,
`create Float64ObservableUpDownCounter "%s" failed with option: %s`,
metricName, gjson.MustEncodeString(metricOption),
)
}
return &localObservableUpDownCounterPerformer{
Float64ObservableUpDownCounter: counter,
}, nil
}

View File

@ -0,0 +1,143 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric
import (
"context"
"fmt"
"reflect"
otelmetric "go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk/metric"
"github.com/gogf/gf/v2/container/gset"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gmetric"
)
// localMeterPerformer implements interface gmetric.Performer.
type localMeterPerformer struct {
gmetric.MeterOption
*metric.MeterProvider
}
// newPerformer creates and returns gmetric.Meter.
func newMeterPerformer(provider *metric.MeterProvider, option gmetric.MeterOption) gmetric.MeterPerformer {
meterPerformer := &localMeterPerformer{
MeterOption: option,
MeterProvider: provider,
}
return meterPerformer
}
// CounterPerformer creates and returns a CounterPerformer that performs
// the operations for Counter metric.
func (l *localMeterPerformer) CounterPerformer(name string, option gmetric.MetricOption) (gmetric.CounterPerformer, error) {
return l.newCounterPerformer(l.createMeter(), name, option)
}
// UpDownCounterPerformer creates and returns a UpDownCounterPerformer that performs
// the operations for UpDownCounter metric.
func (l *localMeterPerformer) UpDownCounterPerformer(name string, option gmetric.MetricOption) (gmetric.UpDownCounterPerformer, error) {
return l.newUpDownCounterPerformer(l.createMeter(), name, option)
}
// HistogramPerformer creates and returns a HistogramPerformer that performs
// the operations for Histogram metric.
func (l *localMeterPerformer) HistogramPerformer(name string, option gmetric.MetricOption) (gmetric.HistogramPerformer, error) {
return l.newHistogramPerformer(l.createMeter(), name, option)
}
// ObservableCounterPerformer creates and returns an ObservableMetric that performs
// the operations for ObservableCounter metric.
func (l *localMeterPerformer) ObservableCounterPerformer(name string, option gmetric.MetricOption) (gmetric.ObservableMetric, error) {
return l.newObservableCounterPerformer(l.createMeter(), name, option)
}
// ObservableUpDownCounterPerformer creates and returns an ObservableMetric that performs
// the operations for ObservableUpDownCounter metric.
func (l *localMeterPerformer) ObservableUpDownCounterPerformer(name string, option gmetric.MetricOption) (gmetric.ObservableMetric, error) {
return l.newObservableUpDownCounterPerformer(l.createMeter(), name, option)
}
// ObservableGaugePerformer creates and returns an ObservableMetric that performs
// the operations for ObservableGauge metric.
func (l *localMeterPerformer) ObservableGaugePerformer(name string, option gmetric.MetricOption) (gmetric.ObservableMetric, error) {
return l.newObservableGaugePerformer(l.createMeter(), name, option)
}
// RegisterCallback registers callback on certain metrics.
// A callback is bound to certain component and version, it is called when the associated metrics are read.
// Multiple callbacks on the same component and version will be called by their registered sequence.
func (l *localMeterPerformer) RegisterCallback(
callback gmetric.Callback, observableMetrics ...gmetric.ObservableMetric,
) error {
var metrics = make([]gmetric.Metric, 0)
for _, v := range observableMetrics {
m, ok := v.(gmetric.Metric)
if !ok {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid metric parameter "%s" for RegisterCallback, which does not implement interface Metric`,
reflect.TypeOf(v).String(),
)
}
metrics = append(metrics, m)
}
// group the metric by instrument and instrument version.
var (
instrumentSet = gset.NewStrSet()
underlyingMeterMap = map[otelmetric.Meter][]otelmetric.Observable{}
)
for _, m := range metrics {
var meter = l.Meter(
m.Info().Instrument().Name(),
otelmetric.WithInstrumentationVersion(m.Info().Instrument().Version()),
)
instrumentSet.Add(fmt.Sprintf(
`%s@%s`,
m.Info().Instrument().Name(),
m.Info().Instrument().Version(),
))
if _, ok := underlyingMeterMap[meter]; !ok {
underlyingMeterMap[meter] = make([]otelmetric.Observable, 0)
}
underlyingMeterMap[meter] = append(underlyingMeterMap[meter], metricToFloat64Observable(m))
}
if len(underlyingMeterMap) > 1 {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
`multiple instrument or instrument version metrics used in the same callback: %s`,
instrumentSet.Join(","),
)
}
// do callback registering.
for meter, observables := range underlyingMeterMap {
_, err := meter.RegisterCallback(
func(ctx context.Context, observer otelmetric.Observer) error {
return callback(ctx, newObserver(observer, l.MeterOption))
},
observables...,
)
if err != nil {
return gerror.WrapCode(
gcode.CodeInternalError, err,
`RegisterCallback failed`,
)
}
}
return nil
}
// createMeter creates and returns an OpenTelemetry Meter.
func (l *localMeterPerformer) createMeter() otelmetric.Meter {
return l.Meter(
l.Instrument,
otelmetric.WithInstrumentationVersion(l.InstrumentVersion),
)
}

View File

@ -0,0 +1,70 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric
import (
"context"
"go.opentelemetry.io/otel/metric"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gmetric"
)
// localUpDownCounterPerformer is an implementer for interface gmetric.UpDownCounterPerformer.
type localUpDownCounterPerformer struct {
gmetric.MeterOption
gmetric.MetricOption
metric.Float64UpDownCounter
constOption metric.MeasurementOption
}
// newUpDownCounterPerformer creates and returns a CounterPerformer that truly takes action to implement Counter.
func (l *localMeterPerformer) newUpDownCounterPerformer(
meter metric.Meter,
metricName string,
metricOption gmetric.MetricOption,
) (gmetric.UpDownCounterPerformer, error) {
var (
options = []metric.Float64UpDownCounterOption{
metric.WithDescription(metricOption.Help),
metric.WithUnit(metricOption.Unit),
}
)
counter, err := meter.Float64UpDownCounter(metricName, options...)
if err != nil {
return nil, gerror.WrapCodef(
gcode.CodeInternalError,
err,
`create Float64Counter "%s" failed with config: %s`,
metricName, gjson.MustEncodeString(metricOption),
)
}
return &localUpDownCounterPerformer{
MeterOption: l.MeterOption,
MetricOption: metricOption,
Float64UpDownCounter: counter,
constOption: genConstOptionForMetric(l.MeterOption, metricOption),
}, nil
}
// Inc increments the counter by 1.
func (l *localUpDownCounterPerformer) Inc(ctx context.Context, option ...gmetric.Option) {
l.Add(ctx, 1, option...)
}
// Dec decrements the counter by 1.
func (l *localUpDownCounterPerformer) Dec(ctx context.Context, option ...gmetric.Option) {
l.Add(ctx, -1, option...)
}
// Add adds the given value to the counter.
func (l *localUpDownCounterPerformer) Add(ctx context.Context, increment float64, option ...gmetric.Option) {
l.Float64UpDownCounter.Add(ctx, increment, generateAddOptions(l.MeterOption, l.constOption, option...)...)
}

View File

@ -0,0 +1,56 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric
import (
"go.opentelemetry.io/otel/metric"
"github.com/gogf/gf/v2/os/gmetric"
)
// localMetricObserver implements interface gmetric.CallbackObserver.
type localMetricObserver struct {
gmetric.MeterOption
gmetric.MetricOption
metric.Float64Observer
}
func (l *localMeterPerformer) newMetricObserver(
metricOption gmetric.MetricOption,
float64Observer metric.Float64Observer,
) gmetric.MetricObserver {
return &localMetricObserver{
MeterOption: l.MeterOption,
MetricOption: metricOption,
Float64Observer: float64Observer,
}
}
// Observe observes the value for certain initialized Metric.
// It adds the value to total result if the observed Metrics is type of Counter.
// It sets the value as the result if the observed Metrics is type of Gauge.
func (l *localMetricObserver) Observe(value float64, option ...gmetric.Option) {
var (
constOption = genConstOptionForMetric(l.MeterOption, l.MetricOption)
dynamicOption = getDynamicOptionByMetricOption(option...)
globalAttributesOption = getGlobalAttributesOption(gmetric.GetGlobalAttributesOption{
Instrument: l.Instrument,
InstrumentVersion: l.InstrumentVersion,
})
observeOptions = make([]metric.ObserveOption, 0)
)
if globalAttributesOption != nil {
observeOptions = append(observeOptions, globalAttributesOption)
}
if constOption != nil {
observeOptions = append(observeOptions, constOption)
}
if dynamicOption != nil {
observeOptions = append(observeOptions, dynamicOption)
}
l.Float64Observer.Observe(value, observeOptions...)
}

View File

@ -0,0 +1,108 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric
import (
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
)
// newProviderConfigByOptions returns a config configured with options.
func newProviderConfigByOptions(options []Option) providerConfig {
conf := providerConfig{}
for _, o := range options {
conf = o.apply(conf)
}
return conf
}
// Option applies a configuration option value to a MeterProvider.
type Option interface {
apply(providerConfig) providerConfig
}
// optionFunc applies a set of options to a config.
type optionFunc func(providerConfig) providerConfig
// apply returns a config with option(s) applied.
func (o optionFunc) apply(conf providerConfig) providerConfig {
return o(conf)
}
// providerConfig is the configuration for Provider.
type providerConfig struct {
viewOption metric.Option
readerOption metric.Option
resourceOption metric.Option
enabledBuiltInMetrics bool
}
// IsBuiltInMetricsEnabled returns whether the builtin metrics is enabled.
func (cfg providerConfig) IsBuiltInMetricsEnabled() bool {
return cfg.enabledBuiltInMetrics
}
// MetricOptions converts and returns the providerConfig as metrics options.
func (cfg providerConfig) MetricOptions() []metric.Option {
var metricOptions = make([]metric.Option, 0)
if cfg.viewOption != nil {
metricOptions = append(metricOptions, cfg.viewOption)
}
if cfg.readerOption != nil {
metricOptions = append(metricOptions, cfg.readerOption)
}
if cfg.resourceOption != nil {
metricOptions = append(metricOptions, cfg.resourceOption)
}
return metricOptions
}
// WithBuiltInMetrics enables builtin metrics.
func WithBuiltInMetrics() Option {
return optionFunc(func(cfg providerConfig) providerConfig {
cfg.enabledBuiltInMetrics = true
return cfg
})
}
// WithResource associates a Resource with a MeterProvider. This Resource
// represents the entity producing telemetry and is associated with all Meters
// the MeterProvider will create.
func WithResource(res *resource.Resource) Option {
return optionFunc(func(cfg providerConfig) providerConfig {
cfg.resourceOption = metric.WithResource(res)
return cfg
})
}
// WithReader associates Reader r with a MeterProvider.
//
// By default, if this option is not used, the MeterProvider will perform no
// operations; no data will be exported without a Reader.
func WithReader(reader metric.Reader) Option {
return optionFunc(func(cfg providerConfig) providerConfig {
if reader == nil {
return cfg
}
cfg.readerOption = metric.WithReader(reader)
return cfg
})
}
// WithView associates views a MeterProvider.
//
// Views are appended to existing ones in a MeterProvider if this option is
// used multiple times.
//
// By default, if this option is not used, the MeterProvider will use the
// default view.
func WithView(views ...metric.View) Option {
return optionFunc(func(cfg providerConfig) providerConfig {
cfg.viewOption = metric.WithView(views...)
return cfg
})
}

View File

@ -0,0 +1,34 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
// PrometheusHandler returns the http handler for prometheus metrics exporting.
func PrometheusHandler(r *ghttp.Request) {
// Remove all builtin metrics that are produced by prometheus client.
prometheus.Unregister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
prometheus.Unregister(collectors.NewGoCollector())
handler := promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{})
handler.ServeHTTP(r.Response.Writer, r.Request)
}
// StartPrometheusMetricsServer starts running a http server for metrics exporting.
func StartPrometheusMetricsServer(port int, path string) {
s := g.Server()
s.BindHandler(path, PrometheusHandler)
s.SetPort(port)
s.Run()
}

View File

@ -0,0 +1,151 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric
import (
"time"
"go.opentelemetry.io/contrib/instrumentation/runtime"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/metric"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gmetric"
)
// localProvider implements interface gmetric.Provider.
type localProvider struct {
*metric.MeterProvider
}
// newProvider creates and returns an object that implements gmetric.Provider.
// DO NOT set this as global provider internally.
func newProvider(options ...Option) (gmetric.Provider, error) {
// TODO global logger set for otel.
// otel.SetLogger()
var (
err error
metrics = gmetric.GetAllMetrics()
builtinViews = createViewsForBuiltInMetrics()
callbacks = gmetric.GetRegisteredCallbacks()
)
options = append(options, WithView(builtinViews...))
var (
config = newProviderConfigByOptions(options)
provider = &localProvider{
// MeterProvider is the core object that can create otel metrics.
MeterProvider: metric.NewMeterProvider(config.MetricOptions()...),
}
)
if err = provider.initializeMetrics(metrics); err != nil {
return nil, err
}
if err = provider.initializeCallback(callbacks); err != nil {
return nil, err
}
// builtin metrics: golang.
if config.IsBuiltInMetricsEnabled() {
err = runtime.Start(
runtime.WithMinimumReadMemStatsInterval(time.Second),
runtime.WithMeterProvider(provider),
)
}
if err != nil {
return nil, gerror.WrapCode(
gcode.CodeInternalError, err, `start built-in runtime metrics failed`,
)
}
return provider, nil
}
// SetAsGlobal sets current provider as global meter provider for current process,
// which makes the following metrics creating on this Provider, especially the metrics created in runtime.
func (l *localProvider) SetAsGlobal() {
gmetric.SetGlobalProvider(l)
otel.SetMeterProvider(l)
}
// MeterPerformer creates and returns a MeterPerformer.
// A Performer can produce types of Metric performer.
func (l *localProvider) MeterPerformer(option gmetric.MeterOption) gmetric.MeterPerformer {
return newMeterPerformer(l.MeterProvider, option)
}
// createViewsForBuiltInMetrics creates and returns views for builtin metrics.
func createViewsForBuiltInMetrics() []metric.View {
var views = make([]metric.View, 0)
views = append(views, metric.NewView(
metric.Instrument{
Name: "process.runtime.go.gc.pause_ns",
Scope: instrumentation.Scope{
Name: runtime.ScopeName,
Version: runtime.Version(),
},
},
metric.Stream{
Aggregation: metric.AggregationExplicitBucketHistogram{
Boundaries: []float64{
500, 1000, 5000, 10000, 50000, 100000, 500000, 1000000,
},
},
},
))
views = append(views, metric.NewView(
metric.Instrument{
Name: "runtime.uptime",
Scope: instrumentation.Scope{
Name: runtime.ScopeName,
Version: runtime.Version(),
},
},
metric.Stream{
Name: "process.runtime.uptime",
},
))
return views
}
// initializeMetrics initializes all metrics in provider creating.
// The initialization replaces the underlying metric performer using noop-performer with truly performer
// that implements operations for types of metric.
func (l *localProvider) initializeMetrics(metrics []gmetric.Metric) error {
for _, m := range metrics {
if initializer, ok := m.(gmetric.MetricInitializer); ok {
if err := initializer.Init(l); err != nil {
return err
}
}
}
return nil
}
func (l *localProvider) initializeCallback(callbackItems []gmetric.CallbackItem) error {
var err error
for _, callbackItem := range callbackItems {
if callbackItem.Provider != nil {
continue
}
if len(callbackItem.Metrics) == 0 {
continue
}
callbackItem.Provider = l
if err = l.MeterPerformer(callbackItem.MeterOption).RegisterCallback(
callbackItem.Callback, callbackItem.Metrics...,
); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,170 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric
import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gmetric"
"github.com/gogf/gf/v2/util/gconv"
)
func generateAddOptions(
meterOption gmetric.MeterOption, constOption metric.MeasurementOption, option ...gmetric.Option,
) []metric.AddOption {
var (
addOptions = make([]metric.AddOption, 0)
globalAttributesOption = getGlobalAttributesOption(gmetric.GetGlobalAttributesOption{
Instrument: meterOption.Instrument,
InstrumentVersion: meterOption.InstrumentVersion,
})
)
if constOption != nil {
addOptions = append(addOptions, constOption)
}
if globalAttributesOption != nil {
addOptions = append(addOptions, globalAttributesOption)
}
if len(option) > 0 {
addOptions = append(
addOptions,
metric.WithAttributes(attributesToKeyValues(option[0].Attributes)...),
)
}
return addOptions
}
func getGlobalAttributesOption(option gmetric.GetGlobalAttributesOption) metric.MeasurementOption {
var (
globalAttributesOption metric.MeasurementOption
globalAttributes = gmetric.GetGlobalAttributes(gmetric.GetGlobalAttributesOption{})
instrumentAttributes gmetric.Attributes
)
if option.Instrument != "" {
instrumentAttributes = gmetric.GetGlobalAttributes(option)
}
if len(globalAttributes) > 0 {
globalAttributesOption = metric.WithAttributes(attributesToKeyValues(globalAttributes)...)
}
if len(instrumentAttributes) > 0 {
globalAttributesOption = metric.WithAttributes(attributesToKeyValues(instrumentAttributes)...)
}
return globalAttributesOption
}
func getDynamicOptionByMetricOption(option ...gmetric.Option) metric.MeasurementOption {
var (
usedOption gmetric.Option
dynamicOption metric.MeasurementOption
)
if len(option) > 0 {
usedOption = option[0]
}
if len(usedOption.Attributes) > 0 {
dynamicOption = metric.WithAttributes(attributesToKeyValues(usedOption.Attributes)...)
}
return dynamicOption
}
func genConstOptionForMetric(
meterOption gmetric.MeterOption,
metricOption gmetric.MetricOption,
) metric.MeasurementOption {
return genConstOptionForMetricByAttributes(meterOption.Attributes, metricOption.Attributes)
}
func getConstOptionByMetric(meterOption gmetric.MeterOption, m gmetric.Metric) metric.MeasurementOption {
return genConstOptionForMetricByAttributes(meterOption.Attributes, m.Info().Attributes())
}
func genConstOptionForMetricByAttributes(
meterAttrs gmetric.Attributes,
metricAttrs gmetric.Attributes,
) metric.MeasurementOption {
var (
constOption metric.MeasurementOption
attributes = make([]attribute.KeyValue, 0)
)
if len(meterAttrs) > 0 {
attributes = append(attributes, attributesToKeyValues(meterAttrs)...)
}
if len(metricAttrs) > 0 {
attributes = append(attributes, attributesToKeyValues(metricAttrs)...)
}
constOption = metric.WithAttributes(attributes...)
return constOption
}
func metricToFloat64Observable(m gmetric.Metric) metric.Float64Observable {
performer := m.(gmetric.PerformerExporter).Performer()
switch m.Info().Type() {
case gmetric.MetricTypeObservableCounter:
return performer.(*localObservableCounterPerformer).Float64ObservableCounter
case gmetric.MetricTypeObservableUpDownCounter:
return performer.(*localObservableUpDownCounterPerformer).Float64ObservableUpDownCounter
case gmetric.MetricTypeObservableGauge:
return performer.(*localObservableGaugePerformer).Float64ObservableGauge
default:
panic(gerror.NewCode(
gcode.CodeInvalidParameter,
`Histogram is not support for converting to metric.Float64Observable`,
))
}
return nil
}
// attributesToKeyValues converts attributes to OpenTelemetry key-value pair attributes.
func attributesToKeyValues(attrs gmetric.Attributes) []attribute.KeyValue {
var keyValues = make([]attribute.KeyValue, 0)
for _, attr := range attrs {
keyValues = append(keyValues, attributeToKeyValue(attr))
}
return keyValues
}
// attributeToKeyValue converts attribute to OpenTelemetry key-value pair attribute.
func attributeToKeyValue(attr gmetric.Attribute) attribute.KeyValue {
var (
key = string(attr.Key())
value = attr.Value()
)
switch result := value.(type) {
case bool:
return attribute.Bool(key, result)
case []bool:
return attribute.BoolSlice(key, result)
case int:
return attribute.Int(key, result)
case []int:
return attribute.IntSlice(key, result)
case int64:
return attribute.Int64(key, result)
case []int64:
return attribute.Int64Slice(key, result)
case float64:
return attribute.Float64(key, result)
case []float64:
return attribute.Float64Slice(key, result)
case string:
return attribute.String(key, result)
case []string:
return attribute.StringSlice(key, result)
default:
return attribute.String(key, gconv.String(value))
}
}

View File

@ -0,0 +1,94 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric_test
import (
"fmt"
"testing"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/otel/exporters/prometheus"
"github.com/gogf/gf/contrib/metric/otelmetric/v2"
"github.com/gogf/gf/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gmetric"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/guid"
)
func Test_HTTP_Server(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := g.Server(guid.S())
s.BindHandler("/user/:id", func(r *ghttp.Request) {
r.Response.Write("user")
})
s.BindHandler("/order/:id", func(r *ghttp.Request) {
r.Response.Write("order")
})
s.BindHandler("/metrics", ghttp.WrapH(promhttp.Handler()))
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
var ctx = gctx.New()
// Prometheus exporter to export metrics as Prometheus format.
exporter, err := prometheus.New(
prometheus.WithoutCounterSuffixes(),
prometheus.WithoutUnits(),
)
if err != nil {
g.Log().Fatal(ctx, err)
}
// OpenTelemetry provider.
provider := otelmetric.MustProvider(otelmetric.WithReader(exporter))
defer provider.Shutdown(ctx)
gmetric.SetGlobalProvider(provider)
defer gmetric.SetGlobalProvider(nil)
c := g.Client()
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
c.GetContent(ctx, "/user/1")
c.PutContent(ctx, "/user/1", "123")
c.PostContent(ctx, "/user/2", "123")
c.DeleteContent(ctx, "/user/3")
c.GetContent(ctx, "/order/1")
c.PutContent(ctx, "/order/1", "1234")
c.PostContent(ctx, "/order/2", "1234")
c.DeleteContent(ctx, "/order/3")
var (
metricsContent = c.GetContent(ctx, "/metrics")
expectContent = gtest.DataContent("http.prometheus.metrics.txt")
)
expectContent, _ = gregex.ReplaceString(
`otel_scope_version=".+?"`,
fmt.Sprintf(`otel_scope_version="%s"`, gf.VERSION),
expectContent,
)
expectContent, _ = gregex.ReplaceString(
`server_port=".+?"`,
fmt.Sprintf(`server_port="%d"`, s.GetListenedPort()),
expectContent,
)
//fmt.Println(metricsContent)
for _, line := range gstr.SplitAndTrim(expectContent, "\n") {
//fmt.Println(line)
t.Assert(gstr.Contains(metricsContent, line), true)
}
})
}

View File

@ -0,0 +1,320 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package otelmetric_test
import (
"context"
"testing"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"github.com/gogf/gf/contrib/metric/otelmetric/v2"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gmetric"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/text/gstr"
)
func Test_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var (
ctx = gctx.New()
meterV11 = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{
Instrument: "github.com/gogf/gf/example/metric/basic",
InstrumentVersion: "v1.1",
})
meterV12 = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{
Instrument: "github.com/gogf/gf/example/metric/basic",
InstrumentVersion: "v1.2",
})
meterV13 = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{
Instrument: "github.com/gogf/gf/example/metric/basic",
InstrumentVersion: "v1.3",
})
meterV14 = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{
Instrument: "github.com/gogf/gf/example/metric/basic",
InstrumentVersion: "v1.4",
})
counter = meterV11.MustCounter(
"goframe.metric.demo.counter",
gmetric.MetricOption{
Help: "This is a simple demo for Counter usage",
Unit: "%",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_1", 1),
},
},
)
upDownCounter = meterV12.MustUpDownCounter(
"goframe.metric.demo.updown_counter",
gmetric.MetricOption{
Help: "This is a simple demo for UpDownCounter usage",
Unit: "%",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_2", 2),
},
},
)
histogram = meterV13.MustHistogram(
"goframe.metric.demo.histogram",
gmetric.MetricOption{
Help: "This is a simple demo for histogram usage",
Unit: "ms",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_3", 3),
},
Buckets: []float64{0, 10, 20, 50, 100, 500, 1000, 2000, 5000, 10000},
},
)
observableCounter = meterV14.MustObservableCounter(
"goframe.metric.demo.observable_counter",
gmetric.MetricOption{
Help: "This is a simple demo for ObservableCounter usage",
Unit: "%",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_4", 4),
},
},
)
observableUpDownCounter = meterV14.MustObservableUpDownCounter(
"goframe.metric.demo.observable_updown_counter",
gmetric.MetricOption{
Help: "This is a simple demo for ObservableUpDownCounter usage",
Unit: "%",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_5", 5),
},
},
)
observableGauge = meterV14.MustObservableGauge(
"goframe.metric.demo.observable_gauge",
gmetric.MetricOption{
Help: "This is a simple demo for ObservableGauge usage",
Unit: "%",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_6", 6),
},
},
)
)
meterV14.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error {
obs.Observe(observableCounter, 10, gmetric.Option{
Attributes: gmetric.Attributes{gmetric.NewAttribute("dynamic_label_4", "4")},
})
obs.Observe(observableUpDownCounter, 20, gmetric.Option{
Attributes: gmetric.Attributes{gmetric.NewAttribute("dynamic_label_5", "5")},
})
obs.Observe(observableGauge, 30, gmetric.Option{
Attributes: gmetric.Attributes{gmetric.NewAttribute("dynamic_label_6", "6")},
})
return nil
}, observableCounter, observableUpDownCounter, observableGauge)
reader := metric.NewManualReader()
// OpenTelemetry provider.
provider := otelmetric.MustProvider(otelmetric.WithReader(reader))
defer provider.Shutdown(ctx)
// Counter.
counter.Inc(ctx)
counter.Add(ctx, 10, gmetric.Option{
Attributes: gmetric.Attributes{gmetric.NewAttribute("dynamic_label_1", "1")},
})
upDownCounter.Add(ctx, 10)
upDownCounter.Dec(ctx, gmetric.Option{
Attributes: gmetric.Attributes{gmetric.NewAttribute("dynamic_label_2", "2")},
})
// Record values for histogram.
histogram.Record(1)
histogram.Record(20)
histogram.Record(30)
histogram.Record(101)
histogram.Record(2000)
histogram.Record(9000)
histogram.Record(20000)
histogramOption := gmetric.Option{
Attributes: gmetric.Attributes{gmetric.NewAttribute("dynamic_label_3", "3")},
}
histogram.Record(100, histogramOption)
histogram.Record(200, histogramOption)
rm := metricdata.ResourceMetrics{}
err := reader.Collect(ctx, &rm)
t.AssertNil(err)
metricsJsonContent := gjson.MustEncodeString(rm)
t.Assert(len(rm.ScopeMetrics), 4)
t.Assert(gstr.Count(metricsJsonContent, `goframe.metric.demo.counter`), 1)
t.Assert(gstr.Count(metricsJsonContent, `goframe.metric.demo.updown_counter`), 1)
t.Assert(gstr.Count(metricsJsonContent, `goframe.metric.demo.histogram`), 1)
t.Assert(gstr.Count(metricsJsonContent, `goframe.metric.demo.observable_counter`), 1)
t.Assert(gstr.Count(metricsJsonContent, `goframe.metric.demo.observable_updown_counter"`), 1)
t.Assert(gstr.Count(metricsJsonContent, `goframe.metric.demo.observable_gauge`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"const_label_2","Value":{"Type":"INT64","Value":2}}`), 2)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"dynamic_label_2","Value":{"Type":"STRING","Value":"2"}}`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"const_label_3","Value":{"Type":"INT64","Value":3}}`), 2)
t.Assert(gstr.Count(metricsJsonContent, `"Count":7,"Bounds":[0,10,20,50,100,500,1000,2000,5000,10000],"BucketCounts":[0,1,1,1,0,1,0,1,0,1,1],"Min":1,"Max":20000,"Sum":31152`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"const_label_3","Value":{"Type":"INT64","Value":3}}`), 2)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"dynamic_label_3","Value":{"Type":"STRING","Value":"3"}}`), 1)
t.Assert(gstr.Count(metricsJsonContent, `"Count":2,"Bounds":[0,10,20,50,100,500,1000,2000,5000,10000],"BucketCounts":[0,0,0,0,1,1,0,0,0,0,0],"Min":100,"Max":200,"Sum":300`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"const_label_4","Value":{"Type":"INT64","Value":4}}`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"dynamic_label_4","Value":{"Type":"STRING","Value":"4"}}`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"const_label_5","Value":{"Type":"INT64","Value":5}}`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"dynamic_label_5","Value":{"Type":"STRING","Value":"5"}}`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"const_label_6","Value":{"Type":"INT64","Value":6}}`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"dynamic_label_6","Value":{"Type":"STRING","Value":"6"}}`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"const_label_1","Value":{"Type":"INT64","Value":1}}`), 2)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"dynamic_label_1","Value":{"Type":"STRING","Value":"1"}}`), 1)
})
}
func Test_GlobalAttributes(t *testing.T) {
gmetric.SetGlobalAttributes(gmetric.Attributes{
gmetric.NewAttribute("g1", 1),
}, gmetric.SetGlobalAttributesOption{
Instrument: "github.com/gogf/gf/example/metric/basic",
InstrumentVersion: "v1.1",
InstrumentPattern: "",
})
gmetric.SetGlobalAttributes(gmetric.Attributes{
gmetric.NewAttribute("g2", 2),
}, gmetric.SetGlobalAttributesOption{
Instrument: "github.com/gogf/gf/example/metric/basic",
InstrumentVersion: "v1.3",
InstrumentPattern: "",
})
gtest.C(t, func(t *gtest.T) {
var (
ctx = gctx.New()
meterV11 = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{
Instrument: "github.com/gogf/gf/example/metric/basic",
InstrumentVersion: "v1.1",
})
meterV12 = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{
Instrument: "github.com/gogf/gf/example/metric/basic",
InstrumentVersion: "v1.2",
})
meterV13 = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{
Instrument: "github.com/gogf/gf/example/metric/basic",
InstrumentVersion: "v1.3",
})
counter = meterV11.MustCounter(
"goframe.metric.demo.counter",
gmetric.MetricOption{
Help: "This is a simple demo for Counter usage",
Unit: "%",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_1", 1),
},
},
)
histogram = meterV12.MustHistogram(
"goframe.metric.demo.histogram",
gmetric.MetricOption{
Help: "This is a simple demo for histogram usage",
Unit: "ms",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_2", 2),
},
Buckets: []float64{0, 10, 20, 50, 100, 500, 1000, 2000, 5000, 10000},
},
)
observableCounter = meterV13.MustObservableCounter(
"goframe.metric.demo.observable_counter",
gmetric.MetricOption{
Help: "This is a simple demo for ObservableCounter usage",
Unit: "%",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_3", 3),
},
},
)
observableGauge = meterV13.MustObservableGauge(
"goframe.metric.demo.observable_gauge",
gmetric.MetricOption{
Help: "This is a simple demo for ObservableGauge usage",
Unit: "%",
Attributes: gmetric.Attributes{
gmetric.NewAttribute("const_label_4", 4),
},
},
)
)
meterV13.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error {
obs.Observe(observableCounter, 10, gmetric.Option{
Attributes: gmetric.Attributes{gmetric.NewAttribute("dynamic_label_3", "3")},
})
obs.Observe(observableGauge, 10, gmetric.Option{
Attributes: gmetric.Attributes{gmetric.NewAttribute("dynamic_label_4", "4")},
})
return nil
}, observableCounter, observableGauge)
reader := metric.NewManualReader()
// OpenTelemetry provider.
provider := otelmetric.MustProvider(otelmetric.WithReader(reader))
defer provider.Shutdown(ctx)
// Add value for counter.
counter.Inc(ctx)
counter.Add(ctx, 10, gmetric.Option{
Attributes: gmetric.Attributes{gmetric.NewAttribute("dynamic_label_1", "1")},
})
// Record values for histogram.
histogram.Record(1)
histogram.Record(20)
histogram.Record(30)
histogram.Record(101)
histogram.Record(2000)
histogram.Record(9000)
histogram.Record(20000)
histogramOption := gmetric.Option{
Attributes: gmetric.Attributes{gmetric.NewAttribute("dynamic_label_2", "2")},
}
histogram.Record(100, histogramOption)
histogram.Record(200, histogramOption)
rm := metricdata.ResourceMetrics{}
err := reader.Collect(ctx, &rm)
t.AssertNil(err)
metricsJsonContent := gjson.MustEncodeString(rm)
t.Assert(len(rm.ScopeMetrics), 3)
t.Assert(gstr.Count(metricsJsonContent, `goframe.metric.demo.counter`), 1)
t.Assert(gstr.Count(metricsJsonContent, `goframe.metric.demo.histogram`), 1)
t.Assert(gstr.Count(metricsJsonContent, `goframe.metric.demo.observable_counter`), 1)
t.Assert(gstr.Count(metricsJsonContent, `goframe.metric.demo.observable_gauge`), 1)
t.Assert(gstr.Count(metricsJsonContent, `goframe.metric.demo.observable_gauge`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"const_label_1","Value":{"Type":"INT64","Value":1}}`), 2)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"g1","Value":{"Type":"INT64","Value":1}}`), 2)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"dynamic_label_1","Value":{"Type":"STRING","Value":"1"}}`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"const_label_2","Value":{"Type":"INT64","Value":2}}`), 2)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"dynamic_label_2","Value":{"Type":"STRING","Value":"2"}}`), 1)
t.Assert(gstr.Count(metricsJsonContent, `"Count":2,"Bounds":[0,10,20,50,100,500,1000,2000,5000,10000],"BucketCounts":[0,0,0,0,1,1,0,0,0,0,0],"Min":100,"Max":200,"Sum":300`), 1)
t.Assert(gstr.Count(metricsJsonContent, `"Count":7,"Bounds":[0,10,20,50,100,500,1000,2000,5000,10000],"BucketCounts":[0,1,1,1,0,1,0,1,0,1,1],"Min":1,"Max":20000,"Sum":31152`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"const_label_3","Value":{"Type":"INT64","Value":3}}`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"dynamic_label_3","Value":{"Type":"STRING","Value":"3"}}`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"g2","Value":{"Type":"INT64","Value":2}}`), 2)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"const_label_4","Value":{"Type":"INT64","Value":4}}`), 1)
t.Assert(gstr.Count(metricsJsonContent, `{"Key":"dynamic_label_4","Value":{"Type":"STRING","Value":"4"}}`), 1)
})
}

View File

@ -0,0 +1,141 @@
# HELP http_client_connection_duration Measures the connection establish duration of client requests.
# TYPE http_client_connection_duration histogram
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="1"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="5"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="10"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="25"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="50"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="75"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="100"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="250"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="500"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="750"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="1000"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="2500"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="5000"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="7500"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="10000"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="30000"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="60000"}
http_client_connection_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="+Inf"}
http_client_connection_duration_sum{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730"}
http_client_connection_duration_count{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730"} 9
# HELP http_client_request_active Number of active client requests.
# TYPE http_client_request_active gauge
http_client_request_active{http_request_method="DELETE",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
http_client_request_active{http_request_method="GET",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 1
http_client_request_active{http_request_method="POST",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
http_client_request_active{http_request_method="PUT",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
# HELP http_client_request_body_size Outgoing request bytes total.
# TYPE http_client_request_body_size counter
http_client_request_body_size{http_request_method="POST",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 7
http_client_request_body_size{http_request_method="PUT",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 7
# HELP http_client_request_duration Measures the duration of client requests.
# TYPE http_client_request_duration histogram
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="1"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="5"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="10"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="25"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="50"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="75"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="100"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="250"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="500"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="750"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="1000"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="2500"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="5000"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="7500"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="10000"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="30000"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="60000"}
http_client_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="+Inf"}
http_client_request_duration_sum{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730"}
http_client_request_duration_count{otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730"} 8
# HELP http_client_request_duration_total Total execution duration of request.
# TYPE http_client_request_duration_total counter
http_client_request_duration_total{http_request_method="DELETE",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"}
http_client_request_duration_total{http_request_method="GET",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"}
http_client_request_duration_total{http_request_method="POST",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"}
http_client_request_duration_total{http_request_method="PUT",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"}
# HELP http_client_request_total Total processed request number.
# TYPE http_client_request_total counter
http_client_request_total{http_request_method="DELETE",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 2
http_client_request_total{http_request_method="GET",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 2
http_client_request_total{http_request_method="POST",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 2
http_client_request_total{http_request_method="PUT",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/gclient.Client",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 2
# HELP http_server_request_active Number of active server requests.
# TYPE http_server_request_active gauge
http_server_request_active{http_request_method="DELETE",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
http_server_request_active{http_request_method="DELETE",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
http_server_request_active{http_request_method="GET",http_route="/metrics",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 1
http_server_request_active{http_request_method="GET",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
http_server_request_active{http_request_method="GET",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
http_server_request_active{http_request_method="POST",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
http_server_request_active{http_request_method="POST",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
http_server_request_active{http_request_method="PUT",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
http_server_request_active{http_request_method="PUT",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
# HELP http_server_request_body_size Incoming request bytes total.
# TYPE http_server_request_body_size counter
http_server_request_body_size{http_request_method="DELETE",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
http_server_request_body_size{http_request_method="DELETE",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
http_server_request_body_size{http_request_method="GET",http_route="/metrics",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
http_server_request_body_size{http_request_method="GET",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
http_server_request_body_size{http_request_method="GET",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 0
http_server_request_body_size{http_request_method="POST",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 4
http_server_request_body_size{http_request_method="POST",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 3
http_server_request_body_size{http_request_method="PUT",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 4
http_server_request_body_size{http_request_method="PUT",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 3
# HELP http_server_request_duration Measures the duration of inbound request.
# TYPE http_server_request_duration histogram
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="1"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="5"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="10"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="25"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="50"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="75"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="100"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="250"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="500"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="750"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="1000"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="2500"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="5000"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="7500"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="10000"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="30000"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="60000"}
http_server_request_duration_bucket{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",le="+Inf"}
http_server_request_duration_sum{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730"}
http_server_request_duration_count{otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730"}
# HELP http_server_request_duration_total Total execution duration of request.
# TYPE http_server_request_duration_total counter
http_server_request_duration_total{error_code="0",http_request_method="DELETE",http_response_status_code="200",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"}
http_server_request_duration_total{error_code="0",http_request_method="DELETE",http_response_status_code="200",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"}
http_server_request_duration_total{error_code="0",http_request_method="GET",http_response_status_code="200",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"}
http_server_request_duration_total{error_code="0",http_request_method="GET",http_response_status_code="200",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"}
http_server_request_duration_total{error_code="0",http_request_method="POST",http_response_status_code="200",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"}
http_server_request_duration_total{error_code="0",http_request_method="POST",http_response_status_code="200",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"}
http_server_request_duration_total{error_code="0",http_request_method="PUT",http_response_status_code="200",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"}
http_server_request_duration_total{error_code="0",http_request_method="PUT",http_response_status_code="200",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"}
# HELP http_server_request_total Total processed request number.
# TYPE http_server_request_total counter
http_server_request_total{error_code="0",http_request_method="DELETE",http_response_status_code="200",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 1
http_server_request_total{error_code="0",http_request_method="DELETE",http_response_status_code="200",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 1
http_server_request_total{error_code="0",http_request_method="GET",http_response_status_code="200",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 1
http_server_request_total{error_code="0",http_request_method="GET",http_response_status_code="200",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 1
http_server_request_total{error_code="0",http_request_method="POST",http_response_status_code="200",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 1
http_server_request_total{error_code="0",http_request_method="POST",http_response_status_code="200",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 1
http_server_request_total{error_code="0",http_request_method="PUT",http_response_status_code="200",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 1
http_server_request_total{error_code="0",http_request_method="PUT",http_response_status_code="200",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 1
# HELP http_server_response_body_size Response bytes total.
# TYPE http_server_response_body_size counter
http_server_response_body_size{error_code="0",http_request_method="DELETE",http_response_status_code="200",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 5
http_server_response_body_size{error_code="0",http_request_method="DELETE",http_response_status_code="200",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 4
http_server_response_body_size{error_code="0",http_request_method="GET",http_response_status_code="200",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 5
http_server_response_body_size{error_code="0",http_request_method="GET",http_response_status_code="200",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 4
http_server_response_body_size{error_code="0",http_request_method="POST",http_response_status_code="200",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 5
http_server_response_body_size{error_code="0",http_request_method="POST",http_response_status_code="200",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 4
http_server_response_body_size{error_code="0",http_request_method="PUT",http_response_status_code="200",http_route="/order/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 5
http_server_response_body_size{error_code="0",http_request_method="PUT",http_response_status_code="200",http_route="/user/:id",network_protocol_version="1.1",otel_scope_name="github.com/gogf/gf/v2/net/ghttp.Server",otel_scope_version="v2.6.4",server_address="127.0.0.1",server_port="62730",url_schema="http"} 4

View File

@ -3,7 +3,7 @@ module github.com/gogf/gf/contrib/nosql/redis/v2
go 1.18
require (
github.com/gogf/gf/v2 v2.6.3
github.com/gogf/gf/v2 v2.7.0
github.com/redis/go-redis/v9 v9.2.1
go.opentelemetry.io/otel v1.14.0
go.opentelemetry.io/otel/trace v1.14.0
@ -16,16 +16,15 @@ require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grokify/html-strip-tags-go v0.0.1 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
go.opentelemetry.io/otel/sdk v1.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect

View File

@ -14,8 +14,8 @@ github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBD
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
@ -28,19 +28,15 @@ github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg=
github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
@ -51,7 +47,6 @@ go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+go
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=

View File

@ -44,23 +44,25 @@ func init() {
func New(config *gredis.Config) *Redis {
fillWithDefaultConfiguration(config)
opts := &redis.UniversalOptions{
Addrs: gstr.SplitAndTrim(config.Address, ","),
Username: config.User,
Password: config.Pass,
DB: config.Db,
MaxRetries: defaultMaxRetries,
PoolSize: config.MaxActive,
MinIdleConns: config.MinIdle,
MaxIdleConns: config.MaxIdle,
ConnMaxLifetime: config.MaxConnLifetime,
ConnMaxIdleTime: config.IdleTimeout,
PoolTimeout: config.WaitTimeout,
DialTimeout: config.DialTimeout,
ReadTimeout: config.ReadTimeout,
WriteTimeout: config.WriteTimeout,
MasterName: config.MasterName,
TLSConfig: config.TLSConfig,
Protocol: config.Protocol,
Addrs: gstr.SplitAndTrim(config.Address, ","),
Username: config.User,
Password: config.Pass,
SentinelUsername: config.SentinelUser,
SentinelPassword: config.SentinelPass,
DB: config.Db,
MaxRetries: defaultMaxRetries,
PoolSize: config.MaxActive,
MinIdleConns: config.MinIdle,
MaxIdleConns: config.MaxIdle,
ConnMaxLifetime: config.MaxConnLifetime,
ConnMaxIdleTime: config.IdleTimeout,
PoolTimeout: config.WaitTimeout,
DialTimeout: config.DialTimeout,
ReadTimeout: config.ReadTimeout,
WriteTimeout: config.WriteTimeout,
MasterName: config.MasterName,
TLSConfig: config.TLSConfig,
Protocol: config.Protocol,
}
var client redis.UniversalClient

View File

@ -18,7 +18,7 @@ require github.com/gogf/gf/contrib/registry/etcd/v2 latest
### Reference example
[server](example/registry/etcd/server/main.go)
[server](../../../example/registry/etcd/http/server/server.go)
```go
package main
@ -41,7 +41,7 @@ func main() {
}
```
[client](example/registry/etcd/client/main.go)
[client](../../../example/registry/etcd/http/client/client.go)
```go
package main

View File

@ -8,6 +8,7 @@
package etcd
import (
"strings"
"time"
etcd3 "go.etcd.io/etcd/client/v3"
@ -45,14 +46,41 @@ const (
)
// New creates and returns a new etcd registry.
// Support Etcd Address format: ip:port,ip:port...,ip:port@username:password
func New(address string, option ...Option) gsvc.Registry {
endpoints := gstr.SplitAndTrim(address, ",")
if address == "" {
panic(gerror.NewCode(gcode.CodeInvalidParameter, `invalid etcd address ""`))
}
addressAndAuth := gstr.SplitAndTrim(address, "@")
var (
endpoints []string
userName, password string
)
switch len(addressAndAuth) {
case 1:
endpoints = gstr.SplitAndTrim(address, ",")
default:
endpoints = gstr.SplitAndTrim(addressAndAuth[0], ",")
parts := gstr.SplitAndTrim(strings.Join(addressAndAuth[1:], "@"), ":")
switch len(parts) {
case 2:
userName = parts[0]
password = parts[1]
default:
panic(gerror.NewCode(gcode.CodeInvalidParameter, `invalid etcd auth not support ":" at username or password `))
}
}
if len(endpoints) == 0 {
panic(gerror.NewCodef(gcode.CodeInvalidParameter, `invalid etcd address "%s"`, address))
}
client, err := etcd3.New(etcd3.Config{
Endpoints: endpoints,
})
cfg := etcd3.Config{Endpoints: endpoints}
if userName != "" {
cfg.Username = userName
}
if password != "" {
cfg.Password = password
}
client, err := etcd3.New(cfg)
if err != nil {
panic(gerror.Wrap(err, `create etcd client failed`))
}

View File

@ -19,7 +19,7 @@ import (
func TestRegistry(t *testing.T) {
var (
ctx = gctx.GetInitCtx()
registry = etcd.New(`127.0.0.1:2379`)
registry = etcd.New(`127.0.0.1:2379@root:123`)
)
svc := &gsvc.LocalService{
Name: guid.S(),
@ -86,7 +86,7 @@ func TestRegistry(t *testing.T) {
func TestWatch(t *testing.T) {
var (
ctx = gctx.GetInitCtx()
registry = etcd.New(`127.0.0.1:2379`)
registry = etcd.New(`127.0.0.1:2379@root:123`)
)
svc1 := &gsvc.LocalService{

Some files were not shown because too many files have changed in this diff Show More