parent
c4d22be7c9
commit
3f0ab29c10
@ -0,0 +1,31 @@
|
||||
package golog
|
||||
|
||||
import (
|
||||
"gorm.io/datatypes"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 模型
|
||||
type apiPostgresqlLogJson struct {
|
||||
LogId uint `gorm:"primaryKey;comment:【记录】编号" json:"log_id,omitempty"` //【记录】编号
|
||||
TraceId string `gorm:"index;comment:【系统】跟踪编号" json:"trace_id,omitempty"` //【系统】跟踪编号
|
||||
RequestTime time.Time `gorm:"index;comment:【请求】时间" json:"request_time,omitempty"` //【请求】时间
|
||||
RequestUri string `gorm:"comment:【请求】链接" json:"request_uri,omitempty"` //【请求】链接
|
||||
RequestUrl string `gorm:"comment:【请求】链接" json:"request_url,omitempty"` //【请求】链接
|
||||
RequestApi string `gorm:"index;comment:【请求】接口" json:"request_api,omitempty"` //【请求】接口
|
||||
RequestMethod string `gorm:"index;comment:【请求】方式" json:"request_method,omitempty"` //【请求】方式
|
||||
RequestParams datatypes.JSON `gorm:"type:jsonb;comment:【请求】参数" json:"request_params,omitempty"` //【请求】参数
|
||||
RequestHeader datatypes.JSON `gorm:"type:jsonb;comment:【请求】头部" json:"request_header,omitempty"` //【请求】头部
|
||||
RequestIp string `gorm:"index;comment:【请求】请求Ip" json:"request_ip,omitempty"` //【请求】请求Ip
|
||||
ResponseHeader datatypes.JSON `gorm:"type:jsonb;comment:【返回】头部" json:"response_header,omitempty"` //【返回】头部
|
||||
ResponseStatusCode int `gorm:"index;comment:【返回】状态码" json:"response_status_code,omitempty"` //【返回】状态码
|
||||
ResponseBody datatypes.JSON `gorm:"type:jsonb;comment:【返回】数据" json:"response_content,omitempty"` //【返回】数据
|
||||
ResponseContentLength int64 `gorm:"comment:【返回】大小" json:"response_content_length,omitempty"` //【返回】大小
|
||||
ResponseTime time.Time `gorm:"index;comment:【返回】时间" json:"response_time,omitempty"` //【返回】时间
|
||||
SystemHostName string `gorm:"index;comment:【系统】主机名" json:"system_host_name,omitempty"` //【系统】主机名
|
||||
SystemInsideIp string `gorm:"index;comment:【系统】内网ip" json:"system_inside_ip,omitempty"` //【系统】内网ip
|
||||
SystemOs string `gorm:"index;comment:【系统】系统类型" json:"system_os,omitempty"` //【系统】系统类型
|
||||
SystemArch string `gorm:"index;comment:【系统】系统架构" json:"system_arch,omitempty"` //【系统】系统架构
|
||||
GoVersion string `gorm:"comment:【程序】Go版本" json:"go_version,omitempty"` //【程序】Go版本
|
||||
SdkVersion string `gorm:"comment:【程序】Sdk版本" json:"sdk_version,omitempty"` //【程序】Sdk版本
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package golog
|
||||
|
||||
import "time"
|
||||
|
||||
// 模型
|
||||
type apiPostgresqlLogString struct {
|
||||
LogId uint `gorm:"primaryKey;comment:【记录】编号" json:"log_id,omitempty"` //【记录】编号
|
||||
TraceId string `gorm:"index;comment:【系统】跟踪编号" json:"trace_id,omitempty"` //【系统】跟踪编号
|
||||
RequestTime time.Time `gorm:"index;comment:【请求】时间" json:"request_time,omitempty"` //【请求】时间
|
||||
RequestUri string `gorm:"comment:【请求】链接" json:"request_uri,omitempty"` //【请求】链接
|
||||
RequestUrl string `gorm:"comment:【请求】链接" json:"request_url,omitempty"` //【请求】链接
|
||||
RequestApi string `gorm:"index;comment:【请求】接口" json:"request_api,omitempty"` //【请求】接口
|
||||
RequestMethod string `gorm:"index;comment:【请求】方式" json:"request_method,omitempty"` //【请求】方式
|
||||
RequestParams string `gorm:"comment:【请求】参数" json:"request_params,omitempty"` //【请求】参数
|
||||
RequestHeader string `gorm:"comment:【请求】头部" json:"request_header,omitempty"` //【请求】头部
|
||||
RequestIp string `gorm:"index;comment:【请求】请求Ip" json:"request_ip,omitempty"` //【请求】请求Ip
|
||||
ResponseHeader string `gorm:"comment:【返回】头部" json:"response_header,omitempty"` //【返回】头部
|
||||
ResponseStatusCode int `gorm:"index;comment:【返回】状态码" json:"response_status_code,omitempty"` //【返回】状态码
|
||||
ResponseBody string `gorm:"comment:【返回】数据" json:"response_content,omitempty"` //【返回】数据
|
||||
ResponseContentLength int64 `gorm:"comment:【返回】大小" json:"response_content_length,omitempty"` //【返回】大小
|
||||
ResponseTime time.Time `gorm:"index;comment:【返回】时间" json:"response_time,omitempty"` //【返回】时间
|
||||
SystemHostName string `gorm:"index;comment:【系统】主机名" json:"system_host_name,omitempty"` //【系统】主机名
|
||||
SystemInsideIp string `gorm:"index;comment:【系统】内网ip" json:"system_inside_ip,omitempty"` //【系统】内网ip
|
||||
SystemOs string `gorm:"index;comment:【系统】系统类型" json:"system_os,omitempty"` //【系统】系统类型
|
||||
SystemArch string `gorm:"index;comment:【系统】系统架构" json:"system_arch,omitempty"` //【系统】系统架构
|
||||
GoVersion string `gorm:"comment:【程序】Go版本" json:"go_version,omitempty"` //【程序】Go版本
|
||||
SdkVersion string `gorm:"comment:【程序】Sdk版本" json:"sdk_version,omitempty"` //【程序】Sdk版本
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
package golog
|
||||
|
||||
const (
|
||||
Version = "1.0.78"
|
||||
Version = "1.0.79"
|
||||
)
|
||||
|
@ -0,0 +1,42 @@
|
||||
package golog
|
||||
|
||||
import (
|
||||
"gorm.io/datatypes"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 模型
|
||||
type ginPostgresqlLogJson struct {
|
||||
LogId uint `gorm:"primaryKey;comment:【记录】编号" json:"log_id,omitempty"` //【记录】编号
|
||||
TraceId string `gorm:"index;comment:【系统】跟踪编号" json:"trace_id,omitempty"` //【系统】跟踪编号
|
||||
RequestTime time.Time `gorm:"index;comment:【请求】时间" json:"request_time,omitempty"` //【请求】时间
|
||||
RequestUri string `gorm:"comment:【请求】请求链接 域名+路径+参数" json:"request_uri,omitempty"` //【请求】请求链接 域名+路径+参数
|
||||
RequestUrl string `gorm:"comment:【请求】请求链接 域名+路径" json:"request_url,omitempty"` //【请求】请求链接 域名+路径
|
||||
RequestApi string `gorm:"index;comment:【请求】请求接口 路径" json:"request_api,omitempty"` //【请求】请求接口 路径
|
||||
RequestMethod string `gorm:"index;comment:【请求】请求方式" json:"request_method,omitempty"` //【请求】请求方式
|
||||
RequestProto string `gorm:"comment:【请求】请求协议" json:"request_proto,omitempty"` //【请求】请求协议
|
||||
RequestUa string `gorm:"comment:【请求】请求UA" json:"request_ua,omitempty"` //【请求】请求UA
|
||||
RequestReferer string `gorm:"comment:【请求】请求referer" json:"request_referer,omitempty"` //【请求】请求referer
|
||||
RequestBody datatypes.JSON `gorm:"type:jsonb;comment:【请求】请求主体" json:"request_body,omitempty"` //【请求】请求主体
|
||||
RequestUrlQuery datatypes.JSON `gorm:"type:jsonb;comment:【请求】请求URL参数" json:"request_url_query,omitempty"` //【请求】请求URL参数
|
||||
RequestIp string `gorm:"index;comment:【请求】请求客户端Ip" json:"request_ip,omitempty"` //【请求】请求客户端Ip
|
||||
RequestIpCountry string `gorm:"index;comment:【请求】请求客户端城市" json:"request_ip_country,omitempty"` //【请求】请求客户端城市
|
||||
RequestIpProvince string `gorm:"index;comment:【请求】请求客户端省份" json:"request_ip_province,omitempty"` //【请求】请求客户端省份
|
||||
RequestIpCity string `gorm:"index;comment:【请求】请求客户端城市" json:"request_ip_city,omitempty"` //【请求】请求客户端城市
|
||||
RequestIpIsp string `gorm:"index;comment:【请求】请求客户端运营商" json:"request_ip_isp,omitempty"` //【请求】请求客户端运营商
|
||||
RequestIpLongitude float64 `gorm:"index;comment:【请求】请求客户端经度" json:"request_ip_longitude,omitempty"` //【请求】请求客户端经度
|
||||
RequestIpLatitude float64 `gorm:"index;comment:【请求】请求客户端纬度" json:"request_ip_latitude,omitempty"` //【请求】请求客户端纬度
|
||||
//RequestIpLocation `gorm:"index;comment:【请求】请求客户端位置" json:"request_ip_location,omitempty"` //【请求】请求客户端位置
|
||||
RequestHeader datatypes.JSON `gorm:"type:jsonb;comment:【请求】请求头" json:"request_header,omitempty"` //【请求】请求头
|
||||
ResponseTime time.Time `gorm:"index;comment:【返回】时间" json:"response_time,omitempty"` //【返回】时间
|
||||
ResponseCode int `gorm:"index;comment:【返回】状态码" json:"response_code,omitempty"` //【返回】状态码
|
||||
ResponseMsg string `gorm:"comment:【返回】描述" json:"response_msg,omitempty"` //【返回】描述
|
||||
ResponseData datatypes.JSON `gorm:"type:jsonb;comment:【返回】数据" json:"response_data,omitempty"` //【返回】数据
|
||||
CostTime int64 `gorm:"comment:【系统】花费时间" json:"cost_time,omitempty"` //【系统】花费时间
|
||||
SystemHostName string `gorm:"index;comment:【系统】主机名" json:"system_host_name,omitempty"` //【系统】主机名
|
||||
SystemInsideIp string `gorm:"index;comment:【系统】内网ip" json:"system_inside_ip,omitempty"` //【系统】内网ip
|
||||
SystemOs string `gorm:"index;comment:【系统】系统类型" json:"system_os,omitempty"` //【系统】系统类型
|
||||
SystemArch string `gorm:"index;comment:【系统】系统架构" json:"system_arch,omitempty"` //【系统】系统架构
|
||||
GoVersion string `gorm:"comment:【程序】Go版本" json:"go_version,omitempty"` //【程序】Go版本
|
||||
SdkVersion string `gorm:"comment:【程序】Sdk版本" json:"sdk_version,omitempty"` //【程序】Sdk版本
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package golog
|
||||
|
||||
import "time"
|
||||
|
||||
// 模型
|
||||
type ginPostgresqlLogString struct {
|
||||
LogId uint `gorm:"primaryKey;comment:【记录】编号" json:"log_id,omitempty"` //【记录】编号
|
||||
TraceId string `gorm:"index;comment:【系统】跟踪编号" json:"trace_id,omitempty"` //【系统】跟踪编号
|
||||
RequestTime time.Time `gorm:"index;comment:【请求】时间" json:"request_time,omitempty"` //【请求】时间
|
||||
RequestUri string `gorm:"comment:【请求】请求链接 域名+路径+参数" json:"request_uri,omitempty"` //【请求】请求链接 域名+路径+参数
|
||||
RequestUrl string `gorm:"comment:【请求】请求链接 域名+路径" json:"request_url,omitempty"` //【请求】请求链接 域名+路径
|
||||
RequestApi string `gorm:"index;comment:【请求】请求接口 路径" json:"request_api,omitempty"` //【请求】请求接口 路径
|
||||
RequestMethod string `gorm:"index;comment:【请求】请求方式" json:"request_method,omitempty"` //【请求】请求方式
|
||||
RequestProto string `gorm:"comment:【请求】请求协议" json:"request_proto,omitempty"` //【请求】请求协议
|
||||
RequestUa string `gorm:"comment:【请求】请求UA" json:"request_ua,omitempty"` //【请求】请求UA
|
||||
RequestReferer string `gorm:"comment:【请求】请求referer" json:"request_referer,omitempty"` //【请求】请求referer
|
||||
RequestBody string `gorm:"comment:【请求】请求主体" json:"request_body,omitempty"` //【请求】请求主体
|
||||
RequestUrlQuery string `gorm:"comment:【请求】请求URL参数" json:"request_url_query,omitempty"` //【请求】请求URL参数
|
||||
RequestIp string `gorm:"index;comment:【请求】请求客户端Ip" json:"request_ip,omitempty"` //【请求】请求客户端Ip
|
||||
RequestIpCountry string `gorm:"index;comment:【请求】请求客户端城市" json:"request_ip_country,omitempty"` //【请求】请求客户端城市
|
||||
RequestIpProvince string `gorm:"index;comment:【请求】请求客户端省份" json:"request_ip_province,omitempty"` //【请求】请求客户端省份
|
||||
RequestIpCity string `gorm:"index;comment:【请求】请求客户端城市" json:"request_ip_city,omitempty"` //【请求】请求客户端城市
|
||||
RequestIpIsp string `gorm:"index;comment:【请求】请求客户端运营商" json:"request_ip_isp,omitempty"` //【请求】请求客户端运营商
|
||||
RequestIpLongitude float64 `gorm:"index;comment:【请求】请求客户端经度" json:"request_ip_longitude,omitempty"` //【请求】请求客户端经度
|
||||
RequestIpLatitude float64 `gorm:"index;comment:【请求】请求客户端纬度" json:"request_ip_latitude,omitempty"` //【请求】请求客户端纬度
|
||||
//RequestIpLocation `gorm:"index;comment:【请求】请求客户端位置" json:"request_ip_location,omitempty"` //【请求】请求客户端位置
|
||||
RequestHeader string `gorm:"comment:【请求】请求头" json:"request_header,omitempty"` //【请求】请求头
|
||||
ResponseTime time.Time `gorm:"index;comment:【返回】时间" json:"response_time,omitempty"` //【返回】时间
|
||||
ResponseCode int `gorm:"index;comment:【返回】状态码" json:"response_code,omitempty"` //【返回】状态码
|
||||
ResponseMsg string `gorm:"comment:【返回】描述" json:"response_msg,omitempty"` //【返回】描述
|
||||
ResponseData string `gorm:"comment:【返回】数据" json:"response_data,omitempty"` //【返回】数据
|
||||
CostTime int64 `gorm:"comment:【系统】花费时间" json:"cost_time,omitempty"` //【系统】花费时间
|
||||
SystemHostName string `gorm:"index;comment:【系统】主机名" json:"system_host_name,omitempty"` //【系统】主机名
|
||||
SystemInsideIp string `gorm:"index;comment:【系统】内网ip" json:"system_inside_ip,omitempty"` //【系统】内网ip
|
||||
SystemOs string `gorm:"index;comment:【系统】系统类型" json:"system_os,omitempty"` //【系统】系统类型
|
||||
SystemArch string `gorm:"index;comment:【系统】系统架构" json:"system_arch,omitempty"` //【系统】系统架构
|
||||
GoVersion string `gorm:"comment:【程序】Go版本" json:"go_version,omitempty"` //【程序】Go版本
|
||||
SdkVersion string `gorm:"comment:【程序】Sdk版本" json:"sdk_version,omitempty"` //【程序】Sdk版本
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-NOW Jinzhu <wosmvp@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
@ -0,0 +1,94 @@
|
||||
# GORM Data Types
|
||||
|
||||
## JSON
|
||||
|
||||
sqlite, mysql, postgres supported
|
||||
|
||||
```go
|
||||
import "gorm.io/datatypes"
|
||||
|
||||
type UserWithJSON struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
Attributes datatypes.JSON
|
||||
}
|
||||
|
||||
DB.Create(&User{
|
||||
Name: "json-1",
|
||||
Attributes: datatypes.JSON([]byte(`{"name": "jinzhu", "age": 18, "tags": ["tag1", "tag2"], "orgs": {"orga": "orga"}}`)),
|
||||
}
|
||||
|
||||
// Check JSON has keys
|
||||
datatypes.JSONQuery("attributes").HasKey(value, keys...)
|
||||
|
||||
db.Find(&user, datatypes.JSONQuery("attributes").HasKey("role"))
|
||||
db.Find(&user, datatypes.JSONQuery("attributes").HasKey("orgs", "orga"))
|
||||
// MySQL
|
||||
// SELECT * FROM `users` WHERE JSON_EXTRACT(`attributes`, '$.role') IS NOT NULL
|
||||
// SELECT * FROM `users` WHERE JSON_EXTRACT(`attributes`, '$.orgs.orga') IS NOT NULL
|
||||
|
||||
// PostgreSQL
|
||||
// SELECT * FROM "user" WHERE "attributes"::jsonb ? 'role'
|
||||
// SELECT * FROM "user" WHERE "attributes"::jsonb -> 'orgs' ? 'orga'
|
||||
|
||||
|
||||
// Check JSON extract value from keys equal to value
|
||||
datatypes.JSONQuery("attributes").Equals(value, keys...)
|
||||
|
||||
DB.First(&user, datatypes.JSONQuery("attributes").Equals("jinzhu", "name"))
|
||||
DB.First(&user, datatypes.JSONQuery("attributes").Equals("orgb", "orgs", "orgb"))
|
||||
// MySQL
|
||||
// SELECT * FROM `user` WHERE JSON_EXTRACT(`attributes`, '$.name') = "jinzhu"
|
||||
// SELECT * FROM `user` WHERE JSON_EXTRACT(`attributes`, '$.orgs.orgb') = "orgb"
|
||||
|
||||
// PostgreSQL
|
||||
// SELECT * FROM "user" WHERE json_extract_path_text("attributes"::json,'name') = 'jinzhu'
|
||||
// SELECT * FROM "user" WHERE json_extract_path_text("attributes"::json,'orgs','orgb') = 'orgb'
|
||||
```
|
||||
|
||||
NOTE: SQlite need to build with `json1` tag, e.g: `go build --tags json1`, refer https://github.com/mattn/go-sqlite3#usage
|
||||
|
||||
## Date
|
||||
|
||||
```go
|
||||
import "gorm.io/datatypes"
|
||||
|
||||
type UserWithDate struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
Date datatypes.Date
|
||||
}
|
||||
|
||||
user := UserWithDate{Name: "jinzhu", Date: datatypes.Date(time.Now())}
|
||||
DB.Create(&user)
|
||||
// INSERT INTO `user_with_dates` (`name`,`date`) VALUES ("jinzhu","2020-07-17 00:00:00")
|
||||
|
||||
DB.First(&result, "name = ? AND date = ?", "jinzhu", datatypes.Date(curTime))
|
||||
// SELECT * FROM user_with_dates WHERE name = "jinzhu" AND date = "2020-07-17 00:00:00" ORDER BY `user_with_dates`.`id` LIMIT 1
|
||||
```
|
||||
|
||||
## Time
|
||||
|
||||
MySQL, PostgreSQL, SQLite, SQLServer are supported.
|
||||
|
||||
Time with nanoseconds is supported for some databases which support for time with fractional second scale.
|
||||
|
||||
```go
|
||||
import "gorm.io/datatypes"
|
||||
|
||||
type UserWithTime struct {
|
||||
gorm.Model
|
||||
Name string
|
||||
Time datatypes.Time
|
||||
}
|
||||
|
||||
user := UserWithTime{Name: "jinzhu", Time: datatypes.NewTime(1, 2, 3, 0)}
|
||||
DB.Create(&user)
|
||||
// INSERT INTO `user_with_times` (`name`,`time`) VALUES ("jinzhu","01:02:03")
|
||||
|
||||
DB.First(&result, "name = ? AND time = ?", "jinzhu", datatypes.NewTime(1, 2, 3, 0))
|
||||
// SELECT * FROM user_with_times WHERE name = "jinzhu" AND time = "01:02:03" ORDER BY `user_with_times`.`id` LIMIT 1
|
||||
```
|
||||
|
||||
NOTE: If the current using database is SQLite, the field column type is defined as `TEXT` type
|
||||
when GORM AutoMigrate because SQLite doesn't have time type.
|
@ -0,0 +1,42 @@
|
||||
package datatypes
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Date time.Time
|
||||
|
||||
func (date *Date) Scan(value interface{}) (err error) {
|
||||
nullTime := &sql.NullTime{}
|
||||
err = nullTime.Scan(value)
|
||||
*date = Date(nullTime.Time)
|
||||
return
|
||||
}
|
||||
|
||||
func (date Date) Value() (driver.Value, error) {
|
||||
y, m, d := time.Time(date).Date()
|
||||
return time.Date(y, m, d, 0, 0, 0, 0, time.Time(date).Location()), nil
|
||||
}
|
||||
|
||||
// GormDataType gorm common data type
|
||||
func (date Date) GormDataType() string {
|
||||
return "date"
|
||||
}
|
||||
|
||||
func (date Date) GobEncode() ([]byte, error) {
|
||||
return time.Time(date).GobEncode()
|
||||
}
|
||||
|
||||
func (date *Date) GobDecode(b []byte) error {
|
||||
return (*time.Time)(date).GobDecode(b)
|
||||
}
|
||||
|
||||
func (date Date) MarshalJSON() ([]byte, error) {
|
||||
return time.Time(date).MarshalJSON()
|
||||
}
|
||||
|
||||
func (date *Date) UnmarshalJSON(b []byte) error {
|
||||
return (*time.Time)(date).UnmarshalJSON(b)
|
||||
}
|
@ -0,0 +1,280 @@
|
||||
package datatypes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
// JSON defined JSON data type, need to implements driver.Valuer, sql.Scanner interface
|
||||
type JSON json.RawMessage
|
||||
|
||||
// Value return json value, implement driver.Valuer interface
|
||||
func (j JSON) Value() (driver.Value, error) {
|
||||
if len(j) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return string(j), nil
|
||||
}
|
||||
|
||||
// Scan scan value into Jsonb, implements sql.Scanner interface
|
||||
func (j *JSON) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
*j = JSON("null")
|
||||
return nil
|
||||
}
|
||||
var bytes []byte
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
if len(v) > 0 {
|
||||
bytes = make([]byte, len(v))
|
||||
copy(bytes, v)
|
||||
}
|
||||
case string:
|
||||
bytes = []byte(v)
|
||||
default:
|
||||
return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
|
||||
}
|
||||
|
||||
result := json.RawMessage(bytes)
|
||||
*j = JSON(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON to output non base64 encoded []byte
|
||||
func (j JSON) MarshalJSON() ([]byte, error) {
|
||||
return json.RawMessage(j).MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON to deserialize []byte
|
||||
func (j *JSON) UnmarshalJSON(b []byte) error {
|
||||
result := json.RawMessage{}
|
||||
err := result.UnmarshalJSON(b)
|
||||
*j = JSON(result)
|
||||
return err
|
||||
}
|
||||
|
||||
func (j JSON) String() string {
|
||||
return string(j)
|
||||
}
|
||||
|
||||
// GormDataType gorm common data type
|
||||
func (JSON) GormDataType() string {
|
||||
return "json"
|
||||
}
|
||||
|
||||
// GormDBDataType gorm db data type
|
||||
func (JSON) GormDBDataType(db *gorm.DB, field *schema.Field) string {
|
||||
switch db.Dialector.Name() {
|
||||
case "sqlite":
|
||||
return "JSON"
|
||||
case "mysql":
|
||||
return "JSON"
|
||||
case "postgres":
|
||||
return "JSONB"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (js JSON) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
|
||||
if len(js) == 0 {
|
||||
return gorm.Expr("NULL")
|
||||
}
|
||||
|
||||
data, _ := js.MarshalJSON()
|
||||
|
||||
switch db.Dialector.Name() {
|
||||
case "mysql":
|
||||
if v, ok := db.Dialector.(*mysql.Dialector); ok && !strings.Contains(v.ServerVersion, "MariaDB") {
|
||||
return gorm.Expr("CAST(? AS JSON)", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
return gorm.Expr("?", string(data))
|
||||
}
|
||||
|
||||
// JSONQueryExpression json query expression, implements clause.Expression interface to use as querier
|
||||
type JSONQueryExpression struct {
|
||||
column string
|
||||
keys []string
|
||||
hasKeys bool
|
||||
equals bool
|
||||
equalsValue interface{}
|
||||
extract bool
|
||||
path string
|
||||
}
|
||||
|
||||
// JSONQuery query column as json
|
||||
func JSONQuery(column string) *JSONQueryExpression {
|
||||
return &JSONQueryExpression{column: column}
|
||||
}
|
||||
|
||||
// Extract extract json with path
|
||||
func (jsonQuery *JSONQueryExpression) Extract(path string) *JSONQueryExpression {
|
||||
jsonQuery.extract = true
|
||||
jsonQuery.path = path
|
||||
return jsonQuery
|
||||
}
|
||||
|
||||
// HasKey returns clause.Expression
|
||||
func (jsonQuery *JSONQueryExpression) HasKey(keys ...string) *JSONQueryExpression {
|
||||
jsonQuery.keys = keys
|
||||
jsonQuery.hasKeys = true
|
||||
return jsonQuery
|
||||
}
|
||||
|
||||
// Keys returns clause.Expression
|
||||
func (jsonQuery *JSONQueryExpression) Equals(value interface{}, keys ...string) *JSONQueryExpression {
|
||||
jsonQuery.keys = keys
|
||||
jsonQuery.equals = true
|
||||
jsonQuery.equalsValue = value
|
||||
return jsonQuery
|
||||
}
|
||||
|
||||
// Build implements clause.Expression
|
||||
func (jsonQuery *JSONQueryExpression) Build(builder clause.Builder) {
|
||||
if stmt, ok := builder.(*gorm.Statement); ok {
|
||||
switch stmt.Dialector.Name() {
|
||||
case "mysql", "sqlite":
|
||||
switch {
|
||||
case jsonQuery.extract:
|
||||
builder.WriteString("JSON_EXTRACT(")
|
||||
builder.WriteQuoted(jsonQuery.column)
|
||||
builder.WriteByte(',')
|
||||
builder.AddVar(stmt, jsonQuery.path)
|
||||
builder.WriteString(")")
|
||||
case jsonQuery.hasKeys:
|
||||
if len(jsonQuery.keys) > 0 {
|
||||
builder.WriteString("JSON_EXTRACT(")
|
||||
builder.WriteQuoted(jsonQuery.column)
|
||||
builder.WriteByte(',')
|
||||
builder.AddVar(stmt, jsonQueryJoin(jsonQuery.keys))
|
||||
builder.WriteString(") IS NOT NULL")
|
||||
}
|
||||
case jsonQuery.equals:
|
||||
if len(jsonQuery.keys) > 0 {
|
||||
builder.WriteString("JSON_EXTRACT(")
|
||||
builder.WriteQuoted(jsonQuery.column)
|
||||
builder.WriteByte(',')
|
||||
builder.AddVar(stmt, jsonQueryJoin(jsonQuery.keys))
|
||||
builder.WriteString(") = ")
|
||||
if value, ok := jsonQuery.equalsValue.(bool); ok {
|
||||
builder.WriteString(strconv.FormatBool(value))
|
||||
} else {
|
||||
stmt.AddVar(builder, jsonQuery.equalsValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "postgres":
|
||||
switch {
|
||||
case jsonQuery.hasKeys:
|
||||
if len(jsonQuery.keys) > 0 {
|
||||
stmt.WriteQuoted(jsonQuery.column)
|
||||
stmt.WriteString("::jsonb")
|
||||
for _, key := range jsonQuery.keys[0 : len(jsonQuery.keys)-1] {
|
||||
stmt.WriteString(" -> ")
|
||||
stmt.AddVar(builder, key)
|
||||
}
|
||||
|
||||
stmt.WriteString(" ? ")
|
||||
stmt.AddVar(builder, jsonQuery.keys[len(jsonQuery.keys)-1])
|
||||
}
|
||||
case jsonQuery.equals:
|
||||
if len(jsonQuery.keys) > 0 {
|
||||
builder.WriteString(fmt.Sprintf("json_extract_path_text(%v::json,", stmt.Quote(jsonQuery.column)))
|
||||
|
||||
for idx, key := range jsonQuery.keys {
|
||||
if idx > 0 {
|
||||
builder.WriteByte(',')
|
||||
}
|
||||
stmt.AddVar(builder, key)
|
||||
}
|
||||
builder.WriteString(") = ")
|
||||
|
||||
if _, ok := jsonQuery.equalsValue.(string); ok {
|
||||
stmt.AddVar(builder, jsonQuery.equalsValue)
|
||||
} else {
|
||||
stmt.AddVar(builder, fmt.Sprint(jsonQuery.equalsValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// JSONOverlapsExpression JSON_OVERLAPS expression, implements clause.Expression interface to use as querier
|
||||
type JSONOverlapsExpression struct {
|
||||
column clause.Expression
|
||||
val string
|
||||
}
|
||||
|
||||
// JSONOverlaps query column as json
|
||||
func JSONOverlaps(column clause.Expression, value string) *JSONOverlapsExpression {
|
||||
return &JSONOverlapsExpression{
|
||||
column: column,
|
||||
val: value,
|
||||
}
|
||||
}
|
||||
|
||||
// Build implements clause.Expression
|
||||
// only mysql support JSON_OVERLAPS
|
||||
func (json *JSONOverlapsExpression) Build(builder clause.Builder) {
|
||||
if stmt, ok := builder.(*gorm.Statement); ok {
|
||||
switch stmt.Dialector.Name() {
|
||||
case "mysql":
|
||||
builder.WriteString("JSON_OVERLAPS(")
|
||||
json.column.Build(builder)
|
||||
builder.WriteString(",")
|
||||
builder.AddVar(stmt, json.val)
|
||||
builder.WriteString(")")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type columnExpression string
|
||||
|
||||
func Column(col string) columnExpression {
|
||||
return columnExpression(col)
|
||||
}
|
||||
|
||||
func (col columnExpression) Build(builder clause.Builder) {
|
||||
if stmt, ok := builder.(*gorm.Statement); ok {
|
||||
switch stmt.Dialector.Name() {
|
||||
case "mysql", "sqlite", "postgres":
|
||||
builder.WriteString(stmt.Quote(string(col)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const prefix = "$."
|
||||
|
||||
func jsonQueryJoin(keys []string) string {
|
||||
if len(keys) == 1 {
|
||||
return prefix + keys[0]
|
||||
}
|
||||
|
||||
n := len(prefix)
|
||||
n += len(keys) - 1
|
||||
for i := 0; i < len(keys); i++ {
|
||||
n += len(keys[i])
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.Grow(n)
|
||||
b.WriteString(prefix)
|
||||
b.WriteString(keys[0])
|
||||
for _, key := range keys[1:] {
|
||||
b.WriteString(".")
|
||||
b.WriteString(key)
|
||||
}
|
||||
return b.String()
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package datatypes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
// JSONMap defined JSON data type, need to implements driver.Valuer, sql.Scanner interface
|
||||
type JSONMap map[string]interface{}
|
||||
|
||||
// Value return json value, implement driver.Valuer interface
|
||||
func (m JSONMap) Value() (driver.Value, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
ba, err := m.MarshalJSON()
|
||||
return string(ba), err
|
||||
}
|
||||
|
||||
// Scan scan value into Jsonb, implements sql.Scanner interface
|
||||
func (m *JSONMap) Scan(val interface{}) error {
|
||||
if val == nil {
|
||||
*m = make(JSONMap)
|
||||
return nil
|
||||
}
|
||||
var ba []byte
|
||||
switch v := val.(type) {
|
||||
case []byte:
|
||||
ba = v
|
||||
case string:
|
||||
ba = []byte(v)
|
||||
default:
|
||||
return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", val))
|
||||
}
|
||||
t := map[string]interface{}{}
|
||||
err := json.Unmarshal(ba, &t)
|
||||
*m = t
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalJSON to output non base64 encoded []byte
|
||||
func (m JSONMap) MarshalJSON() ([]byte, error) {
|
||||
if m == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
t := (map[string]interface{})(m)
|
||||
return json.Marshal(t)
|
||||
}
|
||||
|
||||
// UnmarshalJSON to deserialize []byte
|
||||
func (m *JSONMap) UnmarshalJSON(b []byte) error {
|
||||
t := map[string]interface{}{}
|
||||
err := json.Unmarshal(b, &t)
|
||||
*m = JSONMap(t)
|
||||
return err
|
||||
}
|
||||
|
||||
// GormDataType gorm common data type
|
||||
func (m JSONMap) GormDataType() string {
|
||||
return "jsonmap"
|
||||
}
|
||||
|
||||
// GormDBDataType gorm db data type
|
||||
func (JSONMap) GormDBDataType(db *gorm.DB, field *schema.Field) string {
|
||||
switch db.Dialector.Name() {
|
||||
case "sqlite":
|
||||
return "JSON"
|
||||
case "mysql":
|
||||
return "JSON"
|
||||
case "postgres":
|
||||
return "JSONB"
|
||||
case "sqlserver":
|
||||
return "NVARCHAR(MAX)"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (jm JSONMap) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
|
||||
data, _ := jm.MarshalJSON()
|
||||
switch db.Dialector.Name() {
|
||||
case "mysql":
|
||||
if v, ok := db.Dialector.(*mysql.Dialector); ok && !strings.Contains(v.ServerVersion, "MariaDB") {
|
||||
return gorm.Expr("CAST(? AS JSON)", string(data))
|
||||
}
|
||||
}
|
||||
return gorm.Expr("?", string(data))
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
dialects=("postgres" "postgres_simple" "mysql" "mssql" "sqlite")
|
||||
|
||||
for dialect in "${dialects[@]}" ; do
|
||||
if [ "$GORM_DIALECT" = "" ] || [ "$GORM_DIALECT" = "${dialect}" ]
|
||||
then
|
||||
GORM_DIALECT=${dialect} go test --tags "json1"
|
||||
fi
|
||||
done
|
@ -0,0 +1,123 @@
|
||||
package datatypes
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
// Time is time data type.
|
||||
type Time time.Duration
|
||||
|
||||
// NewTime is a constructor for Time and returns new Time.
|
||||
func NewTime(hour, min, sec, nsec int) Time {
|
||||
return newTime(hour, min, sec, nsec)
|
||||
}
|
||||
|
||||
func newTime(hour, min, sec, nsec int) Time {
|
||||
return Time(
|
||||
time.Duration(hour)*time.Hour +
|
||||
time.Duration(min)*time.Minute +
|
||||
time.Duration(sec)*time.Second +
|
||||
time.Duration(nsec)*time.Nanosecond,
|
||||
)
|
||||
}
|
||||
|
||||
// GormDataType returns gorm common data type. This type is used for the field's column type.
|
||||
func (Time) GormDataType() string {
|
||||
return "time"
|
||||
}
|
||||
|
||||
// GormDBDataType returns gorm DB data type based on the current using database.
|
||||
func (Time) GormDBDataType(db *gorm.DB, field *schema.Field) string {
|
||||
switch db.Dialector.Name() {
|
||||
case "mysql":
|
||||
return "TIME"
|
||||
case "postgres":
|
||||
return "TIME"
|
||||
case "sqlserver":
|
||||
return "TIME"
|
||||
case "sqlite":
|
||||
return "TEXT"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Scan implements sql.Scanner interface and scans value into Time,
|
||||
func (t *Time) Scan(src interface{}) error {
|
||||
switch v := src.(type) {
|
||||
case []byte:
|
||||
t.setFromString(string(v))
|
||||
case string:
|
||||
t.setFromString(v)
|
||||
case time.Time:
|
||||
t.setFromTime(v)
|
||||
default:
|
||||
return errors.New(fmt.Sprintf("failed to scan value: %v", v))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Time) setFromString(str string) {
|
||||
var h, m, s, n int
|
||||
fmt.Sscanf(str, "%02d:%02d:%02d.%09d", &h, &m, &s, &n)
|
||||
*t = newTime(h, m, s, n)
|
||||
}
|
||||
|
||||
func (t *Time) setFromTime(src time.Time) {
|
||||
*t = newTime(src.Hour(), src.Minute(), src.Second(), src.Nanosecond())
|
||||
}
|
||||
|
||||
// Value implements driver.Valuer interface and returns string format of Time.
|
||||
func (t Time) Value() (driver.Value, error) {
|
||||
return t.String(), nil
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer interface.
|
||||
func (t Time) String() string {
|
||||
if nsec := t.nanoseconds(); nsec > 0 {
|
||||
return fmt.Sprintf("%02d:%02d:%02d.%09d", t.hours(), t.minutes(), t.seconds(), nsec)
|
||||
} else {
|
||||
// omit nanoseconds unless any value is specified
|
||||
return fmt.Sprintf("%02d:%02d:%02d", t.hours(), t.minutes(), t.seconds())
|
||||
}
|
||||
}
|
||||
|
||||
func (t Time) hours() int {
|
||||
return int(time.Duration(t).Truncate(time.Hour).Hours())
|
||||
}
|
||||
|
||||
func (t Time) minutes() int {
|
||||
return int((time.Duration(t) % time.Hour).Truncate(time.Minute).Minutes())
|
||||
}
|
||||
|
||||
func (t Time) seconds() int {
|
||||
return int((time.Duration(t) % time.Minute).Truncate(time.Second).Seconds())
|
||||
}
|
||||
|
||||
func (t Time) nanoseconds() int {
|
||||
return int((time.Duration(t) % time.Second).Nanoseconds())
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler to convert Time to json serialization.
|
||||
func (t Time) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(t.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler to deserialize json data.
|
||||
func (t *Time) UnmarshalJSON(data []byte) error {
|
||||
// ignore null
|
||||
if string(data) == "null" {
|
||||
return nil
|
||||
}
|
||||
t.setFromString(strings.Trim(string(data), `"`))
|
||||
return nil
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package datatypes
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
type URL url.URL
|
||||
|
||||
func (u URL) Value() (driver.Value, error) {
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
func (u *URL) Scan(value interface{}) error {
|
||||
var us string
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
us = string(v)
|
||||
case string:
|
||||
us = v
|
||||
default:
|
||||
return errors.New(fmt.Sprint("Failed to parse URL:", value))
|
||||
}
|
||||
uu, err := url.Parse(us)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*u = URL(*uu)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (URL) GormDataType() string {
|
||||
return "url"
|
||||
}
|
||||
|
||||
func (URL) GormDBDataType(db *gorm.DB, field *schema.Field) string {
|
||||
return "TEXT"
|
||||
}
|
||||
|
||||
func (u *URL) String() string {
|
||||
return (*url.URL)(u).String()
|
||||
}
|
||||
|
||||
func (u URL) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(u.String())
|
||||
}
|
||||
|
||||
func (u *URL) UnmarshalJSON(data []byte) error {
|
||||
// ignore null
|
||||
if string(data) == "null" {
|
||||
return nil
|
||||
}
|
||||
uu, err := url.Parse(strings.Trim(string(data), `"'`))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*u = URL(*uu)
|
||||
return nil
|
||||
}
|
Loading…
Reference in new issue