parent
83ef4742cb
commit
7c835284e1
@ -0,0 +1,25 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
/.tags
|
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Bastien Gysler
|
||||||
|
|
||||||
|
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,82 @@
|
|||||||
|
# goxml2json [![CircleCI](https://circleci.com/gh/basgys/goxml2json.svg?style=svg)](https://circleci.com/gh/basgys/goxml2json)
|
||||||
|
|
||||||
|
Go package that converts XML to JSON
|
||||||
|
|
||||||
|
### Install
|
||||||
|
|
||||||
|
go get -u github.com/basgys/goxml2json
|
||||||
|
|
||||||
|
### Importing
|
||||||
|
|
||||||
|
import github.com/basgys/goxml2json
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
**Code example**
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
xj "github.com/basgys/goxml2json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// xml is an io.Reader
|
||||||
|
xml := strings.NewReader(`<?xml version="1.0" encoding="UTF-8"?><hello>world</hello>`)
|
||||||
|
json, err := xj.Convert(xml)
|
||||||
|
if err != nil {
|
||||||
|
panic("That's embarrassing...")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(json.String())
|
||||||
|
// {"hello": "world"}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
**Input**
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<osm version="0.6" generator="CGImap 0.0.2">
|
||||||
|
<bounds minlat="54.0889580" minlon="12.2487570" maxlat="54.0913900" maxlon="12.2524800"/>
|
||||||
|
<foo>bar</foo>
|
||||||
|
</osm>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"osm": {
|
||||||
|
"-version": "0.6",
|
||||||
|
"-generator": "CGImap 0.0.2",
|
||||||
|
"bounds": {
|
||||||
|
"-minlat": "54.0889580",
|
||||||
|
"-minlon": "12.2487570",
|
||||||
|
"-maxlat": "54.0913900",
|
||||||
|
"-maxlon": "12.2524800"
|
||||||
|
},
|
||||||
|
"foo": "bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
Feel free to contribute to this project if you want to fix/extend/improve it.
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
|
||||||
|
- [DirectX](https://github.com/directx)
|
||||||
|
- [samuelhug](https://github.com/samuelhug)
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
|
||||||
|
* Extract data types in JSON (numbers, boolean, ...)
|
||||||
|
* Categorise errors
|
||||||
|
* Option to prettify the JSON output
|
||||||
|
* Benchmark
|
@ -0,0 +1,25 @@
|
|||||||
|
package xml2json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Convert converts the given XML document to JSON
|
||||||
|
func Convert(r io.Reader) (*bytes.Buffer, error) {
|
||||||
|
// Decode XML document
|
||||||
|
root := &Node{}
|
||||||
|
err := NewDecoder(r).Decode(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then encode it in JSON
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err = NewEncoder(buf).Encode(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
@ -0,0 +1,140 @@
|
|||||||
|
package xml2json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"golang.org/x/net/html/charset"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
attrPrefix = "-"
|
||||||
|
contentPrefix = "#"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Decoder reads and decodes XML objects from an input stream.
|
||||||
|
type Decoder struct {
|
||||||
|
r io.Reader
|
||||||
|
err error
|
||||||
|
attributePrefix string
|
||||||
|
contentPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
type element struct {
|
||||||
|
parent *element
|
||||||
|
n *Node
|
||||||
|
label string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) SetAttributePrefix(prefix string) {
|
||||||
|
dec.attributePrefix = prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) SetContentPrefix(prefix string) {
|
||||||
|
dec.contentPrefix = prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) DecodeWithCustomPrefixes(root *Node, contentPrefix string, attributePrefix string) error {
|
||||||
|
dec.contentPrefix = contentPrefix
|
||||||
|
dec.attributePrefix = attributePrefix
|
||||||
|
return dec.Decode(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoder returns a new decoder that reads from r.
|
||||||
|
func NewDecoder(r io.Reader) *Decoder {
|
||||||
|
return &Decoder{r: r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode reads the next JSON-encoded value from its
|
||||||
|
// input and stores it in the value pointed to by v.
|
||||||
|
func (dec *Decoder) Decode(root *Node) error {
|
||||||
|
|
||||||
|
if dec.contentPrefix == "" {
|
||||||
|
dec.contentPrefix = contentPrefix
|
||||||
|
}
|
||||||
|
if dec.attributePrefix == "" {
|
||||||
|
dec.attributePrefix = attrPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlDec := xml.NewDecoder(dec.r)
|
||||||
|
|
||||||
|
// That will convert the charset if the provided XML is non-UTF-8
|
||||||
|
xmlDec.CharsetReader = charset.NewReaderLabel
|
||||||
|
|
||||||
|
// Create first element from the root node
|
||||||
|
elem := &element{
|
||||||
|
parent: nil,
|
||||||
|
n: root,
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
t, _ := xmlDec.Token()
|
||||||
|
if t == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch se := t.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
// Build new a new current element and link it to its parent
|
||||||
|
elem = &element{
|
||||||
|
parent: elem,
|
||||||
|
n: &Node{},
|
||||||
|
label: se.Name.Local,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract attributes as children
|
||||||
|
for _, a := range se.Attr {
|
||||||
|
elem.n.AddChild(dec.attributePrefix+a.Name.Local, &Node{Data: a.Value})
|
||||||
|
}
|
||||||
|
case xml.CharData:
|
||||||
|
// Extract XML data (if any)
|
||||||
|
elem.n.Data = trimNonGraphic(string(xml.CharData(se)))
|
||||||
|
case xml.EndElement:
|
||||||
|
// And add it to its parent list
|
||||||
|
if elem.parent != nil {
|
||||||
|
elem.parent.n.AddChild(elem.label, elem.n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then change the current element to its parent
|
||||||
|
elem = elem.parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// trimNonGraphic returns a slice of the string s, with all leading and trailing
|
||||||
|
// non graphic characters and spaces removed.
|
||||||
|
//
|
||||||
|
// Graphic characters include letters, marks, numbers, punctuation, symbols,
|
||||||
|
// and spaces, from categories L, M, N, P, S, Zs.
|
||||||
|
// Spacing characters are set by category Z and property Pattern_White_Space.
|
||||||
|
func trimNonGraphic(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
var first *int
|
||||||
|
var last int
|
||||||
|
for i, r := range []rune(s) {
|
||||||
|
if !unicode.IsGraphic(r) || unicode.IsSpace(r) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if first == nil {
|
||||||
|
f := i // copy i
|
||||||
|
first = &f
|
||||||
|
last = i
|
||||||
|
} else {
|
||||||
|
last = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If first is nil, it means there are no graphic characters
|
||||||
|
if first == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return string([]rune(s)[*first : last+1])
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
// Package xml2json is an XML to JSON converter
|
||||||
|
package xml2json
|
@ -0,0 +1,197 @@
|
|||||||
|
package xml2json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Encoder writes JSON objects to an output stream.
|
||||||
|
type Encoder struct {
|
||||||
|
w io.Writer
|
||||||
|
err error
|
||||||
|
contentPrefix string
|
||||||
|
attributePrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoder returns a new encoder that writes to w.
|
||||||
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
|
return &Encoder{w: w}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) SetAttributePrefix(prefix string) {
|
||||||
|
enc.attributePrefix = prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) SetContentPrefix(prefix string) {
|
||||||
|
enc.contentPrefix = prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) EncodeWithCustomPrefixes(root *Node, contentPrefix string, attributePrefix string) error {
|
||||||
|
enc.contentPrefix = contentPrefix
|
||||||
|
enc.attributePrefix = attributePrefix
|
||||||
|
return enc.Encode(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode writes the JSON encoding of v to the stream
|
||||||
|
func (enc *Encoder) Encode(root *Node) error {
|
||||||
|
if enc.err != nil {
|
||||||
|
return enc.err
|
||||||
|
}
|
||||||
|
if root == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if enc.contentPrefix == "" {
|
||||||
|
enc.contentPrefix = contentPrefix
|
||||||
|
}
|
||||||
|
if enc.attributePrefix == "" {
|
||||||
|
enc.attributePrefix = attrPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
enc.err = enc.format(root, 0)
|
||||||
|
|
||||||
|
// Terminate each value with a newline.
|
||||||
|
// This makes the output look a little nicer
|
||||||
|
// when debugging, and some kind of space
|
||||||
|
// is required if the encoded value was a number,
|
||||||
|
// so that the reader knows there aren't more
|
||||||
|
// digits coming.
|
||||||
|
enc.write("\n")
|
||||||
|
|
||||||
|
return enc.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) format(n *Node, lvl int) error {
|
||||||
|
if n.IsComplex() {
|
||||||
|
enc.write("{")
|
||||||
|
|
||||||
|
// Add data as an additional attibute (if any)
|
||||||
|
if len(n.Data) > 0 {
|
||||||
|
enc.write("\"")
|
||||||
|
enc.write(enc.contentPrefix)
|
||||||
|
enc.write("content")
|
||||||
|
enc.write("\": ")
|
||||||
|
enc.write(sanitiseString(n.Data))
|
||||||
|
enc.write(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
tot := len(n.Children)
|
||||||
|
for label, children := range n.Children {
|
||||||
|
enc.write("\"")
|
||||||
|
enc.write(label)
|
||||||
|
enc.write("\": ")
|
||||||
|
|
||||||
|
if len(children) > 1 {
|
||||||
|
// Array
|
||||||
|
enc.write("[")
|
||||||
|
for j, c := range children {
|
||||||
|
enc.format(c, lvl+1)
|
||||||
|
|
||||||
|
if j < len(children)-1 {
|
||||||
|
enc.write(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enc.write("]")
|
||||||
|
} else {
|
||||||
|
// Map
|
||||||
|
enc.format(children[0], lvl+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < tot-1 {
|
||||||
|
enc.write(", ")
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
enc.write("}")
|
||||||
|
} else {
|
||||||
|
// TODO : Extract data type
|
||||||
|
enc.write(sanitiseString(n.Data))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) write(s string) {
|
||||||
|
enc.w.Write([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://golang.org/src/encoding/json/encode.go?s=5584:5627#L788
|
||||||
|
var hex = "0123456789abcdef"
|
||||||
|
|
||||||
|
func sanitiseString(s string) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
buf.WriteByte('"')
|
||||||
|
start := 0
|
||||||
|
for i := 0; i < len(s); {
|
||||||
|
if b := s[i]; b < utf8.RuneSelf {
|
||||||
|
if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if start < i {
|
||||||
|
buf.WriteString(s[start:i])
|
||||||
|
}
|
||||||
|
switch b {
|
||||||
|
case '\\', '"':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte(b)
|
||||||
|
case '\n':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte('n')
|
||||||
|
case '\r':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte('r')
|
||||||
|
case '\t':
|
||||||
|
buf.WriteByte('\\')
|
||||||
|
buf.WriteByte('t')
|
||||||
|
default:
|
||||||
|
// This encodes bytes < 0x20 except for \n and \r,
|
||||||
|
// as well as <, > and &. The latter are escaped because they
|
||||||
|
// can lead to security holes when user-controlled strings
|
||||||
|
// are rendered into JSON and served to some browsers.
|
||||||
|
buf.WriteString(`\u00`)
|
||||||
|
buf.WriteByte(hex[b>>4])
|
||||||
|
buf.WriteByte(hex[b&0xF])
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c, size := utf8.DecodeRuneInString(s[i:])
|
||||||
|
if c == utf8.RuneError && size == 1 {
|
||||||
|
if start < i {
|
||||||
|
buf.WriteString(s[start:i])
|
||||||
|
}
|
||||||
|
buf.WriteString(`\ufffd`)
|
||||||
|
i += size
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// U+2028 is LINE SEPARATOR.
|
||||||
|
// U+2029 is PARAGRAPH SEPARATOR.
|
||||||
|
// They are both technically valid characters in JSON strings,
|
||||||
|
// but don't work in JSONP, which has to be evaluated as JavaScript,
|
||||||
|
// and can lead to security holes there. It is valid JSON to
|
||||||
|
// escape them, so we do so unconditionally.
|
||||||
|
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
|
||||||
|
if c == '\u2028' || c == '\u2029' {
|
||||||
|
if start < i {
|
||||||
|
buf.WriteString(s[start:i])
|
||||||
|
}
|
||||||
|
buf.WriteString(`\u202`)
|
||||||
|
buf.WriteByte(hex[c&0xF])
|
||||||
|
i += size
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i += size
|
||||||
|
}
|
||||||
|
if start < len(s) {
|
||||||
|
buf.WriteString(s[start:])
|
||||||
|
}
|
||||||
|
buf.WriteByte('"')
|
||||||
|
return buf.String()
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package xml2json
|
||||||
|
|
||||||
|
// Node is a data element on a tree
|
||||||
|
type Node struct {
|
||||||
|
Children map[string]Nodes
|
||||||
|
Data string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nodes is a list of nodes
|
||||||
|
type Nodes []*Node
|
||||||
|
|
||||||
|
// AddChild appends a node to the list of children
|
||||||
|
func (n *Node) AddChild(s string, c *Node) {
|
||||||
|
// Lazy lazy
|
||||||
|
if n.Children == nil {
|
||||||
|
n.Children = map[string]Nodes{}
|
||||||
|
}
|
||||||
|
|
||||||
|
n.Children[s] = append(n.Children[s], c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsComplex returns whether it is a complex type (has children)
|
||||||
|
func (n *Node) IsComplex() bool {
|
||||||
|
return len(n.Children) > 0
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
Copyright 2014 astaxie
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
@ -0,0 +1,6 @@
|
|||||||
|
package clauses
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExprSep = "__"
|
||||||
|
ExprDot = "."
|
||||||
|
)
|
@ -0,0 +1,104 @@
|
|||||||
|
package order_clause
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Sort int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
None Sort = 0
|
||||||
|
Ascending Sort = 1
|
||||||
|
Descending Sort = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type Option func(order *Order)
|
||||||
|
|
||||||
|
type Order struct {
|
||||||
|
column string
|
||||||
|
sort Sort
|
||||||
|
isRaw bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Clause(options ...Option) *Order {
|
||||||
|
o := &Order{}
|
||||||
|
for _, option := range options {
|
||||||
|
option(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Order) GetColumn() string {
|
||||||
|
return o.column
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Order) GetSort() Sort {
|
||||||
|
return o.sort
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Order) SortString() string {
|
||||||
|
switch o.GetSort() {
|
||||||
|
case Ascending:
|
||||||
|
return "ASC"
|
||||||
|
case Descending:
|
||||||
|
return "DESC"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ``
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Order) IsRaw() bool {
|
||||||
|
return o.isRaw
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseOrder(expressions ...string) []*Order {
|
||||||
|
var orders []*Order
|
||||||
|
for _, expression := range expressions {
|
||||||
|
sort := Ascending
|
||||||
|
column := strings.ReplaceAll(expression, clauses.ExprSep, clauses.ExprDot)
|
||||||
|
if column[0] == '-' {
|
||||||
|
sort = Descending
|
||||||
|
column = column[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
orders = append(orders, &Order{
|
||||||
|
column: column,
|
||||||
|
sort: sort,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return orders
|
||||||
|
}
|
||||||
|
|
||||||
|
func Column(column string) Option {
|
||||||
|
return func(order *Order) {
|
||||||
|
order.column = strings.ReplaceAll(column, clauses.ExprSep, clauses.ExprDot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sort(sort Sort) Option {
|
||||||
|
return func(order *Order) {
|
||||||
|
order.sort = sort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SortAscending() Option {
|
||||||
|
return sort(Ascending)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SortDescending() Option {
|
||||||
|
return sort(Descending)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SortNone() Option {
|
||||||
|
return sort(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Raw() Option {
|
||||||
|
return func(order *Order) {
|
||||||
|
order.isRaw = true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,299 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type commander interface {
|
||||||
|
Parse([]string)
|
||||||
|
Run() error
|
||||||
|
}
|
||||||
|
|
||||||
|
var commands = make(map[string]commander)
|
||||||
|
|
||||||
|
// print help.
|
||||||
|
func printHelp(errs ...string) {
|
||||||
|
content := `orm command usage:
|
||||||
|
|
||||||
|
syncdb - auto create tables
|
||||||
|
sqlall - print sql of create tables
|
||||||
|
help - print this help
|
||||||
|
`
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
fmt.Println(errs[0])
|
||||||
|
}
|
||||||
|
fmt.Println(content)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunCommand listens for orm command and runs if command arguments have been passed.
|
||||||
|
func RunCommand() {
|
||||||
|
if len(os.Args) < 2 || os.Args[1] != "orm" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
BootStrap()
|
||||||
|
|
||||||
|
args := argString(os.Args[2:])
|
||||||
|
name := args.Get(0)
|
||||||
|
|
||||||
|
if name == "help" {
|
||||||
|
printHelp()
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd, ok := commands[name]; ok {
|
||||||
|
cmd.Parse(os.Args[3:])
|
||||||
|
cmd.Run()
|
||||||
|
os.Exit(0)
|
||||||
|
} else {
|
||||||
|
if name == "" {
|
||||||
|
printHelp()
|
||||||
|
} else {
|
||||||
|
printHelp(fmt.Sprintf("unknown command %s", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync database struct command interface.
|
||||||
|
type commandSyncDb struct {
|
||||||
|
al *alias
|
||||||
|
force bool
|
||||||
|
verbose bool
|
||||||
|
noInfo bool
|
||||||
|
rtOnError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the orm command line arguments.
|
||||||
|
func (d *commandSyncDb) Parse(args []string) {
|
||||||
|
var name string
|
||||||
|
|
||||||
|
flagSet := flag.NewFlagSet("orm command: syncdb", flag.ExitOnError)
|
||||||
|
flagSet.StringVar(&name, "db", "default", "DataBase alias name")
|
||||||
|
flagSet.BoolVar(&d.force, "force", false, "drop tables before create")
|
||||||
|
flagSet.BoolVar(&d.verbose, "v", false, "verbose info")
|
||||||
|
flagSet.Parse(args)
|
||||||
|
|
||||||
|
d.al = getDbAlias(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run orm line command.
|
||||||
|
func (d *commandSyncDb) Run() error {
|
||||||
|
var drops []string
|
||||||
|
var err error
|
||||||
|
if d.force {
|
||||||
|
drops, err = defaultModelCache.getDbDropSQL(d.al)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db := d.al.DB
|
||||||
|
|
||||||
|
if d.force && len(drops) > 0 {
|
||||||
|
for i, mi := range defaultModelCache.allOrdered() {
|
||||||
|
query := drops[i]
|
||||||
|
if !d.noInfo {
|
||||||
|
fmt.Printf("drop table `%s`\n", mi.table)
|
||||||
|
}
|
||||||
|
_, err := db.Exec(query)
|
||||||
|
if d.verbose {
|
||||||
|
fmt.Printf(" %s\n\n", query)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if d.rtOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf(" %s\n", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createQueries, indexes, err := defaultModelCache.getDbCreateSQL(d.al)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tables, err := d.al.DbBaser.GetTables(db)
|
||||||
|
if err != nil {
|
||||||
|
if d.rtOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf(" %s\n", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
for i, mi := range defaultModelCache.allOrdered() {
|
||||||
|
|
||||||
|
if !isApplicableTableForDB(mi.addrField, d.al.Name) {
|
||||||
|
fmt.Printf("table `%s` is not applicable to database '%s'\n", mi.table, d.al.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tables[mi.table] {
|
||||||
|
if !d.noInfo {
|
||||||
|
fmt.Printf("table `%s` already exists, skip\n", mi.table)
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields []*fieldInfo
|
||||||
|
columns, err := d.al.DbBaser.GetColumns(ctx, db, mi.table)
|
||||||
|
if err != nil {
|
||||||
|
if d.rtOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf(" %s\n", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fi := range mi.fields.fieldsDB {
|
||||||
|
if _, ok := columns[fi.column]; !ok {
|
||||||
|
fields = append(fields, fi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fi := range fields {
|
||||||
|
query := getColumnAddQuery(d.al, fi)
|
||||||
|
|
||||||
|
if !d.noInfo {
|
||||||
|
fmt.Printf("add column `%s` for table `%s`\n", fi.fullName, mi.table)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := db.Exec(query)
|
||||||
|
if d.verbose {
|
||||||
|
fmt.Printf(" %s\n", query)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if d.rtOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf(" %s\n", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, idx := range indexes[mi.table] {
|
||||||
|
if !d.al.DbBaser.IndexExists(ctx, db, idx.Table, idx.Name) {
|
||||||
|
if !d.noInfo {
|
||||||
|
fmt.Printf("create index `%s` for table `%s`\n", idx.Name, idx.Table)
|
||||||
|
}
|
||||||
|
|
||||||
|
query := idx.SQL
|
||||||
|
_, err := db.Exec(query)
|
||||||
|
if d.verbose {
|
||||||
|
fmt.Printf(" %s\n", query)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if d.rtOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf(" %s\n", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !d.noInfo {
|
||||||
|
fmt.Printf("create table `%s` \n", mi.table)
|
||||||
|
}
|
||||||
|
|
||||||
|
queries := []string{createQueries[i]}
|
||||||
|
for _, idx := range indexes[mi.table] {
|
||||||
|
queries = append(queries, idx.SQL)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, query := range queries {
|
||||||
|
_, err := db.Exec(query)
|
||||||
|
if d.verbose {
|
||||||
|
query = " " + strings.Join(strings.Split(query, "\n"), "\n ")
|
||||||
|
fmt.Println(query)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if d.rtOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf(" %s\n", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d.verbose {
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// database creation commander interface implement.
|
||||||
|
type commandSQLAll struct {
|
||||||
|
al *alias
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse orm command line arguments.
|
||||||
|
func (d *commandSQLAll) Parse(args []string) {
|
||||||
|
var name string
|
||||||
|
|
||||||
|
flagSet := flag.NewFlagSet("orm command: sqlall", flag.ExitOnError)
|
||||||
|
flagSet.StringVar(&name, "db", "default", "DataBase alias name")
|
||||||
|
flagSet.Parse(args)
|
||||||
|
|
||||||
|
d.al = getDbAlias(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run orm line command.
|
||||||
|
func (d *commandSQLAll) Run() error {
|
||||||
|
createQueries, indexes, err := defaultModelCache.getDbCreateSQL(d.al)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var all []string
|
||||||
|
for i, mi := range defaultModelCache.allOrdered() {
|
||||||
|
queries := []string{createQueries[i]}
|
||||||
|
for _, idx := range indexes[mi.table] {
|
||||||
|
queries = append(queries, idx.SQL)
|
||||||
|
}
|
||||||
|
sql := strings.Join(queries, "\n")
|
||||||
|
all = append(all, sql)
|
||||||
|
}
|
||||||
|
fmt.Println(strings.Join(all, "\n\n"))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commands["syncdb"] = new(commandSyncDb)
|
||||||
|
commands["sqlall"] = new(commandSQLAll)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunSyncdb run syncdb command line.
|
||||||
|
// name: Table's alias name (default is "default")
|
||||||
|
// force: Run the next sql command even if the current gave an error
|
||||||
|
// verbose: Print all information, useful for debugging
|
||||||
|
func RunSyncdb(name string, force bool, verbose bool) error {
|
||||||
|
BootStrap()
|
||||||
|
|
||||||
|
al := getDbAlias(name)
|
||||||
|
cmd := new(commandSyncDb)
|
||||||
|
cmd.al = al
|
||||||
|
cmd.force = force
|
||||||
|
cmd.noInfo = !verbose
|
||||||
|
cmd.verbose = verbose
|
||||||
|
cmd.rtOnError = true
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dbIndex struct {
|
||||||
|
Table string
|
||||||
|
Name string
|
||||||
|
SQL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// get database column type string.
|
||||||
|
func getColumnTyp(al *alias, fi *fieldInfo) (col string) {
|
||||||
|
T := al.DbBaser.DbTypes()
|
||||||
|
fieldType := fi.fieldType
|
||||||
|
fieldSize := fi.size
|
||||||
|
|
||||||
|
checkColumn:
|
||||||
|
switch fieldType {
|
||||||
|
case TypeBooleanField:
|
||||||
|
col = T["bool"]
|
||||||
|
case TypeVarCharField:
|
||||||
|
if al.Driver == DRPostgres && fi.toText {
|
||||||
|
col = T["string-text"]
|
||||||
|
} else {
|
||||||
|
col = fmt.Sprintf(T["string"], fieldSize)
|
||||||
|
}
|
||||||
|
case TypeCharField:
|
||||||
|
col = fmt.Sprintf(T["string-char"], fieldSize)
|
||||||
|
case TypeTextField:
|
||||||
|
col = T["string-text"]
|
||||||
|
case TypeTimeField:
|
||||||
|
col = T["time.Time-clock"]
|
||||||
|
case TypeDateField:
|
||||||
|
col = T["time.Time-date"]
|
||||||
|
case TypeDateTimeField:
|
||||||
|
// the precision of sqlite is not implemented
|
||||||
|
if al.Driver == 2 || fi.timePrecision == nil {
|
||||||
|
col = T["time.Time"]
|
||||||
|
} else {
|
||||||
|
s := T["time.Time-precision"]
|
||||||
|
col = fmt.Sprintf(s, *fi.timePrecision)
|
||||||
|
}
|
||||||
|
|
||||||
|
case TypeBitField:
|
||||||
|
col = T["int8"]
|
||||||
|
case TypeSmallIntegerField:
|
||||||
|
col = T["int16"]
|
||||||
|
case TypeIntegerField:
|
||||||
|
col = T["int32"]
|
||||||
|
case TypeBigIntegerField:
|
||||||
|
if al.Driver == DRSqlite {
|
||||||
|
fieldType = TypeIntegerField
|
||||||
|
goto checkColumn
|
||||||
|
}
|
||||||
|
col = T["int64"]
|
||||||
|
case TypePositiveBitField:
|
||||||
|
col = T["uint8"]
|
||||||
|
case TypePositiveSmallIntegerField:
|
||||||
|
col = T["uint16"]
|
||||||
|
case TypePositiveIntegerField:
|
||||||
|
col = T["uint32"]
|
||||||
|
case TypePositiveBigIntegerField:
|
||||||
|
col = T["uint64"]
|
||||||
|
case TypeFloatField:
|
||||||
|
col = T["float64"]
|
||||||
|
case TypeDecimalField:
|
||||||
|
s := T["float64-decimal"]
|
||||||
|
if !strings.Contains(s, "%d") {
|
||||||
|
col = s
|
||||||
|
} else {
|
||||||
|
col = fmt.Sprintf(s, fi.digits, fi.decimals)
|
||||||
|
}
|
||||||
|
case TypeJSONField:
|
||||||
|
if al.Driver != DRPostgres {
|
||||||
|
fieldType = TypeVarCharField
|
||||||
|
goto checkColumn
|
||||||
|
}
|
||||||
|
col = T["json"]
|
||||||
|
case TypeJsonbField:
|
||||||
|
if al.Driver != DRPostgres {
|
||||||
|
fieldType = TypeVarCharField
|
||||||
|
goto checkColumn
|
||||||
|
}
|
||||||
|
col = T["jsonb"]
|
||||||
|
case RelForeignKey, RelOneToOne:
|
||||||
|
fieldType = fi.relModelInfo.fields.pk.fieldType
|
||||||
|
fieldSize = fi.relModelInfo.fields.pk.size
|
||||||
|
goto checkColumn
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// create alter sql string.
|
||||||
|
func getColumnAddQuery(al *alias, fi *fieldInfo) string {
|
||||||
|
Q := al.DbBaser.TableQuote()
|
||||||
|
typ := getColumnTyp(al, fi)
|
||||||
|
|
||||||
|
if !fi.null {
|
||||||
|
typ += " " + "NOT NULL"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("ALTER TABLE %s%s%s ADD COLUMN %s%s%s %s %s",
|
||||||
|
Q, fi.mi.table, Q,
|
||||||
|
Q, fi.column, Q,
|
||||||
|
typ, getColumnDefault(fi),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get string value for the attribute "DEFAULT" for the CREATE, ALTER commands
|
||||||
|
func getColumnDefault(fi *fieldInfo) string {
|
||||||
|
var v, t, d string
|
||||||
|
|
||||||
|
// Skip default attribute if field is in relations
|
||||||
|
if fi.rel || fi.reverse {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
t = " DEFAULT '%s' "
|
||||||
|
|
||||||
|
// These defaults will be useful if there no config value orm:"default" and NOT NULL is on
|
||||||
|
switch fi.fieldType {
|
||||||
|
case TypeTimeField, TypeDateField, TypeDateTimeField, TypeTextField:
|
||||||
|
return v
|
||||||
|
|
||||||
|
case TypeBitField, TypeSmallIntegerField, TypeIntegerField,
|
||||||
|
TypeBigIntegerField, TypePositiveBitField, TypePositiveSmallIntegerField,
|
||||||
|
TypePositiveIntegerField, TypePositiveBigIntegerField, TypeFloatField,
|
||||||
|
TypeDecimalField:
|
||||||
|
t = " DEFAULT %s "
|
||||||
|
d = "0"
|
||||||
|
case TypeBooleanField:
|
||||||
|
t = " DEFAULT %s "
|
||||||
|
d = "FALSE"
|
||||||
|
case TypeJSONField, TypeJsonbField:
|
||||||
|
d = "{}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.colDefault {
|
||||||
|
if !fi.initial.Exist() {
|
||||||
|
v = fmt.Sprintf(t, "")
|
||||||
|
} else {
|
||||||
|
v = fmt.Sprintf(t, fi.initial.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !fi.null {
|
||||||
|
v = fmt.Sprintf(t, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,599 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
lru "github.com/hashicorp/golang-lru"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DriverType database driver constant int.
|
||||||
|
type DriverType int
|
||||||
|
|
||||||
|
// Enum the Database driver
|
||||||
|
const (
|
||||||
|
_ DriverType = iota // int enum type
|
||||||
|
DRMySQL // mysql
|
||||||
|
DRSqlite // sqlite
|
||||||
|
DROracle // oracle
|
||||||
|
DRPostgres // pgsql
|
||||||
|
DRTiDB // TiDB
|
||||||
|
)
|
||||||
|
|
||||||
|
// database driver string.
|
||||||
|
type driver string
|
||||||
|
|
||||||
|
// get type constant int of current driver..
|
||||||
|
func (d driver) Type() DriverType {
|
||||||
|
a, _ := dataBaseCache.get(string(d))
|
||||||
|
return a.Driver
|
||||||
|
}
|
||||||
|
|
||||||
|
// get name of current driver
|
||||||
|
func (d driver) Name() string {
|
||||||
|
return string(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check driver iis implemented Driver interface or not.
|
||||||
|
var _ Driver = new(driver)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dataBaseCache = &_dbCache{cache: make(map[string]*alias)}
|
||||||
|
drivers = map[string]DriverType{
|
||||||
|
"mysql": DRMySQL,
|
||||||
|
"postgres": DRPostgres,
|
||||||
|
"sqlite3": DRSqlite,
|
||||||
|
"tidb": DRTiDB,
|
||||||
|
"oracle": DROracle,
|
||||||
|
"oci8": DROracle, // github.com/mattn/go-oci8
|
||||||
|
"ora": DROracle, // https://github.com/rana/ora
|
||||||
|
}
|
||||||
|
dbBasers = map[DriverType]dbBaser{
|
||||||
|
DRMySQL: newdbBaseMysql(),
|
||||||
|
DRSqlite: newdbBaseSqlite(),
|
||||||
|
DROracle: newdbBaseOracle(),
|
||||||
|
DRPostgres: newdbBasePostgres(),
|
||||||
|
DRTiDB: newdbBaseTidb(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// database alias cacher.
|
||||||
|
type _dbCache struct {
|
||||||
|
mux sync.RWMutex
|
||||||
|
cache map[string]*alias
|
||||||
|
}
|
||||||
|
|
||||||
|
// add database alias with original name.
|
||||||
|
func (ac *_dbCache) add(name string, al *alias) (added bool) {
|
||||||
|
ac.mux.Lock()
|
||||||
|
defer ac.mux.Unlock()
|
||||||
|
if _, ok := ac.cache[name]; !ok {
|
||||||
|
ac.cache[name] = al
|
||||||
|
added = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get database alias if cached.
|
||||||
|
func (ac *_dbCache) get(name string) (al *alias, ok bool) {
|
||||||
|
ac.mux.RLock()
|
||||||
|
defer ac.mux.RUnlock()
|
||||||
|
al, ok = ac.cache[name]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get default alias.
|
||||||
|
func (ac *_dbCache) getDefault() (al *alias) {
|
||||||
|
al, _ = ac.get("default")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
*sync.RWMutex
|
||||||
|
DB *sql.DB
|
||||||
|
stmtDecorators *lru.Cache
|
||||||
|
stmtDecoratorsLimit int
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ dbQuerier = new(DB)
|
||||||
|
_ txer = new(DB)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *DB) Begin() (*sql.Tx, error) {
|
||||||
|
return d.DB.Begin()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) {
|
||||||
|
return d.DB.BeginTx(ctx, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// su must call release to release *sql.Stmt after using
|
||||||
|
func (d *DB) getStmtDecorator(query string) (*stmtDecorator, error) {
|
||||||
|
d.RLock()
|
||||||
|
c, ok := d.stmtDecorators.Get(query)
|
||||||
|
if ok {
|
||||||
|
c.(*stmtDecorator).acquire()
|
||||||
|
d.RUnlock()
|
||||||
|
return c.(*stmtDecorator), nil
|
||||||
|
}
|
||||||
|
d.RUnlock()
|
||||||
|
|
||||||
|
d.Lock()
|
||||||
|
c, ok = d.stmtDecorators.Get(query)
|
||||||
|
if ok {
|
||||||
|
c.(*stmtDecorator).acquire()
|
||||||
|
d.Unlock()
|
||||||
|
return c.(*stmtDecorator), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := d.Prepare(query)
|
||||||
|
if err != nil {
|
||||||
|
d.Unlock()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sd := newStmtDecorator(stmt)
|
||||||
|
sd.acquire()
|
||||||
|
d.stmtDecorators.Add(query, sd)
|
||||||
|
d.Unlock()
|
||||||
|
|
||||||
|
return sd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) Prepare(query string) (*sql.Stmt, error) {
|
||||||
|
return d.DB.Prepare(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
|
||||||
|
return d.DB.PrepareContext(ctx, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
return d.ExecContext(context.Background(), query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
if d.stmtDecorators == nil {
|
||||||
|
return d.DB.ExecContext(ctx, query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
sd, err := d.getStmtDecorator(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stmt := sd.getStmt()
|
||||||
|
defer sd.release()
|
||||||
|
return stmt.ExecContext(ctx, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
return d.QueryContext(context.Background(), query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
if d.stmtDecorators == nil {
|
||||||
|
return d.DB.QueryContext(ctx, query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
sd, err := d.getStmtDecorator(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stmt := sd.getStmt()
|
||||||
|
defer sd.release()
|
||||||
|
return stmt.QueryContext(ctx, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||||
|
return d.QueryRowContext(context.Background(), query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
||||||
|
if d.stmtDecorators == nil {
|
||||||
|
return d.DB.QueryRowContext(ctx, query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
sd, err := d.getStmtDecorator(query)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
stmt := sd.getStmt()
|
||||||
|
defer sd.release()
|
||||||
|
return stmt.QueryRowContext(ctx, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TxDB struct {
|
||||||
|
tx *sql.Tx
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ dbQuerier = new(TxDB)
|
||||||
|
_ txEnder = new(TxDB)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *TxDB) Commit() error {
|
||||||
|
return t.tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TxDB) Rollback() error {
|
||||||
|
return t.tx.Rollback()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TxDB) RollbackUnlessCommit() error {
|
||||||
|
err := t.tx.Rollback()
|
||||||
|
if err != sql.ErrTxDone {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ dbQuerier = new(TxDB)
|
||||||
|
_ txEnder = new(TxDB)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *TxDB) Prepare(query string) (*sql.Stmt, error) {
|
||||||
|
return t.PrepareContext(context.Background(), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TxDB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
|
||||||
|
return t.tx.PrepareContext(ctx, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TxDB) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
return t.ExecContext(context.Background(), query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TxDB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
return t.tx.ExecContext(ctx, query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TxDB) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
return t.QueryContext(context.Background(), query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TxDB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
return t.tx.QueryContext(ctx, query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TxDB) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||||
|
return t.QueryRowContext(context.Background(), query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TxDB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
||||||
|
return t.tx.QueryRowContext(ctx, query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type alias struct {
|
||||||
|
Name string
|
||||||
|
Driver DriverType
|
||||||
|
DriverName string
|
||||||
|
DataSource string
|
||||||
|
MaxIdleConns int
|
||||||
|
MaxOpenConns int
|
||||||
|
ConnMaxLifetime time.Duration
|
||||||
|
StmtCacheSize int
|
||||||
|
DB *DB
|
||||||
|
DbBaser dbBaser
|
||||||
|
TZ *time.Location
|
||||||
|
Engine string
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectTZ(al *alias) {
|
||||||
|
// orm timezone system match database
|
||||||
|
// default use Local
|
||||||
|
al.TZ = DefaultTimeLoc
|
||||||
|
|
||||||
|
if al.DriverName == "sphinx" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch al.Driver {
|
||||||
|
case DRMySQL:
|
||||||
|
row := al.DB.QueryRow("SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP)")
|
||||||
|
var tz string
|
||||||
|
row.Scan(&tz)
|
||||||
|
if len(tz) >= 8 {
|
||||||
|
if tz[0] != '-' {
|
||||||
|
tz = "+" + tz
|
||||||
|
}
|
||||||
|
t, err := time.Parse("-07:00:00", tz)
|
||||||
|
if err == nil {
|
||||||
|
if t.Location().String() != "" {
|
||||||
|
al.TZ = t.Location()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get default engine from current database
|
||||||
|
row = al.DB.QueryRow("SELECT ENGINE, TRANSACTIONS FROM information_schema.engines WHERE SUPPORT = 'DEFAULT'")
|
||||||
|
var engine string
|
||||||
|
var tx bool
|
||||||
|
row.Scan(&engine, &tx)
|
||||||
|
|
||||||
|
if engine != "" {
|
||||||
|
al.Engine = engine
|
||||||
|
} else {
|
||||||
|
al.Engine = "INNODB"
|
||||||
|
}
|
||||||
|
|
||||||
|
case DRSqlite, DROracle:
|
||||||
|
al.TZ = time.UTC
|
||||||
|
|
||||||
|
case DRPostgres:
|
||||||
|
row := al.DB.QueryRow("SELECT current_setting('TIMEZONE')")
|
||||||
|
var tz string
|
||||||
|
row.Scan(&tz)
|
||||||
|
loc, err := time.LoadLocation(tz)
|
||||||
|
if err == nil {
|
||||||
|
al.TZ = loc
|
||||||
|
} else {
|
||||||
|
DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addAliasWthDB(aliasName, driverName string, db *sql.DB, params ...DBOption) (*alias, error) {
|
||||||
|
existErr := fmt.Errorf("DataBase alias name `%s` already registered, cannot reuse", aliasName)
|
||||||
|
if _, ok := dataBaseCache.get(aliasName); ok {
|
||||||
|
return nil, existErr
|
||||||
|
}
|
||||||
|
|
||||||
|
al, err := newAliasWithDb(aliasName, driverName, db, params...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dataBaseCache.add(aliasName, al) {
|
||||||
|
return nil, existErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return al, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAliasWithDb(aliasName, driverName string, db *sql.DB, params ...DBOption) (*alias, error) {
|
||||||
|
al := &alias{}
|
||||||
|
al.DB = &DB{
|
||||||
|
RWMutex: new(sync.RWMutex),
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range params {
|
||||||
|
p(al)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stmtCache *lru.Cache
|
||||||
|
var stmtCacheSize int
|
||||||
|
|
||||||
|
if al.StmtCacheSize > 0 {
|
||||||
|
_stmtCache, errC := newStmtDecoratorLruWithEvict(al.StmtCacheSize)
|
||||||
|
if errC != nil {
|
||||||
|
return nil, errC
|
||||||
|
} else {
|
||||||
|
stmtCache = _stmtCache
|
||||||
|
stmtCacheSize = al.StmtCacheSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
al.Name = aliasName
|
||||||
|
al.DriverName = driverName
|
||||||
|
al.DB.stmtDecorators = stmtCache
|
||||||
|
al.DB.stmtDecoratorsLimit = stmtCacheSize
|
||||||
|
|
||||||
|
if dr, ok := drivers[driverName]; ok {
|
||||||
|
al.DbBaser = dbBasers[dr]
|
||||||
|
al.Driver = dr
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("driver name `%s` have not registered", driverName)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("register db Ping `%s`, %s", aliasName, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
detectTZ(al)
|
||||||
|
|
||||||
|
return al, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMaxIdleConns Change the max idle conns for *sql.DB, use specify database alias name
|
||||||
|
// Deprecated you should not use this, we will remove it in the future
|
||||||
|
func SetMaxIdleConns(aliasName string, maxIdleConns int) {
|
||||||
|
al := getDbAlias(aliasName)
|
||||||
|
al.SetMaxIdleConns(maxIdleConns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name
|
||||||
|
// Deprecated you should not use this, we will remove it in the future
|
||||||
|
func SetMaxOpenConns(aliasName string, maxOpenConns int) {
|
||||||
|
al := getDbAlias(aliasName)
|
||||||
|
al.SetMaxOpenConns(maxOpenConns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMaxIdleConns Change the max idle conns for *sql.DB, use specify database alias name
|
||||||
|
func (al *alias) SetMaxIdleConns(maxIdleConns int) {
|
||||||
|
al.MaxIdleConns = maxIdleConns
|
||||||
|
al.DB.DB.SetMaxIdleConns(maxIdleConns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name
|
||||||
|
func (al *alias) SetMaxOpenConns(maxOpenConns int) {
|
||||||
|
al.MaxOpenConns = maxOpenConns
|
||||||
|
al.DB.DB.SetMaxOpenConns(maxOpenConns)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (al *alias) SetConnMaxLifetime(lifeTime time.Duration) {
|
||||||
|
al.ConnMaxLifetime = lifeTime
|
||||||
|
al.DB.DB.SetConnMaxLifetime(lifeTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAliasWthDB add a aliasName for the drivename
|
||||||
|
func AddAliasWthDB(aliasName, driverName string, db *sql.DB, params ...DBOption) error {
|
||||||
|
_, err := addAliasWthDB(aliasName, driverName, db, params...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterDataBase Setting the database connect params. Use the database driver self dataSource args.
|
||||||
|
func RegisterDataBase(aliasName, driverName, dataSource string, params ...DBOption) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
db *sql.DB
|
||||||
|
al *alias
|
||||||
|
)
|
||||||
|
|
||||||
|
db, err = sql.Open(driverName, dataSource)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("register db `%s`, %s", aliasName, err.Error())
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
|
||||||
|
al, err = addAliasWthDB(aliasName, driverName, db, params...)
|
||||||
|
if err != nil {
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
|
||||||
|
al.DataSource = dataSource
|
||||||
|
|
||||||
|
end:
|
||||||
|
if err != nil {
|
||||||
|
if db != nil {
|
||||||
|
db.Close()
|
||||||
|
}
|
||||||
|
DebugLog.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterDriver Register a database driver use specify driver name, this can be definition the driver is which database type.
|
||||||
|
func RegisterDriver(driverName string, typ DriverType) error {
|
||||||
|
if t, ok := drivers[driverName]; !ok {
|
||||||
|
drivers[driverName] = typ
|
||||||
|
} else {
|
||||||
|
if t != typ {
|
||||||
|
return fmt.Errorf("driverName `%s` db driver already registered and is other type", driverName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDataBaseTZ Change the database default used timezone
|
||||||
|
func SetDataBaseTZ(aliasName string, tz *time.Location) error {
|
||||||
|
if al, ok := dataBaseCache.get(aliasName); ok {
|
||||||
|
al.TZ = tz
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("DataBase alias name `%s` not registered", aliasName)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDB Get *sql.DB from registered database by db alias name.
|
||||||
|
// Use "default" as alias name if you not set.
|
||||||
|
func GetDB(aliasNames ...string) (*sql.DB, error) {
|
||||||
|
var name string
|
||||||
|
if len(aliasNames) > 0 {
|
||||||
|
name = aliasNames[0]
|
||||||
|
} else {
|
||||||
|
name = "default"
|
||||||
|
}
|
||||||
|
al, ok := dataBaseCache.get(name)
|
||||||
|
if ok {
|
||||||
|
return al.DB.DB, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("DataBase of alias name `%s` not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
type stmtDecorator struct {
|
||||||
|
wg sync.WaitGroup
|
||||||
|
stmt *sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stmtDecorator) getStmt() *sql.Stmt {
|
||||||
|
return s.stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
// acquire will add one
|
||||||
|
// since this method will be used inside read lock scope,
|
||||||
|
// so we can not do more things here
|
||||||
|
// we should think about refactor this
|
||||||
|
func (s *stmtDecorator) acquire() {
|
||||||
|
s.wg.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stmtDecorator) release() {
|
||||||
|
s.wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// garbage recycle for stmt
|
||||||
|
func (s *stmtDecorator) destroy() {
|
||||||
|
go func() {
|
||||||
|
s.wg.Wait()
|
||||||
|
_ = s.stmt.Close()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStmtDecorator(sqlStmt *sql.Stmt) *stmtDecorator {
|
||||||
|
return &stmtDecorator{
|
||||||
|
stmt: sqlStmt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStmtDecoratorLruWithEvict(cacheSize int) (*lru.Cache, error) {
|
||||||
|
cache, err := lru.NewWithEvict(cacheSize, func(key interface{}, value interface{}) {
|
||||||
|
value.(*stmtDecorator).destroy()
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DBOption func(al *alias)
|
||||||
|
|
||||||
|
// MaxIdleConnections return a hint about MaxIdleConnections
|
||||||
|
func MaxIdleConnections(maxIdleConn int) DBOption {
|
||||||
|
return func(al *alias) {
|
||||||
|
al.SetMaxIdleConns(maxIdleConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxOpenConnections return a hint about MaxOpenConnections
|
||||||
|
func MaxOpenConnections(maxOpenConn int) DBOption {
|
||||||
|
return func(al *alias) {
|
||||||
|
al.SetMaxOpenConns(maxOpenConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnMaxLifetime return a hint about ConnMaxLifetime
|
||||||
|
func ConnMaxLifetime(v time.Duration) DBOption {
|
||||||
|
return func(al *alias) {
|
||||||
|
al.SetConnMaxLifetime(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxStmtCacheSize return a hint about MaxStmtCacheSize
|
||||||
|
func MaxStmtCacheSize(v int) DBOption {
|
||||||
|
return func(al *alias) {
|
||||||
|
al.StmtCacheSize = v
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,192 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mysql operators.
|
||||||
|
var mysqlOperators = map[string]string{
|
||||||
|
"exact": "= ?",
|
||||||
|
"iexact": "LIKE ?",
|
||||||
|
"strictexact": "= BINARY ?",
|
||||||
|
"contains": "LIKE BINARY ?",
|
||||||
|
"icontains": "LIKE ?",
|
||||||
|
// "regex": "REGEXP BINARY ?",
|
||||||
|
// "iregex": "REGEXP ?",
|
||||||
|
"gt": "> ?",
|
||||||
|
"gte": ">= ?",
|
||||||
|
"lt": "< ?",
|
||||||
|
"lte": "<= ?",
|
||||||
|
"eq": "= ?",
|
||||||
|
"ne": "!= ?",
|
||||||
|
"startswith": "LIKE BINARY ?",
|
||||||
|
"endswith": "LIKE BINARY ?",
|
||||||
|
"istartswith": "LIKE ?",
|
||||||
|
"iendswith": "LIKE ?",
|
||||||
|
}
|
||||||
|
|
||||||
|
// mysql column field types.
|
||||||
|
var mysqlTypes = map[string]string{
|
||||||
|
"auto": "AUTO_INCREMENT NOT NULL PRIMARY KEY",
|
||||||
|
"pk": "NOT NULL PRIMARY KEY",
|
||||||
|
"bool": "bool",
|
||||||
|
"string": "varchar(%d)",
|
||||||
|
"string-char": "char(%d)",
|
||||||
|
"string-text": "longtext",
|
||||||
|
"time.Time-date": "date",
|
||||||
|
"time.Time": "datetime",
|
||||||
|
"int8": "tinyint",
|
||||||
|
"int16": "smallint",
|
||||||
|
"int32": "integer",
|
||||||
|
"int64": "bigint",
|
||||||
|
"uint8": "tinyint unsigned",
|
||||||
|
"uint16": "smallint unsigned",
|
||||||
|
"uint32": "integer unsigned",
|
||||||
|
"uint64": "bigint unsigned",
|
||||||
|
"float64": "double precision",
|
||||||
|
"float64-decimal": "numeric(%d, %d)",
|
||||||
|
"time.Time-precision": "datetime(%d)",
|
||||||
|
}
|
||||||
|
|
||||||
|
// mysql dbBaser implementation.
|
||||||
|
type dbBaseMysql struct {
|
||||||
|
dbBase
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ dbBaser = new(dbBaseMysql)
|
||||||
|
|
||||||
|
// get mysql operator.
|
||||||
|
func (d *dbBaseMysql) OperatorSQL(operator string) string {
|
||||||
|
return mysqlOperators[operator]
|
||||||
|
}
|
||||||
|
|
||||||
|
// get mysql table field types.
|
||||||
|
func (d *dbBaseMysql) DbTypes() map[string]string {
|
||||||
|
return mysqlTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
// show table sql for mysql.
|
||||||
|
func (d *dbBaseMysql) ShowTablesQuery() string {
|
||||||
|
return "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema = DATABASE()"
|
||||||
|
}
|
||||||
|
|
||||||
|
// show columns sql of table for mysql.
|
||||||
|
func (d *dbBaseMysql) ShowColumnsQuery(table string) string {
|
||||||
|
return fmt.Sprintf("SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE FROM information_schema.columns "+
|
||||||
|
"WHERE table_schema = DATABASE() AND table_name = '%s'", table)
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute sql to check index exist.
|
||||||
|
func (d *dbBaseMysql) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
||||||
|
row := db.QueryRowContext(ctx, "SELECT count(*) FROM information_schema.statistics "+
|
||||||
|
"WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?", table, name)
|
||||||
|
var cnt int
|
||||||
|
row.Scan(&cnt)
|
||||||
|
return cnt > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertOrUpdate a row
|
||||||
|
// If your primary key or unique column conflict will update
|
||||||
|
// If no will insert
|
||||||
|
// Add "`" for mysql sql building
|
||||||
|
func (d *dbBaseMysql) InsertOrUpdate(ctx context.Context, q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) {
|
||||||
|
var iouStr string
|
||||||
|
argsMap := map[string]string{}
|
||||||
|
|
||||||
|
iouStr = "ON DUPLICATE KEY UPDATE"
|
||||||
|
|
||||||
|
// Get on the key-value pairs
|
||||||
|
for _, v := range args {
|
||||||
|
kv := strings.Split(v, "=")
|
||||||
|
if len(kv) == 2 {
|
||||||
|
argsMap[strings.ToLower(kv[0])] = kv[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isMulti := false
|
||||||
|
names := make([]string, 0, len(mi.fields.dbcols)-1)
|
||||||
|
Q := d.ins.TableQuote()
|
||||||
|
values, _, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, a.TZ)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
marks := make([]string, len(names))
|
||||||
|
updateValues := make([]interface{}, 0)
|
||||||
|
updates := make([]string, len(names))
|
||||||
|
|
||||||
|
for i, v := range names {
|
||||||
|
marks[i] = "?"
|
||||||
|
valueStr := argsMap[strings.ToLower(v)]
|
||||||
|
if valueStr != "" {
|
||||||
|
updates[i] = "`" + v + "`" + "=" + valueStr
|
||||||
|
} else {
|
||||||
|
updates[i] = "`" + v + "`" + "=?"
|
||||||
|
updateValues = append(updateValues, values[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
values = append(values, updateValues...)
|
||||||
|
|
||||||
|
sep := fmt.Sprintf("%s, %s", Q, Q)
|
||||||
|
qmarks := strings.Join(marks, ", ")
|
||||||
|
qupdates := strings.Join(updates, ", ")
|
||||||
|
columns := strings.Join(names, sep)
|
||||||
|
|
||||||
|
multi := len(values) / len(names)
|
||||||
|
|
||||||
|
if isMulti {
|
||||||
|
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
|
||||||
|
}
|
||||||
|
// conflitValue maybe is a int,can`t use fmt.Sprintf
|
||||||
|
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s) %s "+qupdates, Q, mi.table, Q, Q, columns, Q, qmarks, iouStr)
|
||||||
|
|
||||||
|
d.ins.ReplaceMarks(&query)
|
||||||
|
|
||||||
|
if isMulti || !d.ins.HasReturningID(mi, &query) {
|
||||||
|
res, err := q.ExecContext(ctx, query, values...)
|
||||||
|
if err == nil {
|
||||||
|
if isMulti {
|
||||||
|
return res.RowsAffected()
|
||||||
|
}
|
||||||
|
|
||||||
|
lastInsertId, err := res.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
DebugLog.Println(ErrLastInsertIdUnavailable, ':', err)
|
||||||
|
return lastInsertId, ErrLastInsertIdUnavailable
|
||||||
|
} else {
|
||||||
|
return lastInsertId, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
row := q.QueryRowContext(ctx, query, values...)
|
||||||
|
var id int64
|
||||||
|
err = row.Scan(&id)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new mysql dbBaser.
|
||||||
|
func newdbBaseMysql() dbBaser {
|
||||||
|
b := new(dbBaseMysql)
|
||||||
|
b.ins = b
|
||||||
|
return b
|
||||||
|
}
|
@ -0,0 +1,171 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/beego/beego/v2/client/orm/hints"
|
||||||
|
)
|
||||||
|
|
||||||
|
// oracle operators.
|
||||||
|
var oracleOperators = map[string]string{
|
||||||
|
"exact": "= ?",
|
||||||
|
"gt": "> ?",
|
||||||
|
"gte": ">= ?",
|
||||||
|
"lt": "< ?",
|
||||||
|
"lte": "<= ?",
|
||||||
|
"//iendswith": "LIKE ?",
|
||||||
|
}
|
||||||
|
|
||||||
|
// oracle column field types.
|
||||||
|
var oracleTypes = map[string]string{
|
||||||
|
"pk": "NOT NULL PRIMARY KEY",
|
||||||
|
"bool": "bool",
|
||||||
|
"string": "VARCHAR2(%d)",
|
||||||
|
"string-char": "CHAR(%d)",
|
||||||
|
"string-text": "VARCHAR2(%d)",
|
||||||
|
"time.Time-date": "DATE",
|
||||||
|
"time.Time": "TIMESTAMP",
|
||||||
|
"int8": "INTEGER",
|
||||||
|
"int16": "INTEGER",
|
||||||
|
"int32": "INTEGER",
|
||||||
|
"int64": "INTEGER",
|
||||||
|
"uint8": "INTEGER",
|
||||||
|
"uint16": "INTEGER",
|
||||||
|
"uint32": "INTEGER",
|
||||||
|
"uint64": "INTEGER",
|
||||||
|
"float64": "NUMBER",
|
||||||
|
"float64-decimal": "NUMBER(%d, %d)",
|
||||||
|
"time.Time-precision": "TIMESTAMP(%d)",
|
||||||
|
}
|
||||||
|
|
||||||
|
// oracle dbBaser
|
||||||
|
type dbBaseOracle struct {
|
||||||
|
dbBase
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ dbBaser = new(dbBaseOracle)
|
||||||
|
|
||||||
|
// create oracle dbBaser.
|
||||||
|
func newdbBaseOracle() dbBaser {
|
||||||
|
b := new(dbBaseOracle)
|
||||||
|
b.ins = b
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// OperatorSQL get oracle operator.
|
||||||
|
func (d *dbBaseOracle) OperatorSQL(operator string) string {
|
||||||
|
return oracleOperators[operator]
|
||||||
|
}
|
||||||
|
|
||||||
|
// DbTypes get oracle table field types.
|
||||||
|
func (d *dbBaseOracle) DbTypes() map[string]string {
|
||||||
|
return oracleTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShowTablesQuery show all the tables in database
|
||||||
|
func (d *dbBaseOracle) ShowTablesQuery() string {
|
||||||
|
return "SELECT TABLE_NAME FROM USER_TABLES"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Oracle
|
||||||
|
func (d *dbBaseOracle) ShowColumnsQuery(table string) string {
|
||||||
|
return fmt.Sprintf("SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS "+
|
||||||
|
"WHERE TABLE_NAME ='%s'", strings.ToUpper(table))
|
||||||
|
}
|
||||||
|
|
||||||
|
// check index is exist
|
||||||
|
func (d *dbBaseOracle) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
||||||
|
row := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM USER_IND_COLUMNS, USER_INDEXES "+
|
||||||
|
"WHERE USER_IND_COLUMNS.INDEX_NAME = USER_INDEXES.INDEX_NAME "+
|
||||||
|
"AND USER_IND_COLUMNS.TABLE_NAME = ? AND USER_IND_COLUMNS.INDEX_NAME = ?", strings.ToUpper(table), strings.ToUpper(name))
|
||||||
|
|
||||||
|
var cnt int
|
||||||
|
row.Scan(&cnt)
|
||||||
|
return cnt > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbBaseOracle) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string {
|
||||||
|
var s []string
|
||||||
|
Q := d.TableQuote()
|
||||||
|
for _, index := range indexes {
|
||||||
|
tmp := fmt.Sprintf(`%s%s%s`, Q, index, Q)
|
||||||
|
s = append(s, tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var hint string
|
||||||
|
|
||||||
|
switch useIndex {
|
||||||
|
case hints.KeyUseIndex, hints.KeyForceIndex:
|
||||||
|
hint = `INDEX`
|
||||||
|
case hints.KeyIgnoreIndex:
|
||||||
|
hint = `NO_INDEX`
|
||||||
|
default:
|
||||||
|
DebugLog.Println("[WARN] Not a valid specifying action, so that action is ignored")
|
||||||
|
return ``
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(` /*+ %s(%s %s)*/ `, hint, tableName, strings.Join(s, `,`))
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute insert sql with given struct and given values.
|
||||||
|
// insert the given values, not the field values in struct.
|
||||||
|
func (d *dbBaseOracle) InsertValue(ctx context.Context, q dbQuerier, mi *modelInfo, isMulti bool, names []string, values []interface{}) (int64, error) {
|
||||||
|
Q := d.ins.TableQuote()
|
||||||
|
|
||||||
|
marks := make([]string, len(names))
|
||||||
|
for i := range marks {
|
||||||
|
marks[i] = ":" + names[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
sep := fmt.Sprintf("%s, %s", Q, Q)
|
||||||
|
qmarks := strings.Join(marks, ", ")
|
||||||
|
columns := strings.Join(names, sep)
|
||||||
|
|
||||||
|
multi := len(values) / len(names)
|
||||||
|
|
||||||
|
if isMulti {
|
||||||
|
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s)", Q, mi.table, Q, Q, columns, Q, qmarks)
|
||||||
|
|
||||||
|
d.ins.ReplaceMarks(&query)
|
||||||
|
|
||||||
|
if isMulti || !d.ins.HasReturningID(mi, &query) {
|
||||||
|
res, err := q.ExecContext(ctx, query, values...)
|
||||||
|
if err == nil {
|
||||||
|
if isMulti {
|
||||||
|
return res.RowsAffected()
|
||||||
|
}
|
||||||
|
|
||||||
|
lastInsertId, err := res.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
DebugLog.Println(ErrLastInsertIdUnavailable, ':', err)
|
||||||
|
return lastInsertId, ErrLastInsertIdUnavailable
|
||||||
|
} else {
|
||||||
|
return lastInsertId, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
row := q.QueryRowContext(ctx, query, values...)
|
||||||
|
var id int64
|
||||||
|
err := row.Scan(&id)
|
||||||
|
return id, err
|
||||||
|
}
|
@ -0,0 +1,197 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// postgresql operators.
|
||||||
|
var postgresOperators = map[string]string{
|
||||||
|
"exact": "= ?",
|
||||||
|
"iexact": "= UPPER(?)",
|
||||||
|
"contains": "LIKE ?",
|
||||||
|
"icontains": "LIKE UPPER(?)",
|
||||||
|
"gt": "> ?",
|
||||||
|
"gte": ">= ?",
|
||||||
|
"lt": "< ?",
|
||||||
|
"lte": "<= ?",
|
||||||
|
"eq": "= ?",
|
||||||
|
"ne": "!= ?",
|
||||||
|
"startswith": "LIKE ?",
|
||||||
|
"endswith": "LIKE ?",
|
||||||
|
"istartswith": "LIKE UPPER(?)",
|
||||||
|
"iendswith": "LIKE UPPER(?)",
|
||||||
|
}
|
||||||
|
|
||||||
|
// postgresql column field types.
|
||||||
|
var postgresTypes = map[string]string{
|
||||||
|
"auto": "serial NOT NULL PRIMARY KEY",
|
||||||
|
"pk": "NOT NULL PRIMARY KEY",
|
||||||
|
"bool": "bool",
|
||||||
|
"string": "varchar(%d)",
|
||||||
|
"string-char": "char(%d)",
|
||||||
|
"string-text": "text",
|
||||||
|
"time.Time-date": "date",
|
||||||
|
"time.Time": "timestamp with time zone",
|
||||||
|
"int8": `smallint CHECK("%COL%" >= -127 AND "%COL%" <= 128)`,
|
||||||
|
"int16": "smallint",
|
||||||
|
"int32": "integer",
|
||||||
|
"int64": "bigint",
|
||||||
|
"uint8": `smallint CHECK("%COL%" >= 0 AND "%COL%" <= 255)`,
|
||||||
|
"uint16": `integer CHECK("%COL%" >= 0)`,
|
||||||
|
"uint32": `bigint CHECK("%COL%" >= 0)`,
|
||||||
|
"uint64": `bigint CHECK("%COL%" >= 0)`,
|
||||||
|
"float64": "double precision",
|
||||||
|
"float64-decimal": "numeric(%d, %d)",
|
||||||
|
"json": "json",
|
||||||
|
"jsonb": "jsonb",
|
||||||
|
"time.Time-precision": "timestamp(%d) with time zone",
|
||||||
|
}
|
||||||
|
|
||||||
|
// postgresql dbBaser.
|
||||||
|
type dbBasePostgres struct {
|
||||||
|
dbBase
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ dbBaser = new(dbBasePostgres)
|
||||||
|
|
||||||
|
// get postgresql operator.
|
||||||
|
func (d *dbBasePostgres) OperatorSQL(operator string) string {
|
||||||
|
return postgresOperators[operator]
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate functioned sql string, such as contains(text).
|
||||||
|
func (d *dbBasePostgres) GenerateOperatorLeftCol(fi *fieldInfo, operator string, leftCol *string) {
|
||||||
|
switch operator {
|
||||||
|
case "contains", "startswith", "endswith":
|
||||||
|
*leftCol = fmt.Sprintf("%s::text", *leftCol)
|
||||||
|
case "iexact", "icontains", "istartswith", "iendswith":
|
||||||
|
*leftCol = fmt.Sprintf("UPPER(%s::text)", *leftCol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// postgresql unsupports updating joined record.
|
||||||
|
func (d *dbBasePostgres) SupportUpdateJoin() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbBasePostgres) MaxLimit() uint64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// postgresql quote is ".
|
||||||
|
func (d *dbBasePostgres) TableQuote() string {
|
||||||
|
return `"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// postgresql value placeholder is $n.
|
||||||
|
// replace default ? to $n.
|
||||||
|
func (d *dbBasePostgres) ReplaceMarks(query *string) {
|
||||||
|
q := *query
|
||||||
|
num := 0
|
||||||
|
for _, c := range q {
|
||||||
|
if c == '?' {
|
||||||
|
num++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if num == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data := make([]byte, 0, len(q)+num)
|
||||||
|
num = 1
|
||||||
|
for i := 0; i < len(q); i++ {
|
||||||
|
c := q[i]
|
||||||
|
if c == '?' {
|
||||||
|
data = append(data, '$')
|
||||||
|
data = append(data, []byte(strconv.Itoa(num))...)
|
||||||
|
num++
|
||||||
|
} else {
|
||||||
|
data = append(data, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*query = string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make returning sql support for postgresql.
|
||||||
|
func (d *dbBasePostgres) HasReturningID(mi *modelInfo, query *string) bool {
|
||||||
|
fi := mi.fields.pk
|
||||||
|
if fi.fieldType&IsPositiveIntegerField == 0 && fi.fieldType&IsIntegerField == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if query != nil {
|
||||||
|
*query = fmt.Sprintf(`%s RETURNING "%s"`, *query, fi.column)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync auto key
|
||||||
|
func (d *dbBasePostgres) setval(ctx context.Context, db dbQuerier, mi *modelInfo, autoFields []string) error {
|
||||||
|
if len(autoFields) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
Q := d.ins.TableQuote()
|
||||||
|
for _, name := range autoFields {
|
||||||
|
query := fmt.Sprintf("SELECT setval(pg_get_serial_sequence('%s', '%s'), (SELECT MAX(%s%s%s) FROM %s%s%s));",
|
||||||
|
mi.table, name,
|
||||||
|
Q, name, Q,
|
||||||
|
Q, mi.table, Q)
|
||||||
|
if _, err := db.ExecContext(ctx, query); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// show table sql for postgresql.
|
||||||
|
func (d *dbBasePostgres) ShowTablesQuery() string {
|
||||||
|
return "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema NOT IN ('pg_catalog', 'information_schema')"
|
||||||
|
}
|
||||||
|
|
||||||
|
// show table columns sql for postgresql.
|
||||||
|
func (d *dbBasePostgres) ShowColumnsQuery(table string) string {
|
||||||
|
return fmt.Sprintf("SELECT column_name, data_type, is_nullable FROM information_schema.columns where table_schema NOT IN ('pg_catalog', 'information_schema') and table_name = '%s'", table)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get column types of postgresql.
|
||||||
|
func (d *dbBasePostgres) DbTypes() map[string]string {
|
||||||
|
return postgresTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
// check index exist in postgresql.
|
||||||
|
func (d *dbBasePostgres) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
||||||
|
query := fmt.Sprintf("SELECT COUNT(*) FROM pg_indexes WHERE tablename = '%s' AND indexname = '%s'", table, name)
|
||||||
|
row := db.QueryRowContext(ctx, query)
|
||||||
|
var cnt int
|
||||||
|
row.Scan(&cnt)
|
||||||
|
return cnt > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSpecifyIndex return a specifying index clause
|
||||||
|
func (d *dbBasePostgres) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string {
|
||||||
|
DebugLog.Println("[WARN] Not support any specifying index action, so that action is ignored")
|
||||||
|
return ``
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new postgresql dbBaser.
|
||||||
|
func newdbBasePostgres() dbBaser {
|
||||||
|
b := new(dbBasePostgres)
|
||||||
|
b.ins = b
|
||||||
|
return b
|
||||||
|
}
|
@ -0,0 +1,184 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beego/beego/v2/client/orm/hints"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sqlite operators.
|
||||||
|
var sqliteOperators = map[string]string{
|
||||||
|
"exact": "= ?",
|
||||||
|
"iexact": "LIKE ? ESCAPE '\\'",
|
||||||
|
"contains": "LIKE ? ESCAPE '\\'",
|
||||||
|
"icontains": "LIKE ? ESCAPE '\\'",
|
||||||
|
"gt": "> ?",
|
||||||
|
"gte": ">= ?",
|
||||||
|
"lt": "< ?",
|
||||||
|
"lte": "<= ?",
|
||||||
|
"eq": "= ?",
|
||||||
|
"ne": "!= ?",
|
||||||
|
"startswith": "LIKE ? ESCAPE '\\'",
|
||||||
|
"endswith": "LIKE ? ESCAPE '\\'",
|
||||||
|
"istartswith": "LIKE ? ESCAPE '\\'",
|
||||||
|
"iendswith": "LIKE ? ESCAPE '\\'",
|
||||||
|
}
|
||||||
|
|
||||||
|
// sqlite column types.
|
||||||
|
var sqliteTypes = map[string]string{
|
||||||
|
"auto": "integer NOT NULL PRIMARY KEY AUTOINCREMENT",
|
||||||
|
"pk": "NOT NULL PRIMARY KEY",
|
||||||
|
"bool": "bool",
|
||||||
|
"string": "varchar(%d)",
|
||||||
|
"string-char": "character(%d)",
|
||||||
|
"string-text": "text",
|
||||||
|
"time.Time-date": "date",
|
||||||
|
"time.Time": "datetime",
|
||||||
|
"time.Time-precision": "datetime(%d)",
|
||||||
|
"int8": "tinyint",
|
||||||
|
"int16": "smallint",
|
||||||
|
"int32": "integer",
|
||||||
|
"int64": "bigint",
|
||||||
|
"uint8": "tinyint unsigned",
|
||||||
|
"uint16": "smallint unsigned",
|
||||||
|
"uint32": "integer unsigned",
|
||||||
|
"uint64": "bigint unsigned",
|
||||||
|
"float64": "real",
|
||||||
|
"float64-decimal": "decimal",
|
||||||
|
}
|
||||||
|
|
||||||
|
// sqlite dbBaser.
|
||||||
|
type dbBaseSqlite struct {
|
||||||
|
dbBase
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ dbBaser = new(dbBaseSqlite)
|
||||||
|
|
||||||
|
// override base db read for update behavior as SQlite does not support syntax
|
||||||
|
func (d *dbBaseSqlite) Read(ctx context.Context, q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string, isForUpdate bool) error {
|
||||||
|
if isForUpdate {
|
||||||
|
DebugLog.Println("[WARN] SQLite does not support SELECT FOR UPDATE query, isForUpdate param is ignored and always as false to do the work")
|
||||||
|
}
|
||||||
|
return d.dbBase.Read(ctx, q, mi, ind, tz, cols, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get sqlite operator.
|
||||||
|
func (d *dbBaseSqlite) OperatorSQL(operator string) string {
|
||||||
|
return sqliteOperators[operator]
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate functioned sql for sqlite.
|
||||||
|
// only support DATE(text).
|
||||||
|
func (d *dbBaseSqlite) GenerateOperatorLeftCol(fi *fieldInfo, operator string, leftCol *string) {
|
||||||
|
if fi.fieldType == TypeDateField {
|
||||||
|
*leftCol = fmt.Sprintf("DATE(%s)", *leftCol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unable updating joined record in sqlite.
|
||||||
|
func (d *dbBaseSqlite) SupportUpdateJoin() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// max int in sqlite.
|
||||||
|
func (d *dbBaseSqlite) MaxLimit() uint64 {
|
||||||
|
return 9223372036854775807
|
||||||
|
}
|
||||||
|
|
||||||
|
// get column types in sqlite.
|
||||||
|
func (d *dbBaseSqlite) DbTypes() map[string]string {
|
||||||
|
return sqliteTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
// get show tables sql in sqlite.
|
||||||
|
func (d *dbBaseSqlite) ShowTablesQuery() string {
|
||||||
|
return "SELECT name FROM sqlite_master WHERE type = 'table'"
|
||||||
|
}
|
||||||
|
|
||||||
|
// get columns in sqlite.
|
||||||
|
func (d *dbBaseSqlite) GetColumns(ctx context.Context, db dbQuerier, table string) (map[string][3]string, error) {
|
||||||
|
query := d.ins.ShowColumnsQuery(table)
|
||||||
|
rows, err := db.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
columns := make(map[string][3]string)
|
||||||
|
for rows.Next() {
|
||||||
|
var tmp, name, typ, null sql.NullString
|
||||||
|
err := rows.Scan(&tmp, &name, &typ, &null, &tmp, &tmp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
columns[name.String] = [3]string{name.String, typ.String, null.String}
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get show columns sql in sqlite.
|
||||||
|
func (d *dbBaseSqlite) ShowColumnsQuery(table string) string {
|
||||||
|
return fmt.Sprintf("pragma table_info('%s')", table)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check index exist in sqlite.
|
||||||
|
func (d *dbBaseSqlite) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
||||||
|
query := fmt.Sprintf("PRAGMA index_list('%s')", table)
|
||||||
|
rows, err := db.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var tmp, index sql.NullString
|
||||||
|
rows.Scan(&tmp, &index, &tmp, &tmp, &tmp)
|
||||||
|
if name == index.String {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSpecifyIndex return a specifying index clause
|
||||||
|
func (d *dbBaseSqlite) GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string {
|
||||||
|
var s []string
|
||||||
|
Q := d.TableQuote()
|
||||||
|
for _, index := range indexes {
|
||||||
|
tmp := fmt.Sprintf(`%s%s%s`, Q, index, Q)
|
||||||
|
s = append(s, tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch useIndex {
|
||||||
|
case hints.KeyUseIndex, hints.KeyForceIndex:
|
||||||
|
return fmt.Sprintf(` INDEXED BY %s `, strings.Join(s, `,`))
|
||||||
|
default:
|
||||||
|
DebugLog.Println("[WARN] Not a valid specifying action, so that action is ignored")
|
||||||
|
return ``
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new sqlite dbBaser.
|
||||||
|
func newdbBaseSqlite() dbBaser {
|
||||||
|
b := new(dbBaseSqlite)
|
||||||
|
b.ins = b
|
||||||
|
return b
|
||||||
|
}
|
@ -0,0 +1,499 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses"
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
// table info struct.
|
||||||
|
type dbTable struct {
|
||||||
|
id int
|
||||||
|
index string
|
||||||
|
name string
|
||||||
|
names []string
|
||||||
|
sel bool
|
||||||
|
inner bool
|
||||||
|
mi *modelInfo
|
||||||
|
fi *fieldInfo
|
||||||
|
jtl *dbTable
|
||||||
|
}
|
||||||
|
|
||||||
|
// tables collection struct, contains some tables.
|
||||||
|
type dbTables struct {
|
||||||
|
tablesM map[string]*dbTable
|
||||||
|
tables []*dbTable
|
||||||
|
mi *modelInfo
|
||||||
|
base dbBaser
|
||||||
|
skipEnd bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// set table info to collection.
|
||||||
|
// if not exist, create new.
|
||||||
|
func (t *dbTables) set(names []string, mi *modelInfo, fi *fieldInfo, inner bool) *dbTable {
|
||||||
|
name := strings.Join(names, ExprSep)
|
||||||
|
if j, ok := t.tablesM[name]; ok {
|
||||||
|
j.name = name
|
||||||
|
j.mi = mi
|
||||||
|
j.fi = fi
|
||||||
|
j.inner = inner
|
||||||
|
} else {
|
||||||
|
i := len(t.tables) + 1
|
||||||
|
jt := &dbTable{i, fmt.Sprintf("T%d", i), name, names, false, inner, mi, fi, nil}
|
||||||
|
t.tablesM[name] = jt
|
||||||
|
t.tables = append(t.tables, jt)
|
||||||
|
}
|
||||||
|
return t.tablesM[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// add table info to collection.
|
||||||
|
func (t *dbTables) add(names []string, mi *modelInfo, fi *fieldInfo, inner bool) (*dbTable, bool) {
|
||||||
|
name := strings.Join(names, ExprSep)
|
||||||
|
if _, ok := t.tablesM[name]; !ok {
|
||||||
|
i := len(t.tables) + 1
|
||||||
|
jt := &dbTable{i, fmt.Sprintf("T%d", i), name, names, false, inner, mi, fi, nil}
|
||||||
|
t.tablesM[name] = jt
|
||||||
|
t.tables = append(t.tables, jt)
|
||||||
|
return jt, true
|
||||||
|
}
|
||||||
|
return t.tablesM[name], false
|
||||||
|
}
|
||||||
|
|
||||||
|
// get table info in collection.
|
||||||
|
func (t *dbTables) get(name string) (*dbTable, bool) {
|
||||||
|
j, ok := t.tablesM[name]
|
||||||
|
return j, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// get related fields info in recursive depth loop.
|
||||||
|
// loop once, depth decreases one.
|
||||||
|
func (t *dbTables) loopDepth(depth int, prefix string, fi *fieldInfo, related []string) []string {
|
||||||
|
if depth < 0 || fi.fieldType == RelManyToMany {
|
||||||
|
return related
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix == "" {
|
||||||
|
prefix = fi.name
|
||||||
|
} else {
|
||||||
|
prefix = prefix + ExprSep + fi.name
|
||||||
|
}
|
||||||
|
related = append(related, prefix)
|
||||||
|
|
||||||
|
depth--
|
||||||
|
for _, fi := range fi.relModelInfo.fields.fieldsRel {
|
||||||
|
related = t.loopDepth(depth, prefix, fi, related)
|
||||||
|
}
|
||||||
|
|
||||||
|
return related
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse related fields.
|
||||||
|
func (t *dbTables) parseRelated(rels []string, depth int) {
|
||||||
|
relsNum := len(rels)
|
||||||
|
related := make([]string, relsNum)
|
||||||
|
copy(related, rels)
|
||||||
|
|
||||||
|
relDepth := depth
|
||||||
|
|
||||||
|
if relsNum != 0 {
|
||||||
|
relDepth = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
relDepth--
|
||||||
|
for _, fi := range t.mi.fields.fieldsRel {
|
||||||
|
related = t.loopDepth(relDepth, "", fi, related)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, s := range related {
|
||||||
|
var (
|
||||||
|
exs = strings.Split(s, ExprSep)
|
||||||
|
names = make([]string, 0, len(exs))
|
||||||
|
mmi = t.mi
|
||||||
|
cancel = true
|
||||||
|
jtl *dbTable
|
||||||
|
)
|
||||||
|
|
||||||
|
inner := true
|
||||||
|
|
||||||
|
for _, ex := range exs {
|
||||||
|
if fi, ok := mmi.fields.GetByAny(ex); ok && fi.rel && fi.fieldType != RelManyToMany {
|
||||||
|
names = append(names, fi.name)
|
||||||
|
mmi = fi.relModelInfo
|
||||||
|
|
||||||
|
if fi.null || t.skipEnd {
|
||||||
|
inner = false
|
||||||
|
}
|
||||||
|
|
||||||
|
jt := t.set(names, mmi, fi, inner)
|
||||||
|
jt.jtl = jtl
|
||||||
|
|
||||||
|
if fi.reverse {
|
||||||
|
cancel = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if cancel {
|
||||||
|
jt.sel = depth > 0
|
||||||
|
|
||||||
|
if i < relsNum {
|
||||||
|
jt.sel = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jtl = jt
|
||||||
|
|
||||||
|
} else {
|
||||||
|
panic(fmt.Errorf("unknown model/table name `%s`", ex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate join string.
|
||||||
|
func (t *dbTables) getJoinSQL() (join string) {
|
||||||
|
Q := t.base.TableQuote()
|
||||||
|
|
||||||
|
for _, jt := range t.tables {
|
||||||
|
if jt.inner {
|
||||||
|
join += "INNER JOIN "
|
||||||
|
} else {
|
||||||
|
join += "LEFT OUTER JOIN "
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
table string
|
||||||
|
t1, t2 string
|
||||||
|
c1, c2 string
|
||||||
|
)
|
||||||
|
t1 = "T0"
|
||||||
|
if jt.jtl != nil {
|
||||||
|
t1 = jt.jtl.index
|
||||||
|
}
|
||||||
|
t2 = jt.index
|
||||||
|
table = jt.mi.table
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case jt.fi.fieldType == RelManyToMany || jt.fi.fieldType == RelReverseMany || jt.fi.reverse && jt.fi.reverseFieldInfo.fieldType == RelManyToMany:
|
||||||
|
c1 = jt.fi.mi.fields.pk.column
|
||||||
|
for _, ffi := range jt.mi.fields.fieldsRel {
|
||||||
|
if jt.fi.mi == ffi.relModelInfo {
|
||||||
|
c2 = ffi.column
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
c1 = jt.fi.column
|
||||||
|
c2 = jt.fi.relModelInfo.fields.pk.column
|
||||||
|
|
||||||
|
if jt.fi.reverse {
|
||||||
|
c1 = jt.mi.fields.pk.column
|
||||||
|
c2 = jt.fi.reverseFieldInfo.column
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
join += fmt.Sprintf("%s%s%s %s ON %s.%s%s%s = %s.%s%s%s ", Q, table, Q, t2,
|
||||||
|
t2, Q, c2, Q, t1, Q, c1, Q)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse orm model struct field tag expression.
|
||||||
|
func (t *dbTables) parseExprs(mi *modelInfo, exprs []string) (index, name string, info *fieldInfo, success bool) {
|
||||||
|
var (
|
||||||
|
jtl *dbTable
|
||||||
|
fi *fieldInfo
|
||||||
|
fiN *fieldInfo
|
||||||
|
mmi = mi
|
||||||
|
)
|
||||||
|
|
||||||
|
num := len(exprs) - 1
|
||||||
|
var names []string
|
||||||
|
|
||||||
|
inner := true
|
||||||
|
|
||||||
|
loopFor:
|
||||||
|
for i, ex := range exprs {
|
||||||
|
|
||||||
|
var ok, okN bool
|
||||||
|
|
||||||
|
if fiN != nil {
|
||||||
|
fi = fiN
|
||||||
|
ok = true
|
||||||
|
fiN = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
fi, ok = mmi.fields.GetByAny(ex)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = okN
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
|
||||||
|
isRel := fi.rel || fi.reverse
|
||||||
|
|
||||||
|
names = append(names, fi.name)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case fi.rel:
|
||||||
|
mmi = fi.relModelInfo
|
||||||
|
if fi.fieldType == RelManyToMany {
|
||||||
|
mmi = fi.relThroughModelInfo
|
||||||
|
}
|
||||||
|
case fi.reverse:
|
||||||
|
mmi = fi.reverseFieldInfo.mi
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < num {
|
||||||
|
fiN, okN = mmi.fields.GetByAny(exprs[i+1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if isRel && (!fi.mi.isThrough || num != i) {
|
||||||
|
if fi.null || t.skipEnd {
|
||||||
|
inner = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.skipEnd && okN || !t.skipEnd {
|
||||||
|
if t.skipEnd && okN && fiN.pk {
|
||||||
|
goto loopEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
jt, _ := t.add(names, mmi, fi, inner)
|
||||||
|
jt.jtl = jtl
|
||||||
|
jtl = jt
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if num != i {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
loopEnd:
|
||||||
|
|
||||||
|
if i == 0 || jtl == nil {
|
||||||
|
index = "T0"
|
||||||
|
} else {
|
||||||
|
index = jtl.index
|
||||||
|
}
|
||||||
|
|
||||||
|
info = fi
|
||||||
|
|
||||||
|
if jtl == nil {
|
||||||
|
name = fi.name
|
||||||
|
} else {
|
||||||
|
name = jtl.name + ExprSep + fi.name
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case fi.rel:
|
||||||
|
|
||||||
|
case fi.reverse:
|
||||||
|
switch fi.reverseFieldInfo.fieldType {
|
||||||
|
case RelOneToOne, RelForeignKey:
|
||||||
|
index = jtl.index
|
||||||
|
info = fi.reverseFieldInfo.mi.fields.pk
|
||||||
|
name = info.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break loopFor
|
||||||
|
|
||||||
|
} else {
|
||||||
|
index = ""
|
||||||
|
name = ""
|
||||||
|
info = nil
|
||||||
|
success = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
success = index != "" && info != nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate condition sql.
|
||||||
|
func (t *dbTables) getCondSQL(cond *Condition, sub bool, tz *time.Location) (where string, params []interface{}) {
|
||||||
|
if cond == nil || cond.IsEmpty() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Q := t.base.TableQuote()
|
||||||
|
|
||||||
|
mi := t.mi
|
||||||
|
|
||||||
|
for i, p := range cond.params {
|
||||||
|
if i > 0 {
|
||||||
|
if p.isOr {
|
||||||
|
where += "OR "
|
||||||
|
} else {
|
||||||
|
where += "AND "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.isNot {
|
||||||
|
where += "NOT "
|
||||||
|
}
|
||||||
|
if p.isCond {
|
||||||
|
w, ps := t.getCondSQL(p.cond, true, tz)
|
||||||
|
if w != "" {
|
||||||
|
w = fmt.Sprintf("( %s) ", w)
|
||||||
|
}
|
||||||
|
where += w
|
||||||
|
params = append(params, ps...)
|
||||||
|
} else {
|
||||||
|
exprs := p.exprs
|
||||||
|
|
||||||
|
num := len(exprs) - 1
|
||||||
|
operator := ""
|
||||||
|
if operators[exprs[num]] {
|
||||||
|
operator = exprs[num]
|
||||||
|
exprs = exprs[:num]
|
||||||
|
}
|
||||||
|
|
||||||
|
index, _, fi, suc := t.parseExprs(mi, exprs)
|
||||||
|
if !suc {
|
||||||
|
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(p.exprs, ExprSep)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if operator == "" {
|
||||||
|
operator = "exact"
|
||||||
|
}
|
||||||
|
|
||||||
|
var operSQL string
|
||||||
|
var args []interface{}
|
||||||
|
if p.isRaw {
|
||||||
|
operSQL = p.sql
|
||||||
|
} else {
|
||||||
|
operSQL, args = t.base.GenerateOperatorSQL(mi, fi, operator, p.args, tz)
|
||||||
|
}
|
||||||
|
|
||||||
|
leftCol := fmt.Sprintf("%s.%s%s%s", index, Q, fi.column, Q)
|
||||||
|
t.base.GenerateOperatorLeftCol(fi, operator, &leftCol)
|
||||||
|
|
||||||
|
where += fmt.Sprintf("%s %s ", leftCol, operSQL)
|
||||||
|
params = append(params, args...)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sub && where != "" {
|
||||||
|
where = "WHERE " + where
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate group sql.
|
||||||
|
func (t *dbTables) getGroupSQL(groups []string) (groupSQL string) {
|
||||||
|
if len(groups) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Q := t.base.TableQuote()
|
||||||
|
|
||||||
|
groupSqls := make([]string, 0, len(groups))
|
||||||
|
for _, group := range groups {
|
||||||
|
exprs := strings.Split(group, ExprSep)
|
||||||
|
|
||||||
|
index, _, fi, suc := t.parseExprs(t.mi, exprs)
|
||||||
|
if !suc {
|
||||||
|
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep)))
|
||||||
|
}
|
||||||
|
|
||||||
|
groupSqls = append(groupSqls, fmt.Sprintf("%s.%s%s%s", index, Q, fi.column, Q))
|
||||||
|
}
|
||||||
|
|
||||||
|
groupSQL = fmt.Sprintf("GROUP BY %s ", strings.Join(groupSqls, ", "))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate order sql.
|
||||||
|
func (t *dbTables) getOrderSQL(orders []*order_clause.Order) (orderSQL string) {
|
||||||
|
if len(orders) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Q := t.base.TableQuote()
|
||||||
|
|
||||||
|
orderSqls := make([]string, 0, len(orders))
|
||||||
|
for _, order := range orders {
|
||||||
|
column := order.GetColumn()
|
||||||
|
clause := strings.Split(column, clauses.ExprDot)
|
||||||
|
|
||||||
|
if order.IsRaw() {
|
||||||
|
if len(clause) == 2 {
|
||||||
|
orderSqls = append(orderSqls, fmt.Sprintf("%s.%s%s%s %s", clause[0], Q, clause[1], Q, order.SortString()))
|
||||||
|
} else if len(clause) == 1 {
|
||||||
|
orderSqls = append(orderSqls, fmt.Sprintf("%s%s%s %s", Q, clause[0], Q, order.SortString()))
|
||||||
|
} else {
|
||||||
|
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(clause, ExprSep)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
index, _, fi, suc := t.parseExprs(t.mi, clause)
|
||||||
|
if !suc {
|
||||||
|
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(clause, ExprSep)))
|
||||||
|
}
|
||||||
|
|
||||||
|
orderSqls = append(orderSqls, fmt.Sprintf("%s.%s%s%s %s", index, Q, fi.column, Q, order.SortString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
orderSQL = fmt.Sprintf("ORDER BY %s ", strings.Join(orderSqls, ", "))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate limit sql.
|
||||||
|
func (t *dbTables) getLimitSQL(mi *modelInfo, offset int64, limit int64) (limits string) {
|
||||||
|
if limit == 0 {
|
||||||
|
limit = int64(DefaultRowsLimit)
|
||||||
|
}
|
||||||
|
if limit < 0 {
|
||||||
|
// no limit
|
||||||
|
if offset > 0 {
|
||||||
|
maxLimit := t.base.MaxLimit()
|
||||||
|
if maxLimit == 0 {
|
||||||
|
limits = fmt.Sprintf("OFFSET %d", offset)
|
||||||
|
} else {
|
||||||
|
limits = fmt.Sprintf("LIMIT %d OFFSET %d", maxLimit, offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if offset <= 0 {
|
||||||
|
limits = fmt.Sprintf("LIMIT %d", limit)
|
||||||
|
} else {
|
||||||
|
limits = fmt.Sprintf("LIMIT %d OFFSET %d", limit, offset)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// getIndexSql generate index sql.
|
||||||
|
func (t *dbTables) getIndexSql(tableName string, useIndex int, indexes []string) (clause string) {
|
||||||
|
if len(indexes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return t.base.GenerateSpecifyIndex(tableName, useIndex, indexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// crete new tables collection.
|
||||||
|
func newDbTables(mi *modelInfo, base dbBaser) *dbTables {
|
||||||
|
tables := &dbTables{}
|
||||||
|
tables.tablesM = make(map[string]*dbTable)
|
||||||
|
tables.mi = mi
|
||||||
|
tables.base = base
|
||||||
|
return tables
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2015 TiDB Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mysql dbBaser implementation.
|
||||||
|
type dbBaseTidb struct {
|
||||||
|
dbBase
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ dbBaser = new(dbBaseTidb)
|
||||||
|
|
||||||
|
// get mysql operator.
|
||||||
|
func (d *dbBaseTidb) OperatorSQL(operator string) string {
|
||||||
|
return mysqlOperators[operator]
|
||||||
|
}
|
||||||
|
|
||||||
|
// get mysql table field types.
|
||||||
|
func (d *dbBaseTidb) DbTypes() map[string]string {
|
||||||
|
return mysqlTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
// show table sql for mysql.
|
||||||
|
func (d *dbBaseTidb) ShowTablesQuery() string {
|
||||||
|
return "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema = DATABASE()"
|
||||||
|
}
|
||||||
|
|
||||||
|
// show columns sql of table for mysql.
|
||||||
|
func (d *dbBaseTidb) ShowColumnsQuery(table string) string {
|
||||||
|
return fmt.Sprintf("SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE FROM information_schema.columns "+
|
||||||
|
"WHERE table_schema = DATABASE() AND table_name = '%s'", table)
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute sql to check index exist.
|
||||||
|
func (d *dbBaseTidb) IndexExists(ctx context.Context, db dbQuerier, table string, name string) bool {
|
||||||
|
row := db.QueryRowContext(ctx, "SELECT count(*) FROM information_schema.statistics "+
|
||||||
|
"WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?", table, name)
|
||||||
|
var cnt int
|
||||||
|
row.Scan(&cnt)
|
||||||
|
return cnt > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new mysql dbBaser.
|
||||||
|
func newdbBaseTidb() dbBaser {
|
||||||
|
b := new(dbBaseTidb)
|
||||||
|
b.ins = b
|
||||||
|
return b
|
||||||
|
}
|
@ -0,0 +1,175 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// get table alias.
|
||||||
|
func getDbAlias(name string) *alias {
|
||||||
|
if al, ok := dataBaseCache.get(name); ok {
|
||||||
|
return al
|
||||||
|
}
|
||||||
|
panic(fmt.Errorf("unknown DataBase alias name %s", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// get pk column info.
|
||||||
|
func getExistPk(mi *modelInfo, ind reflect.Value) (column string, value interface{}, exist bool) {
|
||||||
|
fi := mi.fields.pk
|
||||||
|
|
||||||
|
v := ind.FieldByIndex(fi.fieldIndex)
|
||||||
|
if fi.fieldType&IsPositiveIntegerField > 0 {
|
||||||
|
vu := v.Uint()
|
||||||
|
exist = vu > 0
|
||||||
|
value = vu
|
||||||
|
} else if fi.fieldType&IsIntegerField > 0 {
|
||||||
|
vu := v.Int()
|
||||||
|
exist = true
|
||||||
|
value = vu
|
||||||
|
} else if fi.fieldType&IsRelField > 0 {
|
||||||
|
_, value, exist = getExistPk(fi.relModelInfo, reflect.Indirect(v))
|
||||||
|
} else {
|
||||||
|
vu := v.String()
|
||||||
|
exist = vu != ""
|
||||||
|
value = vu
|
||||||
|
}
|
||||||
|
|
||||||
|
column = fi.column
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get fields description as flatted string.
|
||||||
|
func getFlatParams(fi *fieldInfo, args []interface{}, tz *time.Location) (params []interface{}) {
|
||||||
|
outFor:
|
||||||
|
for _, arg := range args {
|
||||||
|
if arg == nil {
|
||||||
|
params = append(params, arg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val := reflect.ValueOf(arg)
|
||||||
|
kind := val.Kind()
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
val = val.Elem()
|
||||||
|
kind = val.Kind()
|
||||||
|
arg = val.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.String:
|
||||||
|
v := val.String()
|
||||||
|
if fi != nil {
|
||||||
|
if fi.fieldType == TypeTimeField || fi.fieldType == TypeDateField || fi.fieldType == TypeDateTimeField {
|
||||||
|
var t time.Time
|
||||||
|
var err error
|
||||||
|
if len(v) >= 19 {
|
||||||
|
s := v[:19]
|
||||||
|
t, err = time.ParseInLocation(formatDateTime, s, DefaultTimeLoc)
|
||||||
|
} else if len(v) >= 10 {
|
||||||
|
s := v
|
||||||
|
if len(v) > 10 {
|
||||||
|
s = v[:10]
|
||||||
|
}
|
||||||
|
t, err = time.ParseInLocation(formatDate, s, tz)
|
||||||
|
} else {
|
||||||
|
s := v
|
||||||
|
if len(s) > 8 {
|
||||||
|
s = v[:8]
|
||||||
|
}
|
||||||
|
t, err = time.ParseInLocation(formatTime, s, tz)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if fi.fieldType == TypeDateField {
|
||||||
|
v = t.In(tz).Format(formatDate)
|
||||||
|
} else if fi.fieldType == TypeDateTimeField {
|
||||||
|
v = t.In(tz).Format(formatDateTime)
|
||||||
|
} else {
|
||||||
|
v = t.In(tz).Format(formatTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arg = v
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
arg = val.Int()
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
arg = val.Uint()
|
||||||
|
case reflect.Float32:
|
||||||
|
arg, _ = StrTo(ToStr(arg)).Float64()
|
||||||
|
case reflect.Float64:
|
||||||
|
arg = val.Float()
|
||||||
|
case reflect.Bool:
|
||||||
|
arg = val.Bool()
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
if _, ok := arg.([]byte); ok {
|
||||||
|
continue outFor
|
||||||
|
}
|
||||||
|
|
||||||
|
var args []interface{}
|
||||||
|
for i := 0; i < val.Len(); i++ {
|
||||||
|
v := val.Index(i)
|
||||||
|
|
||||||
|
var vu interface{}
|
||||||
|
if v.CanInterface() {
|
||||||
|
vu = v.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
if vu == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
args = append(args, vu)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) > 0 {
|
||||||
|
p := getFlatParams(fi, args, tz)
|
||||||
|
params = append(params, p...)
|
||||||
|
}
|
||||||
|
continue outFor
|
||||||
|
case reflect.Struct:
|
||||||
|
if v, ok := arg.(time.Time); ok {
|
||||||
|
if fi != nil && fi.fieldType == TypeDateField {
|
||||||
|
arg = v.In(tz).Format(formatDate)
|
||||||
|
} else if fi != nil && fi.fieldType == TypeDateTimeField {
|
||||||
|
arg = v.In(tz).Format(formatDateTime)
|
||||||
|
} else if fi != nil && fi.fieldType == TypeTimeField {
|
||||||
|
arg = v.In(tz).Format(formatTime)
|
||||||
|
} else {
|
||||||
|
arg = v.In(tz).Format(formatDateTime)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
typ := val.Type()
|
||||||
|
name := getFullName(typ)
|
||||||
|
var value interface{}
|
||||||
|
if mmi, ok := defaultModelCache.getByFullName(name); ok {
|
||||||
|
if _, vu, exist := getExistPk(mmi, val); exist {
|
||||||
|
value = vu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arg = value
|
||||||
|
|
||||||
|
if arg == nil {
|
||||||
|
panic(fmt.Errorf("need a valid args value, unknown table or value `%s`", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params = append(params, arg)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,181 @@
|
|||||||
|
// Copyright 2020 beego
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/beego/beego/v2/core/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DoNothingOrm won't do anything, usually you use this to custom your mock Ormer implementation
|
||||||
|
// I think golang mocking interface is hard to use
|
||||||
|
// this may help you to integrate with Ormer
|
||||||
|
|
||||||
|
var _ Ormer = new(DoNothingOrm)
|
||||||
|
|
||||||
|
type DoNothingOrm struct{}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) Read(md interface{}, cols ...string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) ReadForUpdate(md interface{}, cols ...string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
||||||
|
return false, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
||||||
|
return false, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) QueryM2M(md interface{}, name string) QueryM2Mer {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||||
|
func (d *DoNothingOrm) QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) QueryTable(ptrStructOrTableName interface{}) QuerySeter {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||||
|
func (d *DoNothingOrm) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) DBStats() *sql.DBStats {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) Insert(md interface{}) (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) InsertMulti(bulk int, mds interface{}) (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) Update(md interface{}, cols ...string) (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) Delete(md interface{}, cols ...string) (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) Raw(query string, args ...interface{}) RawSeter {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) Driver() Driver {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) Begin() (TxOrmer, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) BeginWithCtx(ctx context.Context) (TxOrmer, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingOrm) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoNothingTxOrm is similar with DoNothingOrm, usually you use it to test
|
||||||
|
type DoNothingTxOrm struct {
|
||||||
|
DoNothingOrm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingTxOrm) Commit() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DoNothingTxOrm) Rollback() error {
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2020 beego
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FilterChain is used to build a Filter
|
||||||
|
// don't forget to call next(...) inside your Filter
|
||||||
|
type FilterChain func(next Filter) Filter
|
||||||
|
|
||||||
|
// Filter's behavior is a little big strange.
|
||||||
|
// it's only be called when users call methods of Ormer
|
||||||
|
// return value is an array. it's a little bit hard to understand,
|
||||||
|
// for example, the Ormer's Read method only return error
|
||||||
|
// so the filter processing this method should return an array whose first element is error
|
||||||
|
// and, Ormer's ReadOrCreateWithCtx return three values, so the Filter's result should contains three values
|
||||||
|
type Filter func(ctx context.Context, inv *Invocation) []interface{}
|
||||||
|
|
||||||
|
var globalFilterChains = make([]FilterChain, 0, 4)
|
||||||
|
|
||||||
|
// AddGlobalFilterChain adds a new FilterChain
|
||||||
|
// All orm instances built after this invocation will use this filterChain,
|
||||||
|
// but instances built before this invocation will not be affected
|
||||||
|
func AddGlobalFilterChain(filterChain ...FilterChain) {
|
||||||
|
globalFilterChains = append(globalFilterChains, filterChain...)
|
||||||
|
}
|
@ -0,0 +1,534 @@
|
|||||||
|
// Copyright 2020 beego
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beego/beego/v2/core/logs"
|
||||||
|
"github.com/beego/beego/v2/core/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TxNameKey = "TxName"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ Ormer = new(filterOrmDecorator)
|
||||||
|
_ TxOrmer = new(filterOrmDecorator)
|
||||||
|
)
|
||||||
|
|
||||||
|
type filterOrmDecorator struct {
|
||||||
|
ormer
|
||||||
|
TxBeginner
|
||||||
|
TxCommitter
|
||||||
|
|
||||||
|
root Filter
|
||||||
|
|
||||||
|
insideTx bool
|
||||||
|
txStartTime time.Time
|
||||||
|
txName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFilterOrmDecorator(delegate Ormer, filterChains ...FilterChain) Ormer {
|
||||||
|
res := &filterOrmDecorator{
|
||||||
|
ormer: delegate,
|
||||||
|
TxBeginner: delegate,
|
||||||
|
root: func(ctx context.Context, inv *Invocation) []interface{} {
|
||||||
|
return inv.execute(ctx)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(filterChains) - 1; i >= 0; i-- {
|
||||||
|
node := filterChains[i]
|
||||||
|
res.root = node(res.root)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFilterTxOrmDecorator(delegate TxOrmer, root Filter, txName string) TxOrmer {
|
||||||
|
res := &filterOrmDecorator{
|
||||||
|
ormer: delegate,
|
||||||
|
TxCommitter: delegate,
|
||||||
|
root: root,
|
||||||
|
insideTx: true,
|
||||||
|
txStartTime: time.Now(),
|
||||||
|
txName: txName,
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) Read(md interface{}, cols ...string) error {
|
||||||
|
return f.ReadWithCtx(context.Background(), md, cols...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
||||||
|
mi, _ := defaultModelCache.getByMd(md)
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "ReadWithCtx",
|
||||||
|
Args: []interface{}{md, cols},
|
||||||
|
Md: md,
|
||||||
|
mi: mi,
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
err := f.ormer.ReadWithCtx(c, md, cols...)
|
||||||
|
return []interface{}{err}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(ctx, inv)
|
||||||
|
return f.convertError(res[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) ReadForUpdate(md interface{}, cols ...string) error {
|
||||||
|
return f.ReadForUpdateWithCtx(context.Background(), md, cols...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
||||||
|
mi, _ := defaultModelCache.getByMd(md)
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "ReadForUpdateWithCtx",
|
||||||
|
Args: []interface{}{md, cols},
|
||||||
|
Md: md,
|
||||||
|
mi: mi,
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
err := f.ormer.ReadForUpdateWithCtx(c, md, cols...)
|
||||||
|
return []interface{}{err}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(ctx, inv)
|
||||||
|
return f.convertError(res[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
||||||
|
return f.ReadOrCreateWithCtx(context.Background(), md, col1, cols...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
||||||
|
mi, _ := defaultModelCache.getByMd(md)
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "ReadOrCreateWithCtx",
|
||||||
|
Args: []interface{}{md, col1, cols},
|
||||||
|
Md: md,
|
||||||
|
mi: mi,
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
ok, res, err := f.ormer.ReadOrCreateWithCtx(c, md, col1, cols...)
|
||||||
|
return []interface{}{ok, res, err}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(ctx, inv)
|
||||||
|
return res[0].(bool), res[1].(int64), f.convertError(res[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) {
|
||||||
|
return f.LoadRelatedWithCtx(context.Background(), md, name, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error) {
|
||||||
|
mi, _ := defaultModelCache.getByMd(md)
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "LoadRelatedWithCtx",
|
||||||
|
Args: []interface{}{md, name, args},
|
||||||
|
Md: md,
|
||||||
|
mi: mi,
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
res, err := f.ormer.LoadRelatedWithCtx(c, md, name, args...)
|
||||||
|
return []interface{}{res, err}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(ctx, inv)
|
||||||
|
return res[0].(int64), f.convertError(res[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) QueryM2M(md interface{}, name string) QueryM2Mer {
|
||||||
|
mi, _ := defaultModelCache.getByMd(md)
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "QueryM2M",
|
||||||
|
Args: []interface{}{md, name},
|
||||||
|
Md: md,
|
||||||
|
mi: mi,
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
res := f.ormer.QueryM2M(md, name)
|
||||||
|
return []interface{}{res}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(context.Background(), inv)
|
||||||
|
if res[0] == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return res[0].(QueryM2Mer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||||
|
func (f *filterOrmDecorator) QueryM2MWithCtx(_ context.Context, md interface{}, name string) QueryM2Mer {
|
||||||
|
logs.Warn("QueryM2MWithCtx is DEPRECATED. Use methods with `WithCtx` on QueryM2Mer suffix as replacement.")
|
||||||
|
return f.QueryM2M(md, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) QueryTable(ptrStructOrTableName interface{}) QuerySeter {
|
||||||
|
var (
|
||||||
|
name string
|
||||||
|
md interface{}
|
||||||
|
mi *modelInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
if table, ok := ptrStructOrTableName.(string); ok {
|
||||||
|
name = table
|
||||||
|
} else {
|
||||||
|
name = getFullName(indirectType(reflect.TypeOf(ptrStructOrTableName)))
|
||||||
|
md = ptrStructOrTableName
|
||||||
|
}
|
||||||
|
|
||||||
|
if m, ok := defaultModelCache.getByFullName(name); ok {
|
||||||
|
mi = m
|
||||||
|
}
|
||||||
|
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "QueryTable",
|
||||||
|
Args: []interface{}{ptrStructOrTableName},
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
Md: md,
|
||||||
|
mi: mi,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
res := f.ormer.QueryTable(ptrStructOrTableName)
|
||||||
|
return []interface{}{res}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(context.Background(), inv)
|
||||||
|
|
||||||
|
if res[0] == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return res[0].(QuerySeter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||||
|
func (f *filterOrmDecorator) QueryTableWithCtx(_ context.Context, ptrStructOrTableName interface{}) QuerySeter {
|
||||||
|
logs.Warn("QueryTableWithCtx is DEPRECATED. Use methods with `WithCtx`on QuerySeter suffix as replacement.")
|
||||||
|
return f.QueryTable(ptrStructOrTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) DBStats() *sql.DBStats {
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "DBStats",
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
res := f.ormer.DBStats()
|
||||||
|
return []interface{}{res}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(context.Background(), inv)
|
||||||
|
|
||||||
|
if res[0] == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return res[0].(*sql.DBStats)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) Insert(md interface{}) (int64, error) {
|
||||||
|
return f.InsertWithCtx(context.Background(), md)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) {
|
||||||
|
mi, _ := defaultModelCache.getByMd(md)
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "InsertWithCtx",
|
||||||
|
Args: []interface{}{md},
|
||||||
|
Md: md,
|
||||||
|
mi: mi,
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
res, err := f.ormer.InsertWithCtx(c, md)
|
||||||
|
return []interface{}{res, err}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(ctx, inv)
|
||||||
|
return res[0].(int64), f.convertError(res[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) {
|
||||||
|
return f.InsertOrUpdateWithCtx(context.Background(), md, colConflitAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) {
|
||||||
|
mi, _ := defaultModelCache.getByMd(md)
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "InsertOrUpdateWithCtx",
|
||||||
|
Args: []interface{}{md, colConflitAndArgs},
|
||||||
|
Md: md,
|
||||||
|
mi: mi,
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
res, err := f.ormer.InsertOrUpdateWithCtx(c, md, colConflitAndArgs...)
|
||||||
|
return []interface{}{res, err}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(ctx, inv)
|
||||||
|
return res[0].(int64), f.convertError(res[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) InsertMulti(bulk int, mds interface{}) (int64, error) {
|
||||||
|
return f.InsertMultiWithCtx(context.Background(), bulk, mds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertMultiWithCtx uses the first element's model info
|
||||||
|
func (f *filterOrmDecorator) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) {
|
||||||
|
var (
|
||||||
|
md interface{}
|
||||||
|
mi *modelInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
sind := reflect.Indirect(reflect.ValueOf(mds))
|
||||||
|
|
||||||
|
if (sind.Kind() == reflect.Array || sind.Kind() == reflect.Slice) && sind.Len() > 0 {
|
||||||
|
ind := reflect.Indirect(sind.Index(0))
|
||||||
|
md = ind.Interface()
|
||||||
|
mi, _ = defaultModelCache.getByMd(md)
|
||||||
|
}
|
||||||
|
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "InsertMultiWithCtx",
|
||||||
|
Args: []interface{}{bulk, mds},
|
||||||
|
Md: md,
|
||||||
|
mi: mi,
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
res, err := f.ormer.InsertMultiWithCtx(c, bulk, mds)
|
||||||
|
return []interface{}{res, err}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(ctx, inv)
|
||||||
|
return res[0].(int64), f.convertError(res[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) Update(md interface{}, cols ...string) (int64, error) {
|
||||||
|
return f.UpdateWithCtx(context.Background(), md, cols...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
||||||
|
mi, _ := defaultModelCache.getByMd(md)
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "UpdateWithCtx",
|
||||||
|
Args: []interface{}{md, cols},
|
||||||
|
Md: md,
|
||||||
|
mi: mi,
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
res, err := f.ormer.UpdateWithCtx(c, md, cols...)
|
||||||
|
return []interface{}{res, err}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(ctx, inv)
|
||||||
|
return res[0].(int64), f.convertError(res[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) Delete(md interface{}, cols ...string) (int64, error) {
|
||||||
|
return f.DeleteWithCtx(context.Background(), md, cols...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
||||||
|
mi, _ := defaultModelCache.getByMd(md)
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "DeleteWithCtx",
|
||||||
|
Args: []interface{}{md, cols},
|
||||||
|
Md: md,
|
||||||
|
mi: mi,
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
res, err := f.ormer.DeleteWithCtx(c, md, cols...)
|
||||||
|
return []interface{}{res, err}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(ctx, inv)
|
||||||
|
return res[0].(int64), f.convertError(res[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) Raw(query string, args ...interface{}) RawSeter {
|
||||||
|
return f.RawWithCtx(context.Background(), query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter {
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "RawWithCtx",
|
||||||
|
Args: []interface{}{query, args},
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
res := f.ormer.RawWithCtx(c, query, args...)
|
||||||
|
return []interface{}{res}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(ctx, inv)
|
||||||
|
|
||||||
|
if res[0] == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return res[0].(RawSeter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) Driver() Driver {
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "Driver",
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
res := f.ormer.Driver()
|
||||||
|
return []interface{}{res}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(context.Background(), inv)
|
||||||
|
if res[0] == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return res[0].(Driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) Begin() (TxOrmer, error) {
|
||||||
|
return f.BeginWithCtxAndOpts(context.Background(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) BeginWithCtx(ctx context.Context) (TxOrmer, error) {
|
||||||
|
return f.BeginWithCtxAndOpts(ctx, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) {
|
||||||
|
return f.BeginWithCtxAndOpts(context.Background(), opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) {
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "BeginWithCtxAndOpts",
|
||||||
|
Args: []interface{}{opts},
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
res, err := f.TxBeginner.BeginWithCtxAndOpts(c, opts)
|
||||||
|
res = NewFilterTxOrmDecorator(res, f.root, getTxNameFromCtx(c))
|
||||||
|
return []interface{}{res, err}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(ctx, inv)
|
||||||
|
return res[0].(TxOrmer), f.convertError(res[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||||
|
return f.DoTxWithCtxAndOpts(context.Background(), nil, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||||
|
return f.DoTxWithCtxAndOpts(ctx, nil, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||||
|
return f.DoTxWithCtxAndOpts(context.Background(), opts, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "DoTxWithCtxAndOpts",
|
||||||
|
Args: []interface{}{opts, task},
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
TxName: getTxNameFromCtx(ctx),
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
err := doTxTemplate(c, f, opts, task)
|
||||||
|
return []interface{}{err}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(ctx, inv)
|
||||||
|
return f.convertError(res[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) Commit() error {
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "Commit",
|
||||||
|
Args: []interface{}{},
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
TxName: f.txName,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
err := f.TxCommitter.Commit()
|
||||||
|
return []interface{}{err}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(context.Background(), inv)
|
||||||
|
return f.convertError(res[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) Rollback() error {
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "Rollback",
|
||||||
|
Args: []interface{}{},
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
TxName: f.txName,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
err := f.TxCommitter.Rollback()
|
||||||
|
return []interface{}{err}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(context.Background(), inv)
|
||||||
|
return f.convertError(res[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *filterOrmDecorator) RollbackUnlessCommit() error {
|
||||||
|
inv := &Invocation{
|
||||||
|
Method: "RollbackUnlessCommit",
|
||||||
|
Args: []interface{}{},
|
||||||
|
InsideTx: f.insideTx,
|
||||||
|
TxStartTime: f.txStartTime,
|
||||||
|
TxName: f.txName,
|
||||||
|
f: func(c context.Context) []interface{} {
|
||||||
|
err := f.TxCommitter.RollbackUnlessCommit()
|
||||||
|
return []interface{}{err}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res := f.root(context.Background(), inv)
|
||||||
|
return f.convertError(res[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*filterOrmDecorator) convertError(v interface{}) error {
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTxNameFromCtx(ctx context.Context) string {
|
||||||
|
txName := ""
|
||||||
|
if n, ok := ctx.Value(TxNameKey).(string); ok {
|
||||||
|
txName = n
|
||||||
|
}
|
||||||
|
return txName
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
// Copyright 2020 beego-dev
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package hints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/beego/beego/v2/core/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// query level
|
||||||
|
KeyForceIndex = iota
|
||||||
|
KeyUseIndex
|
||||||
|
KeyIgnoreIndex
|
||||||
|
KeyForUpdate
|
||||||
|
KeyLimit
|
||||||
|
KeyOffset
|
||||||
|
KeyOrderBy
|
||||||
|
KeyRelDepth
|
||||||
|
)
|
||||||
|
|
||||||
|
type Hint struct {
|
||||||
|
key interface{}
|
||||||
|
value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ utils.KV = new(Hint)
|
||||||
|
|
||||||
|
// GetKey return key
|
||||||
|
func (s *Hint) GetKey() interface{} {
|
||||||
|
return s.key
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue return value
|
||||||
|
func (s *Hint) GetValue() interface{} {
|
||||||
|
return s.value
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ utils.KV = new(Hint)
|
||||||
|
|
||||||
|
// ForceIndex return a hint about ForceIndex
|
||||||
|
func ForceIndex(indexes ...string) *Hint {
|
||||||
|
return NewHint(KeyForceIndex, indexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseIndex return a hint about UseIndex
|
||||||
|
func UseIndex(indexes ...string) *Hint {
|
||||||
|
return NewHint(KeyUseIndex, indexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IgnoreIndex return a hint about IgnoreIndex
|
||||||
|
func IgnoreIndex(indexes ...string) *Hint {
|
||||||
|
return NewHint(KeyIgnoreIndex, indexes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForUpdate return a hint about ForUpdate
|
||||||
|
func ForUpdate() *Hint {
|
||||||
|
return NewHint(KeyForUpdate, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultRelDepth return a hint about DefaultRelDepth
|
||||||
|
func DefaultRelDepth() *Hint {
|
||||||
|
return NewHint(KeyRelDepth, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RelDepth return a hint about RelDepth
|
||||||
|
func RelDepth(d int) *Hint {
|
||||||
|
return NewHint(KeyRelDepth, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit return a hint about Limit
|
||||||
|
func Limit(d int64) *Hint {
|
||||||
|
return NewHint(KeyLimit, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset return a hint about Offset
|
||||||
|
func Offset(d int64) *Hint {
|
||||||
|
return NewHint(KeyOffset, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrderBy return a hint about OrderBy
|
||||||
|
func OrderBy(s string) *Hint {
|
||||||
|
return NewHint(KeyOrderBy, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHint return a hint
|
||||||
|
func NewHint(key interface{}, value interface{}) *Hint {
|
||||||
|
return &Hint{
|
||||||
|
key: key,
|
||||||
|
value: value,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
// Copyright 2020 beego
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Invocation represents an "Orm" invocation
|
||||||
|
type Invocation struct {
|
||||||
|
Method string
|
||||||
|
// Md may be nil in some cases. It depends on method
|
||||||
|
Md interface{}
|
||||||
|
// the args are all arguments except context.Context
|
||||||
|
Args []interface{}
|
||||||
|
|
||||||
|
mi *modelInfo
|
||||||
|
// f is the Orm operation
|
||||||
|
f func(ctx context.Context) []interface{}
|
||||||
|
|
||||||
|
// insideTx indicates whether this is inside a transaction
|
||||||
|
InsideTx bool
|
||||||
|
TxStartTime time.Time
|
||||||
|
TxName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inv *Invocation) GetTableName() string {
|
||||||
|
if inv.mi != nil {
|
||||||
|
return inv.mi.table
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inv *Invocation) execute(ctx context.Context) []interface{} {
|
||||||
|
return inv.f(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPkFieldName return the primary key of this table
|
||||||
|
// if not found, "" is returned
|
||||||
|
func (inv *Invocation) GetPkFieldName() string {
|
||||||
|
if inv.mi.fields.pk != nil {
|
||||||
|
return inv.mi.fields.pk.name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
@ -0,0 +1,573 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
odCascade = "cascade"
|
||||||
|
odSetNULL = "set_null"
|
||||||
|
odSetDefault = "set_default"
|
||||||
|
odDoNothing = "do_nothing"
|
||||||
|
defaultStructTagName = "orm"
|
||||||
|
defaultStructTagDelim = ";"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultModelCache = NewModelCacheHandler()
|
||||||
|
|
||||||
|
// model info collection
|
||||||
|
type modelCache struct {
|
||||||
|
sync.RWMutex // only used outsite for bootStrap
|
||||||
|
orders []string
|
||||||
|
cache map[string]*modelInfo
|
||||||
|
cacheByFullName map[string]*modelInfo
|
||||||
|
done bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewModelCacheHandler generator of modelCache
|
||||||
|
func NewModelCacheHandler() *modelCache {
|
||||||
|
return &modelCache{
|
||||||
|
cache: make(map[string]*modelInfo),
|
||||||
|
cacheByFullName: make(map[string]*modelInfo),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all model info
|
||||||
|
func (mc *modelCache) all() map[string]*modelInfo {
|
||||||
|
m := make(map[string]*modelInfo, len(mc.cache))
|
||||||
|
for k, v := range mc.cache {
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// get ordered model info
|
||||||
|
func (mc *modelCache) allOrdered() []*modelInfo {
|
||||||
|
m := make([]*modelInfo, 0, len(mc.orders))
|
||||||
|
for _, table := range mc.orders {
|
||||||
|
m = append(m, mc.cache[table])
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// get model info by table name
|
||||||
|
func (mc *modelCache) get(table string) (mi *modelInfo, ok bool) {
|
||||||
|
mi, ok = mc.cache[table]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get model info by full name
|
||||||
|
func (mc *modelCache) getByFullName(name string) (mi *modelInfo, ok bool) {
|
||||||
|
mi, ok = mc.cacheByFullName[name]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *modelCache) getByMd(md interface{}) (*modelInfo, bool) {
|
||||||
|
val := reflect.ValueOf(md)
|
||||||
|
ind := reflect.Indirect(val)
|
||||||
|
typ := ind.Type()
|
||||||
|
name := getFullName(typ)
|
||||||
|
return mc.getByFullName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set model info to collection
|
||||||
|
func (mc *modelCache) set(table string, mi *modelInfo) *modelInfo {
|
||||||
|
mii := mc.cache[table]
|
||||||
|
mc.cache[table] = mi
|
||||||
|
mc.cacheByFullName[mi.fullName] = mi
|
||||||
|
if mii == nil {
|
||||||
|
mc.orders = append(mc.orders, table)
|
||||||
|
}
|
||||||
|
return mii
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean all model info.
|
||||||
|
func (mc *modelCache) clean() {
|
||||||
|
mc.Lock()
|
||||||
|
defer mc.Unlock()
|
||||||
|
|
||||||
|
mc.orders = make([]string, 0)
|
||||||
|
mc.cache = make(map[string]*modelInfo)
|
||||||
|
mc.cacheByFullName = make(map[string]*modelInfo)
|
||||||
|
mc.done = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// bootstrap bootstrap for models
|
||||||
|
func (mc *modelCache) bootstrap() {
|
||||||
|
mc.Lock()
|
||||||
|
defer mc.Unlock()
|
||||||
|
if mc.done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
models map[string]*modelInfo
|
||||||
|
)
|
||||||
|
if dataBaseCache.getDefault() == nil {
|
||||||
|
err = fmt.Errorf("must have one register DataBase alias named `default`")
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
|
||||||
|
// set rel and reverse model
|
||||||
|
// RelManyToMany set the relTable
|
||||||
|
models = mc.all()
|
||||||
|
for _, mi := range models {
|
||||||
|
for _, fi := range mi.fields.columns {
|
||||||
|
if fi.rel || fi.reverse {
|
||||||
|
elm := fi.addrValue.Type().Elem()
|
||||||
|
if fi.fieldType == RelReverseMany || fi.fieldType == RelManyToMany {
|
||||||
|
elm = elm.Elem()
|
||||||
|
}
|
||||||
|
// check the rel or reverse model already register
|
||||||
|
name := getFullName(elm)
|
||||||
|
mii, ok := mc.getByFullName(name)
|
||||||
|
if !ok || mii.pkg != elm.PkgPath() {
|
||||||
|
err = fmt.Errorf("can not find rel in field `%s`, `%s` may be miss register", fi.fullName, elm.String())
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
fi.relModelInfo = mii
|
||||||
|
|
||||||
|
switch fi.fieldType {
|
||||||
|
case RelManyToMany:
|
||||||
|
if fi.relThrough != "" {
|
||||||
|
if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) {
|
||||||
|
pn := fi.relThrough[:i]
|
||||||
|
rmi, ok := mc.getByFullName(fi.relThrough)
|
||||||
|
if !ok || pn != rmi.pkg {
|
||||||
|
err = fmt.Errorf("field `%s` wrong rel_through value `%s` cannot find table", fi.fullName, fi.relThrough)
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
fi.relThroughModelInfo = rmi
|
||||||
|
fi.relTable = rmi.table
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough)
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i := newM2MModelInfo(mi, mii)
|
||||||
|
if fi.relTable != "" {
|
||||||
|
i.table = fi.relTable
|
||||||
|
}
|
||||||
|
if v := mc.set(i.table, i); v != nil {
|
||||||
|
err = fmt.Errorf("the rel table name `%s` already registered, cannot be use, please change one", fi.relTable)
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
fi.relTable = i.table
|
||||||
|
fi.relThroughModelInfo = i
|
||||||
|
}
|
||||||
|
|
||||||
|
fi.relThroughModelInfo.isThrough = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the rel filed while the relModelInfo also has filed point to current model
|
||||||
|
// if not exist, add a new field to the relModelInfo
|
||||||
|
models = mc.all()
|
||||||
|
for _, mi := range models {
|
||||||
|
for _, fi := range mi.fields.fieldsRel {
|
||||||
|
switch fi.fieldType {
|
||||||
|
case RelForeignKey, RelOneToOne, RelManyToMany:
|
||||||
|
inModel := false
|
||||||
|
for _, ffi := range fi.relModelInfo.fields.fieldsReverse {
|
||||||
|
if ffi.relModelInfo == mi {
|
||||||
|
inModel = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !inModel {
|
||||||
|
rmi := fi.relModelInfo
|
||||||
|
ffi := new(fieldInfo)
|
||||||
|
ffi.name = mi.name
|
||||||
|
ffi.column = ffi.name
|
||||||
|
ffi.fullName = rmi.fullName + "." + ffi.name
|
||||||
|
ffi.reverse = true
|
||||||
|
ffi.relModelInfo = mi
|
||||||
|
ffi.mi = rmi
|
||||||
|
if fi.fieldType == RelOneToOne {
|
||||||
|
ffi.fieldType = RelReverseOne
|
||||||
|
} else {
|
||||||
|
ffi.fieldType = RelReverseMany
|
||||||
|
}
|
||||||
|
if !rmi.fields.Add(ffi) {
|
||||||
|
added := false
|
||||||
|
for cnt := 0; cnt < 5; cnt++ {
|
||||||
|
ffi.name = fmt.Sprintf("%s%d", mi.name, cnt)
|
||||||
|
ffi.column = ffi.name
|
||||||
|
ffi.fullName = rmi.fullName + "." + ffi.name
|
||||||
|
if added = rmi.fields.Add(ffi); added {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !added {
|
||||||
|
panic(fmt.Errorf("cannot generate auto reverse field info `%s` to `%s`", fi.fullName, ffi.fullName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
models = mc.all()
|
||||||
|
for _, mi := range models {
|
||||||
|
for _, fi := range mi.fields.fieldsRel {
|
||||||
|
switch fi.fieldType {
|
||||||
|
case RelManyToMany:
|
||||||
|
for _, ffi := range fi.relThroughModelInfo.fields.fieldsRel {
|
||||||
|
switch ffi.fieldType {
|
||||||
|
case RelOneToOne, RelForeignKey:
|
||||||
|
if ffi.relModelInfo == fi.relModelInfo {
|
||||||
|
fi.reverseFieldInfoTwo = ffi
|
||||||
|
}
|
||||||
|
if ffi.relModelInfo == mi {
|
||||||
|
fi.reverseField = ffi.name
|
||||||
|
fi.reverseFieldInfo = ffi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fi.reverseFieldInfoTwo == nil {
|
||||||
|
err = fmt.Errorf("can not find m2m field for m2m model `%s`, ensure your m2m model defined correct",
|
||||||
|
fi.relThroughModelInfo.fullName)
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
models = mc.all()
|
||||||
|
for _, mi := range models {
|
||||||
|
for _, fi := range mi.fields.fieldsReverse {
|
||||||
|
switch fi.fieldType {
|
||||||
|
case RelReverseOne:
|
||||||
|
found := false
|
||||||
|
mForA:
|
||||||
|
for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelOneToOne] {
|
||||||
|
if ffi.relModelInfo == mi {
|
||||||
|
found = true
|
||||||
|
fi.reverseField = ffi.name
|
||||||
|
fi.reverseFieldInfo = ffi
|
||||||
|
|
||||||
|
ffi.reverseField = fi.name
|
||||||
|
ffi.reverseFieldInfo = fi
|
||||||
|
break mForA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
err = fmt.Errorf("reverse field `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName)
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
case RelReverseMany:
|
||||||
|
found := false
|
||||||
|
mForB:
|
||||||
|
for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelForeignKey] {
|
||||||
|
if ffi.relModelInfo == mi {
|
||||||
|
found = true
|
||||||
|
fi.reverseField = ffi.name
|
||||||
|
fi.reverseFieldInfo = ffi
|
||||||
|
|
||||||
|
ffi.reverseField = fi.name
|
||||||
|
ffi.reverseFieldInfo = fi
|
||||||
|
|
||||||
|
break mForB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
mForC:
|
||||||
|
for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelManyToMany] {
|
||||||
|
conditions := fi.relThrough != "" && fi.relThrough == ffi.relThrough ||
|
||||||
|
fi.relTable != "" && fi.relTable == ffi.relTable ||
|
||||||
|
fi.relThrough == "" && fi.relTable == ""
|
||||||
|
if ffi.relModelInfo == mi && conditions {
|
||||||
|
found = true
|
||||||
|
|
||||||
|
fi.reverseField = ffi.reverseFieldInfoTwo.name
|
||||||
|
fi.reverseFieldInfo = ffi.reverseFieldInfoTwo
|
||||||
|
fi.relThroughModelInfo = ffi.relThroughModelInfo
|
||||||
|
fi.reverseFieldInfoTwo = ffi.reverseFieldInfo
|
||||||
|
fi.reverseFieldInfoM2M = ffi
|
||||||
|
ffi.reverseFieldInfoM2M = fi
|
||||||
|
|
||||||
|
break mForC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
err = fmt.Errorf("reverse field for `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName)
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
debug.PrintStack()
|
||||||
|
}
|
||||||
|
mc.done = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// register register models to model cache
|
||||||
|
func (mc *modelCache) register(prefixOrSuffixStr string, prefixOrSuffix bool, models ...interface{}) (err error) {
|
||||||
|
for _, model := range models {
|
||||||
|
val := reflect.ValueOf(model)
|
||||||
|
typ := reflect.Indirect(val).Type()
|
||||||
|
|
||||||
|
if val.Kind() != reflect.Ptr {
|
||||||
|
err = fmt.Errorf("<orm.RegisterModel> cannot use non-ptr model struct `%s`", getFullName(typ))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// For this case:
|
||||||
|
// u := &User{}
|
||||||
|
// registerModel(&u)
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
err = fmt.Errorf("<orm.RegisterModel> only allow ptr model struct, it looks you use two reference to the struct `%s`", typ)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if val.Elem().Kind() == reflect.Slice {
|
||||||
|
val = reflect.New(val.Elem().Type().Elem())
|
||||||
|
}
|
||||||
|
table := getTableName(val)
|
||||||
|
|
||||||
|
if prefixOrSuffixStr != "" {
|
||||||
|
if prefixOrSuffix {
|
||||||
|
table = prefixOrSuffixStr + table
|
||||||
|
} else {
|
||||||
|
table = table + prefixOrSuffixStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// models's fullname is pkgpath + struct name
|
||||||
|
name := getFullName(typ)
|
||||||
|
if _, ok := mc.getByFullName(name); ok {
|
||||||
|
err = fmt.Errorf("<orm.RegisterModel> model `%s` repeat register, must be unique\n", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := mc.get(table); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mi := newModelInfo(val)
|
||||||
|
if mi.fields.pk == nil {
|
||||||
|
outFor:
|
||||||
|
for _, fi := range mi.fields.fieldsDB {
|
||||||
|
if strings.ToLower(fi.name) == "id" {
|
||||||
|
switch fi.addrValue.Elem().Kind() {
|
||||||
|
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||||
|
fi.auto = true
|
||||||
|
fi.pk = true
|
||||||
|
mi.fields.pk = fi
|
||||||
|
break outFor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mi.table = table
|
||||||
|
mi.pkg = typ.PkgPath()
|
||||||
|
mi.model = model
|
||||||
|
mi.manual = true
|
||||||
|
|
||||||
|
mc.set(table, mi)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDbDropSQL get database scheme drop sql queries
|
||||||
|
func (mc *modelCache) getDbDropSQL(al *alias) (queries []string, err error) {
|
||||||
|
if len(mc.cache) == 0 {
|
||||||
|
err = errors.New("no Model found, need register your model")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Q := al.DbBaser.TableQuote()
|
||||||
|
|
||||||
|
for _, mi := range mc.allOrdered() {
|
||||||
|
queries = append(queries, fmt.Sprintf(`DROP TABLE IF EXISTS %s%s%s`, Q, mi.table, Q))
|
||||||
|
}
|
||||||
|
return queries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDbCreateSQL get database scheme creation sql queries
|
||||||
|
func (mc *modelCache) getDbCreateSQL(al *alias) (queries []string, tableIndexes map[string][]dbIndex, err error) {
|
||||||
|
if len(mc.cache) == 0 {
|
||||||
|
err = errors.New("no Model found, need register your model")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Q := al.DbBaser.TableQuote()
|
||||||
|
T := al.DbBaser.DbTypes()
|
||||||
|
sep := fmt.Sprintf("%s, %s", Q, Q)
|
||||||
|
|
||||||
|
tableIndexes = make(map[string][]dbIndex)
|
||||||
|
|
||||||
|
for _, mi := range mc.allOrdered() {
|
||||||
|
sql := fmt.Sprintf("-- %s\n", strings.Repeat("-", 50))
|
||||||
|
sql += fmt.Sprintf("-- Table Structure for `%s`\n", mi.fullName)
|
||||||
|
sql += fmt.Sprintf("-- %s\n", strings.Repeat("-", 50))
|
||||||
|
|
||||||
|
sql += fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s%s%s (\n", Q, mi.table, Q)
|
||||||
|
|
||||||
|
columns := make([]string, 0, len(mi.fields.fieldsDB))
|
||||||
|
|
||||||
|
sqlIndexes := [][]string{}
|
||||||
|
var commentIndexes []int // store comment indexes for postgres
|
||||||
|
|
||||||
|
for i, fi := range mi.fields.fieldsDB {
|
||||||
|
column := fmt.Sprintf(" %s%s%s ", Q, fi.column, Q)
|
||||||
|
col := getColumnTyp(al, fi)
|
||||||
|
|
||||||
|
if fi.auto {
|
||||||
|
switch al.Driver {
|
||||||
|
case DRSqlite, DRPostgres:
|
||||||
|
column += T["auto"]
|
||||||
|
default:
|
||||||
|
column += col + " " + T["auto"]
|
||||||
|
}
|
||||||
|
} else if fi.pk {
|
||||||
|
column += col + " " + T["pk"]
|
||||||
|
} else {
|
||||||
|
column += col
|
||||||
|
|
||||||
|
if !fi.null {
|
||||||
|
column += " " + "NOT NULL"
|
||||||
|
}
|
||||||
|
|
||||||
|
// if fi.initial.String() != "" {
|
||||||
|
// column += " DEFAULT " + fi.initial.String()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Append attribute DEFAULT
|
||||||
|
column += getColumnDefault(fi)
|
||||||
|
|
||||||
|
if fi.unique {
|
||||||
|
column += " " + "UNIQUE"
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.index {
|
||||||
|
sqlIndexes = append(sqlIndexes, []string{fi.column})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(column, "%COL%") {
|
||||||
|
column = strings.Replace(column, "%COL%", fi.column, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.description != "" && al.Driver != DRSqlite {
|
||||||
|
if al.Driver == DRPostgres {
|
||||||
|
commentIndexes = append(commentIndexes, i)
|
||||||
|
} else {
|
||||||
|
column += " " + fmt.Sprintf("COMMENT '%s'", fi.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
columns = append(columns, column)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mi.model != nil {
|
||||||
|
allnames := getTableUnique(mi.addrField)
|
||||||
|
if !mi.manual && len(mi.uniques) > 0 {
|
||||||
|
allnames = append(allnames, mi.uniques)
|
||||||
|
}
|
||||||
|
for _, names := range allnames {
|
||||||
|
cols := make([]string, 0, len(names))
|
||||||
|
for _, name := range names {
|
||||||
|
if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol {
|
||||||
|
cols = append(cols, fi.column)
|
||||||
|
} else {
|
||||||
|
panic(fmt.Errorf("cannot found column `%s` when parse UNIQUE in `%s.TableUnique`", name, mi.fullName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column := fmt.Sprintf(" UNIQUE (%s%s%s)", Q, strings.Join(cols, sep), Q)
|
||||||
|
columns = append(columns, column)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += strings.Join(columns, ",\n")
|
||||||
|
sql += "\n)"
|
||||||
|
|
||||||
|
if al.Driver == DRMySQL {
|
||||||
|
var engine string
|
||||||
|
if mi.model != nil {
|
||||||
|
engine = getTableEngine(mi.addrField)
|
||||||
|
}
|
||||||
|
if engine == "" {
|
||||||
|
engine = al.Engine
|
||||||
|
}
|
||||||
|
sql += " ENGINE=" + engine
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += ";"
|
||||||
|
if al.Driver == DRPostgres && len(commentIndexes) > 0 {
|
||||||
|
// append comments for postgres only
|
||||||
|
for _, index := range commentIndexes {
|
||||||
|
sql += fmt.Sprintf("\nCOMMENT ON COLUMN %s%s%s.%s%s%s is '%s';",
|
||||||
|
Q,
|
||||||
|
mi.table,
|
||||||
|
Q,
|
||||||
|
Q,
|
||||||
|
mi.fields.fieldsDB[index].column,
|
||||||
|
Q,
|
||||||
|
mi.fields.fieldsDB[index].description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queries = append(queries, sql)
|
||||||
|
|
||||||
|
if mi.model != nil {
|
||||||
|
for _, names := range getTableIndex(mi.addrField) {
|
||||||
|
cols := make([]string, 0, len(names))
|
||||||
|
for _, name := range names {
|
||||||
|
if fi, ok := mi.fields.GetByAny(name); ok && fi.dbcol {
|
||||||
|
cols = append(cols, fi.column)
|
||||||
|
} else {
|
||||||
|
panic(fmt.Errorf("cannot found column `%s` when parse INDEX in `%s.TableIndex`", name, mi.fullName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlIndexes = append(sqlIndexes, cols)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, names := range sqlIndexes {
|
||||||
|
name := mi.table + "_" + strings.Join(names, "_")
|
||||||
|
cols := strings.Join(names, sep)
|
||||||
|
sql := fmt.Sprintf("CREATE INDEX %s%s%s ON %s%s%s (%s%s%s);", Q, name, Q, Q, mi.table, Q, Q, cols, Q)
|
||||||
|
|
||||||
|
index := dbIndex{}
|
||||||
|
index.Table = mi.table
|
||||||
|
index.Name = name
|
||||||
|
index.SQL = sql
|
||||||
|
|
||||||
|
tableIndexes[mi.table] = append(tableIndexes[mi.table], index)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetModelCache Clean model cache. Then you can re-RegisterModel.
|
||||||
|
// Common use this api for test case.
|
||||||
|
func ResetModelCache() {
|
||||||
|
defaultModelCache.clean()
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
// RegisterModel register models
|
||||||
|
func RegisterModel(models ...interface{}) {
|
||||||
|
RegisterModelWithPrefix("", models...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterModelWithPrefix register models with a prefix
|
||||||
|
func RegisterModelWithPrefix(prefix string, models ...interface{}) {
|
||||||
|
if err := defaultModelCache.register(prefix, true, models...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterModelWithSuffix register models with a suffix
|
||||||
|
func RegisterModelWithSuffix(suffix string, models ...interface{}) {
|
||||||
|
if err := defaultModelCache.register(suffix, false, models...); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootStrap bootstrap models.
|
||||||
|
// make all model parsed and can not add more models
|
||||||
|
func BootStrap() {
|
||||||
|
defaultModelCache.bootstrap()
|
||||||
|
}
|
@ -0,0 +1,485 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errSkipField = errors.New("skip field")
|
||||||
|
|
||||||
|
// field info collection
|
||||||
|
type fields struct {
|
||||||
|
pk *fieldInfo
|
||||||
|
columns map[string]*fieldInfo
|
||||||
|
fields map[string]*fieldInfo
|
||||||
|
fieldsLow map[string]*fieldInfo
|
||||||
|
fieldsByType map[int][]*fieldInfo
|
||||||
|
fieldsRel []*fieldInfo
|
||||||
|
fieldsReverse []*fieldInfo
|
||||||
|
fieldsDB []*fieldInfo
|
||||||
|
rels []*fieldInfo
|
||||||
|
orders []string
|
||||||
|
dbcols []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// add field info
|
||||||
|
func (f *fields) Add(fi *fieldInfo) (added bool) {
|
||||||
|
if f.fields[fi.name] == nil && f.columns[fi.column] == nil {
|
||||||
|
f.columns[fi.column] = fi
|
||||||
|
f.fields[fi.name] = fi
|
||||||
|
f.fieldsLow[strings.ToLower(fi.name)] = fi
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := f.fieldsByType[fi.fieldType]; !ok {
|
||||||
|
f.fieldsByType[fi.fieldType] = make([]*fieldInfo, 0)
|
||||||
|
}
|
||||||
|
f.fieldsByType[fi.fieldType] = append(f.fieldsByType[fi.fieldType], fi)
|
||||||
|
f.orders = append(f.orders, fi.column)
|
||||||
|
if fi.dbcol {
|
||||||
|
f.dbcols = append(f.dbcols, fi.column)
|
||||||
|
f.fieldsDB = append(f.fieldsDB, fi)
|
||||||
|
}
|
||||||
|
if fi.rel {
|
||||||
|
f.fieldsRel = append(f.fieldsRel, fi)
|
||||||
|
}
|
||||||
|
if fi.reverse {
|
||||||
|
f.fieldsReverse = append(f.fieldsReverse, fi)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// get field info by name
|
||||||
|
func (f *fields) GetByName(name string) *fieldInfo {
|
||||||
|
return f.fields[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// get field info by column name
|
||||||
|
func (f *fields) GetByColumn(column string) *fieldInfo {
|
||||||
|
return f.columns[column]
|
||||||
|
}
|
||||||
|
|
||||||
|
// get field info by string, name is prior
|
||||||
|
func (f *fields) GetByAny(name string) (*fieldInfo, bool) {
|
||||||
|
if fi, ok := f.fields[name]; ok {
|
||||||
|
return fi, ok
|
||||||
|
}
|
||||||
|
if fi, ok := f.fieldsLow[strings.ToLower(name)]; ok {
|
||||||
|
return fi, ok
|
||||||
|
}
|
||||||
|
if fi, ok := f.columns[name]; ok {
|
||||||
|
return fi, ok
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new field info collection
|
||||||
|
func newFields() *fields {
|
||||||
|
f := new(fields)
|
||||||
|
f.fields = make(map[string]*fieldInfo)
|
||||||
|
f.fieldsLow = make(map[string]*fieldInfo)
|
||||||
|
f.columns = make(map[string]*fieldInfo)
|
||||||
|
f.fieldsByType = make(map[int][]*fieldInfo)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// single field info
|
||||||
|
type fieldInfo struct {
|
||||||
|
dbcol bool // table column fk and onetoone
|
||||||
|
inModel bool
|
||||||
|
auto bool
|
||||||
|
pk bool
|
||||||
|
null bool
|
||||||
|
index bool
|
||||||
|
unique bool
|
||||||
|
colDefault bool // whether has default tag
|
||||||
|
toText bool
|
||||||
|
autoNow bool
|
||||||
|
autoNowAdd bool
|
||||||
|
rel bool // if type equal to RelForeignKey, RelOneToOne, RelManyToMany then true
|
||||||
|
reverse bool
|
||||||
|
isFielder bool // implement Fielder interface
|
||||||
|
mi *modelInfo
|
||||||
|
fieldIndex []int
|
||||||
|
fieldType int
|
||||||
|
name string
|
||||||
|
fullName string
|
||||||
|
column string
|
||||||
|
addrValue reflect.Value
|
||||||
|
sf reflect.StructField
|
||||||
|
initial StrTo // store the default value
|
||||||
|
size int
|
||||||
|
reverseField string
|
||||||
|
reverseFieldInfo *fieldInfo
|
||||||
|
reverseFieldInfoTwo *fieldInfo
|
||||||
|
reverseFieldInfoM2M *fieldInfo
|
||||||
|
relTable string
|
||||||
|
relThrough string
|
||||||
|
relThroughModelInfo *modelInfo
|
||||||
|
relModelInfo *modelInfo
|
||||||
|
digits int
|
||||||
|
decimals int
|
||||||
|
onDelete string
|
||||||
|
description string
|
||||||
|
timePrecision *int
|
||||||
|
}
|
||||||
|
|
||||||
|
// new field info
|
||||||
|
func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mName string) (fi *fieldInfo, err error) {
|
||||||
|
var (
|
||||||
|
tag string
|
||||||
|
tagValue string
|
||||||
|
initial StrTo // store the default value
|
||||||
|
fieldType int
|
||||||
|
attrs map[string]bool
|
||||||
|
tags map[string]string
|
||||||
|
addrField reflect.Value
|
||||||
|
)
|
||||||
|
|
||||||
|
fi = new(fieldInfo)
|
||||||
|
|
||||||
|
// if field which CanAddr is the follow type
|
||||||
|
// A value is addressable if it is an element of a slice,
|
||||||
|
// an element of an addressable array, a field of an
|
||||||
|
// addressable struct, or the result of dereferencing a pointer.
|
||||||
|
addrField = field
|
||||||
|
if field.CanAddr() && field.Kind() != reflect.Ptr {
|
||||||
|
addrField = field.Addr()
|
||||||
|
if _, ok := addrField.Interface().(Fielder); !ok {
|
||||||
|
if field.Kind() == reflect.Slice {
|
||||||
|
addrField = field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs, tags = parseStructTag(sf.Tag.Get(defaultStructTagName))
|
||||||
|
|
||||||
|
if _, ok := attrs["-"]; ok {
|
||||||
|
return nil, errSkipField
|
||||||
|
}
|
||||||
|
|
||||||
|
digits := tags["digits"]
|
||||||
|
decimals := tags["decimals"]
|
||||||
|
size := tags["size"]
|
||||||
|
onDelete := tags["on_delete"]
|
||||||
|
precision := tags["precision"]
|
||||||
|
initial.Clear()
|
||||||
|
if v, ok := tags["default"]; ok {
|
||||||
|
initial.Set(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkType:
|
||||||
|
switch f := addrField.Interface().(type) {
|
||||||
|
case Fielder:
|
||||||
|
fi.isFielder = true
|
||||||
|
if field.Kind() == reflect.Ptr {
|
||||||
|
err = fmt.Errorf("the model Fielder can not be use ptr")
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
fieldType = f.FieldType()
|
||||||
|
if fieldType&IsRelField > 0 {
|
||||||
|
err = fmt.Errorf("unsupport type custom field, please refer to https://github.com/beego/beego/v2/blob/master/orm/models_fields.go#L24-L42")
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
tag = "rel"
|
||||||
|
tagValue = tags[tag]
|
||||||
|
if tagValue != "" {
|
||||||
|
switch tagValue {
|
||||||
|
case "fk":
|
||||||
|
fieldType = RelForeignKey
|
||||||
|
break checkType
|
||||||
|
case "one":
|
||||||
|
fieldType = RelOneToOne
|
||||||
|
break checkType
|
||||||
|
case "m2m":
|
||||||
|
fieldType = RelManyToMany
|
||||||
|
if tv := tags["rel_table"]; tv != "" {
|
||||||
|
fi.relTable = tv
|
||||||
|
} else if tv := tags["rel_through"]; tv != "" {
|
||||||
|
fi.relThrough = tv
|
||||||
|
}
|
||||||
|
break checkType
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("rel only allow these value: fk, one, m2m")
|
||||||
|
goto wrongTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag = "reverse"
|
||||||
|
tagValue = tags[tag]
|
||||||
|
if tagValue != "" {
|
||||||
|
switch tagValue {
|
||||||
|
case "one":
|
||||||
|
fieldType = RelReverseOne
|
||||||
|
break checkType
|
||||||
|
case "many":
|
||||||
|
fieldType = RelReverseMany
|
||||||
|
if tv := tags["rel_table"]; tv != "" {
|
||||||
|
fi.relTable = tv
|
||||||
|
} else if tv := tags["rel_through"]; tv != "" {
|
||||||
|
fi.relThrough = tv
|
||||||
|
}
|
||||||
|
break checkType
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("reverse only allow these value: one, many")
|
||||||
|
goto wrongTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldType, err = getFieldType(addrField)
|
||||||
|
if err != nil {
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
if fieldType == TypeVarCharField {
|
||||||
|
switch tags["type"] {
|
||||||
|
case "char":
|
||||||
|
fieldType = TypeCharField
|
||||||
|
case "text":
|
||||||
|
fieldType = TypeTextField
|
||||||
|
case "json":
|
||||||
|
fieldType = TypeJSONField
|
||||||
|
case "jsonb":
|
||||||
|
fieldType = TypeJsonbField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fieldType == TypeFloatField && (digits != "" || decimals != "") {
|
||||||
|
fieldType = TypeDecimalField
|
||||||
|
}
|
||||||
|
if fieldType == TypeDateTimeField && tags["type"] == "date" {
|
||||||
|
fieldType = TypeDateField
|
||||||
|
}
|
||||||
|
if fieldType == TypeTimeField && tags["type"] == "time" {
|
||||||
|
fieldType = TypeTimeField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the rel and reverse type
|
||||||
|
// rel should Ptr
|
||||||
|
// reverse should slice []*struct
|
||||||
|
switch fieldType {
|
||||||
|
case RelForeignKey, RelOneToOne, RelReverseOne:
|
||||||
|
if field.Kind() != reflect.Ptr {
|
||||||
|
err = fmt.Errorf("rel/reverse:one field must be *%s", field.Type().Name())
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
case RelManyToMany, RelReverseMany:
|
||||||
|
if field.Kind() != reflect.Slice {
|
||||||
|
err = fmt.Errorf("rel/reverse:many field must be slice")
|
||||||
|
goto end
|
||||||
|
} else {
|
||||||
|
if field.Type().Elem().Kind() != reflect.Ptr {
|
||||||
|
err = fmt.Errorf("rel/reverse:many slice must be []*%s", field.Type().Elem().Name())
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldType&IsFieldType == 0 {
|
||||||
|
err = fmt.Errorf("wrong field type")
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
|
||||||
|
fi.fieldType = fieldType
|
||||||
|
fi.name = sf.Name
|
||||||
|
fi.column = getColumnName(fieldType, addrField, sf, tags["column"])
|
||||||
|
fi.addrValue = addrField
|
||||||
|
fi.sf = sf
|
||||||
|
fi.fullName = mi.fullName + mName + "." + sf.Name
|
||||||
|
|
||||||
|
fi.description = tags["description"]
|
||||||
|
fi.null = attrs["null"]
|
||||||
|
fi.index = attrs["index"]
|
||||||
|
fi.auto = attrs["auto"]
|
||||||
|
fi.pk = attrs["pk"]
|
||||||
|
fi.unique = attrs["unique"]
|
||||||
|
|
||||||
|
// Mark object property if there is attribute "default" in the orm configuration
|
||||||
|
if _, ok := tags["default"]; ok {
|
||||||
|
fi.colDefault = true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fieldType {
|
||||||
|
case RelManyToMany, RelReverseMany, RelReverseOne:
|
||||||
|
fi.null = false
|
||||||
|
fi.index = false
|
||||||
|
fi.auto = false
|
||||||
|
fi.pk = false
|
||||||
|
fi.unique = false
|
||||||
|
default:
|
||||||
|
fi.dbcol = true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fieldType {
|
||||||
|
case RelForeignKey, RelOneToOne, RelManyToMany:
|
||||||
|
fi.rel = true
|
||||||
|
if fieldType == RelOneToOne {
|
||||||
|
fi.unique = true
|
||||||
|
}
|
||||||
|
case RelReverseMany, RelReverseOne:
|
||||||
|
fi.reverse = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.rel && fi.dbcol {
|
||||||
|
switch onDelete {
|
||||||
|
case odCascade, odDoNothing:
|
||||||
|
case odSetDefault:
|
||||||
|
if !initial.Exist() {
|
||||||
|
err = errors.New("on_delete: set_default need set field a default value")
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
case odSetNULL:
|
||||||
|
if !fi.null {
|
||||||
|
err = errors.New("on_delete: set_null need set field null")
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if onDelete == "" {
|
||||||
|
onDelete = odCascade
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("on_delete value expected choice in `cascade,set_null,set_default,do_nothing`, unknown `%s`", onDelete)
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fi.onDelete = onDelete
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fieldType {
|
||||||
|
case TypeBooleanField:
|
||||||
|
case TypeVarCharField, TypeCharField, TypeJSONField, TypeJsonbField:
|
||||||
|
if size != "" {
|
||||||
|
v, e := StrTo(size).Int32()
|
||||||
|
if e != nil {
|
||||||
|
err = fmt.Errorf("wrong size value `%s`", size)
|
||||||
|
} else {
|
||||||
|
fi.size = int(v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fi.size = 255
|
||||||
|
fi.toText = true
|
||||||
|
}
|
||||||
|
case TypeTextField:
|
||||||
|
fi.index = false
|
||||||
|
fi.unique = false
|
||||||
|
case TypeTimeField, TypeDateField, TypeDateTimeField:
|
||||||
|
if fieldType == TypeDateTimeField {
|
||||||
|
if precision != "" {
|
||||||
|
v, e := StrTo(precision).Int()
|
||||||
|
if e != nil {
|
||||||
|
err = fmt.Errorf("convert %s to int error:%v", precision, e)
|
||||||
|
} else {
|
||||||
|
fi.timePrecision = &v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if attrs["auto_now"] {
|
||||||
|
fi.autoNow = true
|
||||||
|
} else if attrs["auto_now_add"] {
|
||||||
|
fi.autoNowAdd = true
|
||||||
|
}
|
||||||
|
case TypeFloatField:
|
||||||
|
case TypeDecimalField:
|
||||||
|
d1 := digits
|
||||||
|
d2 := decimals
|
||||||
|
v1, er1 := StrTo(d1).Int8()
|
||||||
|
v2, er2 := StrTo(d2).Int8()
|
||||||
|
if er1 != nil || er2 != nil {
|
||||||
|
err = fmt.Errorf("wrong digits/decimals value %s/%s", d2, d1)
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
fi.digits = int(v1)
|
||||||
|
fi.decimals = int(v2)
|
||||||
|
default:
|
||||||
|
switch {
|
||||||
|
case fieldType&IsIntegerField > 0:
|
||||||
|
case fieldType&IsRelField > 0:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldType&IsIntegerField == 0 {
|
||||||
|
if fi.auto {
|
||||||
|
err = fmt.Errorf("non-integer type cannot set auto")
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.auto || fi.pk {
|
||||||
|
if fi.auto {
|
||||||
|
switch addrField.Elem().Kind() {
|
||||||
|
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("auto primary key only support int, int32, int64, uint, uint32, uint64 but found `%s`", addrField.Elem().Kind())
|
||||||
|
goto end
|
||||||
|
}
|
||||||
|
fi.pk = true
|
||||||
|
}
|
||||||
|
fi.null = false
|
||||||
|
fi.index = false
|
||||||
|
fi.unique = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.unique {
|
||||||
|
fi.index = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// can not set default for these type
|
||||||
|
if fi.auto || fi.pk || fi.unique || fieldType == TypeTimeField || fieldType == TypeDateField || fieldType == TypeDateTimeField {
|
||||||
|
initial.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
if initial.Exist() {
|
||||||
|
v := initial
|
||||||
|
switch fieldType {
|
||||||
|
case TypeBooleanField:
|
||||||
|
_, err = v.Bool()
|
||||||
|
case TypeFloatField, TypeDecimalField:
|
||||||
|
_, err = v.Float64()
|
||||||
|
case TypeBitField:
|
||||||
|
_, err = v.Int8()
|
||||||
|
case TypeSmallIntegerField:
|
||||||
|
_, err = v.Int16()
|
||||||
|
case TypeIntegerField:
|
||||||
|
_, err = v.Int32()
|
||||||
|
case TypeBigIntegerField:
|
||||||
|
_, err = v.Int64()
|
||||||
|
case TypePositiveBitField:
|
||||||
|
_, err = v.Uint8()
|
||||||
|
case TypePositiveSmallIntegerField:
|
||||||
|
_, err = v.Uint16()
|
||||||
|
case TypePositiveIntegerField:
|
||||||
|
_, err = v.Uint32()
|
||||||
|
case TypePositiveBigIntegerField:
|
||||||
|
_, err = v.Uint64()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
tag, tagValue = "default", tags["default"]
|
||||||
|
goto wrongTag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fi.initial = initial
|
||||||
|
end:
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
wrongTag:
|
||||||
|
return nil, fmt.Errorf("wrong tag format: `%s:\"%s\"`, %s", tag, tagValue, err)
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// single model info
|
||||||
|
type modelInfo struct {
|
||||||
|
manual bool
|
||||||
|
isThrough bool
|
||||||
|
pkg string
|
||||||
|
name string
|
||||||
|
fullName string
|
||||||
|
table string
|
||||||
|
model interface{}
|
||||||
|
fields *fields
|
||||||
|
addrField reflect.Value // store the original struct value
|
||||||
|
uniques []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// new model info
|
||||||
|
func newModelInfo(val reflect.Value) (mi *modelInfo) {
|
||||||
|
mi = &modelInfo{}
|
||||||
|
mi.fields = newFields()
|
||||||
|
ind := reflect.Indirect(val)
|
||||||
|
mi.addrField = val
|
||||||
|
mi.name = ind.Type().Name()
|
||||||
|
mi.fullName = getFullName(ind.Type())
|
||||||
|
addModelFields(mi, ind, "", []int{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// index: FieldByIndex returns the nested field corresponding to index
|
||||||
|
func addModelFields(mi *modelInfo, ind reflect.Value, mName string, index []int) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
fi *fieldInfo
|
||||||
|
sf reflect.StructField
|
||||||
|
)
|
||||||
|
|
||||||
|
for i := 0; i < ind.NumField(); i++ {
|
||||||
|
field := ind.Field(i)
|
||||||
|
sf = ind.Type().Field(i)
|
||||||
|
// if the field is unexported skip
|
||||||
|
if sf.PkgPath != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// add anonymous struct fields
|
||||||
|
if sf.Anonymous {
|
||||||
|
addModelFields(mi, field, mName+"."+sf.Name, append(index, i))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err = newFieldInfo(mi, field, sf, mName)
|
||||||
|
if err == errSkipField {
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// record current field index
|
||||||
|
fi.fieldIndex = append(fi.fieldIndex, index...)
|
||||||
|
fi.fieldIndex = append(fi.fieldIndex, i)
|
||||||
|
fi.mi = mi
|
||||||
|
fi.inModel = true
|
||||||
|
if !mi.fields.Add(fi) {
|
||||||
|
err = fmt.Errorf("duplicate column name: %s", fi.column)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if fi.pk {
|
||||||
|
if mi.fields.pk != nil {
|
||||||
|
err = fmt.Errorf("one model must have one pk field only")
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
mi.fields.pk = fi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(fmt.Errorf("field: %s.%s, %s", ind.Type(), sf.Name, err))
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// combine related model info to new model info.
|
||||||
|
// prepare for relation models query.
|
||||||
|
func newM2MModelInfo(m1, m2 *modelInfo) (mi *modelInfo) {
|
||||||
|
mi = new(modelInfo)
|
||||||
|
mi.fields = newFields()
|
||||||
|
mi.table = m1.table + "_" + m2.table + "s"
|
||||||
|
mi.name = camelString(mi.table)
|
||||||
|
mi.fullName = m1.pkg + "." + mi.name
|
||||||
|
|
||||||
|
fa := new(fieldInfo) // pk
|
||||||
|
f1 := new(fieldInfo) // m1 table RelForeignKey
|
||||||
|
f2 := new(fieldInfo) // m2 table RelForeignKey
|
||||||
|
fa.fieldType = TypeBigIntegerField
|
||||||
|
fa.auto = true
|
||||||
|
fa.pk = true
|
||||||
|
fa.dbcol = true
|
||||||
|
fa.name = "Id"
|
||||||
|
fa.column = "id"
|
||||||
|
fa.fullName = mi.fullName + "." + fa.name
|
||||||
|
|
||||||
|
f1.dbcol = true
|
||||||
|
f2.dbcol = true
|
||||||
|
f1.fieldType = RelForeignKey
|
||||||
|
f2.fieldType = RelForeignKey
|
||||||
|
f1.name = camelString(m1.table)
|
||||||
|
f2.name = camelString(m2.table)
|
||||||
|
f1.fullName = mi.fullName + "." + f1.name
|
||||||
|
f2.fullName = mi.fullName + "." + f2.name
|
||||||
|
f1.column = m1.table + "_id"
|
||||||
|
f2.column = m2.table + "_id"
|
||||||
|
f1.rel = true
|
||||||
|
f2.rel = true
|
||||||
|
f1.relTable = m1.table
|
||||||
|
f2.relTable = m2.table
|
||||||
|
f1.relModelInfo = m1
|
||||||
|
f2.relModelInfo = m2
|
||||||
|
f1.mi = mi
|
||||||
|
f2.mi = mi
|
||||||
|
|
||||||
|
mi.fields.Add(fa)
|
||||||
|
mi.fields.Add(f1)
|
||||||
|
mi.fields.Add(f2)
|
||||||
|
mi.fields.pk = fa
|
||||||
|
|
||||||
|
mi.uniques = []string{f1.column, f2.column}
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,243 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 1 is attr
|
||||||
|
// 2 is tag
|
||||||
|
var supportTag = map[string]int{
|
||||||
|
"-": 1,
|
||||||
|
"null": 1,
|
||||||
|
"index": 1,
|
||||||
|
"unique": 1,
|
||||||
|
"pk": 1,
|
||||||
|
"auto": 1,
|
||||||
|
"auto_now": 1,
|
||||||
|
"auto_now_add": 1,
|
||||||
|
"size": 2,
|
||||||
|
"column": 2,
|
||||||
|
"default": 2,
|
||||||
|
"rel": 2,
|
||||||
|
"reverse": 2,
|
||||||
|
"rel_table": 2,
|
||||||
|
"rel_through": 2,
|
||||||
|
"digits": 2,
|
||||||
|
"decimals": 2,
|
||||||
|
"on_delete": 2,
|
||||||
|
"type": 2,
|
||||||
|
"description": 2,
|
||||||
|
"precision": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
// get reflect.Type name with package path.
|
||||||
|
func getFullName(typ reflect.Type) string {
|
||||||
|
return typ.PkgPath() + "." + typ.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTableName get struct table name.
|
||||||
|
// If the struct implement the TableName, then get the result as tablename
|
||||||
|
// else use the struct name which will apply snakeString.
|
||||||
|
func getTableName(val reflect.Value) string {
|
||||||
|
if fun := val.MethodByName("TableName"); fun.IsValid() {
|
||||||
|
vals := fun.Call([]reflect.Value{})
|
||||||
|
// has return and the first val is string
|
||||||
|
if len(vals) > 0 && vals[0].Kind() == reflect.String {
|
||||||
|
return vals[0].String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return snakeString(reflect.Indirect(val).Type().Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// get table engine, myisam or innodb.
|
||||||
|
func getTableEngine(val reflect.Value) string {
|
||||||
|
fun := val.MethodByName("TableEngine")
|
||||||
|
if fun.IsValid() {
|
||||||
|
vals := fun.Call([]reflect.Value{})
|
||||||
|
if len(vals) > 0 && vals[0].Kind() == reflect.String {
|
||||||
|
return vals[0].String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// get table index from method.
|
||||||
|
func getTableIndex(val reflect.Value) [][]string {
|
||||||
|
fun := val.MethodByName("TableIndex")
|
||||||
|
if fun.IsValid() {
|
||||||
|
vals := fun.Call([]reflect.Value{})
|
||||||
|
if len(vals) > 0 && vals[0].CanInterface() {
|
||||||
|
if d, ok := vals[0].Interface().([][]string); ok {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get table unique from method
|
||||||
|
func getTableUnique(val reflect.Value) [][]string {
|
||||||
|
fun := val.MethodByName("TableUnique")
|
||||||
|
if fun.IsValid() {
|
||||||
|
vals := fun.Call([]reflect.Value{})
|
||||||
|
if len(vals) > 0 && vals[0].CanInterface() {
|
||||||
|
if d, ok := vals[0].Interface().([][]string); ok {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get whether the table needs to be created for the database alias
|
||||||
|
func isApplicableTableForDB(val reflect.Value, db string) bool {
|
||||||
|
if !val.IsValid() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
fun := val.MethodByName("IsApplicableTableForDB")
|
||||||
|
if fun.IsValid() {
|
||||||
|
vals := fun.Call([]reflect.Value{reflect.ValueOf(db)})
|
||||||
|
if len(vals) > 0 && vals[0].Kind() == reflect.Bool {
|
||||||
|
return vals[0].Bool()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// get snaked column name
|
||||||
|
func getColumnName(ft int, addrField reflect.Value, sf reflect.StructField, col string) string {
|
||||||
|
column := col
|
||||||
|
if col == "" {
|
||||||
|
column = nameStrategyMap[nameStrategy](sf.Name)
|
||||||
|
}
|
||||||
|
switch ft {
|
||||||
|
case RelForeignKey, RelOneToOne:
|
||||||
|
if len(col) == 0 {
|
||||||
|
column = column + "_id"
|
||||||
|
}
|
||||||
|
case RelManyToMany, RelReverseMany, RelReverseOne:
|
||||||
|
column = sf.Name
|
||||||
|
}
|
||||||
|
return column
|
||||||
|
}
|
||||||
|
|
||||||
|
// return field type as type constant from reflect.Value
|
||||||
|
func getFieldType(val reflect.Value) (ft int, err error) {
|
||||||
|
switch val.Type() {
|
||||||
|
case reflect.TypeOf(new(int8)):
|
||||||
|
ft = TypeBitField
|
||||||
|
case reflect.TypeOf(new(int16)):
|
||||||
|
ft = TypeSmallIntegerField
|
||||||
|
case reflect.TypeOf(new(int32)),
|
||||||
|
reflect.TypeOf(new(int)):
|
||||||
|
ft = TypeIntegerField
|
||||||
|
case reflect.TypeOf(new(int64)):
|
||||||
|
ft = TypeBigIntegerField
|
||||||
|
case reflect.TypeOf(new(uint8)):
|
||||||
|
ft = TypePositiveBitField
|
||||||
|
case reflect.TypeOf(new(uint16)):
|
||||||
|
ft = TypePositiveSmallIntegerField
|
||||||
|
case reflect.TypeOf(new(uint32)),
|
||||||
|
reflect.TypeOf(new(uint)):
|
||||||
|
ft = TypePositiveIntegerField
|
||||||
|
case reflect.TypeOf(new(uint64)):
|
||||||
|
ft = TypePositiveBigIntegerField
|
||||||
|
case reflect.TypeOf(new(float32)),
|
||||||
|
reflect.TypeOf(new(float64)):
|
||||||
|
ft = TypeFloatField
|
||||||
|
case reflect.TypeOf(new(bool)):
|
||||||
|
ft = TypeBooleanField
|
||||||
|
case reflect.TypeOf(new(string)):
|
||||||
|
ft = TypeVarCharField
|
||||||
|
case reflect.TypeOf(new(time.Time)):
|
||||||
|
ft = TypeDateTimeField
|
||||||
|
default:
|
||||||
|
elm := reflect.Indirect(val)
|
||||||
|
switch elm.Kind() {
|
||||||
|
case reflect.Int8:
|
||||||
|
ft = TypeBitField
|
||||||
|
case reflect.Int16:
|
||||||
|
ft = TypeSmallIntegerField
|
||||||
|
case reflect.Int32, reflect.Int:
|
||||||
|
ft = TypeIntegerField
|
||||||
|
case reflect.Int64:
|
||||||
|
ft = TypeBigIntegerField
|
||||||
|
case reflect.Uint8:
|
||||||
|
ft = TypePositiveBitField
|
||||||
|
case reflect.Uint16:
|
||||||
|
ft = TypePositiveSmallIntegerField
|
||||||
|
case reflect.Uint32, reflect.Uint:
|
||||||
|
ft = TypePositiveIntegerField
|
||||||
|
case reflect.Uint64:
|
||||||
|
ft = TypePositiveBigIntegerField
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
ft = TypeFloatField
|
||||||
|
case reflect.Bool:
|
||||||
|
ft = TypeBooleanField
|
||||||
|
case reflect.String:
|
||||||
|
ft = TypeVarCharField
|
||||||
|
default:
|
||||||
|
if elm.Interface() == nil {
|
||||||
|
panic(fmt.Errorf("%s is nil pointer, may be miss setting tag", val))
|
||||||
|
}
|
||||||
|
switch elm.Interface().(type) {
|
||||||
|
case sql.NullInt64:
|
||||||
|
ft = TypeBigIntegerField
|
||||||
|
case sql.NullFloat64:
|
||||||
|
ft = TypeFloatField
|
||||||
|
case sql.NullBool:
|
||||||
|
ft = TypeBooleanField
|
||||||
|
case sql.NullString:
|
||||||
|
ft = TypeVarCharField
|
||||||
|
case time.Time:
|
||||||
|
ft = TypeDateTimeField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ft&IsFieldType == 0 {
|
||||||
|
err = fmt.Errorf("unsupport field type %s, may be miss setting tag", val)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse struct tag string
|
||||||
|
func parseStructTag(data string) (attrs map[string]bool, tags map[string]string) {
|
||||||
|
attrs = make(map[string]bool)
|
||||||
|
tags = make(map[string]string)
|
||||||
|
for _, v := range strings.Split(data, defaultStructTagDelim) {
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if t := strings.ToLower(v); supportTag[t] == 1 {
|
||||||
|
attrs[t] = true
|
||||||
|
} else if i := strings.Index(v, "("); i > 0 && strings.Index(v, ")") == len(v)-1 {
|
||||||
|
name := t[:i]
|
||||||
|
if supportTag[name] == 2 {
|
||||||
|
v = v[i+1 : len(v)-1]
|
||||||
|
tags[name] = v
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DebugLog.Println("unsupport orm tag", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,661 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build go1.8
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
// Package orm provide ORM for MySQL/PostgreSQL/sqlite
|
||||||
|
// Simple Usage
|
||||||
|
//
|
||||||
|
// package main
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "fmt"
|
||||||
|
// "github.com/beego/beego/v2/client/orm"
|
||||||
|
// _ "github.com/go-sql-driver/mysql" // import your used driver
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// // Model Struct
|
||||||
|
// type User struct {
|
||||||
|
// Id int `orm:"auto"`
|
||||||
|
// Name string `orm:"size(100)"`
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func init() {
|
||||||
|
// orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// o := orm.NewOrm()
|
||||||
|
// user := User{Name: "slene"}
|
||||||
|
// // insert
|
||||||
|
// id, err := o.Insert(&user)
|
||||||
|
// // update
|
||||||
|
// user.Name = "astaxie"
|
||||||
|
// num, err := o.Update(&user)
|
||||||
|
// // read one
|
||||||
|
// u := User{Id: user.Id}
|
||||||
|
// err = o.Read(&u)
|
||||||
|
// // delete
|
||||||
|
// num, err = o.Delete(&u)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// more docs: http://beego.vip/docs/mvc/model/overview.md
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
||||||
|
"github.com/beego/beego/v2/client/orm/hints"
|
||||||
|
"github.com/beego/beego/v2/core/logs"
|
||||||
|
"github.com/beego/beego/v2/core/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DebugQueries define the debug
|
||||||
|
const (
|
||||||
|
DebugQueries = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// Define common vars
|
||||||
|
var (
|
||||||
|
Debug = false
|
||||||
|
DebugLog = NewLog(os.Stdout)
|
||||||
|
DefaultRowsLimit = -1
|
||||||
|
DefaultRelsDepth = 2
|
||||||
|
DefaultTimeLoc = time.Local
|
||||||
|
ErrTxDone = errors.New("<TxOrmer.Commit/Rollback> transaction already done")
|
||||||
|
ErrMultiRows = errors.New("<QuerySeter> return multi rows")
|
||||||
|
ErrNoRows = errors.New("<QuerySeter> no row found")
|
||||||
|
ErrStmtClosed = errors.New("<QuerySeter> stmt already closed")
|
||||||
|
ErrArgs = errors.New("<Ormer> args error may be empty")
|
||||||
|
ErrNotImplement = errors.New("have not implement")
|
||||||
|
|
||||||
|
ErrLastInsertIdUnavailable = errors.New("<Ormer> last insert id is unavailable")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Params stores the Params
|
||||||
|
type Params map[string]interface{}
|
||||||
|
|
||||||
|
// ParamsList stores paramslist
|
||||||
|
type ParamsList []interface{}
|
||||||
|
|
||||||
|
type ormBase struct {
|
||||||
|
alias *alias
|
||||||
|
db dbQuerier
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ DQL = new(ormBase)
|
||||||
|
_ DML = new(ormBase)
|
||||||
|
_ DriverGetter = new(ormBase)
|
||||||
|
)
|
||||||
|
|
||||||
|
// get model info and model reflect value
|
||||||
|
func (*ormBase) getMi(md interface{}) (mi *modelInfo) {
|
||||||
|
val := reflect.ValueOf(md)
|
||||||
|
ind := reflect.Indirect(val)
|
||||||
|
typ := ind.Type()
|
||||||
|
mi = getTypeMi(typ)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get need ptr model info and model reflect value
|
||||||
|
func (*ormBase) getPtrMiInd(md interface{}) (mi *modelInfo, ind reflect.Value) {
|
||||||
|
val := reflect.ValueOf(md)
|
||||||
|
ind = reflect.Indirect(val)
|
||||||
|
typ := ind.Type()
|
||||||
|
if val.Kind() != reflect.Ptr {
|
||||||
|
panic(fmt.Errorf("<Ormer> cannot use non-ptr model struct `%s`", getFullName(typ)))
|
||||||
|
}
|
||||||
|
mi = getTypeMi(typ)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTypeMi(mdTyp reflect.Type) *modelInfo {
|
||||||
|
name := getFullName(mdTyp)
|
||||||
|
if mi, ok := defaultModelCache.getByFullName(name); ok {
|
||||||
|
return mi
|
||||||
|
}
|
||||||
|
panic(fmt.Errorf("<Ormer> table: `%s` not found, make sure it was registered with `RegisterModel()`", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// get field info from model info by given field name
|
||||||
|
func (*ormBase) getFieldInfo(mi *modelInfo, name string) *fieldInfo {
|
||||||
|
fi, ok := mi.fields.GetByAny(name)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Errorf("<Ormer> cannot find field `%s` for model `%s`", name, mi.fullName))
|
||||||
|
}
|
||||||
|
return fi
|
||||||
|
}
|
||||||
|
|
||||||
|
// read data to model
|
||||||
|
func (o *ormBase) Read(md interface{}, cols ...string) error {
|
||||||
|
return o.ReadWithCtx(context.Background(), md, cols...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ormBase) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
||||||
|
mi, ind := o.getPtrMiInd(md)
|
||||||
|
return o.alias.DbBaser.Read(ctx, o.db, mi, ind, o.alias.TZ, cols, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// read data to model, like Read(), but use "SELECT FOR UPDATE" form
|
||||||
|
func (o *ormBase) ReadForUpdate(md interface{}, cols ...string) error {
|
||||||
|
return o.ReadForUpdateWithCtx(context.Background(), md, cols...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ormBase) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error {
|
||||||
|
mi, ind := o.getPtrMiInd(md)
|
||||||
|
return o.alias.DbBaser.Read(ctx, o.db, mi, ind, o.alias.TZ, cols, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to read a row from the database, or insert one if it doesn't exist
|
||||||
|
func (o *ormBase) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
||||||
|
return o.ReadOrCreateWithCtx(context.Background(), md, col1, cols...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ormBase) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
||||||
|
cols = append([]string{col1}, cols...)
|
||||||
|
mi, ind := o.getPtrMiInd(md)
|
||||||
|
err := o.alias.DbBaser.Read(ctx, o.db, mi, ind, o.alias.TZ, cols, false)
|
||||||
|
if err == ErrNoRows {
|
||||||
|
// Create
|
||||||
|
id, err := o.InsertWithCtx(ctx, md)
|
||||||
|
return err == nil, id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id, vid := int64(0), ind.FieldByIndex(mi.fields.pk.fieldIndex)
|
||||||
|
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
|
||||||
|
id = int64(vid.Uint())
|
||||||
|
} else if mi.fields.pk.rel {
|
||||||
|
return o.ReadOrCreateWithCtx(ctx, vid.Interface(), mi.fields.pk.relModelInfo.fields.pk.name)
|
||||||
|
} else {
|
||||||
|
id = vid.Int()
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert model data to database
|
||||||
|
func (o *ormBase) Insert(md interface{}) (int64, error) {
|
||||||
|
return o.InsertWithCtx(context.Background(), md)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ormBase) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) {
|
||||||
|
mi, ind := o.getPtrMiInd(md)
|
||||||
|
id, err := o.alias.DbBaser.Insert(ctx, o.db, mi, ind, o.alias.TZ)
|
||||||
|
if err != nil {
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
o.setPk(mi, ind, id)
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// set auto pk field
|
||||||
|
func (*ormBase) setPk(mi *modelInfo, ind reflect.Value, id int64) {
|
||||||
|
if mi.fields.pk.auto {
|
||||||
|
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
|
||||||
|
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(uint64(id))
|
||||||
|
} else {
|
||||||
|
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert some models to database
|
||||||
|
func (o *ormBase) InsertMulti(bulk int, mds interface{}) (int64, error) {
|
||||||
|
return o.InsertMultiWithCtx(context.Background(), bulk, mds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ormBase) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) {
|
||||||
|
var cnt int64
|
||||||
|
|
||||||
|
sind := reflect.Indirect(reflect.ValueOf(mds))
|
||||||
|
|
||||||
|
switch sind.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
if sind.Len() == 0 {
|
||||||
|
return cnt, ErrArgs
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return cnt, ErrArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
if bulk <= 1 {
|
||||||
|
for i := 0; i < sind.Len(); i++ {
|
||||||
|
ind := reflect.Indirect(sind.Index(i))
|
||||||
|
mi := o.getMi(ind.Interface())
|
||||||
|
id, err := o.alias.DbBaser.Insert(ctx, o.db, mi, ind, o.alias.TZ)
|
||||||
|
if err != nil {
|
||||||
|
return cnt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
o.setPk(mi, ind, id)
|
||||||
|
|
||||||
|
cnt++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mi := o.getMi(sind.Index(0).Interface())
|
||||||
|
return o.alias.DbBaser.InsertMulti(ctx, o.db, mi, sind, bulk, o.alias.TZ)
|
||||||
|
}
|
||||||
|
return cnt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertOrUpdate data to database
|
||||||
|
func (o *ormBase) InsertOrUpdate(md interface{}, colConflictAndArgs ...string) (int64, error) {
|
||||||
|
return o.InsertOrUpdateWithCtx(context.Background(), md, colConflictAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ormBase) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) {
|
||||||
|
mi, ind := o.getPtrMiInd(md)
|
||||||
|
id, err := o.alias.DbBaser.InsertOrUpdate(ctx, o.db, mi, ind, o.alias, colConflitAndArgs...)
|
||||||
|
if err != nil {
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
o.setPk(mi, ind, id)
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// update model to database.
|
||||||
|
// cols set the columns those want to update.
|
||||||
|
func (o *ormBase) Update(md interface{}, cols ...string) (int64, error) {
|
||||||
|
return o.UpdateWithCtx(context.Background(), md, cols...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ormBase) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
||||||
|
mi, ind := o.getPtrMiInd(md)
|
||||||
|
return o.alias.DbBaser.Update(ctx, o.db, mi, ind, o.alias.TZ, cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete model in database
|
||||||
|
// cols shows the delete conditions values read from. default is pk
|
||||||
|
func (o *ormBase) Delete(md interface{}, cols ...string) (int64, error) {
|
||||||
|
return o.DeleteWithCtx(context.Background(), md, cols...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ormBase) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
|
||||||
|
mi, ind := o.getPtrMiInd(md)
|
||||||
|
num, err := o.alias.DbBaser.Delete(ctx, o.db, mi, ind, o.alias.TZ, cols)
|
||||||
|
return num, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a models to models queryer
|
||||||
|
func (o *ormBase) QueryM2M(md interface{}, name string) QueryM2Mer {
|
||||||
|
mi, ind := o.getPtrMiInd(md)
|
||||||
|
fi := o.getFieldInfo(mi, name)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case fi.fieldType == RelManyToMany:
|
||||||
|
case fi.fieldType == RelReverseMany && fi.reverseFieldInfo.mi.isThrough:
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("<Ormer.QueryM2M> model `%s` . name `%s` is not a m2m field", fi.name, mi.fullName))
|
||||||
|
}
|
||||||
|
|
||||||
|
return newQueryM2M(md, o, mi, fi, ind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||||
|
func (o *ormBase) QueryM2MWithCtx(_ context.Context, md interface{}, name string) QueryM2Mer {
|
||||||
|
logs.Warn("QueryM2MWithCtx is DEPRECATED. Use methods with `WithCtx` suffix on QueryM2M as replacement please.")
|
||||||
|
return o.QueryM2M(md, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load related models to md model.
|
||||||
|
// args are limit, offset int and order string.
|
||||||
|
//
|
||||||
|
// example:
|
||||||
|
// orm.LoadRelated(post,"Tags")
|
||||||
|
// for _,tag := range post.Tags{...}
|
||||||
|
//
|
||||||
|
// make sure the relation is defined in model struct tags.
|
||||||
|
func (o *ormBase) LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error) {
|
||||||
|
return o.LoadRelatedWithCtx(context.Background(), md, name, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ormBase) LoadRelatedWithCtx(_ context.Context, md interface{}, name string, args ...utils.KV) (int64, error) {
|
||||||
|
_, fi, ind, qs := o.queryRelated(md, name)
|
||||||
|
|
||||||
|
var relDepth int
|
||||||
|
var limit, offset int64
|
||||||
|
var order string
|
||||||
|
|
||||||
|
kvs := utils.NewKVs(args...)
|
||||||
|
kvs.IfContains(hints.KeyRelDepth, func(value interface{}) {
|
||||||
|
if v, ok := value.(bool); ok {
|
||||||
|
if v {
|
||||||
|
relDepth = DefaultRelsDepth
|
||||||
|
}
|
||||||
|
} else if v, ok := value.(int); ok {
|
||||||
|
relDepth = v
|
||||||
|
}
|
||||||
|
}).IfContains(hints.KeyLimit, func(value interface{}) {
|
||||||
|
if v, ok := value.(int64); ok {
|
||||||
|
limit = v
|
||||||
|
}
|
||||||
|
}).IfContains(hints.KeyOffset, func(value interface{}) {
|
||||||
|
if v, ok := value.(int64); ok {
|
||||||
|
offset = v
|
||||||
|
}
|
||||||
|
}).IfContains(hints.KeyOrderBy, func(value interface{}) {
|
||||||
|
if v, ok := value.(string); ok {
|
||||||
|
order = v
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
switch fi.fieldType {
|
||||||
|
case RelOneToOne, RelForeignKey, RelReverseOne:
|
||||||
|
limit = 1
|
||||||
|
offset = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
qs.limit = limit
|
||||||
|
qs.offset = offset
|
||||||
|
qs.relDepth = relDepth
|
||||||
|
|
||||||
|
if len(order) > 0 {
|
||||||
|
qs.orders = order_clause.ParseOrder(order)
|
||||||
|
}
|
||||||
|
|
||||||
|
find := ind.FieldByIndex(fi.fieldIndex)
|
||||||
|
|
||||||
|
var nums int64
|
||||||
|
var err error
|
||||||
|
switch fi.fieldType {
|
||||||
|
case RelOneToOne, RelForeignKey, RelReverseOne:
|
||||||
|
val := reflect.New(find.Type().Elem())
|
||||||
|
container := val.Interface()
|
||||||
|
err = qs.One(container)
|
||||||
|
if err == nil {
|
||||||
|
find.Set(val)
|
||||||
|
nums = 1
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
nums, err = qs.All(find.Addr().Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nums, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get QuerySeter for related models to md model
|
||||||
|
func (o *ormBase) queryRelated(md interface{}, name string) (*modelInfo, *fieldInfo, reflect.Value, *querySet) {
|
||||||
|
mi, ind := o.getPtrMiInd(md)
|
||||||
|
fi := o.getFieldInfo(mi, name)
|
||||||
|
|
||||||
|
_, _, exist := getExistPk(mi, ind)
|
||||||
|
if !exist {
|
||||||
|
panic(ErrMissPK)
|
||||||
|
}
|
||||||
|
|
||||||
|
var qs *querySet
|
||||||
|
|
||||||
|
switch fi.fieldType {
|
||||||
|
case RelOneToOne, RelForeignKey, RelManyToMany:
|
||||||
|
if !fi.inModel {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
qs = o.getRelQs(md, mi, fi)
|
||||||
|
case RelReverseOne, RelReverseMany:
|
||||||
|
if !fi.inModel {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
qs = o.getReverseQs(md, mi, fi)
|
||||||
|
}
|
||||||
|
|
||||||
|
if qs == nil {
|
||||||
|
panic(fmt.Errorf("<Ormer> name `%s` for model `%s` is not an available rel/reverse field", md, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return mi, fi, ind, qs
|
||||||
|
}
|
||||||
|
|
||||||
|
// get reverse relation QuerySeter
|
||||||
|
func (o *ormBase) getReverseQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet {
|
||||||
|
switch fi.fieldType {
|
||||||
|
case RelReverseOne, RelReverseMany:
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("<Ormer> name `%s` for model `%s` is not an available reverse field", fi.name, mi.fullName))
|
||||||
|
}
|
||||||
|
|
||||||
|
var q *querySet
|
||||||
|
|
||||||
|
if fi.fieldType == RelReverseMany && fi.reverseFieldInfo.mi.isThrough {
|
||||||
|
q = newQuerySet(o, fi.relModelInfo).(*querySet)
|
||||||
|
q.cond = NewCondition().And(fi.reverseFieldInfoM2M.column+ExprSep+fi.reverseFieldInfo.column, md)
|
||||||
|
} else {
|
||||||
|
q = newQuerySet(o, fi.reverseFieldInfo.mi).(*querySet)
|
||||||
|
q.cond = NewCondition().And(fi.reverseFieldInfo.column, md)
|
||||||
|
}
|
||||||
|
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// get relation QuerySeter
|
||||||
|
func (o *ormBase) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet {
|
||||||
|
switch fi.fieldType {
|
||||||
|
case RelOneToOne, RelForeignKey, RelManyToMany:
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("<Ormer> name `%s` for model `%s` is not an available rel field", fi.name, mi.fullName))
|
||||||
|
}
|
||||||
|
|
||||||
|
q := newQuerySet(o, fi.relModelInfo).(*querySet)
|
||||||
|
q.cond = NewCondition()
|
||||||
|
|
||||||
|
if fi.fieldType == RelManyToMany {
|
||||||
|
q.cond = q.cond.And(fi.reverseFieldInfoM2M.column+ExprSep+fi.reverseFieldInfo.column, md)
|
||||||
|
} else {
|
||||||
|
q.cond = q.cond.And(fi.reverseFieldInfo.column, md)
|
||||||
|
}
|
||||||
|
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a QuerySeter for table operations.
|
||||||
|
// table name can be string or struct.
|
||||||
|
// e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)),
|
||||||
|
func (o *ormBase) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) {
|
||||||
|
var name string
|
||||||
|
if table, ok := ptrStructOrTableName.(string); ok {
|
||||||
|
name = nameStrategyMap[defaultNameStrategy](table)
|
||||||
|
if mi, ok := defaultModelCache.get(name); ok {
|
||||||
|
qs = newQuerySet(o, mi)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
name = getFullName(indirectType(reflect.TypeOf(ptrStructOrTableName)))
|
||||||
|
if mi, ok := defaultModelCache.getByFullName(name); ok {
|
||||||
|
qs = newQuerySet(o, mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if qs == nil {
|
||||||
|
panic(fmt.Errorf("<Ormer.QueryTable> table name: `%s` not exists", name))
|
||||||
|
}
|
||||||
|
return qs
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||||
|
func (o *ormBase) QueryTableWithCtx(_ context.Context, ptrStructOrTableName interface{}) (qs QuerySeter) {
|
||||||
|
logs.Warn("QueryTableWithCtx is DEPRECATED. Use methods with `WithCtx` suffix on QuerySeter as replacement please.")
|
||||||
|
return o.QueryTable(ptrStructOrTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a raw query seter for raw sql string.
|
||||||
|
func (o *ormBase) Raw(query string, args ...interface{}) RawSeter {
|
||||||
|
return o.RawWithCtx(context.Background(), query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ormBase) RawWithCtx(_ context.Context, query string, args ...interface{}) RawSeter {
|
||||||
|
return newRawSet(o, query, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return current using database Driver
|
||||||
|
func (o *ormBase) Driver() Driver {
|
||||||
|
return driver(o.alias.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return sql.DBStats for current database
|
||||||
|
func (o *ormBase) DBStats() *sql.DBStats {
|
||||||
|
if o.alias != nil && o.alias.DB != nil {
|
||||||
|
stats := o.alias.DB.DB.Stats()
|
||||||
|
return &stats
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type orm struct {
|
||||||
|
ormBase
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Ormer = new(orm)
|
||||||
|
|
||||||
|
func (o *orm) Begin() (TxOrmer, error) {
|
||||||
|
return o.BeginWithCtx(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *orm) BeginWithCtx(ctx context.Context) (TxOrmer, error) {
|
||||||
|
return o.BeginWithCtxAndOpts(ctx, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *orm) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) {
|
||||||
|
return o.BeginWithCtxAndOpts(context.Background(), opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *orm) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) {
|
||||||
|
tx, err := o.db.(txer).BeginTx(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_txOrm := &txOrm{
|
||||||
|
ormBase: ormBase{
|
||||||
|
alias: o.alias,
|
||||||
|
db: &TxDB{tx: tx},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
_txOrm.db = newDbQueryLog(o.alias, _txOrm.db)
|
||||||
|
}
|
||||||
|
|
||||||
|
var taskTxOrm TxOrmer = _txOrm
|
||||||
|
return taskTxOrm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *orm) DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||||
|
return o.DoTxWithCtx(context.Background(), task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *orm) DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||||
|
return o.DoTxWithCtxAndOpts(ctx, nil, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *orm) DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||||
|
return o.DoTxWithCtxAndOpts(context.Background(), opts, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *orm) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||||
|
return doTxTemplate(ctx, o, opts, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTxTemplate(ctx context.Context, o TxBeginner, opts *sql.TxOptions,
|
||||||
|
task func(ctx context.Context, txOrm TxOrmer) error) error {
|
||||||
|
_txOrm, err := o.BeginWithCtxAndOpts(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
panicked := true
|
||||||
|
defer func() {
|
||||||
|
if panicked || err != nil {
|
||||||
|
e := _txOrm.Rollback()
|
||||||
|
if e != nil {
|
||||||
|
logs.Error("rollback transaction failed: %v,%v", e, panicked)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
e := _txOrm.Commit()
|
||||||
|
if e != nil {
|
||||||
|
logs.Error("commit transaction failed: %v,%v", e, panicked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
taskTxOrm := _txOrm
|
||||||
|
err = task(ctx, taskTxOrm)
|
||||||
|
panicked = false
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type txOrm struct {
|
||||||
|
ormBase
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ TxOrmer = new(txOrm)
|
||||||
|
|
||||||
|
func (t *txOrm) Commit() error {
|
||||||
|
return t.db.(txEnder).Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *txOrm) Rollback() error {
|
||||||
|
return t.db.(txEnder).Rollback()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *txOrm) RollbackUnlessCommit() error {
|
||||||
|
return t.db.(txEnder).RollbackUnlessCommit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOrm create new orm
|
||||||
|
func NewOrm() Ormer {
|
||||||
|
BootStrap() // execute only once
|
||||||
|
return NewOrmUsingDB(`default`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOrmUsingDB create new orm with the name
|
||||||
|
func NewOrmUsingDB(aliasName string) Ormer {
|
||||||
|
if al, ok := dataBaseCache.get(aliasName); ok {
|
||||||
|
return newDBWithAlias(al)
|
||||||
|
}
|
||||||
|
panic(fmt.Errorf("<Ormer.Using> unknown db alias name `%s`", aliasName))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOrmWithDB create a new ormer object with specify *sql.DB for query
|
||||||
|
func NewOrmWithDB(driverName, aliasName string, db *sql.DB, params ...DBOption) (Ormer, error) {
|
||||||
|
al, err := newAliasWithDb(aliasName, driverName, db, params...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newDBWithAlias(al), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDBWithAlias(al *alias) Ormer {
|
||||||
|
o := new(orm)
|
||||||
|
o.alias = al
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
o.db = newDbQueryLog(al, al.DB)
|
||||||
|
} else {
|
||||||
|
o.db = al.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(globalFilterChains) > 0 {
|
||||||
|
return NewFilterOrmDecorator(o, globalFilterChains...)
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
@ -0,0 +1,160 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExprSep define the expression separation
|
||||||
|
const (
|
||||||
|
ExprSep = clauses.ExprSep
|
||||||
|
)
|
||||||
|
|
||||||
|
type condValue struct {
|
||||||
|
exprs []string
|
||||||
|
args []interface{}
|
||||||
|
cond *Condition
|
||||||
|
isOr bool
|
||||||
|
isNot bool
|
||||||
|
isCond bool
|
||||||
|
isRaw bool
|
||||||
|
sql string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Condition struct.
|
||||||
|
// work for WHERE conditions.
|
||||||
|
type Condition struct {
|
||||||
|
params []condValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCondition return new condition struct
|
||||||
|
func NewCondition() *Condition {
|
||||||
|
c := &Condition{}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw add raw sql to condition
|
||||||
|
func (c Condition) Raw(expr string, sql string) *Condition {
|
||||||
|
if len(sql) == 0 {
|
||||||
|
panic(fmt.Errorf("<Condition.Raw> sql cannot empty"))
|
||||||
|
}
|
||||||
|
c.params = append(c.params, condValue{exprs: strings.Split(expr, ExprSep), sql: sql, isRaw: true})
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// And add expression to condition
|
||||||
|
func (c Condition) And(expr string, args ...interface{}) *Condition {
|
||||||
|
if expr == "" || len(args) == 0 {
|
||||||
|
panic(fmt.Errorf("<Condition.And> args cannot empty"))
|
||||||
|
}
|
||||||
|
c.params = append(c.params, condValue{exprs: strings.Split(expr, ExprSep), args: args})
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// AndNot add NOT expression to condition
|
||||||
|
func (c Condition) AndNot(expr string, args ...interface{}) *Condition {
|
||||||
|
if expr == "" || len(args) == 0 {
|
||||||
|
panic(fmt.Errorf("<Condition.AndNot> args cannot empty"))
|
||||||
|
}
|
||||||
|
c.params = append(c.params, condValue{exprs: strings.Split(expr, ExprSep), args: args, isNot: true})
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// AndCond combine a condition to current condition
|
||||||
|
func (c *Condition) AndCond(cond *Condition) *Condition {
|
||||||
|
if c == cond {
|
||||||
|
panic(fmt.Errorf("<Condition.AndCond> cannot use self as sub cond"))
|
||||||
|
}
|
||||||
|
|
||||||
|
c = c.clone()
|
||||||
|
|
||||||
|
if cond != nil {
|
||||||
|
c.params = append(c.params, condValue{cond: cond, isCond: true})
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// AndNotCond combine a AND NOT condition to current condition
|
||||||
|
func (c *Condition) AndNotCond(cond *Condition) *Condition {
|
||||||
|
c = c.clone()
|
||||||
|
if c == cond {
|
||||||
|
panic(fmt.Errorf("<Condition.AndNotCond> cannot use self as sub cond"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cond != nil {
|
||||||
|
c.params = append(c.params, condValue{cond: cond, isCond: true, isNot: true})
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or add OR expression to condition
|
||||||
|
func (c Condition) Or(expr string, args ...interface{}) *Condition {
|
||||||
|
if expr == "" || len(args) == 0 {
|
||||||
|
panic(fmt.Errorf("<Condition.Or> args cannot empty"))
|
||||||
|
}
|
||||||
|
c.params = append(c.params, condValue{exprs: strings.Split(expr, ExprSep), args: args, isOr: true})
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrNot add OR NOT expression to condition
|
||||||
|
func (c Condition) OrNot(expr string, args ...interface{}) *Condition {
|
||||||
|
if expr == "" || len(args) == 0 {
|
||||||
|
panic(fmt.Errorf("<Condition.OrNot> args cannot empty"))
|
||||||
|
}
|
||||||
|
c.params = append(c.params, condValue{exprs: strings.Split(expr, ExprSep), args: args, isNot: true, isOr: true})
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrCond combine a OR condition to current condition
|
||||||
|
func (c *Condition) OrCond(cond *Condition) *Condition {
|
||||||
|
c = c.clone()
|
||||||
|
if c == cond {
|
||||||
|
panic(fmt.Errorf("<Condition.OrCond> cannot use self as sub cond"))
|
||||||
|
}
|
||||||
|
if cond != nil {
|
||||||
|
c.params = append(c.params, condValue{cond: cond, isCond: true, isOr: true})
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrNotCond combine a OR NOT condition to current condition
|
||||||
|
func (c *Condition) OrNotCond(cond *Condition) *Condition {
|
||||||
|
c = c.clone()
|
||||||
|
if c == cond {
|
||||||
|
panic(fmt.Errorf("<Condition.OrNotCond> cannot use self as sub cond"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cond != nil {
|
||||||
|
c.params = append(c.params, condValue{cond: cond, isCond: true, isNot: true, isOr: true})
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty check the condition arguments are empty or not.
|
||||||
|
func (c *Condition) IsEmpty() bool {
|
||||||
|
return len(c.params) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// clone clone a condition
|
||||||
|
func (c Condition) clone() *Condition {
|
||||||
|
params := make([]condValue, len(c.params))
|
||||||
|
copy(params, c.params)
|
||||||
|
c.params = params
|
||||||
|
return &c
|
||||||
|
}
|
@ -0,0 +1,228 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Log implement the log.Logger
|
||||||
|
type Log struct {
|
||||||
|
*log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// costomer log func
|
||||||
|
var LogFunc func(query map[string]interface{})
|
||||||
|
|
||||||
|
// NewLog set io.Writer to create a Logger.
|
||||||
|
func NewLog(out io.Writer) *Log {
|
||||||
|
d := new(Log)
|
||||||
|
d.Logger = log.New(out, "[ORM]", log.LstdFlags)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error, args ...interface{}) {
|
||||||
|
logMap := make(map[string]interface{})
|
||||||
|
sub := time.Since(t) / 1e5
|
||||||
|
elsp := float64(int(sub)) / 10.0
|
||||||
|
logMap["cost_time"] = elsp
|
||||||
|
flag := " OK"
|
||||||
|
if err != nil {
|
||||||
|
flag = "FAIL"
|
||||||
|
}
|
||||||
|
logMap["flag"] = flag
|
||||||
|
con := fmt.Sprintf(" -[Queries/%s] - [%s / %11s / %7.1fms] - [%s]", alias.Name, flag, operaton, elsp, query)
|
||||||
|
cons := make([]string, 0, len(args))
|
||||||
|
for _, arg := range args {
|
||||||
|
cons = append(cons, fmt.Sprintf("%v", arg))
|
||||||
|
}
|
||||||
|
if len(cons) > 0 {
|
||||||
|
con += fmt.Sprintf(" - `%s`", strings.Join(cons, "`, `"))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
con += " - " + err.Error()
|
||||||
|
}
|
||||||
|
logMap["sql"] = fmt.Sprintf("%s-`%s`", query, strings.Join(cons, "`, `"))
|
||||||
|
if LogFunc != nil {
|
||||||
|
LogFunc(logMap)
|
||||||
|
}
|
||||||
|
DebugLog.Println(con)
|
||||||
|
}
|
||||||
|
|
||||||
|
// statement query logger struct.
|
||||||
|
// if dev mode, use stmtQueryLog, or use stmtQuerier.
|
||||||
|
type stmtQueryLog struct {
|
||||||
|
alias *alias
|
||||||
|
query string
|
||||||
|
stmt stmtQuerier
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ stmtQuerier = new(stmtQueryLog)
|
||||||
|
|
||||||
|
func (d *stmtQueryLog) Close() error {
|
||||||
|
a := time.Now()
|
||||||
|
err := d.stmt.Close()
|
||||||
|
debugLogQueies(d.alias, "st.Close", d.query, a, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stmtQueryLog) Exec(args ...interface{}) (sql.Result, error) {
|
||||||
|
return d.ExecContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stmtQueryLog) ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) {
|
||||||
|
a := time.Now()
|
||||||
|
res, err := d.stmt.ExecContext(ctx, args...)
|
||||||
|
debugLogQueies(d.alias, "st.Exec", d.query, a, err, args...)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stmtQueryLog) Query(args ...interface{}) (*sql.Rows, error) {
|
||||||
|
return d.QueryContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stmtQueryLog) QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
a := time.Now()
|
||||||
|
res, err := d.stmt.QueryContext(ctx, args...)
|
||||||
|
debugLogQueies(d.alias, "st.Query", d.query, a, err, args...)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stmtQueryLog) QueryRow(args ...interface{}) *sql.Row {
|
||||||
|
return d.QueryRowContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stmtQueryLog) QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row {
|
||||||
|
a := time.Now()
|
||||||
|
res := d.stmt.QueryRow(args...)
|
||||||
|
debugLogQueies(d.alias, "st.QueryRow", d.query, a, nil, args...)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStmtQueryLog(alias *alias, stmt stmtQuerier, query string) stmtQuerier {
|
||||||
|
d := new(stmtQueryLog)
|
||||||
|
d.stmt = stmt
|
||||||
|
d.alias = alias
|
||||||
|
d.query = query
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// database query logger struct.
|
||||||
|
// if dev mode, use dbQueryLog, or use dbQuerier.
|
||||||
|
type dbQueryLog struct {
|
||||||
|
alias *alias
|
||||||
|
db dbQuerier
|
||||||
|
tx txer
|
||||||
|
txe txEnder
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ dbQuerier = new(dbQueryLog)
|
||||||
|
_ txer = new(dbQueryLog)
|
||||||
|
_ txEnder = new(dbQueryLog)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *dbQueryLog) Prepare(query string) (*sql.Stmt, error) {
|
||||||
|
return d.PrepareContext(context.Background(), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
|
||||||
|
a := time.Now()
|
||||||
|
stmt, err := d.db.PrepareContext(ctx, query)
|
||||||
|
debugLogQueies(d.alias, "db.Prepare", query, a, err)
|
||||||
|
return stmt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
return d.ExecContext(context.Background(), query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
|
||||||
|
a := time.Now()
|
||||||
|
res, err := d.db.ExecContext(ctx, query, args...)
|
||||||
|
debugLogQueies(d.alias, "db.Exec", query, a, err, args...)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
return d.QueryContext(context.Background(), query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
|
||||||
|
a := time.Now()
|
||||||
|
res, err := d.db.QueryContext(ctx, query, args...)
|
||||||
|
debugLogQueies(d.alias, "db.Query", query, a, err, args...)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) QueryRow(query string, args ...interface{}) *sql.Row {
|
||||||
|
return d.QueryRowContext(context.Background(), query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
|
||||||
|
a := time.Now()
|
||||||
|
res := d.db.QueryRowContext(ctx, query, args...)
|
||||||
|
debugLogQueies(d.alias, "db.QueryRow", query, a, nil, args...)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) Begin() (*sql.Tx, error) {
|
||||||
|
return d.BeginTx(context.Background(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) {
|
||||||
|
a := time.Now()
|
||||||
|
tx, err := d.db.(txer).BeginTx(ctx, opts)
|
||||||
|
debugLogQueies(d.alias, "db.BeginTx", "START TRANSACTION", a, err)
|
||||||
|
return tx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) Commit() error {
|
||||||
|
a := time.Now()
|
||||||
|
err := d.db.(txEnder).Commit()
|
||||||
|
debugLogQueies(d.alias, "tx.Commit", "COMMIT", a, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) Rollback() error {
|
||||||
|
a := time.Now()
|
||||||
|
err := d.db.(txEnder).Rollback()
|
||||||
|
debugLogQueies(d.alias, "tx.Rollback", "ROLLBACK", a, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) RollbackUnlessCommit() error {
|
||||||
|
a := time.Now()
|
||||||
|
err := d.db.(txEnder).RollbackUnlessCommit()
|
||||||
|
debugLogQueies(d.alias, "tx.RollbackUnlessCommit", "ROLLBACK UNLESS COMMIT", a, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dbQueryLog) SetDB(db dbQuerier) {
|
||||||
|
d.db = db
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDbQueryLog(alias *alias, db dbQuerier) dbQuerier {
|
||||||
|
d := new(dbQueryLog)
|
||||||
|
d.alias = alias
|
||||||
|
d.db = db
|
||||||
|
return d
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// an insert queryer struct
|
||||||
|
type insertSet struct {
|
||||||
|
mi *modelInfo
|
||||||
|
orm *ormBase
|
||||||
|
stmt stmtQuerier
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Inserter = new(insertSet)
|
||||||
|
|
||||||
|
// insert model ignore it's registered or not.
|
||||||
|
func (o *insertSet) Insert(md interface{}) (int64, error) {
|
||||||
|
return o.InsertWithCtx(context.Background(), md)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *insertSet) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) {
|
||||||
|
if o.closed {
|
||||||
|
return 0, ErrStmtClosed
|
||||||
|
}
|
||||||
|
val := reflect.ValueOf(md)
|
||||||
|
ind := reflect.Indirect(val)
|
||||||
|
typ := ind.Type()
|
||||||
|
name := getFullName(typ)
|
||||||
|
if val.Kind() != reflect.Ptr {
|
||||||
|
panic(fmt.Errorf("<Inserter.Insert> cannot use non-ptr model struct `%s`", name))
|
||||||
|
}
|
||||||
|
if name != o.mi.fullName {
|
||||||
|
panic(fmt.Errorf("<Inserter.Insert> need model `%s` but found `%s`", o.mi.fullName, name))
|
||||||
|
}
|
||||||
|
id, err := o.orm.alias.DbBaser.InsertStmt(ctx, o.stmt, o.mi, ind, o.orm.alias.TZ)
|
||||||
|
if err != nil {
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
if id > 0 {
|
||||||
|
if o.mi.fields.pk.auto {
|
||||||
|
if o.mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
|
||||||
|
ind.FieldByIndex(o.mi.fields.pk.fieldIndex).SetUint(uint64(id))
|
||||||
|
} else {
|
||||||
|
ind.FieldByIndex(o.mi.fields.pk.fieldIndex).SetInt(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// close insert queryer statement
|
||||||
|
func (o *insertSet) Close() error {
|
||||||
|
if o.closed {
|
||||||
|
return ErrStmtClosed
|
||||||
|
}
|
||||||
|
o.closed = true
|
||||||
|
return o.stmt.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new insert queryer.
|
||||||
|
func newInsertSet(ctx context.Context, orm *ormBase, mi *modelInfo) (Inserter, error) {
|
||||||
|
bi := new(insertSet)
|
||||||
|
bi.orm = orm
|
||||||
|
bi.mi = mi
|
||||||
|
st, query, err := orm.alias.DbBaser.PrepareInsert(ctx, orm.db, mi)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if Debug {
|
||||||
|
bi.stmt = newStmtQueryLog(orm.alias, st, query)
|
||||||
|
} else {
|
||||||
|
bi.stmt = st
|
||||||
|
}
|
||||||
|
return bi, nil
|
||||||
|
}
|
@ -0,0 +1,163 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// model to model struct
|
||||||
|
type queryM2M struct {
|
||||||
|
md interface{}
|
||||||
|
mi *modelInfo
|
||||||
|
fi *fieldInfo
|
||||||
|
qs *querySet
|
||||||
|
ind reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// add models to origin models when creating queryM2M.
|
||||||
|
// example:
|
||||||
|
// m2m := orm.QueryM2M(post,"Tag")
|
||||||
|
// m2m.Add(&Tag1{},&Tag2{})
|
||||||
|
// for _,tag := range post.Tags{}
|
||||||
|
//
|
||||||
|
// make sure the relation is defined in post model struct tag.
|
||||||
|
func (o *queryM2M) Add(mds ...interface{}) (int64, error) {
|
||||||
|
return o.AddWithCtx(context.Background(), mds...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *queryM2M) AddWithCtx(ctx context.Context, mds ...interface{}) (int64, error) {
|
||||||
|
fi := o.fi
|
||||||
|
mi := fi.relThroughModelInfo
|
||||||
|
mfi := fi.reverseFieldInfo
|
||||||
|
rfi := fi.reverseFieldInfoTwo
|
||||||
|
|
||||||
|
orm := o.qs.orm
|
||||||
|
dbase := orm.alias.DbBaser
|
||||||
|
|
||||||
|
var models []interface{}
|
||||||
|
var otherValues []interface{}
|
||||||
|
var otherNames []string
|
||||||
|
|
||||||
|
for _, colname := range mi.fields.dbcols {
|
||||||
|
if colname != mfi.column && colname != rfi.column && colname != fi.mi.fields.pk.column &&
|
||||||
|
mi.fields.columns[colname] != mi.fields.pk {
|
||||||
|
otherNames = append(otherNames, colname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, md := range mds {
|
||||||
|
if reflect.Indirect(reflect.ValueOf(md)).Kind() != reflect.Struct && i > 0 {
|
||||||
|
otherValues = append(otherValues, md)
|
||||||
|
mds = append(mds[:i], mds[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, md := range mds {
|
||||||
|
val := reflect.ValueOf(md)
|
||||||
|
if val.Kind() == reflect.Slice || val.Kind() == reflect.Array {
|
||||||
|
for i := 0; i < val.Len(); i++ {
|
||||||
|
v := val.Index(i)
|
||||||
|
if v.CanInterface() {
|
||||||
|
models = append(models, v.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
models = append(models, md)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, v1, exist := getExistPk(o.mi, o.ind)
|
||||||
|
if !exist {
|
||||||
|
panic(ErrMissPK)
|
||||||
|
}
|
||||||
|
|
||||||
|
names := []string{mfi.column, rfi.column}
|
||||||
|
|
||||||
|
values := make([]interface{}, 0, len(models)*2)
|
||||||
|
for _, md := range models {
|
||||||
|
|
||||||
|
ind := reflect.Indirect(reflect.ValueOf(md))
|
||||||
|
var v2 interface{}
|
||||||
|
if ind.Kind() != reflect.Struct {
|
||||||
|
v2 = ind.Interface()
|
||||||
|
} else {
|
||||||
|
_, v2, exist = getExistPk(fi.relModelInfo, ind)
|
||||||
|
if !exist {
|
||||||
|
panic(ErrMissPK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
values = append(values, v1, v2)
|
||||||
|
|
||||||
|
}
|
||||||
|
names = append(names, otherNames...)
|
||||||
|
values = append(values, otherValues...)
|
||||||
|
return dbase.InsertValue(ctx, orm.db, mi, true, names, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove models following the origin model relationship
|
||||||
|
func (o *queryM2M) Remove(mds ...interface{}) (int64, error) {
|
||||||
|
return o.RemoveWithCtx(context.Background(), mds...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *queryM2M) RemoveWithCtx(ctx context.Context, mds ...interface{}) (int64, error) {
|
||||||
|
fi := o.fi
|
||||||
|
qs := o.qs.Filter(fi.reverseFieldInfo.name, o.md)
|
||||||
|
|
||||||
|
return qs.Filter(fi.reverseFieldInfoTwo.name+ExprSep+"in", mds).Delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
// check model is existed in relationship of origin model
|
||||||
|
func (o *queryM2M) Exist(md interface{}) bool {
|
||||||
|
return o.ExistWithCtx(context.Background(), md)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *queryM2M) ExistWithCtx(ctx context.Context, md interface{}) bool {
|
||||||
|
fi := o.fi
|
||||||
|
return o.qs.Filter(fi.reverseFieldInfo.name, o.md).
|
||||||
|
Filter(fi.reverseFieldInfoTwo.name, md).ExistWithCtx(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean all models in related of origin model
|
||||||
|
func (o *queryM2M) Clear() (int64, error) {
|
||||||
|
return o.ClearWithCtx(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *queryM2M) ClearWithCtx(ctx context.Context) (int64, error) {
|
||||||
|
fi := o.fi
|
||||||
|
return o.qs.Filter(fi.reverseFieldInfo.name, o.md).DeleteWithCtx(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// count all related models of origin model
|
||||||
|
func (o *queryM2M) Count() (int64, error) {
|
||||||
|
return o.CountWithCtx(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *queryM2M) CountWithCtx(ctx context.Context) (int64, error) {
|
||||||
|
fi := o.fi
|
||||||
|
return o.qs.Filter(fi.reverseFieldInfo.name, o.md).CountWithCtx(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ QueryM2Mer = new(queryM2M)
|
||||||
|
|
||||||
|
// create new M2M queryer.
|
||||||
|
func newQueryM2M(md interface{}, o *ormBase, mi *modelInfo, fi *fieldInfo, ind reflect.Value) QueryM2Mer {
|
||||||
|
qm2m := new(queryM2M)
|
||||||
|
qm2m.md = md
|
||||||
|
qm2m.mi = mi
|
||||||
|
qm2m.fi = fi
|
||||||
|
qm2m.ind = ind
|
||||||
|
qm2m.qs = newQuerySet(o, fi.relThroughModelInfo).(*querySet)
|
||||||
|
return qm2m
|
||||||
|
}
|
@ -0,0 +1,376 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
||||||
|
"github.com/beego/beego/v2/client/orm/hints"
|
||||||
|
)
|
||||||
|
|
||||||
|
type colValue struct {
|
||||||
|
value int64
|
||||||
|
opt operator
|
||||||
|
}
|
||||||
|
|
||||||
|
type operator int
|
||||||
|
|
||||||
|
// define Col operations
|
||||||
|
const (
|
||||||
|
ColAdd operator = iota
|
||||||
|
ColMinus
|
||||||
|
ColMultiply
|
||||||
|
ColExcept
|
||||||
|
ColBitAnd
|
||||||
|
ColBitRShift
|
||||||
|
ColBitLShift
|
||||||
|
ColBitXOR
|
||||||
|
ColBitOr
|
||||||
|
)
|
||||||
|
|
||||||
|
// ColValue do the field raw changes. e.g Nums = Nums + 10. usage:
|
||||||
|
// Params{
|
||||||
|
// "Nums": ColValue(Col_Add, 10),
|
||||||
|
// }
|
||||||
|
func ColValue(opt operator, value interface{}) interface{} {
|
||||||
|
switch opt {
|
||||||
|
case ColAdd, ColMinus, ColMultiply, ColExcept, ColBitAnd, ColBitRShift,
|
||||||
|
ColBitLShift, ColBitXOR, ColBitOr:
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("orm.ColValue wrong operator"))
|
||||||
|
}
|
||||||
|
v, err := StrTo(ToStr(value)).Int64()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("orm.ColValue doesn't support non string/numeric type, %s", err))
|
||||||
|
}
|
||||||
|
var val colValue
|
||||||
|
val.value = v
|
||||||
|
val.opt = opt
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// real query struct
|
||||||
|
type querySet struct {
|
||||||
|
mi *modelInfo
|
||||||
|
cond *Condition
|
||||||
|
related []string
|
||||||
|
relDepth int
|
||||||
|
limit int64
|
||||||
|
offset int64
|
||||||
|
groups []string
|
||||||
|
orders []*order_clause.Order
|
||||||
|
distinct bool
|
||||||
|
forUpdate bool
|
||||||
|
useIndex int
|
||||||
|
indexes []string
|
||||||
|
orm *ormBase
|
||||||
|
aggregate string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ QuerySeter = new(querySet)
|
||||||
|
|
||||||
|
// add condition expression to QuerySeter.
|
||||||
|
func (o querySet) Filter(expr string, args ...interface{}) QuerySeter {
|
||||||
|
if o.cond == nil {
|
||||||
|
o.cond = NewCondition()
|
||||||
|
}
|
||||||
|
o.cond = o.cond.And(expr, args...)
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// add raw sql to querySeter.
|
||||||
|
func (o querySet) FilterRaw(expr string, sql string) QuerySeter {
|
||||||
|
if o.cond == nil {
|
||||||
|
o.cond = NewCondition()
|
||||||
|
}
|
||||||
|
o.cond = o.cond.Raw(expr, sql)
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// add NOT condition to querySeter.
|
||||||
|
func (o querySet) Exclude(expr string, args ...interface{}) QuerySeter {
|
||||||
|
if o.cond == nil {
|
||||||
|
o.cond = NewCondition()
|
||||||
|
}
|
||||||
|
o.cond = o.cond.AndNot(expr, args...)
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// set offset number
|
||||||
|
func (o *querySet) setOffset(num interface{}) {
|
||||||
|
o.offset = ToInt64(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add LIMIT value.
|
||||||
|
// args[0] means offset, e.g. LIMIT num,offset.
|
||||||
|
func (o querySet) Limit(limit interface{}, args ...interface{}) QuerySeter {
|
||||||
|
o.limit = ToInt64(limit)
|
||||||
|
if len(args) > 0 {
|
||||||
|
o.setOffset(args[0])
|
||||||
|
}
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// add OFFSET value
|
||||||
|
func (o querySet) Offset(offset interface{}) QuerySeter {
|
||||||
|
o.setOffset(offset)
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// add GROUP expression
|
||||||
|
func (o querySet) GroupBy(exprs ...string) QuerySeter {
|
||||||
|
o.groups = exprs
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// add ORDER expression.
|
||||||
|
// "column" means ASC, "-column" means DESC.
|
||||||
|
func (o querySet) OrderBy(expressions ...string) QuerySeter {
|
||||||
|
if len(expressions) <= 0 {
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
o.orders = order_clause.ParseOrder(expressions...)
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// add ORDER expression.
|
||||||
|
func (o querySet) OrderClauses(orders ...*order_clause.Order) QuerySeter {
|
||||||
|
if len(orders) <= 0 {
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
o.orders = orders
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// add DISTINCT to SELECT
|
||||||
|
func (o querySet) Distinct() QuerySeter {
|
||||||
|
o.distinct = true
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// add FOR UPDATE to SELECT
|
||||||
|
func (o querySet) ForUpdate() QuerySeter {
|
||||||
|
o.forUpdate = true
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForceIndex force index for query
|
||||||
|
func (o querySet) ForceIndex(indexes ...string) QuerySeter {
|
||||||
|
o.useIndex = hints.KeyForceIndex
|
||||||
|
o.indexes = indexes
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseIndex use index for query
|
||||||
|
func (o querySet) UseIndex(indexes ...string) QuerySeter {
|
||||||
|
o.useIndex = hints.KeyUseIndex
|
||||||
|
o.indexes = indexes
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// IgnoreIndex ignore index for query
|
||||||
|
func (o querySet) IgnoreIndex(indexes ...string) QuerySeter {
|
||||||
|
o.useIndex = hints.KeyIgnoreIndex
|
||||||
|
o.indexes = indexes
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// set relation model to query together.
|
||||||
|
// it will query relation models and assign to parent model.
|
||||||
|
func (o querySet) RelatedSel(params ...interface{}) QuerySeter {
|
||||||
|
if len(params) == 0 {
|
||||||
|
o.relDepth = DefaultRelsDepth
|
||||||
|
} else {
|
||||||
|
for _, p := range params {
|
||||||
|
switch val := p.(type) {
|
||||||
|
case string:
|
||||||
|
o.related = append(o.related, val)
|
||||||
|
case int:
|
||||||
|
o.relDepth = val
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("<QuerySeter.RelatedSel> wrong param kind: %v", val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// set condition to QuerySeter.
|
||||||
|
func (o querySet) SetCond(cond *Condition) QuerySeter {
|
||||||
|
o.cond = cond
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// get condition from QuerySeter
|
||||||
|
func (o querySet) GetCond() *Condition {
|
||||||
|
return o.cond
|
||||||
|
}
|
||||||
|
|
||||||
|
// return QuerySeter execution result number
|
||||||
|
func (o *querySet) Count() (int64, error) {
|
||||||
|
return o.CountWithCtx(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *querySet) CountWithCtx(ctx context.Context) (int64, error) {
|
||||||
|
return o.orm.alias.DbBaser.Count(ctx, o.orm.db, o, o.mi, o.cond, o.orm.alias.TZ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check result empty or not after QuerySeter executed
|
||||||
|
func (o *querySet) Exist() bool {
|
||||||
|
return o.ExistWithCtx(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *querySet) ExistWithCtx(ctx context.Context) bool {
|
||||||
|
cnt, _ := o.orm.alias.DbBaser.Count(ctx, o.orm.db, o, o.mi, o.cond, o.orm.alias.TZ)
|
||||||
|
return cnt > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute update with parameters
|
||||||
|
func (o *querySet) Update(values Params) (int64, error) {
|
||||||
|
return o.UpdateWithCtx(context.Background(), values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *querySet) UpdateWithCtx(ctx context.Context, values Params) (int64, error) {
|
||||||
|
return o.orm.alias.DbBaser.UpdateBatch(ctx, o.orm.db, o, o.mi, o.cond, values, o.orm.alias.TZ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute delete
|
||||||
|
func (o *querySet) Delete() (int64, error) {
|
||||||
|
return o.DeleteWithCtx(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *querySet) DeleteWithCtx(ctx context.Context) (int64, error) {
|
||||||
|
return o.orm.alias.DbBaser.DeleteBatch(ctx, o.orm.db, o, o.mi, o.cond, o.orm.alias.TZ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a insert queryer.
|
||||||
|
// it can be used in times.
|
||||||
|
// example:
|
||||||
|
// i,err := sq.PrepareInsert()
|
||||||
|
// i.Add(&user1{},&user2{})
|
||||||
|
func (o *querySet) PrepareInsert() (Inserter, error) {
|
||||||
|
return o.PrepareInsertWithCtx(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *querySet) PrepareInsertWithCtx(ctx context.Context) (Inserter, error) {
|
||||||
|
return newInsertSet(ctx, o.orm, o.mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// query all data and map to containers.
|
||||||
|
// cols means the columns when querying.
|
||||||
|
func (o *querySet) All(container interface{}, cols ...string) (int64, error) {
|
||||||
|
return o.AllWithCtx(context.Background(), container, cols...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *querySet) AllWithCtx(ctx context.Context, container interface{}, cols ...string) (int64, error) {
|
||||||
|
return o.orm.alias.DbBaser.ReadBatch(ctx, o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
// query one row data and map to containers.
|
||||||
|
// cols means the columns when querying.
|
||||||
|
func (o *querySet) One(container interface{}, cols ...string) error {
|
||||||
|
return o.OneWithCtx(context.Background(), container, cols...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *querySet) OneWithCtx(ctx context.Context, container interface{}, cols ...string) error {
|
||||||
|
o.limit = 1
|
||||||
|
num, err := o.orm.alias.DbBaser.ReadBatch(ctx, o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if num == 0 {
|
||||||
|
return ErrNoRows
|
||||||
|
}
|
||||||
|
|
||||||
|
if num > 1 {
|
||||||
|
return ErrMultiRows
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// query all data and map to []map[string]interface.
|
||||||
|
// expres means condition expression.
|
||||||
|
// it converts data to []map[column]value.
|
||||||
|
func (o *querySet) Values(results *[]Params, exprs ...string) (int64, error) {
|
||||||
|
return o.ValuesWithCtx(context.Background(), results, exprs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *querySet) ValuesWithCtx(ctx context.Context, results *[]Params, exprs ...string) (int64, error) {
|
||||||
|
return o.orm.alias.DbBaser.ReadValues(ctx, o.orm.db, o, o.mi, o.cond, exprs, results, o.orm.alias.TZ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// query all data and map to [][]interface
|
||||||
|
// it converts data to [][column_index]value
|
||||||
|
func (o *querySet) ValuesList(results *[]ParamsList, exprs ...string) (int64, error) {
|
||||||
|
return o.ValuesListWithCtx(context.Background(), results, exprs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *querySet) ValuesListWithCtx(ctx context.Context, results *[]ParamsList, exprs ...string) (int64, error) {
|
||||||
|
return o.orm.alias.DbBaser.ReadValues(ctx, o.orm.db, o, o.mi, o.cond, exprs, results, o.orm.alias.TZ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// query all data and map to []interface.
|
||||||
|
// it's designed for one row record set, auto change to []value, not [][column]value.
|
||||||
|
func (o *querySet) ValuesFlat(result *ParamsList, expr string) (int64, error) {
|
||||||
|
return o.ValuesFlatWithCtx(context.Background(), result, expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *querySet) ValuesFlatWithCtx(ctx context.Context, result *ParamsList, expr string) (int64, error) {
|
||||||
|
return o.orm.alias.DbBaser.ReadValues(ctx, o.orm.db, o, o.mi, o.cond, []string{expr}, result, o.orm.alias.TZ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// query all rows into map[string]interface with specify key and value column name.
|
||||||
|
// keyCol = "name", valueCol = "value"
|
||||||
|
// table data
|
||||||
|
// name | value
|
||||||
|
// total | 100
|
||||||
|
// found | 200
|
||||||
|
// to map[string]interface{}{
|
||||||
|
// "total": 100,
|
||||||
|
// "found": 200,
|
||||||
|
// }
|
||||||
|
func (o *querySet) RowsToMap(result *Params, keyCol, valueCol string) (int64, error) {
|
||||||
|
panic(ErrNotImplement)
|
||||||
|
}
|
||||||
|
|
||||||
|
// query all rows into struct with specify key and value column name.
|
||||||
|
// keyCol = "name", valueCol = "value"
|
||||||
|
// table data
|
||||||
|
// name | value
|
||||||
|
// total | 100
|
||||||
|
// found | 200
|
||||||
|
// to struct {
|
||||||
|
// Total int
|
||||||
|
// Found int
|
||||||
|
// }
|
||||||
|
func (o *querySet) RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error) {
|
||||||
|
panic(ErrNotImplement)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new QuerySeter.
|
||||||
|
func newQuerySet(orm *ormBase, mi *modelInfo) QuerySeter {
|
||||||
|
o := new(querySet)
|
||||||
|
o.mi = mi
|
||||||
|
o.orm = orm
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// aggregate func
|
||||||
|
func (o querySet) Aggregate(s string) QuerySeter {
|
||||||
|
o.aggregate = s
|
||||||
|
return &o
|
||||||
|
}
|
@ -0,0 +1,910 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// raw sql string prepared statement
|
||||||
|
type rawPrepare struct {
|
||||||
|
rs *rawSet
|
||||||
|
stmt stmtQuerier
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *rawPrepare) Exec(args ...interface{}) (sql.Result, error) {
|
||||||
|
if o.closed {
|
||||||
|
return nil, ErrStmtClosed
|
||||||
|
}
|
||||||
|
flatParams := getFlatParams(nil, args, o.rs.orm.alias.TZ)
|
||||||
|
return o.stmt.Exec(flatParams...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *rawPrepare) Close() error {
|
||||||
|
o.closed = true
|
||||||
|
return o.stmt.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRawPreparer(rs *rawSet) (RawPreparer, error) {
|
||||||
|
o := new(rawPrepare)
|
||||||
|
o.rs = rs
|
||||||
|
|
||||||
|
query := rs.query
|
||||||
|
rs.orm.alias.DbBaser.ReplaceMarks(&query)
|
||||||
|
|
||||||
|
st, err := rs.orm.db.Prepare(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if Debug {
|
||||||
|
o.stmt = newStmtQueryLog(rs.orm.alias, st, query)
|
||||||
|
} else {
|
||||||
|
o.stmt = st
|
||||||
|
}
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// raw query seter
|
||||||
|
type rawSet struct {
|
||||||
|
query string
|
||||||
|
args []interface{}
|
||||||
|
orm *ormBase
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ RawSeter = new(rawSet)
|
||||||
|
|
||||||
|
// set args for every query
|
||||||
|
func (o rawSet) SetArgs(args ...interface{}) RawSeter {
|
||||||
|
o.args = args
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute raw sql and return sql.Result
|
||||||
|
func (o *rawSet) Exec() (sql.Result, error) {
|
||||||
|
query := o.query
|
||||||
|
o.orm.alias.DbBaser.ReplaceMarks(&query)
|
||||||
|
|
||||||
|
args := getFlatParams(nil, o.args, o.orm.alias.TZ)
|
||||||
|
return o.orm.db.Exec(query, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set field value to row container
|
||||||
|
func (o *rawSet) setFieldValue(ind reflect.Value, value interface{}) {
|
||||||
|
switch ind.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
if value == nil {
|
||||||
|
ind.SetBool(false)
|
||||||
|
} else if v, ok := value.(bool); ok {
|
||||||
|
ind.SetBool(v)
|
||||||
|
} else {
|
||||||
|
v, _ := StrTo(ToStr(value)).Bool()
|
||||||
|
ind.SetBool(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
if value == nil {
|
||||||
|
ind.SetString("")
|
||||||
|
} else {
|
||||||
|
ind.SetString(ToStr(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
if value == nil {
|
||||||
|
ind.SetInt(0)
|
||||||
|
} else {
|
||||||
|
val := reflect.ValueOf(value)
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
ind.SetInt(val.Int())
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
ind.SetInt(int64(val.Uint()))
|
||||||
|
default:
|
||||||
|
v, _ := StrTo(ToStr(value)).Int64()
|
||||||
|
ind.SetInt(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
if value == nil {
|
||||||
|
ind.SetUint(0)
|
||||||
|
} else {
|
||||||
|
val := reflect.ValueOf(value)
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
ind.SetUint(uint64(val.Int()))
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
ind.SetUint(val.Uint())
|
||||||
|
default:
|
||||||
|
v, _ := StrTo(ToStr(value)).Uint64()
|
||||||
|
ind.SetUint(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Float64, reflect.Float32:
|
||||||
|
if value == nil {
|
||||||
|
ind.SetFloat(0)
|
||||||
|
} else {
|
||||||
|
val := reflect.ValueOf(value)
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Float64:
|
||||||
|
ind.SetFloat(val.Float())
|
||||||
|
default:
|
||||||
|
v, _ := StrTo(ToStr(value)).Float64()
|
||||||
|
ind.SetFloat(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
if value == nil {
|
||||||
|
ind.Set(reflect.Zero(ind.Type()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch ind.Interface().(type) {
|
||||||
|
case time.Time:
|
||||||
|
var str string
|
||||||
|
switch d := value.(type) {
|
||||||
|
case time.Time:
|
||||||
|
o.orm.alias.DbBaser.TimeFromDB(&d, o.orm.alias.TZ)
|
||||||
|
ind.Set(reflect.ValueOf(d))
|
||||||
|
case []byte:
|
||||||
|
str = string(d)
|
||||||
|
case string:
|
||||||
|
str = d
|
||||||
|
}
|
||||||
|
if str != "" {
|
||||||
|
if len(str) >= 19 {
|
||||||
|
str = str[:19]
|
||||||
|
t, err := time.ParseInLocation(formatDateTime, str, o.orm.alias.TZ)
|
||||||
|
if err == nil {
|
||||||
|
t = t.In(DefaultTimeLoc)
|
||||||
|
ind.Set(reflect.ValueOf(t))
|
||||||
|
}
|
||||||
|
} else if len(str) >= 10 {
|
||||||
|
str = str[:10]
|
||||||
|
t, err := time.ParseInLocation(formatDate, str, DefaultTimeLoc)
|
||||||
|
if err == nil {
|
||||||
|
ind.Set(reflect.ValueOf(t))
|
||||||
|
}
|
||||||
|
} else if len(str) >= 8 {
|
||||||
|
str = str[:8]
|
||||||
|
t, err := time.ParseInLocation(formatTime, str, DefaultTimeLoc)
|
||||||
|
if err == nil {
|
||||||
|
ind.Set(reflect.ValueOf(t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case sql.NullString, sql.NullInt64, sql.NullFloat64, sql.NullBool:
|
||||||
|
indi := reflect.New(ind.Type()).Interface()
|
||||||
|
sc, ok := indi.(sql.Scanner)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := sc.Scan(value)
|
||||||
|
if err == nil {
|
||||||
|
ind.Set(reflect.Indirect(reflect.ValueOf(sc)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
if value == nil {
|
||||||
|
ind.Set(reflect.Zero(ind.Type()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ind.Set(reflect.New(ind.Type().Elem()))
|
||||||
|
o.setFieldValue(reflect.Indirect(ind), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set field value in loop for slice container
|
||||||
|
func (o *rawSet) loopSetRefs(refs []interface{}, sInds []reflect.Value, nIndsPtr *[]reflect.Value, eTyps []reflect.Type, init bool) {
|
||||||
|
nInds := *nIndsPtr
|
||||||
|
|
||||||
|
cur := 0
|
||||||
|
for i := 0; i < len(sInds); i++ {
|
||||||
|
sInd := sInds[i]
|
||||||
|
eTyp := eTyps[i]
|
||||||
|
|
||||||
|
typ := eTyp
|
||||||
|
isPtr := false
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
isPtr = true
|
||||||
|
typ = typ.Elem()
|
||||||
|
}
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
isPtr = true
|
||||||
|
typ = typ.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
var nInd reflect.Value
|
||||||
|
if init {
|
||||||
|
nInd = reflect.New(sInd.Type()).Elem()
|
||||||
|
} else {
|
||||||
|
nInd = nInds[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
val := reflect.New(typ)
|
||||||
|
ind := val.Elem()
|
||||||
|
|
||||||
|
tpName := ind.Type().String()
|
||||||
|
|
||||||
|
if ind.Kind() == reflect.Struct {
|
||||||
|
if tpName == "time.Time" {
|
||||||
|
value := reflect.ValueOf(refs[cur]).Elem().Interface()
|
||||||
|
if isPtr && value == nil {
|
||||||
|
val = reflect.New(val.Type()).Elem()
|
||||||
|
} else {
|
||||||
|
o.setFieldValue(ind, value)
|
||||||
|
}
|
||||||
|
cur++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value := reflect.ValueOf(refs[cur]).Elem().Interface()
|
||||||
|
if isPtr && value == nil {
|
||||||
|
val = reflect.New(val.Type()).Elem()
|
||||||
|
} else {
|
||||||
|
o.setFieldValue(ind, value)
|
||||||
|
}
|
||||||
|
cur++
|
||||||
|
}
|
||||||
|
|
||||||
|
if nInd.Kind() == reflect.Slice {
|
||||||
|
if isPtr {
|
||||||
|
nInd = reflect.Append(nInd, val)
|
||||||
|
} else {
|
||||||
|
nInd = reflect.Append(nInd, ind)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if isPtr {
|
||||||
|
nInd.Set(val)
|
||||||
|
} else {
|
||||||
|
nInd.Set(ind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nInds[i] = nInd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// query data and map to container
|
||||||
|
func (o *rawSet) QueryRow(containers ...interface{}) error {
|
||||||
|
var (
|
||||||
|
refs = make([]interface{}, 0, len(containers))
|
||||||
|
sInds []reflect.Value
|
||||||
|
eTyps []reflect.Type
|
||||||
|
sMi *modelInfo
|
||||||
|
)
|
||||||
|
structMode := false
|
||||||
|
for _, container := range containers {
|
||||||
|
val := reflect.ValueOf(container)
|
||||||
|
ind := reflect.Indirect(val)
|
||||||
|
|
||||||
|
if val.Kind() != reflect.Ptr {
|
||||||
|
panic(fmt.Errorf("<RawSeter.QueryRow> all args must be use ptr"))
|
||||||
|
}
|
||||||
|
|
||||||
|
etyp := ind.Type()
|
||||||
|
typ := etyp
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
typ = typ.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
sInds = append(sInds, ind)
|
||||||
|
eTyps = append(eTyps, etyp)
|
||||||
|
|
||||||
|
if typ.Kind() == reflect.Struct && typ.String() != "time.Time" {
|
||||||
|
if len(containers) > 1 {
|
||||||
|
panic(fmt.Errorf("<RawSeter.QueryRow> now support one struct only. see #384"))
|
||||||
|
}
|
||||||
|
|
||||||
|
structMode = true
|
||||||
|
fn := getFullName(typ)
|
||||||
|
if mi, ok := defaultModelCache.getByFullName(fn); ok {
|
||||||
|
sMi = mi
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var ref interface{}
|
||||||
|
refs = append(refs, &ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query := o.query
|
||||||
|
o.orm.alias.DbBaser.ReplaceMarks(&query)
|
||||||
|
|
||||||
|
args := getFlatParams(nil, o.args, o.orm.alias.TZ)
|
||||||
|
rows, err := o.orm.db.Query(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return ErrNoRows
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
structTagMap := make(map[reflect.StructTag]map[string]string)
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
if rows.Next() {
|
||||||
|
if structMode {
|
||||||
|
columns, err := rows.Columns()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
columnsMp := make(map[string]interface{}, len(columns))
|
||||||
|
|
||||||
|
refs = make([]interface{}, 0, len(columns))
|
||||||
|
for _, col := range columns {
|
||||||
|
var ref interface{}
|
||||||
|
columnsMp[col] = &ref
|
||||||
|
refs = append(refs, &ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Scan(refs...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ind := sInds[0]
|
||||||
|
|
||||||
|
if ind.Kind() == reflect.Ptr {
|
||||||
|
if ind.IsNil() || !ind.IsValid() {
|
||||||
|
ind.Set(reflect.New(eTyps[0].Elem()))
|
||||||
|
}
|
||||||
|
ind = ind.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if sMi != nil {
|
||||||
|
for _, col := range columns {
|
||||||
|
if fi := sMi.fields.GetByColumn(col); fi != nil {
|
||||||
|
value := reflect.ValueOf(columnsMp[col]).Elem().Interface()
|
||||||
|
field := ind.FieldByIndex(fi.fieldIndex)
|
||||||
|
if fi.fieldType&IsRelField > 0 {
|
||||||
|
mf := reflect.New(fi.relModelInfo.addrField.Elem().Type())
|
||||||
|
field.Set(mf)
|
||||||
|
field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
|
||||||
|
}
|
||||||
|
if fi.isFielder {
|
||||||
|
fd := field.Addr().Interface().(Fielder)
|
||||||
|
err := fd.SetRaw(value)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Errorf("set raw error:%s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
o.setFieldValue(field, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// define recursive function
|
||||||
|
var recursiveSetField func(rv reflect.Value)
|
||||||
|
recursiveSetField = func(rv reflect.Value) {
|
||||||
|
for i := 0; i < rv.NumField(); i++ {
|
||||||
|
f := rv.Field(i)
|
||||||
|
fe := rv.Type().Field(i)
|
||||||
|
|
||||||
|
// check if the field is a Struct
|
||||||
|
// recursive the Struct type
|
||||||
|
if fe.Type.Kind() == reflect.Struct {
|
||||||
|
recursiveSetField(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// thanks @Gazeboxu.
|
||||||
|
tags := structTagMap[fe.Tag]
|
||||||
|
if tags == nil {
|
||||||
|
_, tags = parseStructTag(fe.Tag.Get(defaultStructTagName))
|
||||||
|
structTagMap[fe.Tag] = tags
|
||||||
|
}
|
||||||
|
var col string
|
||||||
|
if col = tags["column"]; col == "" {
|
||||||
|
col = nameStrategyMap[nameStrategy](fe.Name)
|
||||||
|
}
|
||||||
|
if v, ok := columnsMp[col]; ok {
|
||||||
|
value := reflect.ValueOf(v).Elem().Interface()
|
||||||
|
o.setFieldValue(f, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// init call the recursive function
|
||||||
|
recursiveSetField(ind)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if err := rows.Scan(refs...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nInds := make([]reflect.Value, len(sInds))
|
||||||
|
o.loopSetRefs(refs, sInds, &nInds, eTyps, true)
|
||||||
|
for i, sInd := range sInds {
|
||||||
|
nInd := nInds[i]
|
||||||
|
sInd.Set(nInd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ErrNoRows
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// query data rows and map to container
|
||||||
|
func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) {
|
||||||
|
var (
|
||||||
|
refs = make([]interface{}, 0, len(containers))
|
||||||
|
sInds []reflect.Value
|
||||||
|
eTyps []reflect.Type
|
||||||
|
sMi *modelInfo
|
||||||
|
)
|
||||||
|
structMode := false
|
||||||
|
for _, container := range containers {
|
||||||
|
val := reflect.ValueOf(container)
|
||||||
|
sInd := reflect.Indirect(val)
|
||||||
|
if val.Kind() != reflect.Ptr || sInd.Kind() != reflect.Slice {
|
||||||
|
panic(fmt.Errorf("<RawSeter.QueryRows> all args must be use ptr slice"))
|
||||||
|
}
|
||||||
|
|
||||||
|
etyp := sInd.Type().Elem()
|
||||||
|
typ := etyp
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
typ = typ.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
sInds = append(sInds, sInd)
|
||||||
|
eTyps = append(eTyps, etyp)
|
||||||
|
|
||||||
|
if typ.Kind() == reflect.Struct && typ.String() != "time.Time" {
|
||||||
|
if len(containers) > 1 {
|
||||||
|
panic(fmt.Errorf("<RawSeter.QueryRow> now support one struct only. see #384"))
|
||||||
|
}
|
||||||
|
|
||||||
|
structMode = true
|
||||||
|
fn := getFullName(typ)
|
||||||
|
if mi, ok := defaultModelCache.getByFullName(fn); ok {
|
||||||
|
sMi = mi
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var ref interface{}
|
||||||
|
refs = append(refs, &ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query := o.query
|
||||||
|
o.orm.alias.DbBaser.ReplaceMarks(&query)
|
||||||
|
|
||||||
|
args := getFlatParams(nil, o.args, o.orm.alias.TZ)
|
||||||
|
rows, err := o.orm.db.Query(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var cnt int64
|
||||||
|
nInds := make([]reflect.Value, len(sInds))
|
||||||
|
sInd := sInds[0]
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
|
||||||
|
if structMode {
|
||||||
|
columns, err := rows.Columns()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
columnsMp := make(map[string]interface{}, len(columns))
|
||||||
|
|
||||||
|
refs = make([]interface{}, 0, len(columns))
|
||||||
|
for _, col := range columns {
|
||||||
|
var ref interface{}
|
||||||
|
columnsMp[col] = &ref
|
||||||
|
refs = append(refs, &ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Scan(refs...); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cnt == 0 && !sInd.IsNil() {
|
||||||
|
sInd.Set(reflect.New(sInd.Type()).Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
var ind reflect.Value
|
||||||
|
if eTyps[0].Kind() == reflect.Ptr {
|
||||||
|
ind = reflect.New(eTyps[0].Elem())
|
||||||
|
} else {
|
||||||
|
ind = reflect.New(eTyps[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if ind.Kind() == reflect.Ptr {
|
||||||
|
ind = ind.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if sMi != nil {
|
||||||
|
for _, col := range columns {
|
||||||
|
if fi := sMi.fields.GetByColumn(col); fi != nil {
|
||||||
|
value := reflect.ValueOf(columnsMp[col]).Elem().Interface()
|
||||||
|
field := ind.FieldByIndex(fi.fieldIndex)
|
||||||
|
if fi.fieldType&IsRelField > 0 {
|
||||||
|
mf := reflect.New(fi.relModelInfo.addrField.Elem().Type())
|
||||||
|
field.Set(mf)
|
||||||
|
field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
|
||||||
|
}
|
||||||
|
if fi.isFielder {
|
||||||
|
fd := field.Addr().Interface().(Fielder)
|
||||||
|
err := fd.SetRaw(value)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Errorf("set raw error:%s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
o.setFieldValue(field, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// define recursive function
|
||||||
|
var recursiveSetField func(rv reflect.Value)
|
||||||
|
recursiveSetField = func(rv reflect.Value) {
|
||||||
|
for i := 0; i < rv.NumField(); i++ {
|
||||||
|
f := rv.Field(i)
|
||||||
|
fe := rv.Type().Field(i)
|
||||||
|
|
||||||
|
// check if the field is a Struct
|
||||||
|
// recursive the Struct type
|
||||||
|
if fe.Type.Kind() == reflect.Struct {
|
||||||
|
recursiveSetField(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, tags := parseStructTag(fe.Tag.Get(defaultStructTagName))
|
||||||
|
var col string
|
||||||
|
if col = tags["column"]; col == "" {
|
||||||
|
col = nameStrategyMap[nameStrategy](fe.Name)
|
||||||
|
}
|
||||||
|
if v, ok := columnsMp[col]; ok {
|
||||||
|
value := reflect.ValueOf(v).Elem().Interface()
|
||||||
|
o.setFieldValue(f, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// init call the recursive function
|
||||||
|
recursiveSetField(ind)
|
||||||
|
}
|
||||||
|
|
||||||
|
if eTyps[0].Kind() == reflect.Ptr {
|
||||||
|
ind = ind.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
sInd = reflect.Append(sInd, ind)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if err := rows.Scan(refs...); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
o.loopSetRefs(refs, sInds, &nInds, eTyps, cnt == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt++
|
||||||
|
}
|
||||||
|
|
||||||
|
if cnt > 0 {
|
||||||
|
if structMode {
|
||||||
|
sInds[0].Set(sInd)
|
||||||
|
} else {
|
||||||
|
for i, sInd := range sInds {
|
||||||
|
nInd := nInds[i]
|
||||||
|
sInd.Set(nInd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *rawSet) readValues(container interface{}, needCols []string) (int64, error) {
|
||||||
|
var (
|
||||||
|
maps []Params
|
||||||
|
lists []ParamsList
|
||||||
|
list ParamsList
|
||||||
|
)
|
||||||
|
|
||||||
|
typ := 0
|
||||||
|
switch container.(type) {
|
||||||
|
case *[]Params:
|
||||||
|
typ = 1
|
||||||
|
case *[]ParamsList:
|
||||||
|
typ = 2
|
||||||
|
case *ParamsList:
|
||||||
|
typ = 3
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("<RawSeter> unsupport read values type `%T`", container))
|
||||||
|
}
|
||||||
|
|
||||||
|
query := o.query
|
||||||
|
o.orm.alias.DbBaser.ReplaceMarks(&query)
|
||||||
|
|
||||||
|
args := getFlatParams(nil, o.args, o.orm.alias.TZ)
|
||||||
|
|
||||||
|
var rs *sql.Rows
|
||||||
|
rs, err := o.orm.db.Query(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer rs.Close()
|
||||||
|
|
||||||
|
var (
|
||||||
|
refs []interface{}
|
||||||
|
cnt int64
|
||||||
|
cols []string
|
||||||
|
indexs []int
|
||||||
|
)
|
||||||
|
|
||||||
|
for rs.Next() {
|
||||||
|
if cnt == 0 {
|
||||||
|
columns, err := rs.Columns()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len(needCols) > 0 {
|
||||||
|
indexs = make([]int, 0, len(needCols))
|
||||||
|
} else {
|
||||||
|
indexs = make([]int, 0, len(columns))
|
||||||
|
}
|
||||||
|
|
||||||
|
cols = columns
|
||||||
|
refs = make([]interface{}, len(cols))
|
||||||
|
for i := range refs {
|
||||||
|
var ref sql.NullString
|
||||||
|
refs[i] = &ref
|
||||||
|
|
||||||
|
if len(needCols) > 0 {
|
||||||
|
for _, c := range needCols {
|
||||||
|
if c == cols[i] {
|
||||||
|
indexs = append(indexs, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
indexs = append(indexs, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rs.Scan(refs...); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch typ {
|
||||||
|
case 1:
|
||||||
|
params := make(Params, len(cols))
|
||||||
|
for _, i := range indexs {
|
||||||
|
ref := refs[i]
|
||||||
|
value := reflect.Indirect(reflect.ValueOf(ref)).Interface().(sql.NullString)
|
||||||
|
if value.Valid {
|
||||||
|
params[cols[i]] = value.String
|
||||||
|
} else {
|
||||||
|
params[cols[i]] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maps = append(maps, params)
|
||||||
|
case 2:
|
||||||
|
params := make(ParamsList, 0, len(cols))
|
||||||
|
for _, i := range indexs {
|
||||||
|
ref := refs[i]
|
||||||
|
value := reflect.Indirect(reflect.ValueOf(ref)).Interface().(sql.NullString)
|
||||||
|
if value.Valid {
|
||||||
|
params = append(params, value.String)
|
||||||
|
} else {
|
||||||
|
params = append(params, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lists = append(lists, params)
|
||||||
|
case 3:
|
||||||
|
for _, i := range indexs {
|
||||||
|
ref := refs[i]
|
||||||
|
value := reflect.Indirect(reflect.ValueOf(ref)).Interface().(sql.NullString)
|
||||||
|
if value.Valid {
|
||||||
|
list = append(list, value.String)
|
||||||
|
} else {
|
||||||
|
list = append(list, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt++
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := container.(type) {
|
||||||
|
case *[]Params:
|
||||||
|
*v = maps
|
||||||
|
case *[]ParamsList:
|
||||||
|
*v = lists
|
||||||
|
case *ParamsList:
|
||||||
|
*v = list
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *rawSet) queryRowsTo(container interface{}, keyCol, valueCol string) (int64, error) {
|
||||||
|
var (
|
||||||
|
maps Params
|
||||||
|
ind *reflect.Value
|
||||||
|
)
|
||||||
|
|
||||||
|
var typ int
|
||||||
|
switch container.(type) {
|
||||||
|
case *Params:
|
||||||
|
typ = 1
|
||||||
|
default:
|
||||||
|
typ = 2
|
||||||
|
vl := reflect.ValueOf(container)
|
||||||
|
id := reflect.Indirect(vl)
|
||||||
|
if vl.Kind() != reflect.Ptr || id.Kind() != reflect.Struct {
|
||||||
|
panic(fmt.Errorf("<RawSeter> RowsTo unsupport type `%T` need ptr struct", container))
|
||||||
|
}
|
||||||
|
|
||||||
|
ind = &id
|
||||||
|
}
|
||||||
|
|
||||||
|
query := o.query
|
||||||
|
o.orm.alias.DbBaser.ReplaceMarks(&query)
|
||||||
|
|
||||||
|
args := getFlatParams(nil, o.args, o.orm.alias.TZ)
|
||||||
|
|
||||||
|
rs, err := o.orm.db.Query(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer rs.Close()
|
||||||
|
|
||||||
|
var (
|
||||||
|
refs []interface{}
|
||||||
|
cnt int64
|
||||||
|
cols []string
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
keyIndex = -1
|
||||||
|
valueIndex = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
for rs.Next() {
|
||||||
|
if cnt == 0 {
|
||||||
|
columns, err := rs.Columns()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
cols = columns
|
||||||
|
refs = make([]interface{}, len(cols))
|
||||||
|
for i := range refs {
|
||||||
|
if keyCol == cols[i] {
|
||||||
|
keyIndex = i
|
||||||
|
}
|
||||||
|
if typ == 1 || keyIndex == i {
|
||||||
|
var ref sql.NullString
|
||||||
|
refs[i] = &ref
|
||||||
|
} else {
|
||||||
|
var ref interface{}
|
||||||
|
refs[i] = &ref
|
||||||
|
}
|
||||||
|
if valueCol == cols[i] {
|
||||||
|
valueIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if keyIndex == -1 || valueIndex == -1 {
|
||||||
|
panic(fmt.Errorf("<RawSeter> RowsTo unknown key, value column name `%s: %s`", keyCol, valueCol))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rs.Scan(refs...); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cnt == 0 {
|
||||||
|
switch typ {
|
||||||
|
case 1:
|
||||||
|
maps = make(Params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
key := reflect.Indirect(reflect.ValueOf(refs[keyIndex])).Interface().(sql.NullString).String
|
||||||
|
|
||||||
|
switch typ {
|
||||||
|
case 1:
|
||||||
|
value := reflect.Indirect(reflect.ValueOf(refs[valueIndex])).Interface().(sql.NullString)
|
||||||
|
if value.Valid {
|
||||||
|
maps[key] = value.String
|
||||||
|
} else {
|
||||||
|
maps[key] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
if id := ind.FieldByName(camelString(key)); id.IsValid() {
|
||||||
|
o.setFieldValue(id, reflect.ValueOf(refs[valueIndex]).Elem().Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt++
|
||||||
|
}
|
||||||
|
|
||||||
|
if typ == 1 {
|
||||||
|
v, _ := container.(*Params)
|
||||||
|
*v = maps
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// query data to []map[string]interface
|
||||||
|
func (o *rawSet) Values(container *[]Params, cols ...string) (int64, error) {
|
||||||
|
return o.readValues(container, cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
// query data to [][]interface
|
||||||
|
func (o *rawSet) ValuesList(container *[]ParamsList, cols ...string) (int64, error) {
|
||||||
|
return o.readValues(container, cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
// query data to []interface
|
||||||
|
func (o *rawSet) ValuesFlat(container *ParamsList, cols ...string) (int64, error) {
|
||||||
|
return o.readValues(container, cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
// query all rows into map[string]interface with specify key and value column name.
|
||||||
|
// keyCol = "name", valueCol = "value"
|
||||||
|
// table data
|
||||||
|
// name | value
|
||||||
|
// total | 100
|
||||||
|
// found | 200
|
||||||
|
// to map[string]interface{}{
|
||||||
|
// "total": 100,
|
||||||
|
// "found": 200,
|
||||||
|
// }
|
||||||
|
func (o *rawSet) RowsToMap(result *Params, keyCol, valueCol string) (int64, error) {
|
||||||
|
return o.queryRowsTo(result, keyCol, valueCol)
|
||||||
|
}
|
||||||
|
|
||||||
|
// query all rows into struct with specify key and value column name.
|
||||||
|
// keyCol = "name", valueCol = "value"
|
||||||
|
// table data
|
||||||
|
// name | value
|
||||||
|
// total | 100
|
||||||
|
// found | 200
|
||||||
|
// to struct {
|
||||||
|
// Total int
|
||||||
|
// Found int
|
||||||
|
// }
|
||||||
|
func (o *rawSet) RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error) {
|
||||||
|
return o.queryRowsTo(ptrStruct, keyCol, valueCol)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return prepared raw statement for used in times.
|
||||||
|
func (o *rawSet) Prepare() (RawPreparer, error) {
|
||||||
|
return newRawPreparer(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRawSet(orm *ormBase, query string, args []interface{}) RawSeter {
|
||||||
|
o := new(rawSet)
|
||||||
|
o.query = query
|
||||||
|
o.args = args
|
||||||
|
o.orm = orm
|
||||||
|
return o
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// QueryBuilder is the Query builder interface
|
||||||
|
type QueryBuilder interface {
|
||||||
|
Select(fields ...string) QueryBuilder
|
||||||
|
ForUpdate() QueryBuilder
|
||||||
|
From(tables ...string) QueryBuilder
|
||||||
|
InnerJoin(table string) QueryBuilder
|
||||||
|
LeftJoin(table string) QueryBuilder
|
||||||
|
RightJoin(table string) QueryBuilder
|
||||||
|
On(cond string) QueryBuilder
|
||||||
|
Where(cond string) QueryBuilder
|
||||||
|
And(cond string) QueryBuilder
|
||||||
|
Or(cond string) QueryBuilder
|
||||||
|
In(vals ...string) QueryBuilder
|
||||||
|
OrderBy(fields ...string) QueryBuilder
|
||||||
|
Asc() QueryBuilder
|
||||||
|
Desc() QueryBuilder
|
||||||
|
Limit(limit int) QueryBuilder
|
||||||
|
Offset(offset int) QueryBuilder
|
||||||
|
GroupBy(fields ...string) QueryBuilder
|
||||||
|
Having(cond string) QueryBuilder
|
||||||
|
Update(tables ...string) QueryBuilder
|
||||||
|
Set(kv ...string) QueryBuilder
|
||||||
|
Delete(tables ...string) QueryBuilder
|
||||||
|
InsertInto(table string, fields ...string) QueryBuilder
|
||||||
|
Values(vals ...string) QueryBuilder
|
||||||
|
Subquery(sub string, alias string) string
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQueryBuilder return the QueryBuilder
|
||||||
|
func NewQueryBuilder(driver string) (qb QueryBuilder, err error) {
|
||||||
|
if driver == "mysql" {
|
||||||
|
qb = new(MySQLQueryBuilder)
|
||||||
|
} else if driver == "tidb" {
|
||||||
|
qb = new(TiDBQueryBuilder)
|
||||||
|
} else if driver == "postgres" {
|
||||||
|
qb = new(PostgresQueryBuilder)
|
||||||
|
} else if driver == "sqlite" {
|
||||||
|
err = errors.New("sqlite query builder is not supported yet")
|
||||||
|
} else {
|
||||||
|
err = errors.New("unknown driver for query builder")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,187 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommaSpace is the separation
|
||||||
|
const CommaSpace = ", "
|
||||||
|
|
||||||
|
// MySQLQueryBuilder is the SQL build
|
||||||
|
type MySQLQueryBuilder struct {
|
||||||
|
tokens []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select will join the fields
|
||||||
|
func (qb *MySQLQueryBuilder) Select(fields ...string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "SELECT", strings.Join(fields, CommaSpace))
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForUpdate add the FOR UPDATE clause
|
||||||
|
func (qb *MySQLQueryBuilder) ForUpdate() QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "FOR UPDATE")
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// From join the tables
|
||||||
|
func (qb *MySQLQueryBuilder) From(tables ...string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "FROM", strings.Join(tables, CommaSpace))
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// InnerJoin INNER JOIN the table
|
||||||
|
func (qb *MySQLQueryBuilder) InnerJoin(table string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "INNER JOIN", table)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeftJoin LEFT JOIN the table
|
||||||
|
func (qb *MySQLQueryBuilder) LeftJoin(table string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "LEFT JOIN", table)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// RightJoin RIGHT JOIN the table
|
||||||
|
func (qb *MySQLQueryBuilder) RightJoin(table string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "RIGHT JOIN", table)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// On join with on cond
|
||||||
|
func (qb *MySQLQueryBuilder) On(cond string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "ON", cond)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where join the Where cond
|
||||||
|
func (qb *MySQLQueryBuilder) Where(cond string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "WHERE", cond)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// And join the and cond
|
||||||
|
func (qb *MySQLQueryBuilder) And(cond string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "AND", cond)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or join the or cond
|
||||||
|
func (qb *MySQLQueryBuilder) Or(cond string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "OR", cond)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// In join the IN (vals)
|
||||||
|
func (qb *MySQLQueryBuilder) In(vals ...string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "IN", "(", strings.Join(vals, CommaSpace), ")")
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrderBy join the Order by fields
|
||||||
|
func (qb *MySQLQueryBuilder) OrderBy(fields ...string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "ORDER BY", strings.Join(fields, CommaSpace))
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asc join the asc
|
||||||
|
func (qb *MySQLQueryBuilder) Asc() QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "ASC")
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desc join the desc
|
||||||
|
func (qb *MySQLQueryBuilder) Desc() QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "DESC")
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit join the limit num
|
||||||
|
func (qb *MySQLQueryBuilder) Limit(limit int) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "LIMIT", strconv.Itoa(limit))
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset join the offset num
|
||||||
|
func (qb *MySQLQueryBuilder) Offset(offset int) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "OFFSET", strconv.Itoa(offset))
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupBy join the Group by fields
|
||||||
|
func (qb *MySQLQueryBuilder) GroupBy(fields ...string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "GROUP BY", strings.Join(fields, CommaSpace))
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Having join the Having cond
|
||||||
|
func (qb *MySQLQueryBuilder) Having(cond string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "HAVING", cond)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update join the update table
|
||||||
|
func (qb *MySQLQueryBuilder) Update(tables ...string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "UPDATE", strings.Join(tables, CommaSpace))
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set join the set kv
|
||||||
|
func (qb *MySQLQueryBuilder) Set(kv ...string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "SET", strings.Join(kv, CommaSpace))
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete join the Delete tables
|
||||||
|
func (qb *MySQLQueryBuilder) Delete(tables ...string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "DELETE")
|
||||||
|
if len(tables) != 0 {
|
||||||
|
qb.tokens = append(qb.tokens, strings.Join(tables, CommaSpace))
|
||||||
|
}
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertInto join the insert SQL
|
||||||
|
func (qb *MySQLQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "INSERT INTO", table)
|
||||||
|
if len(fields) != 0 {
|
||||||
|
fieldsStr := strings.Join(fields, CommaSpace)
|
||||||
|
qb.tokens = append(qb.tokens, "(", fieldsStr, ")")
|
||||||
|
}
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values join the Values(vals)
|
||||||
|
func (qb *MySQLQueryBuilder) Values(vals ...string) QueryBuilder {
|
||||||
|
valsStr := strings.Join(vals, CommaSpace)
|
||||||
|
qb.tokens = append(qb.tokens, "VALUES", "(", valsStr, ")")
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subquery join the sub as alias
|
||||||
|
func (qb *MySQLQueryBuilder) Subquery(sub string, alias string) string {
|
||||||
|
return fmt.Sprintf("(%s) AS %s", sub, alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String join all tokens
|
||||||
|
func (qb *MySQLQueryBuilder) String() string {
|
||||||
|
s := strings.Join(qb.tokens, " ")
|
||||||
|
qb.tokens = qb.tokens[:0]
|
||||||
|
return s
|
||||||
|
}
|
@ -0,0 +1,219 @@
|
|||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var quote string = `"`
|
||||||
|
|
||||||
|
// PostgresQueryBuilder is the SQL build
|
||||||
|
type PostgresQueryBuilder struct {
|
||||||
|
tokens []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func processingStr(str []string) string {
|
||||||
|
s := strings.Join(str, `","`)
|
||||||
|
s = fmt.Sprintf("%s%s%s", quote, s, quote)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select will join the fields
|
||||||
|
func (qb *PostgresQueryBuilder) Select(fields ...string) QueryBuilder {
|
||||||
|
var str string
|
||||||
|
n := len(fields)
|
||||||
|
|
||||||
|
if fields[0] == "*" {
|
||||||
|
str = "*"
|
||||||
|
} else {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
sli := strings.Split(fields[i], ".")
|
||||||
|
s := strings.Join(sli, `"."`)
|
||||||
|
s = fmt.Sprintf("%s%s%s", quote, s, quote)
|
||||||
|
if n == 1 || i == n-1 {
|
||||||
|
str += s
|
||||||
|
} else {
|
||||||
|
str += s + ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qb.tokens = append(qb.tokens, "SELECT", str)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForUpdate add the FOR UPDATE clause
|
||||||
|
func (qb *PostgresQueryBuilder) ForUpdate() QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "FOR UPDATE")
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// From join the tables
|
||||||
|
func (qb *PostgresQueryBuilder) From(tables ...string) QueryBuilder {
|
||||||
|
str := processingStr(tables)
|
||||||
|
qb.tokens = append(qb.tokens, "FROM", str)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// InnerJoin INNER JOIN the table
|
||||||
|
func (qb *PostgresQueryBuilder) InnerJoin(table string) QueryBuilder {
|
||||||
|
str := fmt.Sprintf("%s%s%s", quote, table, quote)
|
||||||
|
qb.tokens = append(qb.tokens, "INNER JOIN", str)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeftJoin LEFT JOIN the table
|
||||||
|
func (qb *PostgresQueryBuilder) LeftJoin(table string) QueryBuilder {
|
||||||
|
str := fmt.Sprintf("%s%s%s", quote, table, quote)
|
||||||
|
qb.tokens = append(qb.tokens, "LEFT JOIN", str)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// RightJoin RIGHT JOIN the table
|
||||||
|
func (qb *PostgresQueryBuilder) RightJoin(table string) QueryBuilder {
|
||||||
|
str := fmt.Sprintf("%s%s%s", quote, table, quote)
|
||||||
|
qb.tokens = append(qb.tokens, "RIGHT JOIN", str)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// On join with on cond
|
||||||
|
func (qb *PostgresQueryBuilder) On(cond string) QueryBuilder {
|
||||||
|
var str string
|
||||||
|
cond = strings.Replace(cond, " ", "", -1)
|
||||||
|
slice := strings.Split(cond, "=")
|
||||||
|
for i := 0; i < len(slice); i++ {
|
||||||
|
sli := strings.Split(slice[i], ".")
|
||||||
|
s := strings.Join(sli, `"."`)
|
||||||
|
s = fmt.Sprintf("%s%s%s", quote, s, quote)
|
||||||
|
if i == 0 {
|
||||||
|
str = s + " =" + " "
|
||||||
|
} else {
|
||||||
|
str += s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qb.tokens = append(qb.tokens, "ON", str)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where join the Where cond
|
||||||
|
func (qb *PostgresQueryBuilder) Where(cond string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "WHERE", cond)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// And join the and cond
|
||||||
|
func (qb *PostgresQueryBuilder) And(cond string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "AND", cond)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or join the or cond
|
||||||
|
func (qb *PostgresQueryBuilder) Or(cond string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "OR", cond)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// In join the IN (vals)
|
||||||
|
func (qb *PostgresQueryBuilder) In(vals ...string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "IN", "(", strings.Join(vals, CommaSpace), ")")
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrderBy join the Order by fields
|
||||||
|
func (qb *PostgresQueryBuilder) OrderBy(fields ...string) QueryBuilder {
|
||||||
|
str := processingStr(fields)
|
||||||
|
qb.tokens = append(qb.tokens, "ORDER BY", str)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asc join the asc
|
||||||
|
func (qb *PostgresQueryBuilder) Asc() QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "ASC")
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desc join the desc
|
||||||
|
func (qb *PostgresQueryBuilder) Desc() QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "DESC")
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit join the limit num
|
||||||
|
func (qb *PostgresQueryBuilder) Limit(limit int) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "LIMIT", strconv.Itoa(limit))
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset join the offset num
|
||||||
|
func (qb *PostgresQueryBuilder) Offset(offset int) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "OFFSET", strconv.Itoa(offset))
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupBy join the Group by fields
|
||||||
|
func (qb *PostgresQueryBuilder) GroupBy(fields ...string) QueryBuilder {
|
||||||
|
str := processingStr(fields)
|
||||||
|
qb.tokens = append(qb.tokens, "GROUP BY", str)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Having join the Having cond
|
||||||
|
func (qb *PostgresQueryBuilder) Having(cond string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "HAVING", cond)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update join the update table
|
||||||
|
func (qb *PostgresQueryBuilder) Update(tables ...string) QueryBuilder {
|
||||||
|
str := processingStr(tables)
|
||||||
|
qb.tokens = append(qb.tokens, "UPDATE", str)
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set join the set kv
|
||||||
|
func (qb *PostgresQueryBuilder) Set(kv ...string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "SET", strings.Join(kv, CommaSpace))
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete join the Delete tables
|
||||||
|
func (qb *PostgresQueryBuilder) Delete(tables ...string) QueryBuilder {
|
||||||
|
qb.tokens = append(qb.tokens, "DELETE")
|
||||||
|
if len(tables) != 0 {
|
||||||
|
str := processingStr(tables)
|
||||||
|
qb.tokens = append(qb.tokens, str)
|
||||||
|
}
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertInto join the insert SQL
|
||||||
|
func (qb *PostgresQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder {
|
||||||
|
str := fmt.Sprintf("%s%s%s", quote, table, quote)
|
||||||
|
qb.tokens = append(qb.tokens, "INSERT INTO", str)
|
||||||
|
if len(fields) != 0 {
|
||||||
|
fieldsStr := strings.Join(fields, CommaSpace)
|
||||||
|
qb.tokens = append(qb.tokens, "(", fieldsStr, ")")
|
||||||
|
}
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values join the Values(vals)
|
||||||
|
func (qb *PostgresQueryBuilder) Values(vals ...string) QueryBuilder {
|
||||||
|
valsStr := strings.Join(vals, CommaSpace)
|
||||||
|
qb.tokens = append(qb.tokens, "VALUES", "(", valsStr, ")")
|
||||||
|
return qb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subquery join the sub as alias
|
||||||
|
func (qb *PostgresQueryBuilder) Subquery(sub string, alias string) string {
|
||||||
|
return fmt.Sprintf("(%s) AS %s", sub, alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String join all tokens
|
||||||
|
func (qb *PostgresQueryBuilder) String() string {
|
||||||
|
s := strings.Join(qb.tokens, " ")
|
||||||
|
qb.tokens = qb.tokens[:0]
|
||||||
|
return s
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2015 TiDB Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
// TiDBQueryBuilder is the SQL build
|
||||||
|
type TiDBQueryBuilder struct {
|
||||||
|
MySQLQueryBuilder
|
||||||
|
tokens []string
|
||||||
|
}
|
@ -0,0 +1,658 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beego/beego/v2/client/orm/clauses/order_clause"
|
||||||
|
"github.com/beego/beego/v2/core/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TableNaming is usually used by model
|
||||||
|
// when you custom your table name, please implement this interfaces
|
||||||
|
// for example:
|
||||||
|
// type User struct {
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// func (u *User) TableName() string {
|
||||||
|
// return "USER_TABLE"
|
||||||
|
// }
|
||||||
|
type TableNameI interface {
|
||||||
|
TableName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableEngineI is usually used by model
|
||||||
|
// when you want to use specific engine, like myisam, you can implement this interface
|
||||||
|
// for example:
|
||||||
|
// type User struct {
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// func (u *User) TableEngine() string {
|
||||||
|
// return "myisam"
|
||||||
|
// }
|
||||||
|
type TableEngineI interface {
|
||||||
|
TableEngine() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableIndexI is usually used by model
|
||||||
|
// when you want to create indexes, you can implement this interface
|
||||||
|
// for example:
|
||||||
|
// type User struct {
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// func (u *User) TableIndex() [][]string {
|
||||||
|
// return [][]string{{"Name"}}
|
||||||
|
// }
|
||||||
|
type TableIndexI interface {
|
||||||
|
TableIndex() [][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableUniqueI is usually used by model
|
||||||
|
// when you want to create unique indexes, you can implement this interface
|
||||||
|
// for example:
|
||||||
|
// type User struct {
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// func (u *User) TableUnique() [][]string {
|
||||||
|
// return [][]string{{"Email"}}
|
||||||
|
// }
|
||||||
|
type TableUniqueI interface {
|
||||||
|
TableUnique() [][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsApplicableTableForDB if return false, we won't create table to this db
|
||||||
|
type IsApplicableTableForDB interface {
|
||||||
|
IsApplicableTableForDB(db string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driver define database driver
|
||||||
|
type Driver interface {
|
||||||
|
Name() string
|
||||||
|
Type() DriverType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fielder define field info
|
||||||
|
type Fielder interface {
|
||||||
|
String() string
|
||||||
|
FieldType() int
|
||||||
|
SetRaw(interface{}) error
|
||||||
|
RawValue() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TxBeginner interface {
|
||||||
|
// self control transaction
|
||||||
|
Begin() (TxOrmer, error)
|
||||||
|
BeginWithCtx(ctx context.Context) (TxOrmer, error)
|
||||||
|
BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error)
|
||||||
|
BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error)
|
||||||
|
|
||||||
|
// closure control transaction
|
||||||
|
DoTx(task func(ctx context.Context, txOrm TxOrmer) error) error
|
||||||
|
DoTxWithCtx(ctx context.Context, task func(ctx context.Context, txOrm TxOrmer) error) error
|
||||||
|
DoTxWithOpts(opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error
|
||||||
|
DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(ctx context.Context, txOrm TxOrmer) error) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type TxCommitter interface {
|
||||||
|
txEnder
|
||||||
|
}
|
||||||
|
|
||||||
|
// transaction beginner
|
||||||
|
type txer interface {
|
||||||
|
Begin() (*sql.Tx, error)
|
||||||
|
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// transaction ending
|
||||||
|
type txEnder interface {
|
||||||
|
Commit() error
|
||||||
|
Rollback() error
|
||||||
|
|
||||||
|
// RollbackUnlessCommit if the transaction has been committed, do nothing, or transaction will be rollback
|
||||||
|
// For example:
|
||||||
|
// ```go
|
||||||
|
// txOrm := orm.Begin()
|
||||||
|
// defer txOrm.RollbackUnlessCommit()
|
||||||
|
// err := txOrm.Insert() // do something
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// txOrm.Commit()
|
||||||
|
// ```
|
||||||
|
RollbackUnlessCommit() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data Manipulation Language
|
||||||
|
type DML interface {
|
||||||
|
// insert model data to database
|
||||||
|
// for example:
|
||||||
|
// user := new(User)
|
||||||
|
// id, err = Ormer.Insert(user)
|
||||||
|
// user must be a pointer and Insert will set user's pk field
|
||||||
|
Insert(md interface{}) (int64, error)
|
||||||
|
InsertWithCtx(ctx context.Context, md interface{}) (int64, error)
|
||||||
|
// mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value")
|
||||||
|
// if colu type is integer : can use(+-*/), string : convert(colu,"value")
|
||||||
|
// postgres: InsertOrUpdate(model,"conflictColumnName") or InsertOrUpdate(model,"conflictColumnName","colu=colu+value")
|
||||||
|
// if colu type is integer : can use(+-*/), string : colu || "value"
|
||||||
|
InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error)
|
||||||
|
InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error)
|
||||||
|
// insert some models to database
|
||||||
|
InsertMulti(bulk int, mds interface{}) (int64, error)
|
||||||
|
InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error)
|
||||||
|
// update model to database.
|
||||||
|
// cols set the columns those want to update.
|
||||||
|
// find model by Id(pk) field and update columns specified by fields, if cols is null then update all columns
|
||||||
|
// for example:
|
||||||
|
// user := User{Id: 2}
|
||||||
|
// user.Langs = append(user.Langs, "zh-CN", "en-US")
|
||||||
|
// user.Extra.Name = "beego"
|
||||||
|
// user.Extra.Data = "orm"
|
||||||
|
// num, err = Ormer.Update(&user, "Langs", "Extra")
|
||||||
|
Update(md interface{}, cols ...string) (int64, error)
|
||||||
|
UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error)
|
||||||
|
// delete model in database
|
||||||
|
Delete(md interface{}, cols ...string) (int64, error)
|
||||||
|
DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error)
|
||||||
|
|
||||||
|
// return a raw query seter for raw sql string.
|
||||||
|
// for example:
|
||||||
|
// ormer.Raw("UPDATE `user` SET `user_name` = ? WHERE `user_name` = ?", "slene", "testing").Exec()
|
||||||
|
// // update user testing's name to slene
|
||||||
|
Raw(query string, args ...interface{}) RawSeter
|
||||||
|
RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data Query Language
|
||||||
|
type DQL interface {
|
||||||
|
// read data to model
|
||||||
|
// for example:
|
||||||
|
// this will find User by Id field
|
||||||
|
// u = &User{Id: user.Id}
|
||||||
|
// err = Ormer.Read(u)
|
||||||
|
// this will find User by UserName field
|
||||||
|
// u = &User{UserName: "astaxie", Password: "pass"}
|
||||||
|
// err = Ormer.Read(u, "UserName")
|
||||||
|
Read(md interface{}, cols ...string) error
|
||||||
|
ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error
|
||||||
|
|
||||||
|
// Like Read(), but with "FOR UPDATE" clause, useful in transaction.
|
||||||
|
// Some databases are not support this feature.
|
||||||
|
ReadForUpdate(md interface{}, cols ...string) error
|
||||||
|
ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error
|
||||||
|
|
||||||
|
// Try to read a row from the database, or insert one if it doesn't exist
|
||||||
|
ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error)
|
||||||
|
ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error)
|
||||||
|
|
||||||
|
// load related models to md model.
|
||||||
|
// args are limit, offset int and order string.
|
||||||
|
//
|
||||||
|
// example:
|
||||||
|
// Ormer.LoadRelated(post,"Tags")
|
||||||
|
// for _,tag := range post.Tags{...}
|
||||||
|
// hints.DefaultRelDepth useDefaultRelsDepth ; or depth 0
|
||||||
|
// hints.RelDepth loadRelationDepth
|
||||||
|
// hints.Limit limit default limit 1000
|
||||||
|
// hints.Offset int offset default offset 0
|
||||||
|
// hints.OrderBy string order for example : "-Id"
|
||||||
|
// make sure the relation is defined in model struct tags.
|
||||||
|
LoadRelated(md interface{}, name string, args ...utils.KV) (int64, error)
|
||||||
|
LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...utils.KV) (int64, error)
|
||||||
|
|
||||||
|
// create a models to models queryer
|
||||||
|
// for example:
|
||||||
|
// post := Post{Id: 4}
|
||||||
|
// m2m := Ormer.QueryM2M(&post, "Tags")
|
||||||
|
QueryM2M(md interface{}, name string) QueryM2Mer
|
||||||
|
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||||
|
// Use context.Context directly on methods with `WithCtx` suffix such as InsertWithCtx/UpdateWithCtx
|
||||||
|
QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer
|
||||||
|
|
||||||
|
// return a QuerySeter for table operations.
|
||||||
|
// table name can be string or struct.
|
||||||
|
// e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)),
|
||||||
|
QueryTable(ptrStructOrTableName interface{}) QuerySeter
|
||||||
|
// NOTE: this method is deprecated, context parameter will not take effect.
|
||||||
|
// Use context.Context directly on methods with `WithCtx` suffix such as InsertWithCtx/UpdateWithCtx
|
||||||
|
QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter
|
||||||
|
|
||||||
|
DBStats() *sql.DBStats
|
||||||
|
}
|
||||||
|
|
||||||
|
type DriverGetter interface {
|
||||||
|
Driver() Driver
|
||||||
|
}
|
||||||
|
|
||||||
|
type ormer interface {
|
||||||
|
DQL
|
||||||
|
DML
|
||||||
|
DriverGetter
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryExecutor wrapping for ormer
|
||||||
|
type QueryExecutor interface {
|
||||||
|
ormer
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ormer interface {
|
||||||
|
QueryExecutor
|
||||||
|
TxBeginner
|
||||||
|
}
|
||||||
|
|
||||||
|
type TxOrmer interface {
|
||||||
|
QueryExecutor
|
||||||
|
TxCommitter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inserter insert prepared statement
|
||||||
|
type Inserter interface {
|
||||||
|
Insert(interface{}) (int64, error)
|
||||||
|
InsertWithCtx(context.Context, interface{}) (int64, error)
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuerySeter query seter
|
||||||
|
type QuerySeter interface {
|
||||||
|
// add condition expression to QuerySeter.
|
||||||
|
// for example:
|
||||||
|
// filter by UserName == 'slene'
|
||||||
|
// qs.Filter("UserName", "slene")
|
||||||
|
// sql : left outer join profile on t0.id1==t1.id2 where t1.age == 28
|
||||||
|
// Filter("profile__Age", 28)
|
||||||
|
// // time compare
|
||||||
|
// qs.Filter("created", time.Now())
|
||||||
|
Filter(string, ...interface{}) QuerySeter
|
||||||
|
// add raw sql to querySeter.
|
||||||
|
// for example:
|
||||||
|
// qs.FilterRaw("user_id IN (SELECT id FROM profile WHERE age>=18)")
|
||||||
|
// //sql-> WHERE user_id IN (SELECT id FROM profile WHERE age>=18)
|
||||||
|
FilterRaw(string, string) QuerySeter
|
||||||
|
// add NOT condition to querySeter.
|
||||||
|
// have the same usage as Filter
|
||||||
|
Exclude(string, ...interface{}) QuerySeter
|
||||||
|
// set condition to QuerySeter.
|
||||||
|
// sql's where condition
|
||||||
|
// cond := orm.NewCondition()
|
||||||
|
// cond1 := cond.And("profile__isnull", false).AndNot("status__in", 1).Or("profile__age__gt", 2000)
|
||||||
|
// //sql-> WHERE T0.`profile_id` IS NOT NULL AND NOT T0.`Status` IN (?) OR T1.`age` > 2000
|
||||||
|
// num, err := qs.SetCond(cond1).Count()
|
||||||
|
SetCond(*Condition) QuerySeter
|
||||||
|
// get condition from QuerySeter.
|
||||||
|
// sql's where condition
|
||||||
|
// cond := orm.NewCondition()
|
||||||
|
// cond = cond.And("profile__isnull", false).AndNot("status__in", 1)
|
||||||
|
// qs = qs.SetCond(cond)
|
||||||
|
// cond = qs.GetCond()
|
||||||
|
// cond := cond.Or("profile__age__gt", 2000)
|
||||||
|
// //sql-> WHERE T0.`profile_id` IS NOT NULL AND NOT T0.`Status` IN (?) OR T1.`age` > 2000
|
||||||
|
// num, err := qs.SetCond(cond).Count()
|
||||||
|
GetCond() *Condition
|
||||||
|
// add LIMIT value.
|
||||||
|
// args[0] means offset, e.g. LIMIT num,offset.
|
||||||
|
// if Limit <= 0 then Limit will be set to default limit ,eg 1000
|
||||||
|
// if QuerySeter doesn't call Limit, the sql's Limit will be set to default limit, eg 1000
|
||||||
|
// for example:
|
||||||
|
// qs.Limit(10, 2)
|
||||||
|
// // sql-> limit 10 offset 2
|
||||||
|
Limit(limit interface{}, args ...interface{}) QuerySeter
|
||||||
|
// add OFFSET value
|
||||||
|
// same as Limit function's args[0]
|
||||||
|
Offset(offset interface{}) QuerySeter
|
||||||
|
// add GROUP BY expression
|
||||||
|
// for example:
|
||||||
|
// qs.GroupBy("id")
|
||||||
|
GroupBy(exprs ...string) QuerySeter
|
||||||
|
// add ORDER expression.
|
||||||
|
// "column" means ASC, "-column" means DESC.
|
||||||
|
// for example:
|
||||||
|
// qs.OrderBy("-status")
|
||||||
|
OrderBy(exprs ...string) QuerySeter
|
||||||
|
// add ORDER expression by order clauses
|
||||||
|
// for example:
|
||||||
|
// OrderClauses(
|
||||||
|
// order_clause.Clause(
|
||||||
|
// order.Column("Id"),
|
||||||
|
// order.SortAscending(),
|
||||||
|
// ),
|
||||||
|
// order_clause.Clause(
|
||||||
|
// order.Column("status"),
|
||||||
|
// order.SortDescending(),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// OrderClauses(order_clause.Clause(
|
||||||
|
// order_clause.Column(`user__status`),
|
||||||
|
// order_clause.SortDescending(),//default None
|
||||||
|
// ))
|
||||||
|
// OrderClauses(order_clause.Clause(
|
||||||
|
// order_clause.Column(`random()`),
|
||||||
|
// order_clause.SortNone(),//default None
|
||||||
|
// order_clause.Raw(),//default false.if true, do not check field is valid or not
|
||||||
|
// ))
|
||||||
|
OrderClauses(orders ...*order_clause.Order) QuerySeter
|
||||||
|
// add FORCE INDEX expression.
|
||||||
|
// for example:
|
||||||
|
// qs.ForceIndex(`idx_name1`,`idx_name2`)
|
||||||
|
// ForceIndex, UseIndex , IgnoreIndex are mutually exclusive
|
||||||
|
ForceIndex(indexes ...string) QuerySeter
|
||||||
|
// add USE INDEX expression.
|
||||||
|
// for example:
|
||||||
|
// qs.UseIndex(`idx_name1`,`idx_name2`)
|
||||||
|
// ForceIndex, UseIndex , IgnoreIndex are mutually exclusive
|
||||||
|
UseIndex(indexes ...string) QuerySeter
|
||||||
|
// add IGNORE INDEX expression.
|
||||||
|
// for example:
|
||||||
|
// qs.IgnoreIndex(`idx_name1`,`idx_name2`)
|
||||||
|
// ForceIndex, UseIndex , IgnoreIndex are mutually exclusive
|
||||||
|
IgnoreIndex(indexes ...string) QuerySeter
|
||||||
|
// set relation model to query together.
|
||||||
|
// it will query relation models and assign to parent model.
|
||||||
|
// for example:
|
||||||
|
// // will load all related fields use left join .
|
||||||
|
// qs.RelatedSel().One(&user)
|
||||||
|
// // will load related field only profile
|
||||||
|
// qs.RelatedSel("profile").One(&user)
|
||||||
|
// user.Profile.Age = 32
|
||||||
|
RelatedSel(params ...interface{}) QuerySeter
|
||||||
|
// Set Distinct
|
||||||
|
// for example:
|
||||||
|
// o.QueryTable("policy").Filter("Groups__Group__Users__User", user).
|
||||||
|
// Distinct().
|
||||||
|
// All(&permissions)
|
||||||
|
Distinct() QuerySeter
|
||||||
|
// set FOR UPDATE to query.
|
||||||
|
// for example:
|
||||||
|
// o.QueryTable("user").Filter("uid", uid).ForUpdate().All(&users)
|
||||||
|
ForUpdate() QuerySeter
|
||||||
|
// return QuerySeter execution result number
|
||||||
|
// for example:
|
||||||
|
// num, err = qs.Filter("profile__age__gt", 28).Count()
|
||||||
|
Count() (int64, error)
|
||||||
|
CountWithCtx(context.Context) (int64, error)
|
||||||
|
// check result empty or not after QuerySeter executed
|
||||||
|
// the same as QuerySeter.Count > 0
|
||||||
|
Exist() bool
|
||||||
|
ExistWithCtx(context.Context) bool
|
||||||
|
// execute update with parameters
|
||||||
|
// for example:
|
||||||
|
// num, err = qs.Filter("user_name", "slene").Update(Params{
|
||||||
|
// "Nums": ColValue(Col_Minus, 50),
|
||||||
|
// }) // user slene's Nums will minus 50
|
||||||
|
// num, err = qs.Filter("UserName", "slene").Update(Params{
|
||||||
|
// "user_name": "slene2"
|
||||||
|
// }) // user slene's name will change to slene2
|
||||||
|
Update(values Params) (int64, error)
|
||||||
|
UpdateWithCtx(ctx context.Context, values Params) (int64, error)
|
||||||
|
// delete from table
|
||||||
|
// for example:
|
||||||
|
// num ,err = qs.Filter("user_name__in", "testing1", "testing2").Delete()
|
||||||
|
// //delete two user who's name is testing1 or testing2
|
||||||
|
Delete() (int64, error)
|
||||||
|
DeleteWithCtx(context.Context) (int64, error)
|
||||||
|
// return a insert queryer.
|
||||||
|
// it can be used in times.
|
||||||
|
// example:
|
||||||
|
// i,err := sq.PrepareInsert()
|
||||||
|
// num, err = i.Insert(&user1) // user table will add one record user1 at once
|
||||||
|
// num, err = i.Insert(&user2) // user table will add one record user2 at once
|
||||||
|
// err = i.Close() //don't forget call Close
|
||||||
|
PrepareInsert() (Inserter, error)
|
||||||
|
PrepareInsertWithCtx(context.Context) (Inserter, error)
|
||||||
|
// query all data and map to containers.
|
||||||
|
// cols means the columns when querying.
|
||||||
|
// for example:
|
||||||
|
// var users []*User
|
||||||
|
// qs.All(&users) // users[0],users[1],users[2] ...
|
||||||
|
All(container interface{}, cols ...string) (int64, error)
|
||||||
|
AllWithCtx(ctx context.Context, container interface{}, cols ...string) (int64, error)
|
||||||
|
// query one row data and map to containers.
|
||||||
|
// cols means the columns when querying.
|
||||||
|
// for example:
|
||||||
|
// var user User
|
||||||
|
// qs.One(&user) //user.UserName == "slene"
|
||||||
|
One(container interface{}, cols ...string) error
|
||||||
|
OneWithCtx(ctx context.Context, container interface{}, cols ...string) error
|
||||||
|
// query all data and map to []map[string]interface.
|
||||||
|
// expres means condition expression.
|
||||||
|
// it converts data to []map[column]value.
|
||||||
|
// for example:
|
||||||
|
// var maps []Params
|
||||||
|
// qs.Values(&maps) //maps[0]["UserName"]=="slene"
|
||||||
|
Values(results *[]Params, exprs ...string) (int64, error)
|
||||||
|
ValuesWithCtx(ctx context.Context, results *[]Params, exprs ...string) (int64, error)
|
||||||
|
// query all data and map to [][]interface
|
||||||
|
// it converts data to [][column_index]value
|
||||||
|
// for example:
|
||||||
|
// var list []ParamsList
|
||||||
|
// qs.ValuesList(&list) // list[0][1] == "slene"
|
||||||
|
ValuesList(results *[]ParamsList, exprs ...string) (int64, error)
|
||||||
|
ValuesListWithCtx(ctx context.Context, results *[]ParamsList, exprs ...string) (int64, error)
|
||||||
|
// query all data and map to []interface.
|
||||||
|
// it's designed for one column record set, auto change to []value, not [][column]value.
|
||||||
|
// for example:
|
||||||
|
// var list ParamsList
|
||||||
|
// qs.ValuesFlat(&list, "UserName") // list[0] == "slene"
|
||||||
|
ValuesFlat(result *ParamsList, expr string) (int64, error)
|
||||||
|
ValuesFlatWithCtx(ctx context.Context, result *ParamsList, expr string) (int64, error)
|
||||||
|
// query all rows into map[string]interface with specify key and value column name.
|
||||||
|
// keyCol = "name", valueCol = "value"
|
||||||
|
// table data
|
||||||
|
// name | value
|
||||||
|
// total | 100
|
||||||
|
// found | 200
|
||||||
|
// to map[string]interface{}{
|
||||||
|
// "total": 100,
|
||||||
|
// "found": 200,
|
||||||
|
// }
|
||||||
|
RowsToMap(result *Params, keyCol, valueCol string) (int64, error)
|
||||||
|
// query all rows into struct with specify key and value column name.
|
||||||
|
// keyCol = "name", valueCol = "value"
|
||||||
|
// table data
|
||||||
|
// name | value
|
||||||
|
// total | 100
|
||||||
|
// found | 200
|
||||||
|
// to struct {
|
||||||
|
// Total int
|
||||||
|
// Found int
|
||||||
|
// }
|
||||||
|
RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error)
|
||||||
|
// aggregate func.
|
||||||
|
// for example:
|
||||||
|
// type result struct {
|
||||||
|
// DeptName string
|
||||||
|
// Total int
|
||||||
|
// }
|
||||||
|
// var res []result
|
||||||
|
// o.QueryTable("dept_info").Aggregate("dept_name,sum(salary) as total").GroupBy("dept_name").All(&res)
|
||||||
|
Aggregate(s string) QuerySeter
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryM2Mer model to model query struct
|
||||||
|
// all operations are on the m2m table only, will not affect the origin model table
|
||||||
|
type QueryM2Mer interface {
|
||||||
|
// add models to origin models when creating queryM2M.
|
||||||
|
// example:
|
||||||
|
// m2m := orm.QueryM2M(post,"Tag")
|
||||||
|
// m2m.Add(&Tag1{},&Tag2{})
|
||||||
|
// for _,tag := range post.Tags{}{ ... }
|
||||||
|
// param could also be any of the follow
|
||||||
|
// []*Tag{{Id:3,Name: "TestTag1"}, {Id:4,Name: "TestTag2"}}
|
||||||
|
// &Tag{Id:5,Name: "TestTag3"}
|
||||||
|
// []interface{}{&Tag{Id:6,Name: "TestTag4"}}
|
||||||
|
// insert one or more rows to m2m table
|
||||||
|
// make sure the relation is defined in post model struct tag.
|
||||||
|
Add(...interface{}) (int64, error)
|
||||||
|
AddWithCtx(context.Context, ...interface{}) (int64, error)
|
||||||
|
// remove models following the origin model relationship
|
||||||
|
// only delete rows from m2m table
|
||||||
|
// for example:
|
||||||
|
// tag3 := &Tag{Id:5,Name: "TestTag3"}
|
||||||
|
// num, err = m2m.Remove(tag3)
|
||||||
|
Remove(...interface{}) (int64, error)
|
||||||
|
RemoveWithCtx(context.Context, ...interface{}) (int64, error)
|
||||||
|
// check model is existed in relationship of origin model
|
||||||
|
Exist(interface{}) bool
|
||||||
|
ExistWithCtx(context.Context, interface{}) bool
|
||||||
|
// clean all models in related of origin model
|
||||||
|
Clear() (int64, error)
|
||||||
|
ClearWithCtx(context.Context) (int64, error)
|
||||||
|
// count all related models of origin model
|
||||||
|
Count() (int64, error)
|
||||||
|
CountWithCtx(context.Context) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawPreparer raw query statement
|
||||||
|
type RawPreparer interface {
|
||||||
|
Exec(...interface{}) (sql.Result, error)
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawSeter raw query seter
|
||||||
|
// create From Ormer.Raw
|
||||||
|
// for example:
|
||||||
|
// sql := fmt.Sprintf("SELECT %sid%s,%sname%s FROM %suser%s WHERE id = ?",Q,Q,Q,Q,Q,Q)
|
||||||
|
// rs := Ormer.Raw(sql, 1)
|
||||||
|
type RawSeter interface {
|
||||||
|
// execute sql and get result
|
||||||
|
Exec() (sql.Result, error)
|
||||||
|
// query data and map to container
|
||||||
|
// for example:
|
||||||
|
// var name string
|
||||||
|
// var id int
|
||||||
|
// rs.QueryRow(&id,&name) // id==2 name=="slene"
|
||||||
|
QueryRow(containers ...interface{}) error
|
||||||
|
|
||||||
|
// query data rows and map to container
|
||||||
|
// var ids []int
|
||||||
|
// var names []int
|
||||||
|
// query = fmt.Sprintf("SELECT 'id','name' FROM %suser%s", Q, Q)
|
||||||
|
// num, err = dORM.Raw(query).QueryRows(&ids,&names) // ids=>{1,2},names=>{"nobody","slene"}
|
||||||
|
QueryRows(containers ...interface{}) (int64, error)
|
||||||
|
SetArgs(...interface{}) RawSeter
|
||||||
|
// query data to []map[string]interface
|
||||||
|
// see QuerySeter's Values
|
||||||
|
Values(container *[]Params, cols ...string) (int64, error)
|
||||||
|
// query data to [][]interface
|
||||||
|
// see QuerySeter's ValuesList
|
||||||
|
ValuesList(container *[]ParamsList, cols ...string) (int64, error)
|
||||||
|
// query data to []interface
|
||||||
|
// see QuerySeter's ValuesFlat
|
||||||
|
ValuesFlat(container *ParamsList, cols ...string) (int64, error)
|
||||||
|
// query all rows into map[string]interface with specify key and value column name.
|
||||||
|
// keyCol = "name", valueCol = "value"
|
||||||
|
// table data
|
||||||
|
// name | value
|
||||||
|
// total | 100
|
||||||
|
// found | 200
|
||||||
|
// to map[string]interface{}{
|
||||||
|
// "total": 100,
|
||||||
|
// "found": 200,
|
||||||
|
// }
|
||||||
|
RowsToMap(result *Params, keyCol, valueCol string) (int64, error)
|
||||||
|
// query all rows into struct with specify key and value column name.
|
||||||
|
// keyCol = "name", valueCol = "value"
|
||||||
|
// table data
|
||||||
|
// name | value
|
||||||
|
// total | 100
|
||||||
|
// found | 200
|
||||||
|
// to struct {
|
||||||
|
// Total int
|
||||||
|
// Found int
|
||||||
|
// }
|
||||||
|
RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) (int64, error)
|
||||||
|
|
||||||
|
// return prepared raw statement for used in times.
|
||||||
|
// for example:
|
||||||
|
// pre, err := dORM.Raw("INSERT INTO tag (name) VALUES (?)").Prepare()
|
||||||
|
// r, err := pre.Exec("name1") // INSERT INTO tag (name) VALUES (`name1`)
|
||||||
|
Prepare() (RawPreparer, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stmtQuerier statement querier
|
||||||
|
type stmtQuerier interface {
|
||||||
|
Close() error
|
||||||
|
Exec(args ...interface{}) (sql.Result, error)
|
||||||
|
ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error)
|
||||||
|
Query(args ...interface{}) (*sql.Rows, error)
|
||||||
|
QueryContext(ctx context.Context, args ...interface{}) (*sql.Rows, error)
|
||||||
|
QueryRow(args ...interface{}) *sql.Row
|
||||||
|
QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row
|
||||||
|
}
|
||||||
|
|
||||||
|
// db querier
|
||||||
|
type dbQuerier interface {
|
||||||
|
Prepare(query string) (*sql.Stmt, error)
|
||||||
|
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
|
||||||
|
Exec(query string, args ...interface{}) (sql.Result, error)
|
||||||
|
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
|
||||||
|
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||||
|
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
|
||||||
|
QueryRow(query string, args ...interface{}) *sql.Row
|
||||||
|
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
|
||||||
|
}
|
||||||
|
|
||||||
|
// type DB interface {
|
||||||
|
// Begin() (*sql.Tx, error)
|
||||||
|
// Prepare(query string) (stmtQuerier, error)
|
||||||
|
// Exec(query string, args ...interface{}) (sql.Result, error)
|
||||||
|
// Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||||
|
// QueryRow(query string, args ...interface{}) *sql.Row
|
||||||
|
// }
|
||||||
|
|
||||||
|
// base database struct
|
||||||
|
type dbBaser interface {
|
||||||
|
Read(context.Context, dbQuerier, *modelInfo, reflect.Value, *time.Location, []string, bool) error
|
||||||
|
ReadBatch(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, interface{}, *time.Location, []string) (int64, error)
|
||||||
|
Count(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error)
|
||||||
|
ReadValues(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, []string, interface{}, *time.Location) (int64, error)
|
||||||
|
|
||||||
|
Insert(context.Context, dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
|
||||||
|
InsertOrUpdate(context.Context, dbQuerier, *modelInfo, reflect.Value, *alias, ...string) (int64, error)
|
||||||
|
InsertMulti(context.Context, dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error)
|
||||||
|
InsertValue(context.Context, dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error)
|
||||||
|
InsertStmt(context.Context, stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
|
||||||
|
|
||||||
|
Update(context.Context, dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error)
|
||||||
|
UpdateBatch(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, Params, *time.Location) (int64, error)
|
||||||
|
|
||||||
|
Delete(context.Context, dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error)
|
||||||
|
DeleteBatch(context.Context, dbQuerier, *querySet, *modelInfo, *Condition, *time.Location) (int64, error)
|
||||||
|
|
||||||
|
SupportUpdateJoin() bool
|
||||||
|
OperatorSQL(string) string
|
||||||
|
GenerateOperatorSQL(*modelInfo, *fieldInfo, string, []interface{}, *time.Location) (string, []interface{})
|
||||||
|
GenerateOperatorLeftCol(*fieldInfo, string, *string)
|
||||||
|
PrepareInsert(context.Context, dbQuerier, *modelInfo) (stmtQuerier, string, error)
|
||||||
|
MaxLimit() uint64
|
||||||
|
TableQuote() string
|
||||||
|
ReplaceMarks(*string)
|
||||||
|
HasReturningID(*modelInfo, *string) bool
|
||||||
|
TimeFromDB(*time.Time, *time.Location)
|
||||||
|
TimeToDB(*time.Time, *time.Location)
|
||||||
|
DbTypes() map[string]string
|
||||||
|
GetTables(dbQuerier) (map[string]bool, error)
|
||||||
|
GetColumns(context.Context, dbQuerier, string) (map[string][3]string, error)
|
||||||
|
ShowTablesQuery() string
|
||||||
|
ShowColumnsQuery(string) string
|
||||||
|
IndexExists(context.Context, dbQuerier, string, string) bool
|
||||||
|
collectFieldValue(*modelInfo, *fieldInfo, reflect.Value, bool, *time.Location) (interface{}, error)
|
||||||
|
setval(context.Context, dbQuerier, *modelInfo, []string) error
|
||||||
|
|
||||||
|
GenerateSpecifyIndex(tableName string, useIndex int, indexes []string) string
|
||||||
|
}
|
@ -0,0 +1,319 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package orm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fn func(string) string
|
||||||
|
|
||||||
|
var (
|
||||||
|
nameStrategyMap = map[string]fn{
|
||||||
|
defaultNameStrategy: snakeString,
|
||||||
|
SnakeAcronymNameStrategy: snakeStringWithAcronym,
|
||||||
|
}
|
||||||
|
defaultNameStrategy = "snakeString"
|
||||||
|
SnakeAcronymNameStrategy = "snakeStringWithAcronym"
|
||||||
|
nameStrategy = defaultNameStrategy
|
||||||
|
)
|
||||||
|
|
||||||
|
// StrTo is the target string
|
||||||
|
type StrTo string
|
||||||
|
|
||||||
|
// Set string
|
||||||
|
func (f *StrTo) Set(v string) {
|
||||||
|
if v != "" {
|
||||||
|
*f = StrTo(v)
|
||||||
|
} else {
|
||||||
|
f.Clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear string
|
||||||
|
func (f *StrTo) Clear() {
|
||||||
|
*f = StrTo(rune(0x1E))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exist check string exist
|
||||||
|
func (f StrTo) Exist() bool {
|
||||||
|
return string(f) != string(rune(0x1E))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool string to bool
|
||||||
|
func (f StrTo) Bool() (bool, error) {
|
||||||
|
return strconv.ParseBool(f.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32 string to float32
|
||||||
|
func (f StrTo) Float32() (float32, error) {
|
||||||
|
v, err := strconv.ParseFloat(f.String(), 32)
|
||||||
|
return float32(v), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 string to float64
|
||||||
|
func (f StrTo) Float64() (float64, error) {
|
||||||
|
return strconv.ParseFloat(f.String(), 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int string to int
|
||||||
|
func (f StrTo) Int() (int, error) {
|
||||||
|
v, err := strconv.ParseInt(f.String(), 10, 32)
|
||||||
|
return int(v), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int8 string to int8
|
||||||
|
func (f StrTo) Int8() (int8, error) {
|
||||||
|
v, err := strconv.ParseInt(f.String(), 10, 8)
|
||||||
|
return int8(v), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int16 string to int16
|
||||||
|
func (f StrTo) Int16() (int16, error) {
|
||||||
|
v, err := strconv.ParseInt(f.String(), 10, 16)
|
||||||
|
return int16(v), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32 string to int32
|
||||||
|
func (f StrTo) Int32() (int32, error) {
|
||||||
|
v, err := strconv.ParseInt(f.String(), 10, 32)
|
||||||
|
return int32(v), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 string to int64
|
||||||
|
func (f StrTo) Int64() (int64, error) {
|
||||||
|
v, err := strconv.ParseInt(f.String(), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
i := new(big.Int)
|
||||||
|
ni, ok := i.SetString(f.String(), 10) // octal
|
||||||
|
if !ok {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
return ni.Int64(), nil
|
||||||
|
}
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint string to uint
|
||||||
|
func (f StrTo) Uint() (uint, error) {
|
||||||
|
v, err := strconv.ParseUint(f.String(), 10, 32)
|
||||||
|
return uint(v), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8 string to uint8
|
||||||
|
func (f StrTo) Uint8() (uint8, error) {
|
||||||
|
v, err := strconv.ParseUint(f.String(), 10, 8)
|
||||||
|
return uint8(v), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16 string to uint16
|
||||||
|
func (f StrTo) Uint16() (uint16, error) {
|
||||||
|
v, err := strconv.ParseUint(f.String(), 10, 16)
|
||||||
|
return uint16(v), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32 string to uint32
|
||||||
|
func (f StrTo) Uint32() (uint32, error) {
|
||||||
|
v, err := strconv.ParseUint(f.String(), 10, 32)
|
||||||
|
return uint32(v), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 string to uint64
|
||||||
|
func (f StrTo) Uint64() (uint64, error) {
|
||||||
|
v, err := strconv.ParseUint(f.String(), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
i := new(big.Int)
|
||||||
|
ni, ok := i.SetString(f.String(), 10)
|
||||||
|
if !ok {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
return ni.Uint64(), nil
|
||||||
|
}
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// String string to string
|
||||||
|
func (f StrTo) String() string {
|
||||||
|
if f.Exist() {
|
||||||
|
return string(f)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStr interface to string
|
||||||
|
func ToStr(value interface{}, args ...int) (s string) {
|
||||||
|
switch v := value.(type) {
|
||||||
|
case bool:
|
||||||
|
s = strconv.FormatBool(v)
|
||||||
|
case float32:
|
||||||
|
s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32))
|
||||||
|
case float64:
|
||||||
|
s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64))
|
||||||
|
case int:
|
||||||
|
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
|
||||||
|
case int8:
|
||||||
|
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
|
||||||
|
case int16:
|
||||||
|
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
|
||||||
|
case int32:
|
||||||
|
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
|
||||||
|
case int64:
|
||||||
|
s = strconv.FormatInt(v, argInt(args).Get(0, 10))
|
||||||
|
case uint:
|
||||||
|
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
|
||||||
|
case uint8:
|
||||||
|
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
|
||||||
|
case uint16:
|
||||||
|
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
|
||||||
|
case uint32:
|
||||||
|
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
|
||||||
|
case uint64:
|
||||||
|
s = strconv.FormatUint(v, argInt(args).Get(0, 10))
|
||||||
|
case string:
|
||||||
|
s = v
|
||||||
|
case []byte:
|
||||||
|
s = string(v)
|
||||||
|
default:
|
||||||
|
s = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToInt64 interface to int64
|
||||||
|
func ToInt64(value interface{}) (d int64) {
|
||||||
|
val := reflect.ValueOf(value)
|
||||||
|
switch value.(type) {
|
||||||
|
case int, int8, int16, int32, int64:
|
||||||
|
d = val.Int()
|
||||||
|
case uint, uint8, uint16, uint32, uint64:
|
||||||
|
d = int64(val.Uint())
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("ToInt64 need numeric not `%T`", value))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func snakeStringWithAcronym(s string) string {
|
||||||
|
data := make([]byte, 0, len(s)*2)
|
||||||
|
num := len(s)
|
||||||
|
for i := 0; i < num; i++ {
|
||||||
|
d := s[i]
|
||||||
|
before := false
|
||||||
|
after := false
|
||||||
|
if i > 0 {
|
||||||
|
before = s[i-1] >= 'a' && s[i-1] <= 'z'
|
||||||
|
}
|
||||||
|
if i+1 < num {
|
||||||
|
after = s[i+1] >= 'a' && s[i+1] <= 'z'
|
||||||
|
}
|
||||||
|
if i > 0 && d >= 'A' && d <= 'Z' && (before || after) {
|
||||||
|
data = append(data, '_')
|
||||||
|
}
|
||||||
|
data = append(data, d)
|
||||||
|
}
|
||||||
|
return strings.ToLower(string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// snake string, XxYy to xx_yy , XxYY to xx_y_y
|
||||||
|
func snakeString(s string) string {
|
||||||
|
data := make([]byte, 0, len(s)*2)
|
||||||
|
j := false
|
||||||
|
num := len(s)
|
||||||
|
for i := 0; i < num; i++ {
|
||||||
|
d := s[i]
|
||||||
|
if i > 0 && d >= 'A' && d <= 'Z' && j {
|
||||||
|
data = append(data, '_')
|
||||||
|
}
|
||||||
|
if d != '_' {
|
||||||
|
j = true
|
||||||
|
}
|
||||||
|
data = append(data, d)
|
||||||
|
}
|
||||||
|
return strings.ToLower(string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNameStrategy set different name strategy
|
||||||
|
func SetNameStrategy(s string) {
|
||||||
|
if SnakeAcronymNameStrategy != s {
|
||||||
|
nameStrategy = defaultNameStrategy
|
||||||
|
}
|
||||||
|
nameStrategy = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// camel string, xx_yy to XxYy
|
||||||
|
func camelString(s string) string {
|
||||||
|
data := make([]byte, 0, len(s))
|
||||||
|
flag, num := true, len(s)-1
|
||||||
|
for i := 0; i <= num; i++ {
|
||||||
|
d := s[i]
|
||||||
|
if d == '_' {
|
||||||
|
flag = true
|
||||||
|
continue
|
||||||
|
} else if flag {
|
||||||
|
if d >= 'a' && d <= 'z' {
|
||||||
|
d = d - 32
|
||||||
|
}
|
||||||
|
flag = false
|
||||||
|
}
|
||||||
|
data = append(data, d)
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
type argString []string
|
||||||
|
|
||||||
|
// get string by index from string slice
|
||||||
|
func (a argString) Get(i int, args ...string) (r string) {
|
||||||
|
if i >= 0 && i < len(a) {
|
||||||
|
r = a[i]
|
||||||
|
} else if len(args) > 0 {
|
||||||
|
r = args[0]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type argInt []int
|
||||||
|
|
||||||
|
// get int by index from int slice
|
||||||
|
func (a argInt) Get(i int, args ...int) (r int) {
|
||||||
|
if i >= 0 && i < len(a) {
|
||||||
|
r = a[i]
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
r = args[0]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse time to string with location
|
||||||
|
func timeParse(dateString, format string) (time.Time, error) {
|
||||||
|
tp, err := time.ParseInLocation(format, dateString, DefaultTimeLoc)
|
||||||
|
return tp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get pointer indirect type
|
||||||
|
func indirectType(v reflect.Type) reflect.Type {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
return indirectType(v.Elem())
|
||||||
|
default:
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
## logs
|
||||||
|
|
||||||
|
logs is a Go logs manager. It can use many logs adapters. The repo is inspired by `database/sql` .
|
||||||
|
|
||||||
|
## How to install?
|
||||||
|
|
||||||
|
go get github.com/beego/beego/v2/core/logs
|
||||||
|
|
||||||
|
## What adapters are supported?
|
||||||
|
|
||||||
|
As of now this logs support console, file,smtp and conn.
|
||||||
|
|
||||||
|
## How to use it?
|
||||||
|
|
||||||
|
First you must import it
|
||||||
|
|
||||||
|
```golang
|
||||||
|
import (
|
||||||
|
"github.com/beego/beego/v2/core/logs"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Then init a Log (example with console adapter)
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log := logs.NewLogger(10000)
|
||||||
|
log.SetLogger("console", "")
|
||||||
|
```
|
||||||
|
|
||||||
|
> the first params stand for how many channel
|
||||||
|
|
||||||
|
Use it like this:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log.Trace("trace")
|
||||||
|
log.Info("info")
|
||||||
|
log.Warn("warning")
|
||||||
|
log.Debug("debug")
|
||||||
|
log.Critical("critical")
|
||||||
|
```
|
||||||
|
|
||||||
|
## File adapter
|
||||||
|
|
||||||
|
Configure file adapter like this:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conn adapter
|
||||||
|
|
||||||
|
Configure like this:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log := NewLogger(1000)
|
||||||
|
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
|
||||||
|
log.Info("info")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Smtp adapter
|
||||||
|
|
||||||
|
Configure like this:
|
||||||
|
|
||||||
|
```golang
|
||||||
|
log := NewLogger(10000)
|
||||||
|
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
|
||||||
|
log.Critical("sendmail critical")
|
||||||
|
time.Sleep(time.Second * 30)
|
||||||
|
```
|
@ -0,0 +1,93 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
apacheFormatPattern = "%s - - [%s] \"%s %d %d\" %f %s %s"
|
||||||
|
apacheFormat = "APACHE_FORMAT"
|
||||||
|
jsonFormat = "JSON_FORMAT"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessLogRecord is astruct for holding access log data.
|
||||||
|
type AccessLogRecord struct {
|
||||||
|
RemoteAddr string `json:"remote_addr"`
|
||||||
|
RequestTime time.Time `json:"request_time"`
|
||||||
|
RequestMethod string `json:"request_method"`
|
||||||
|
Request string `json:"request"`
|
||||||
|
ServerProtocol string `json:"server_protocol"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
BodyBytesSent int64 `json:"body_bytes_sent"`
|
||||||
|
ElapsedTime time.Duration `json:"elapsed_time"`
|
||||||
|
HTTPReferrer string `json:"http_referrer"`
|
||||||
|
HTTPUserAgent string `json:"http_user_agent"`
|
||||||
|
RemoteUser string `json:"remote_user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccessLogRecord) json() ([]byte, error) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
encoder := json.NewEncoder(buffer)
|
||||||
|
disableEscapeHTML(encoder)
|
||||||
|
|
||||||
|
err := encoder.Encode(r)
|
||||||
|
return buffer.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func disableEscapeHTML(i interface{}) {
|
||||||
|
if e, ok := i.(interface {
|
||||||
|
SetEscapeHTML(bool)
|
||||||
|
}); ok {
|
||||||
|
e.SetEscapeHTML(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccessLog - Format and print access log.
|
||||||
|
func AccessLog(r *AccessLogRecord, format string) {
|
||||||
|
msg := r.format(format)
|
||||||
|
lm := &LogMsg{
|
||||||
|
Msg: strings.TrimSpace(msg),
|
||||||
|
When: time.Now(),
|
||||||
|
Level: levelLoggerImpl,
|
||||||
|
}
|
||||||
|
beeLogger.writeMsg(lm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccessLogRecord) format(format string) string {
|
||||||
|
msg := ""
|
||||||
|
switch format {
|
||||||
|
case apacheFormat:
|
||||||
|
timeFormatted := r.RequestTime.Format("02/Jan/2006 03:04:05")
|
||||||
|
msg = fmt.Sprintf(apacheFormatPattern, r.RemoteAddr, timeFormatted, r.Request, r.Status, r.BodyBytesSent,
|
||||||
|
r.ElapsedTime.Seconds(), r.HTTPReferrer, r.HTTPUserAgent)
|
||||||
|
case jsonFormat:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
jsonData, err := r.json()
|
||||||
|
if err != nil {
|
||||||
|
msg = fmt.Sprintf(`{"Error": "%s"}`, err)
|
||||||
|
} else {
|
||||||
|
msg = string(jsonData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// connWriter implements LoggerInterface.
|
||||||
|
// Writes messages in keep-live tcp connection.
|
||||||
|
type connWriter struct {
|
||||||
|
lg *logWriter
|
||||||
|
innerWriter io.WriteCloser
|
||||||
|
formatter LogFormatter
|
||||||
|
Formatter string `json:"formatter"`
|
||||||
|
ReconnectOnMsg bool `json:"reconnectOnMsg"`
|
||||||
|
Reconnect bool `json:"reconnect"`
|
||||||
|
Net string `json:"net"`
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConn creates new ConnWrite returning as LoggerInterface.
|
||||||
|
func NewConn() Logger {
|
||||||
|
conn := new(connWriter)
|
||||||
|
conn.Level = LevelTrace
|
||||||
|
conn.formatter = conn
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connWriter) Format(lm *LogMsg) string {
|
||||||
|
return lm.OldStyleFormat()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init initializes a connection writer with json config.
|
||||||
|
// json config only needs they "level" key
|
||||||
|
func (c *connWriter) Init(config string) error {
|
||||||
|
res := json.Unmarshal([]byte(config), c)
|
||||||
|
if res == nil && len(c.Formatter) > 0 {
|
||||||
|
fmtr, ok := GetFormatter(c.Formatter)
|
||||||
|
if !ok {
|
||||||
|
return errors.New(fmt.Sprintf("the formatter with name: %s not found", c.Formatter))
|
||||||
|
}
|
||||||
|
c.formatter = fmtr
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connWriter) SetFormatter(f LogFormatter) {
|
||||||
|
c.formatter = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg writes message in connection.
|
||||||
|
// If connection is down, try to re-connect.
|
||||||
|
func (c *connWriter) WriteMsg(lm *LogMsg) error {
|
||||||
|
if lm.Level > c.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c.needToConnectOnMsg() {
|
||||||
|
err := c.connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ReconnectOnMsg {
|
||||||
|
defer c.innerWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := c.formatter.Format(lm)
|
||||||
|
|
||||||
|
_, err := c.lg.writeln(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (c *connWriter) Flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy destroy connection writer and close tcp listener.
|
||||||
|
func (c *connWriter) Destroy() {
|
||||||
|
if c.innerWriter != nil {
|
||||||
|
c.innerWriter.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connWriter) connect() error {
|
||||||
|
if c.innerWriter != nil {
|
||||||
|
c.innerWriter.Close()
|
||||||
|
c.innerWriter = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.Dial(c.Net, c.Addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||||
|
tcpConn.SetKeepAlive(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.innerWriter = conn
|
||||||
|
c.lg = newLogWriter(conn)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connWriter) needToConnectOnMsg() bool {
|
||||||
|
if c.Reconnect {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.innerWriter == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.ReconnectOnMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterConn, NewConn)
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/shiena/ansicolor"
|
||||||
|
)
|
||||||
|
|
||||||
|
// brush is a color join function
|
||||||
|
type brush func(string) string
|
||||||
|
|
||||||
|
// newBrush returns a fix color Brush
|
||||||
|
func newBrush(color string) brush {
|
||||||
|
pre := "\033["
|
||||||
|
reset := "\033[0m"
|
||||||
|
return func(text string) string {
|
||||||
|
return pre + color + "m" + text + reset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var colors = []brush{
|
||||||
|
newBrush("1;37"), // Emergency white
|
||||||
|
newBrush("1;36"), // Alert cyan
|
||||||
|
newBrush("1;35"), // Critical magenta
|
||||||
|
newBrush("1;31"), // Error red
|
||||||
|
newBrush("1;33"), // Warning yellow
|
||||||
|
newBrush("1;32"), // Notice green
|
||||||
|
newBrush("1;34"), // Informational blue
|
||||||
|
newBrush("1;44"), // Debug Background blue
|
||||||
|
}
|
||||||
|
|
||||||
|
// consoleWriter implements LoggerInterface and writes messages to terminal.
|
||||||
|
type consoleWriter struct {
|
||||||
|
lg *logWriter
|
||||||
|
formatter LogFormatter
|
||||||
|
Formatter string `json:"formatter"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
Colorful bool `json:"color"` // this filed is useful only when system's terminal supports color
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *consoleWriter) Format(lm *LogMsg) string {
|
||||||
|
msg := lm.OldStyleFormat()
|
||||||
|
if c.Colorful {
|
||||||
|
msg = strings.Replace(msg, levelPrefix[lm.Level], colors[lm.Level](levelPrefix[lm.Level]), 1)
|
||||||
|
}
|
||||||
|
h, _, _ := formatTimeHeader(lm.When)
|
||||||
|
return string(append(h, msg...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *consoleWriter) SetFormatter(f LogFormatter) {
|
||||||
|
c.formatter = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConsole creates ConsoleWriter returning as LoggerInterface.
|
||||||
|
func NewConsole() Logger {
|
||||||
|
return newConsole()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConsole() *consoleWriter {
|
||||||
|
cw := &consoleWriter{
|
||||||
|
lg: newLogWriter(ansicolor.NewAnsiColorWriter(os.Stdout)),
|
||||||
|
Level: LevelDebug,
|
||||||
|
Colorful: true,
|
||||||
|
}
|
||||||
|
cw.formatter = cw
|
||||||
|
return cw
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init initianlizes the console logger.
|
||||||
|
// jsonConfig must be in the format '{"level":LevelTrace}'
|
||||||
|
func (c *consoleWriter) Init(config string) error {
|
||||||
|
if len(config) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res := json.Unmarshal([]byte(config), c)
|
||||||
|
if res == nil && len(c.Formatter) > 0 {
|
||||||
|
fmtr, ok := GetFormatter(c.Formatter)
|
||||||
|
if !ok {
|
||||||
|
return errors.New(fmt.Sprintf("the formatter with name: %s not found", c.Formatter))
|
||||||
|
}
|
||||||
|
c.formatter = fmtr
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg writes message in console.
|
||||||
|
func (c *consoleWriter) WriteMsg(lm *LogMsg) error {
|
||||||
|
if lm.Level > c.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
msg := c.formatter.Format(lm)
|
||||||
|
c.lg.writeln(msg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy implementing method. empty.
|
||||||
|
func (c *consoleWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (c *consoleWriter) Flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterConsole, NewConsole)
|
||||||
|
}
|
@ -0,0 +1,442 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fileLogWriter implements LoggerInterface.
|
||||||
|
// Writes messages by lines limit, file size limit, or time frequency.
|
||||||
|
type fileLogWriter struct {
|
||||||
|
sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
|
||||||
|
|
||||||
|
Rotate bool `json:"rotate"`
|
||||||
|
Daily bool `json:"daily"`
|
||||||
|
Hourly bool `json:"hourly"`
|
||||||
|
|
||||||
|
// The opened file
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
fileWriter *os.File
|
||||||
|
|
||||||
|
// Rotate at line
|
||||||
|
MaxLines int `json:"maxlines"`
|
||||||
|
maxLinesCurLines int
|
||||||
|
|
||||||
|
MaxFiles int `json:"maxfiles"`
|
||||||
|
MaxFilesCurFiles int
|
||||||
|
|
||||||
|
// Rotate at size
|
||||||
|
MaxSize int `json:"maxsize"`
|
||||||
|
maxSizeCurSize int
|
||||||
|
|
||||||
|
// Rotate daily
|
||||||
|
MaxDays int64 `json:"maxdays"`
|
||||||
|
dailyOpenDate int
|
||||||
|
dailyOpenTime time.Time
|
||||||
|
|
||||||
|
// Rotate hourly
|
||||||
|
MaxHours int64 `json:"maxhours"`
|
||||||
|
hourlyOpenDate int
|
||||||
|
hourlyOpenTime time.Time
|
||||||
|
|
||||||
|
Level int `json:"level"`
|
||||||
|
// Permissions for log file
|
||||||
|
Perm string `json:"perm"`
|
||||||
|
// Permissions for directory if it is specified in FileName
|
||||||
|
DirPerm string `json:"dirperm"`
|
||||||
|
|
||||||
|
RotatePerm string `json:"rotateperm"`
|
||||||
|
|
||||||
|
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
|
||||||
|
|
||||||
|
logFormatter LogFormatter
|
||||||
|
Formatter string `json:"formatter"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFileWriter creates a FileLogWriter returning as LoggerInterface.
|
||||||
|
func newFileWriter() Logger {
|
||||||
|
w := &fileLogWriter{
|
||||||
|
Daily: true,
|
||||||
|
MaxDays: 7,
|
||||||
|
Hourly: false,
|
||||||
|
MaxHours: 168,
|
||||||
|
Rotate: true,
|
||||||
|
RotatePerm: "0440",
|
||||||
|
Level: LevelTrace,
|
||||||
|
Perm: "0660",
|
||||||
|
DirPerm: "0770",
|
||||||
|
MaxLines: 10000000,
|
||||||
|
MaxFiles: 999,
|
||||||
|
MaxSize: 1 << 28,
|
||||||
|
}
|
||||||
|
w.logFormatter = w
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*fileLogWriter) Format(lm *LogMsg) string {
|
||||||
|
msg := lm.OldStyleFormat()
|
||||||
|
hd, _, _ := formatTimeHeader(lm.When)
|
||||||
|
msg = fmt.Sprintf("%s %s\n", string(hd), msg)
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) SetFormatter(f LogFormatter) {
|
||||||
|
w.logFormatter = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init file logger with json config.
|
||||||
|
// jsonConfig like:
|
||||||
|
// {
|
||||||
|
// "filename":"logs/beego.log",
|
||||||
|
// "maxLines":10000,
|
||||||
|
// "maxsize":1024,
|
||||||
|
// "daily":true,
|
||||||
|
// "maxDays":15,
|
||||||
|
// "rotate":true,
|
||||||
|
// "perm":"0600"
|
||||||
|
// }
|
||||||
|
func (w *fileLogWriter) Init(config string) error {
|
||||||
|
err := json.Unmarshal([]byte(config), w)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if w.Filename == "" {
|
||||||
|
return errors.New("jsonconfig must have filename")
|
||||||
|
}
|
||||||
|
w.suffix = filepath.Ext(w.Filename)
|
||||||
|
w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
|
||||||
|
if w.suffix == "" {
|
||||||
|
w.suffix = ".log"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(w.Formatter) > 0 {
|
||||||
|
fmtr, ok := GetFormatter(w.Formatter)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("the formatter with name: %s not found", w.Formatter)
|
||||||
|
}
|
||||||
|
w.logFormatter = fmtr
|
||||||
|
}
|
||||||
|
err = w.startLogger()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// start file logger. create log file and set to locker-inside file writer.
|
||||||
|
func (w *fileLogWriter) startLogger() error {
|
||||||
|
file, err := w.createLogFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if w.fileWriter != nil {
|
||||||
|
w.fileWriter.Close()
|
||||||
|
}
|
||||||
|
w.fileWriter = file
|
||||||
|
return w.initFd()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) needRotateDaily(day int) bool {
|
||||||
|
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||||
|
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||||
|
(w.Daily && day != w.dailyOpenDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) needRotateHourly(hour int) bool {
|
||||||
|
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
|
||||||
|
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
|
||||||
|
(w.Hourly && hour != w.hourlyOpenDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg writes logger message into file.
|
||||||
|
func (w *fileLogWriter) WriteMsg(lm *LogMsg) error {
|
||||||
|
if lm.Level > w.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, d, h := formatTimeHeader(lm.When)
|
||||||
|
|
||||||
|
msg := w.logFormatter.Format(lm)
|
||||||
|
if w.Rotate {
|
||||||
|
w.RLock()
|
||||||
|
if w.needRotateHourly(h) {
|
||||||
|
w.RUnlock()
|
||||||
|
w.Lock()
|
||||||
|
if w.needRotateHourly(h) {
|
||||||
|
if err := w.doRotate(lm.When); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
} else if w.needRotateDaily(d) {
|
||||||
|
w.RUnlock()
|
||||||
|
w.Lock()
|
||||||
|
if w.needRotateDaily(d) {
|
||||||
|
if err := w.doRotate(lm.When); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
} else {
|
||||||
|
w.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Lock()
|
||||||
|
_, err := w.fileWriter.Write([]byte(msg))
|
||||||
|
if err == nil {
|
||||||
|
w.maxLinesCurLines++
|
||||||
|
w.maxSizeCurSize += len(msg)
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) createLogFile() (*os.File, error) {
|
||||||
|
// Open the log file
|
||||||
|
perm, err := strconv.ParseInt(w.Perm, 8, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dirperm, err := strconv.ParseInt(w.DirPerm, 8, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filepath := path.Dir(w.Filename)
|
||||||
|
os.MkdirAll(filepath, os.FileMode(dirperm))
|
||||||
|
|
||||||
|
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
|
||||||
|
if err == nil {
|
||||||
|
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
|
||||||
|
os.Chmod(w.Filename, os.FileMode(perm))
|
||||||
|
}
|
||||||
|
return fd, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) initFd() error {
|
||||||
|
fd := w.fileWriter
|
||||||
|
fInfo, err := fd.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get stat err: %s", err)
|
||||||
|
}
|
||||||
|
w.maxSizeCurSize = int(fInfo.Size())
|
||||||
|
w.dailyOpenTime = time.Now()
|
||||||
|
w.dailyOpenDate = w.dailyOpenTime.Day()
|
||||||
|
w.hourlyOpenTime = time.Now()
|
||||||
|
w.hourlyOpenDate = w.hourlyOpenTime.Hour()
|
||||||
|
w.maxLinesCurLines = 0
|
||||||
|
if w.Hourly {
|
||||||
|
go w.hourlyRotate(w.hourlyOpenTime)
|
||||||
|
} else if w.Daily {
|
||||||
|
go w.dailyRotate(w.dailyOpenTime)
|
||||||
|
}
|
||||||
|
if fInfo.Size() > 0 && w.MaxLines > 0 {
|
||||||
|
count, err := w.lines()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.maxLinesCurLines = count
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) dailyRotate(openTime time.Time) {
|
||||||
|
y, m, d := openTime.Add(24 * time.Hour).Date()
|
||||||
|
nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
|
||||||
|
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
|
||||||
|
<-tm.C
|
||||||
|
w.Lock()
|
||||||
|
if w.needRotateDaily(time.Now().Day()) {
|
||||||
|
if err := w.doRotate(time.Now()); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) hourlyRotate(openTime time.Time) {
|
||||||
|
y, m, d := openTime.Add(1 * time.Hour).Date()
|
||||||
|
h, _, _ := openTime.Add(1 * time.Hour).Clock()
|
||||||
|
nextHour := time.Date(y, m, d, h, 0, 0, 0, openTime.Location())
|
||||||
|
tm := time.NewTimer(time.Duration(nextHour.UnixNano() - openTime.UnixNano() + 100))
|
||||||
|
<-tm.C
|
||||||
|
w.Lock()
|
||||||
|
if w.needRotateHourly(time.Now().Hour()) {
|
||||||
|
if err := w.doRotate(time.Now()); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) lines() (int, error) {
|
||||||
|
fd, err := os.Open(w.Filename)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
buf := make([]byte, 32768) // 32k
|
||||||
|
count := 0
|
||||||
|
lineSep := []byte{'\n'}
|
||||||
|
|
||||||
|
for {
|
||||||
|
c, err := fd.Read(buf)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count += bytes.Count(buf[:c], lineSep)
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoRotate means it needs to write logs into a new file.
|
||||||
|
// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
|
||||||
|
func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
||||||
|
// file exists
|
||||||
|
// Find the next available number
|
||||||
|
num := w.MaxFilesCurFiles + 1
|
||||||
|
fName := ""
|
||||||
|
format := ""
|
||||||
|
var openTime time.Time
|
||||||
|
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = os.Lstat(w.Filename)
|
||||||
|
if err != nil {
|
||||||
|
// even if the file is not exist or other ,we should RESTART the logger
|
||||||
|
goto RESTART_LOGGER
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Hourly {
|
||||||
|
format = "2006010215"
|
||||||
|
openTime = w.hourlyOpenTime
|
||||||
|
} else if w.Daily {
|
||||||
|
format = "2006-01-02"
|
||||||
|
openTime = w.dailyOpenTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// only when one of them be setted, then the file would be splited
|
||||||
|
if w.MaxLines > 0 || w.MaxSize > 0 {
|
||||||
|
for ; err == nil && num <= w.MaxFiles; num++ {
|
||||||
|
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format(format), num, w.suffix)
|
||||||
|
_, err = os.Lstat(fName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", openTime.Format(format), num, w.suffix)
|
||||||
|
_, err = os.Lstat(fName)
|
||||||
|
w.MaxFilesCurFiles = num
|
||||||
|
}
|
||||||
|
|
||||||
|
// return error if the last file checked still existed
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// close fileWriter before rename
|
||||||
|
w.fileWriter.Close()
|
||||||
|
|
||||||
|
// Rename the file to its new found name
|
||||||
|
// even if occurs error,we MUST guarantee to restart new logger
|
||||||
|
err = os.Rename(w.Filename, fName)
|
||||||
|
if err != nil {
|
||||||
|
goto RESTART_LOGGER
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Chmod(fName, os.FileMode(rotatePerm))
|
||||||
|
|
||||||
|
RESTART_LOGGER:
|
||||||
|
|
||||||
|
startLoggerErr := w.startLogger()
|
||||||
|
go w.deleteOldLog()
|
||||||
|
|
||||||
|
if startLoggerErr != nil {
|
||||||
|
return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Rotate: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *fileLogWriter) deleteOldLog() {
|
||||||
|
dir := filepath.Dir(w.Filename)
|
||||||
|
absolutePath, err := filepath.EvalSymlinks(w.Filename)
|
||||||
|
if err == nil {
|
||||||
|
dir = filepath.Dir(absolutePath)
|
||||||
|
}
|
||||||
|
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if info == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if w.Hourly {
|
||||||
|
if !info.IsDir() && info.ModTime().Add(1*time.Hour*time.Duration(w.MaxHours)).Before(time.Now()) {
|
||||||
|
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||||
|
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||||
|
os.Remove(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if w.Daily {
|
||||||
|
if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) {
|
||||||
|
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||||
|
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||||
|
os.Remove(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy close the file description, close file writer.
|
||||||
|
func (w *fileLogWriter) Destroy() {
|
||||||
|
w.fileWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush flushes file logger.
|
||||||
|
// there are no buffering messages in file logger in memory.
|
||||||
|
// flush file means sync file from disk.
|
||||||
|
func (w *fileLogWriter) Flush() {
|
||||||
|
w.fileWriter.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterFile, newFileWriter)
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JLWriter implements beego LoggerInterface and is used to send jiaoliao webhook
|
||||||
|
type JLWriter struct {
|
||||||
|
AuthorName string `json:"authorname"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
WebhookURL string `json:"webhookurl"`
|
||||||
|
RedirectURL string `json:"redirecturl,omitempty"`
|
||||||
|
ImageURL string `json:"imageurl,omitempty"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
|
||||||
|
formatter LogFormatter
|
||||||
|
Formatter string `json:"formatter"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// newJLWriter creates jiaoliao writer.
|
||||||
|
func newJLWriter() Logger {
|
||||||
|
res := &JLWriter{Level: LevelTrace}
|
||||||
|
res.formatter = res
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init JLWriter with json config string
|
||||||
|
func (s *JLWriter) Init(config string) error {
|
||||||
|
res := json.Unmarshal([]byte(config), s)
|
||||||
|
if res == nil && len(s.Formatter) > 0 {
|
||||||
|
fmtr, ok := GetFormatter(s.Formatter)
|
||||||
|
if !ok {
|
||||||
|
return errors.New(fmt.Sprintf("the formatter with name: %s not found", s.Formatter))
|
||||||
|
}
|
||||||
|
s.formatter = fmtr
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JLWriter) Format(lm *LogMsg) string {
|
||||||
|
msg := lm.OldStyleFormat()
|
||||||
|
msg = fmt.Sprintf("%s %s", lm.When.Format("2006-01-02 15:04:05"), msg)
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *JLWriter) SetFormatter(f LogFormatter) {
|
||||||
|
s.formatter = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg writes message in smtp writer.
|
||||||
|
// Sends an email with subject and only this message.
|
||||||
|
func (s *JLWriter) WriteMsg(lm *LogMsg) error {
|
||||||
|
if lm.Level > s.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
text := s.formatter.Format(lm)
|
||||||
|
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("authorName", s.AuthorName)
|
||||||
|
form.Add("title", s.Title)
|
||||||
|
form.Add("text", text)
|
||||||
|
if s.RedirectURL != "" {
|
||||||
|
form.Add("redirectUrl", s.RedirectURL)
|
||||||
|
}
|
||||||
|
if s.ImageURL != "" {
|
||||||
|
form.Add("imageUrl", s.ImageURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.PostForm(s.WebhookURL, form)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (s *JLWriter) Flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy implementing method. empty.
|
||||||
|
func (s *JLWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterJianLiao, newJLWriter)
|
||||||
|
}
|
@ -0,0 +1,782 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package logs provide a general log interface
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// import "github.com/beego/beego/v2/core/logs"
|
||||||
|
//
|
||||||
|
// log := NewLogger(10000)
|
||||||
|
// log.SetLogger("console", "")
|
||||||
|
//
|
||||||
|
// > the first params stand for how many channel
|
||||||
|
//
|
||||||
|
// Use it like this:
|
||||||
|
//
|
||||||
|
// log.Trace("trace")
|
||||||
|
// log.Info("info")
|
||||||
|
// log.Warn("warning")
|
||||||
|
// log.Debug("debug")
|
||||||
|
// log.Critical("critical")
|
||||||
|
//
|
||||||
|
// more docs http://beego.vip/docs/module/logs.md
|
||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RFC5424 log message levels.
|
||||||
|
const (
|
||||||
|
LevelEmergency = iota
|
||||||
|
LevelAlert
|
||||||
|
LevelCritical
|
||||||
|
LevelError
|
||||||
|
LevelWarning
|
||||||
|
LevelNotice
|
||||||
|
LevelInformational
|
||||||
|
LevelDebug
|
||||||
|
)
|
||||||
|
|
||||||
|
// levelLogLogger is defined to implement log.Logger
|
||||||
|
// the real log level will be LevelEmergency
|
||||||
|
const levelLoggerImpl = -1
|
||||||
|
|
||||||
|
// Name for adapter with beego official support
|
||||||
|
const (
|
||||||
|
AdapterConsole = "console"
|
||||||
|
AdapterFile = "file"
|
||||||
|
AdapterMultiFile = "multifile"
|
||||||
|
AdapterMail = "smtp"
|
||||||
|
AdapterConn = "conn"
|
||||||
|
AdapterEs = "es"
|
||||||
|
AdapterJianLiao = "jianliao"
|
||||||
|
AdapterSlack = "slack"
|
||||||
|
AdapterAliLS = "alils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Legacy log level constants to ensure backwards compatibility.
|
||||||
|
const (
|
||||||
|
LevelInfo = LevelInformational
|
||||||
|
LevelTrace = LevelDebug
|
||||||
|
LevelWarn = LevelWarning
|
||||||
|
)
|
||||||
|
|
||||||
|
type newLoggerFunc func() Logger
|
||||||
|
|
||||||
|
// Logger defines the behavior of a log provider.
|
||||||
|
type Logger interface {
|
||||||
|
Init(config string) error
|
||||||
|
WriteMsg(lm *LogMsg) error
|
||||||
|
Destroy()
|
||||||
|
Flush()
|
||||||
|
SetFormatter(f LogFormatter)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
adapters = make(map[string]newLoggerFunc)
|
||||||
|
levelPrefix = [LevelDebug + 1]string{"[M]", "[A]", "[C]", "[E]", "[W]", "[N]", "[I]", "[D]"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register makes a log provide available by the provided name.
|
||||||
|
// If Register is called twice with the same name or if driver is nil,
|
||||||
|
// it panics.
|
||||||
|
func Register(name string, log newLoggerFunc) {
|
||||||
|
if log == nil {
|
||||||
|
panic("logs: Register provide is nil")
|
||||||
|
}
|
||||||
|
if _, dup := adapters[name]; dup {
|
||||||
|
panic("logs: Register called twice for provider " + name)
|
||||||
|
}
|
||||||
|
adapters[name] = log
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeeLogger is default logger in beego application.
|
||||||
|
// Can contain several providers and log message into all providers.
|
||||||
|
type BeeLogger struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
init bool
|
||||||
|
enableFuncCallDepth bool
|
||||||
|
enableFullFilePath bool
|
||||||
|
asynchronous bool
|
||||||
|
wg sync.WaitGroup
|
||||||
|
level int
|
||||||
|
loggerFuncCallDepth int
|
||||||
|
prefix string
|
||||||
|
msgChanLen int64
|
||||||
|
msgChan chan *LogMsg
|
||||||
|
signalChan chan string
|
||||||
|
outputs []*nameLogger
|
||||||
|
globalFormatter string
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultAsyncMsgLen = 1e3
|
||||||
|
|
||||||
|
type nameLogger struct {
|
||||||
|
Logger
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var logMsgPool *sync.Pool
|
||||||
|
|
||||||
|
// NewLogger returns a new BeeLogger.
|
||||||
|
// channelLen: the number of messages in chan(used where asynchronous is true).
|
||||||
|
// if the buffering chan is full, logger adapters write to file or other way.
|
||||||
|
func NewLogger(channelLens ...int64) *BeeLogger {
|
||||||
|
bl := new(BeeLogger)
|
||||||
|
bl.level = LevelDebug
|
||||||
|
bl.loggerFuncCallDepth = 3
|
||||||
|
bl.msgChanLen = append(channelLens, 0)[0]
|
||||||
|
if bl.msgChanLen <= 0 {
|
||||||
|
bl.msgChanLen = defaultAsyncMsgLen
|
||||||
|
}
|
||||||
|
bl.signalChan = make(chan string, 1)
|
||||||
|
bl.setLogger(AdapterConsole)
|
||||||
|
return bl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async sets the log to asynchronous and start the goroutine
|
||||||
|
func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger {
|
||||||
|
bl.lock.Lock()
|
||||||
|
defer bl.lock.Unlock()
|
||||||
|
if bl.asynchronous {
|
||||||
|
return bl
|
||||||
|
}
|
||||||
|
bl.asynchronous = true
|
||||||
|
if len(msgLen) > 0 && msgLen[0] > 0 {
|
||||||
|
bl.msgChanLen = msgLen[0]
|
||||||
|
}
|
||||||
|
bl.msgChan = make(chan *LogMsg, bl.msgChanLen)
|
||||||
|
logMsgPool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &LogMsg{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
bl.wg.Add(1)
|
||||||
|
go bl.startLogger()
|
||||||
|
return bl
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger provides a given logger adapter into BeeLogger with config string.
|
||||||
|
// config must in in JSON format like {"interval":360}}
|
||||||
|
func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error {
|
||||||
|
config := append(configs, "{}")[0]
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
if l.name == adapterName {
|
||||||
|
return fmt.Errorf("logs: duplicate adaptername %q (you have set this logger before)", adapterName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logAdapter, ok := adapters[adapterName]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||||
|
}
|
||||||
|
|
||||||
|
lg := logAdapter()
|
||||||
|
|
||||||
|
// Global formatter overrides the default set formatter
|
||||||
|
if len(bl.globalFormatter) > 0 {
|
||||||
|
fmtr, ok := GetFormatter(bl.globalFormatter)
|
||||||
|
if !ok {
|
||||||
|
return errors.New(fmt.Sprintf("the formatter with name: %s not found", bl.globalFormatter))
|
||||||
|
}
|
||||||
|
lg.SetFormatter(fmtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := lg.Init(config)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger provides a given logger adapter into BeeLogger with config string.
|
||||||
|
// config must in in JSON format like {"interval":360}}
|
||||||
|
func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error {
|
||||||
|
bl.lock.Lock()
|
||||||
|
defer bl.lock.Unlock()
|
||||||
|
if !bl.init {
|
||||||
|
bl.outputs = []*nameLogger{}
|
||||||
|
bl.init = true
|
||||||
|
}
|
||||||
|
return bl.setLogger(adapterName, configs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelLogger removes a logger adapter in BeeLogger.
|
||||||
|
func (bl *BeeLogger) DelLogger(adapterName string) error {
|
||||||
|
bl.lock.Lock()
|
||||||
|
defer bl.lock.Unlock()
|
||||||
|
outputs := []*nameLogger{}
|
||||||
|
for _, lg := range bl.outputs {
|
||||||
|
if lg.name == adapterName {
|
||||||
|
lg.Destroy()
|
||||||
|
} else {
|
||||||
|
outputs = append(outputs, lg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(outputs) == len(bl.outputs) {
|
||||||
|
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||||
|
}
|
||||||
|
bl.outputs = outputs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *BeeLogger) writeToLoggers(lm *LogMsg) {
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
err := l.WriteMsg(lm)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *BeeLogger) Write(p []byte) (n int, err error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
// writeMsg will always add a '\n' character
|
||||||
|
if p[len(p)-1] == '\n' {
|
||||||
|
p = p[0 : len(p)-1]
|
||||||
|
}
|
||||||
|
lm := &LogMsg{
|
||||||
|
Msg: string(p),
|
||||||
|
Level: levelLoggerImpl,
|
||||||
|
When: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// set levelLoggerImpl to ensure all log message will be write out
|
||||||
|
err = bl.writeMsg(lm)
|
||||||
|
if err == nil {
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *BeeLogger) writeMsg(lm *LogMsg) error {
|
||||||
|
if !bl.init {
|
||||||
|
bl.lock.Lock()
|
||||||
|
bl.setLogger(AdapterConsole)
|
||||||
|
bl.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file string
|
||||||
|
line int
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
|
||||||
|
_, file, line, ok = runtime.Caller(bl.loggerFuncCallDepth)
|
||||||
|
if !ok {
|
||||||
|
file = "???"
|
||||||
|
line = 0
|
||||||
|
}
|
||||||
|
lm.FilePath = file
|
||||||
|
lm.LineNumber = line
|
||||||
|
lm.Prefix = bl.prefix
|
||||||
|
|
||||||
|
lm.enableFullFilePath = bl.enableFullFilePath
|
||||||
|
lm.enableFuncCallDepth = bl.enableFuncCallDepth
|
||||||
|
|
||||||
|
// set level info in front of filename info
|
||||||
|
if lm.Level == levelLoggerImpl {
|
||||||
|
// set to emergency to ensure all log will be print out correctly
|
||||||
|
lm.Level = LevelEmergency
|
||||||
|
}
|
||||||
|
|
||||||
|
if bl.asynchronous {
|
||||||
|
logM := logMsgPool.Get().(*LogMsg)
|
||||||
|
logM.Level = lm.Level
|
||||||
|
logM.Msg = lm.Msg
|
||||||
|
logM.When = lm.When
|
||||||
|
logM.Args = lm.Args
|
||||||
|
logM.FilePath = lm.FilePath
|
||||||
|
logM.LineNumber = lm.LineNumber
|
||||||
|
logM.Prefix = lm.Prefix
|
||||||
|
if bl.outputs != nil {
|
||||||
|
bl.msgChan <- lm
|
||||||
|
} else {
|
||||||
|
logMsgPool.Put(lm)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bl.writeToLoggers(lm)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets log message level.
|
||||||
|
// If message level (such as LevelDebug) is higher than logger level (such as LevelWarning),
|
||||||
|
// log providers will not be sent the message.
|
||||||
|
func (bl *BeeLogger) SetLevel(l int) {
|
||||||
|
bl.level = l
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLevel Get Current log message level.
|
||||||
|
func (bl *BeeLogger) GetLevel() int {
|
||||||
|
return bl.level
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogFuncCallDepth set log funcCallDepth
|
||||||
|
func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
|
||||||
|
bl.loggerFuncCallDepth = d
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogFuncCallDepth return log funcCallDepth for wrapper
|
||||||
|
func (bl *BeeLogger) GetLogFuncCallDepth() int {
|
||||||
|
return bl.loggerFuncCallDepth
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableFuncCallDepth enable log funcCallDepth
|
||||||
|
func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
|
||||||
|
bl.enableFuncCallDepth = b
|
||||||
|
}
|
||||||
|
|
||||||
|
// set prefix
|
||||||
|
func (bl *BeeLogger) SetPrefix(s string) {
|
||||||
|
bl.prefix = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// start logger chan reading.
|
||||||
|
// when chan is not empty, write logs.
|
||||||
|
func (bl *BeeLogger) startLogger() {
|
||||||
|
gameOver := false
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case bm := <-bl.msgChan:
|
||||||
|
bl.writeToLoggers(bm)
|
||||||
|
logMsgPool.Put(bm)
|
||||||
|
case sg := <-bl.signalChan:
|
||||||
|
// Now should only send "flush" or "close" to bl.signalChan
|
||||||
|
bl.flush()
|
||||||
|
if sg == "close" {
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
l.Destroy()
|
||||||
|
}
|
||||||
|
bl.outputs = nil
|
||||||
|
gameOver = true
|
||||||
|
}
|
||||||
|
bl.wg.Done()
|
||||||
|
}
|
||||||
|
if gameOver {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *BeeLogger) setGlobalFormatter(fmtter string) error {
|
||||||
|
bl.globalFormatter = fmtter
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGlobalFormatter sets the global formatter for all log adapters
|
||||||
|
// don't forget to register the formatter by invoking RegisterFormatter
|
||||||
|
func SetGlobalFormatter(fmtter string) error {
|
||||||
|
return beeLogger.setGlobalFormatter(fmtter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emergency Log EMERGENCY level message.
|
||||||
|
func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
|
||||||
|
if LevelEmergency > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lm := &LogMsg{
|
||||||
|
Level: LevelEmergency,
|
||||||
|
Msg: format,
|
||||||
|
When: time.Now(),
|
||||||
|
}
|
||||||
|
if len(v) > 0 {
|
||||||
|
lm.Msg = fmt.Sprintf(lm.Msg, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
bl.writeMsg(lm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert Log ALERT level message.
|
||||||
|
func (bl *BeeLogger) Alert(format string, v ...interface{}) {
|
||||||
|
if LevelAlert > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lm := &LogMsg{
|
||||||
|
Level: LevelAlert,
|
||||||
|
Msg: format,
|
||||||
|
When: time.Now(),
|
||||||
|
Args: v,
|
||||||
|
}
|
||||||
|
bl.writeMsg(lm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Critical Log CRITICAL level message.
|
||||||
|
func (bl *BeeLogger) Critical(format string, v ...interface{}) {
|
||||||
|
if LevelCritical > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lm := &LogMsg{
|
||||||
|
Level: LevelCritical,
|
||||||
|
Msg: format,
|
||||||
|
When: time.Now(),
|
||||||
|
Args: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
bl.writeMsg(lm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error Log ERROR level message.
|
||||||
|
func (bl *BeeLogger) Error(format string, v ...interface{}) {
|
||||||
|
if LevelError > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lm := &LogMsg{
|
||||||
|
Level: LevelError,
|
||||||
|
Msg: format,
|
||||||
|
When: time.Now(),
|
||||||
|
Args: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
bl.writeMsg(lm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning Log WARNING level message.
|
||||||
|
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
|
||||||
|
if LevelWarn > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lm := &LogMsg{
|
||||||
|
Level: LevelWarn,
|
||||||
|
Msg: format,
|
||||||
|
When: time.Now(),
|
||||||
|
Args: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
bl.writeMsg(lm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notice Log NOTICE level message.
|
||||||
|
func (bl *BeeLogger) Notice(format string, v ...interface{}) {
|
||||||
|
if LevelNotice > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lm := &LogMsg{
|
||||||
|
Level: LevelNotice,
|
||||||
|
Msg: format,
|
||||||
|
When: time.Now(),
|
||||||
|
Args: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
bl.writeMsg(lm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Informational Log INFORMATIONAL level message.
|
||||||
|
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
|
||||||
|
if LevelInfo > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lm := &LogMsg{
|
||||||
|
Level: LevelInfo,
|
||||||
|
Msg: format,
|
||||||
|
When: time.Now(),
|
||||||
|
Args: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
bl.writeMsg(lm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug Log DEBUG level message.
|
||||||
|
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
|
||||||
|
if LevelDebug > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lm := &LogMsg{
|
||||||
|
Level: LevelDebug,
|
||||||
|
Msg: format,
|
||||||
|
When: time.Now(),
|
||||||
|
Args: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
bl.writeMsg(lm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn Log WARN level message.
|
||||||
|
// compatibility alias for Warning()
|
||||||
|
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
|
||||||
|
if LevelWarn > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lm := &LogMsg{
|
||||||
|
Level: LevelWarn,
|
||||||
|
Msg: format,
|
||||||
|
When: time.Now(),
|
||||||
|
Args: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
bl.writeMsg(lm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info Log INFO level message.
|
||||||
|
// compatibility alias for Informational()
|
||||||
|
func (bl *BeeLogger) Info(format string, v ...interface{}) {
|
||||||
|
if LevelInfo > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lm := &LogMsg{
|
||||||
|
Level: LevelInfo,
|
||||||
|
Msg: format,
|
||||||
|
When: time.Now(),
|
||||||
|
Args: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
bl.writeMsg(lm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace Log TRACE level message.
|
||||||
|
// compatibility alias for Debug()
|
||||||
|
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
|
||||||
|
if LevelDebug > bl.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lm := &LogMsg{
|
||||||
|
Level: LevelDebug,
|
||||||
|
Msg: format,
|
||||||
|
When: time.Now(),
|
||||||
|
Args: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
bl.writeMsg(lm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush flush all chan data.
|
||||||
|
func (bl *BeeLogger) Flush() {
|
||||||
|
if bl.asynchronous {
|
||||||
|
bl.signalChan <- "flush"
|
||||||
|
bl.wg.Wait()
|
||||||
|
bl.wg.Add(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bl.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close close logger, flush all chan data and destroy all adapters in BeeLogger.
|
||||||
|
func (bl *BeeLogger) Close() {
|
||||||
|
if bl.asynchronous {
|
||||||
|
bl.signalChan <- "close"
|
||||||
|
bl.wg.Wait()
|
||||||
|
close(bl.msgChan)
|
||||||
|
} else {
|
||||||
|
bl.flush()
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
l.Destroy()
|
||||||
|
}
|
||||||
|
bl.outputs = nil
|
||||||
|
}
|
||||||
|
close(bl.signalChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset close all outputs, and set bl.outputs to nil
|
||||||
|
func (bl *BeeLogger) Reset() {
|
||||||
|
bl.Flush()
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
l.Destroy()
|
||||||
|
}
|
||||||
|
bl.outputs = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bl *BeeLogger) flush() {
|
||||||
|
if bl.asynchronous {
|
||||||
|
for {
|
||||||
|
if len(bl.msgChan) > 0 {
|
||||||
|
bm := <-bl.msgChan
|
||||||
|
bl.writeToLoggers(bm)
|
||||||
|
logMsgPool.Put(bm)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, l := range bl.outputs {
|
||||||
|
l.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// beeLogger references the used application logger.
|
||||||
|
var beeLogger = NewLogger()
|
||||||
|
|
||||||
|
// GetBeeLogger returns the default BeeLogger
|
||||||
|
func GetBeeLogger() *BeeLogger {
|
||||||
|
return beeLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
var beeLoggerMap = struct {
|
||||||
|
sync.RWMutex
|
||||||
|
logs map[string]*log.Logger
|
||||||
|
}{
|
||||||
|
logs: map[string]*log.Logger{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogger returns the default BeeLogger
|
||||||
|
func GetLogger(prefixes ...string) *log.Logger {
|
||||||
|
prefix := append(prefixes, "")[0]
|
||||||
|
if prefix != "" {
|
||||||
|
prefix = fmt.Sprintf(`[%s] `, strings.ToUpper(prefix))
|
||||||
|
}
|
||||||
|
beeLoggerMap.RLock()
|
||||||
|
l, ok := beeLoggerMap.logs[prefix]
|
||||||
|
if ok {
|
||||||
|
beeLoggerMap.RUnlock()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
beeLoggerMap.RUnlock()
|
||||||
|
beeLoggerMap.Lock()
|
||||||
|
defer beeLoggerMap.Unlock()
|
||||||
|
l, ok = beeLoggerMap.logs[prefix]
|
||||||
|
if !ok {
|
||||||
|
l = log.New(beeLogger, prefix, 0)
|
||||||
|
beeLoggerMap.logs[prefix] = l
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableFullFilePath enables full file path logging. Disabled by default
|
||||||
|
// e.g "/home/Documents/GitHub/beego/mainapp/" instead of "mainapp"
|
||||||
|
func EnableFullFilePath(b bool) {
|
||||||
|
beeLogger.enableFullFilePath = b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset will remove all the adapter
|
||||||
|
func Reset() {
|
||||||
|
beeLogger.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async set the beelogger with Async mode and hold msglen messages
|
||||||
|
func Async(msgLen ...int64) *BeeLogger {
|
||||||
|
return beeLogger.Async(msgLen...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets the global log level used by the simple logger.
|
||||||
|
func SetLevel(l int) {
|
||||||
|
beeLogger.SetLevel(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrefix sets the prefix
|
||||||
|
func SetPrefix(s string) {
|
||||||
|
beeLogger.SetPrefix(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableFuncCallDepth enable log funcCallDepth
|
||||||
|
func EnableFuncCallDepth(b bool) {
|
||||||
|
beeLogger.enableFuncCallDepth = b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogFuncCall set the CallDepth, default is 4
|
||||||
|
func SetLogFuncCall(b bool) {
|
||||||
|
beeLogger.EnableFuncCallDepth(b)
|
||||||
|
beeLogger.SetLogFuncCallDepth(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogFuncCallDepth set log funcCallDepth
|
||||||
|
func SetLogFuncCallDepth(d int) {
|
||||||
|
beeLogger.loggerFuncCallDepth = d
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger sets a new logger.
|
||||||
|
func SetLogger(adapter string, config ...string) error {
|
||||||
|
return beeLogger.SetLogger(adapter, config...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emergency logs a message at emergency level.
|
||||||
|
func Emergency(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Emergency(formatPattern(f, v...), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alert logs a message at alert level.
|
||||||
|
func Alert(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Alert(formatPattern(f, v...), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Critical logs a message at critical level.
|
||||||
|
func Critical(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Critical(formatPattern(f, v...), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs a message at error level.
|
||||||
|
func Error(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Error(formatPattern(f, v...), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning logs a message at warning level.
|
||||||
|
func Warning(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Warn(formatPattern(f, v...), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn compatibility alias for Warning()
|
||||||
|
func Warn(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Warn(formatPattern(f, v...), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notice logs a message at notice level.
|
||||||
|
func Notice(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Notice(formatPattern(f, v...), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Informational logs a message at info level.
|
||||||
|
func Informational(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Info(formatPattern(f, v...), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info compatibility alias for Warning()
|
||||||
|
func Info(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Info(formatPattern(f, v...), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a message at debug level.
|
||||||
|
func Debug(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Debug(formatPattern(f, v...), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace logs a message at trace level.
|
||||||
|
// compatibility alias for Warning()
|
||||||
|
func Trace(f interface{}, v ...interface{}) {
|
||||||
|
beeLogger.Trace(formatPattern(f, v...), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatPattern(f interface{}, v ...interface{}) string {
|
||||||
|
var msg string
|
||||||
|
switch f.(type) {
|
||||||
|
case string:
|
||||||
|
msg = f.(string)
|
||||||
|
if len(v) == 0 {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
if !strings.Contains(msg, "%") {
|
||||||
|
// do not contain format char
|
||||||
|
msg += strings.Repeat(" %v", len(v))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
msg = fmt.Sprint(f)
|
||||||
|
if len(v) == 0 {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
msg += strings.Repeat(" %v", len(v))
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright 2020
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogMsg struct {
|
||||||
|
Level int
|
||||||
|
Msg string
|
||||||
|
When time.Time
|
||||||
|
FilePath string
|
||||||
|
LineNumber int
|
||||||
|
Args []interface{}
|
||||||
|
Prefix string
|
||||||
|
enableFullFilePath bool
|
||||||
|
enableFuncCallDepth bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// OldStyleFormat you should never invoke this
|
||||||
|
func (lm *LogMsg) OldStyleFormat() string {
|
||||||
|
msg := lm.Msg
|
||||||
|
|
||||||
|
if len(lm.Args) > 0 {
|
||||||
|
msg = fmt.Sprintf(lm.Msg, lm.Args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = lm.Prefix + " " + msg
|
||||||
|
|
||||||
|
if lm.enableFuncCallDepth {
|
||||||
|
filePath := lm.FilePath
|
||||||
|
if !lm.enableFullFilePath {
|
||||||
|
_, filePath = path.Split(filePath)
|
||||||
|
}
|
||||||
|
msg = fmt.Sprintf("[%s:%d] %s", filePath, lm.LineNumber, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = levelPrefix[lm.Level] + " " + msg
|
||||||
|
return msg
|
||||||
|
}
|
@ -0,0 +1,178 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type logWriter struct {
|
||||||
|
sync.Mutex
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogWriter(wr io.Writer) *logWriter {
|
||||||
|
return &logWriter{writer: wr}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lg *logWriter) writeln(msg string) (int, error) {
|
||||||
|
lg.Lock()
|
||||||
|
msg += "\n"
|
||||||
|
n, err := lg.writer.Write([]byte(msg))
|
||||||
|
lg.Unlock()
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
y1 = `0123456789`
|
||||||
|
y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||||
|
y3 = `0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999`
|
||||||
|
y4 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||||
|
mo1 = `000000000111`
|
||||||
|
mo2 = `123456789012`
|
||||||
|
d1 = `0000000001111111111222222222233`
|
||||||
|
d2 = `1234567890123456789012345678901`
|
||||||
|
h1 = `000000000011111111112222`
|
||||||
|
h2 = `012345678901234567890123`
|
||||||
|
mi1 = `000000000011111111112222222222333333333344444444445555555555`
|
||||||
|
mi2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||||
|
s1 = `000000000011111111112222222222333333333344444444445555555555`
|
||||||
|
s2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||||
|
ns1 = `0123456789`
|
||||||
|
)
|
||||||
|
|
||||||
|
func formatTimeHeader(when time.Time) ([]byte, int, int) {
|
||||||
|
y, mo, d := when.Date()
|
||||||
|
h, mi, s := when.Clock()
|
||||||
|
ns := when.Nanosecond() / 1000000
|
||||||
|
// len("2006/01/02 15:04:05.123 ")==24
|
||||||
|
var buf [24]byte
|
||||||
|
|
||||||
|
buf[0] = y1[y/1000%10]
|
||||||
|
buf[1] = y2[y/100]
|
||||||
|
buf[2] = y3[y-y/100*100]
|
||||||
|
buf[3] = y4[y-y/100*100]
|
||||||
|
buf[4] = '/'
|
||||||
|
buf[5] = mo1[mo-1]
|
||||||
|
buf[6] = mo2[mo-1]
|
||||||
|
buf[7] = '/'
|
||||||
|
buf[8] = d1[d-1]
|
||||||
|
buf[9] = d2[d-1]
|
||||||
|
buf[10] = ' '
|
||||||
|
buf[11] = h1[h]
|
||||||
|
buf[12] = h2[h]
|
||||||
|
buf[13] = ':'
|
||||||
|
buf[14] = mi1[mi]
|
||||||
|
buf[15] = mi2[mi]
|
||||||
|
buf[16] = ':'
|
||||||
|
buf[17] = s1[s]
|
||||||
|
buf[18] = s2[s]
|
||||||
|
buf[19] = '.'
|
||||||
|
buf[20] = ns1[ns/100]
|
||||||
|
buf[21] = ns1[ns%100/10]
|
||||||
|
buf[22] = ns1[ns%10]
|
||||||
|
|
||||||
|
buf[23] = ' '
|
||||||
|
|
||||||
|
return buf[0:], d, h
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
|
||||||
|
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
|
||||||
|
yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109})
|
||||||
|
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
|
||||||
|
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
|
||||||
|
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
|
||||||
|
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109})
|
||||||
|
|
||||||
|
w32Green = string([]byte{27, 91, 52, 50, 109})
|
||||||
|
w32White = string([]byte{27, 91, 52, 55, 109})
|
||||||
|
w32Yellow = string([]byte{27, 91, 52, 51, 109})
|
||||||
|
w32Red = string([]byte{27, 91, 52, 49, 109})
|
||||||
|
w32Blue = string([]byte{27, 91, 52, 52, 109})
|
||||||
|
w32Magenta = string([]byte{27, 91, 52, 53, 109})
|
||||||
|
w32Cyan = string([]byte{27, 91, 52, 54, 109})
|
||||||
|
|
||||||
|
reset = string([]byte{27, 91, 48, 109})
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
colorMap map[string]string
|
||||||
|
)
|
||||||
|
|
||||||
|
func initColor() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
green = w32Green
|
||||||
|
white = w32White
|
||||||
|
yellow = w32Yellow
|
||||||
|
red = w32Red
|
||||||
|
blue = w32Blue
|
||||||
|
magenta = w32Magenta
|
||||||
|
cyan = w32Cyan
|
||||||
|
}
|
||||||
|
colorMap = map[string]string{
|
||||||
|
// by color
|
||||||
|
"green": green,
|
||||||
|
"white": white,
|
||||||
|
"yellow": yellow,
|
||||||
|
"red": red,
|
||||||
|
// by method
|
||||||
|
"GET": blue,
|
||||||
|
"POST": cyan,
|
||||||
|
"PUT": yellow,
|
||||||
|
"DELETE": red,
|
||||||
|
"PATCH": green,
|
||||||
|
"HEAD": magenta,
|
||||||
|
"OPTIONS": white,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColorByStatus return color by http code
|
||||||
|
// 2xx return Green
|
||||||
|
// 3xx return White
|
||||||
|
// 4xx return Yellow
|
||||||
|
// 5xx return Red
|
||||||
|
func ColorByStatus(code int) string {
|
||||||
|
once.Do(initColor)
|
||||||
|
switch {
|
||||||
|
case code >= 200 && code < 300:
|
||||||
|
return colorMap["green"]
|
||||||
|
case code >= 300 && code < 400:
|
||||||
|
return colorMap["white"]
|
||||||
|
case code >= 400 && code < 500:
|
||||||
|
return colorMap["yellow"]
|
||||||
|
default:
|
||||||
|
return colorMap["red"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColorByMethod return color by http code
|
||||||
|
func ColorByMethod(method string) string {
|
||||||
|
once.Do(initColor)
|
||||||
|
if c := colorMap[method]; c != "" {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return reset
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetColor return reset color
|
||||||
|
func ResetColor() string {
|
||||||
|
return reset
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A filesLogWriter manages several fileLogWriter
|
||||||
|
// filesLogWriter will write logs to the file in json configuration and write the same level log to correspond file
|
||||||
|
// means if the file name in configuration is project.log filesLogWriter will create project.error.log/project.debug.log
|
||||||
|
// and write the error-level logs to project.error.log and write the debug-level logs to project.debug.log
|
||||||
|
// the rotate attribute also acts like fileLogWriter
|
||||||
|
type multiFileLogWriter struct {
|
||||||
|
writers [LevelDebug + 1 + 1]*fileLogWriter // the last one for fullLogWriter
|
||||||
|
fullLogWriter *fileLogWriter
|
||||||
|
Separate []string `json:"separate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelNames = [...]string{"emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"}
|
||||||
|
|
||||||
|
// Init file logger with json config.
|
||||||
|
// jsonConfig like:
|
||||||
|
// {
|
||||||
|
// "filename":"logs/beego.log",
|
||||||
|
// "maxLines":0,
|
||||||
|
// "maxsize":0,
|
||||||
|
// "daily":true,
|
||||||
|
// "maxDays":15,
|
||||||
|
// "rotate":true,
|
||||||
|
// "perm":0600,
|
||||||
|
// "separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"],
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (f *multiFileLogWriter) Init(config string) error {
|
||||||
|
writer := newFileWriter().(*fileLogWriter)
|
||||||
|
err := writer.Init(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.fullLogWriter = writer
|
||||||
|
f.writers[LevelDebug+1] = writer
|
||||||
|
|
||||||
|
// unmarshal "separate" field to f.Separate
|
||||||
|
err = json.Unmarshal([]byte(config), f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonMap := map[string]interface{}{}
|
||||||
|
err = json.Unmarshal([]byte(config), &jsonMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := LevelEmergency; i < LevelDebug+1; i++ {
|
||||||
|
for _, v := range f.Separate {
|
||||||
|
if v == levelNames[i] {
|
||||||
|
jsonMap["filename"] = f.fullLogWriter.fileNameOnly + "." + levelNames[i] + f.fullLogWriter.suffix
|
||||||
|
jsonMap["level"] = i
|
||||||
|
bs, _ := json.Marshal(jsonMap)
|
||||||
|
writer = newFileWriter().(*fileLogWriter)
|
||||||
|
err := writer.Init(string(bs))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.writers[i] = writer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*multiFileLogWriter) Format(lm *LogMsg) string {
|
||||||
|
return lm.OldStyleFormat()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *multiFileLogWriter) SetFormatter(fmt LogFormatter) {
|
||||||
|
f.fullLogWriter.SetFormatter(fmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *multiFileLogWriter) Destroy() {
|
||||||
|
for i := 0; i < len(f.writers); i++ {
|
||||||
|
if f.writers[i] != nil {
|
||||||
|
f.writers[i].Destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *multiFileLogWriter) WriteMsg(lm *LogMsg) error {
|
||||||
|
if f.fullLogWriter != nil {
|
||||||
|
f.fullLogWriter.WriteMsg(lm)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(f.writers)-1; i++ {
|
||||||
|
if f.writers[i] != nil {
|
||||||
|
if lm.Level == f.writers[i].Level {
|
||||||
|
f.writers[i].WriteMsg(lm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *multiFileLogWriter) Flush() {
|
||||||
|
for i := 0; i < len(f.writers); i++ {
|
||||||
|
if f.writers[i] != nil {
|
||||||
|
f.writers[i].Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFilesWriter create a FileLogWriter returning as LoggerInterface.
|
||||||
|
func newFilesWriter() Logger {
|
||||||
|
res := &multiFileLogWriter{}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterMultiFile, newFilesWriter)
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SLACKWriter implements beego LoggerInterface and is used to send jiaoliao webhook
|
||||||
|
type SLACKWriter struct {
|
||||||
|
WebhookURL string `json:"webhookurl"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
formatter LogFormatter
|
||||||
|
Formatter string `json:"formatter"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSLACKWriter creates jiaoliao writer.
|
||||||
|
func newSLACKWriter() Logger {
|
||||||
|
res := &SLACKWriter{Level: LevelTrace}
|
||||||
|
res.formatter = res
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SLACKWriter) Format(lm *LogMsg) string {
|
||||||
|
// text := fmt.Sprintf("{\"text\": \"%s\"}", msg)
|
||||||
|
return lm.When.Format("2006-01-02 15:04:05") + " " + lm.OldStyleFormat()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SLACKWriter) SetFormatter(f LogFormatter) {
|
||||||
|
s.formatter = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init SLACKWriter with json config string
|
||||||
|
func (s *SLACKWriter) Init(config string) error {
|
||||||
|
res := json.Unmarshal([]byte(config), s)
|
||||||
|
|
||||||
|
if res == nil && len(s.Formatter) > 0 {
|
||||||
|
fmtr, ok := GetFormatter(s.Formatter)
|
||||||
|
if !ok {
|
||||||
|
return errors.New(fmt.Sprintf("the formatter with name: %s not found", s.Formatter))
|
||||||
|
}
|
||||||
|
s.formatter = fmtr
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg write message in smtp writer.
|
||||||
|
// Sends an email with subject and only this message.
|
||||||
|
func (s *SLACKWriter) WriteMsg(lm *LogMsg) error {
|
||||||
|
if lm.Level > s.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
msg := s.Format(lm)
|
||||||
|
m := make(map[string]string, 1)
|
||||||
|
m["text"] = msg
|
||||||
|
|
||||||
|
body, _ := json.Marshal(m)
|
||||||
|
// resp, err := http.PostForm(s.WebhookURL, form)
|
||||||
|
resp, err := http.Post(s.WebhookURL, "application/json", bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (s *SLACKWriter) Flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy implementing method. empty.
|
||||||
|
func (s *SLACKWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterSlack, newSLACKWriter)
|
||||||
|
}
|
@ -0,0 +1,172 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package logs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/smtp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SMTPWriter implements LoggerInterface and is used to send emails via given SMTP-server.
|
||||||
|
type SMTPWriter struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
FromAddress string `json:"fromAddress"`
|
||||||
|
RecipientAddresses []string `json:"sendTos"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
formatter LogFormatter
|
||||||
|
Formatter string `json:"formatter"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSMTPWriter creates the smtp writer.
|
||||||
|
func newSMTPWriter() Logger {
|
||||||
|
res := &SMTPWriter{Level: LevelTrace}
|
||||||
|
res.formatter = res
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init smtp writer with json config.
|
||||||
|
// config like:
|
||||||
|
// {
|
||||||
|
// "username":"example@gmail.com",
|
||||||
|
// "password:"password",
|
||||||
|
// "host":"smtp.gmail.com:465",
|
||||||
|
// "subject":"email title",
|
||||||
|
// "fromAddress":"from@example.com",
|
||||||
|
// "sendTos":["email1","email2"],
|
||||||
|
// "level":LevelError
|
||||||
|
// }
|
||||||
|
func (s *SMTPWriter) Init(config string) error {
|
||||||
|
res := json.Unmarshal([]byte(config), s)
|
||||||
|
if res == nil && len(s.Formatter) > 0 {
|
||||||
|
fmtr, ok := GetFormatter(s.Formatter)
|
||||||
|
if !ok {
|
||||||
|
return errors.New(fmt.Sprintf("the formatter with name: %s not found", s.Formatter))
|
||||||
|
}
|
||||||
|
s.formatter = fmtr
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth {
|
||||||
|
if len(strings.Trim(s.Username, " ")) == 0 && len(strings.Trim(s.Password, " ")) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return smtp.PlainAuth(
|
||||||
|
"",
|
||||||
|
s.Username,
|
||||||
|
s.Password,
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SMTPWriter) SetFormatter(f LogFormatter) {
|
||||||
|
s.formatter = f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error {
|
||||||
|
client, err := smtp.Dial(hostAddressWithPort)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
host, _, _ := net.SplitHostPort(hostAddressWithPort)
|
||||||
|
tlsConn := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
ServerName: host,
|
||||||
|
}
|
||||||
|
if err = client.StartTLS(tlsConn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if auth != nil {
|
||||||
|
if err = client.Auth(auth); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = client.Mail(fromAddress); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rec := range recipients {
|
||||||
|
if err = client.Rcpt(rec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := client.Data()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = w.Write(msgContent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.Quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SMTPWriter) Format(lm *LogMsg) string {
|
||||||
|
return lm.OldStyleFormat()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg writes message in smtp writer.
|
||||||
|
// Sends an email with subject and only this message.
|
||||||
|
func (s *SMTPWriter) WriteMsg(lm *LogMsg) error {
|
||||||
|
if lm.Level > s.Level {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hp := strings.Split(s.Host, ":")
|
||||||
|
|
||||||
|
// Set up authentication information.
|
||||||
|
auth := s.getSMTPAuth(hp[0])
|
||||||
|
|
||||||
|
msg := s.Format(lm)
|
||||||
|
|
||||||
|
// Connect to the server, authenticate, set the sender and recipient,
|
||||||
|
// and send the email all in one step.
|
||||||
|
contentType := "Content-Type: text/plain" + "; charset=UTF-8"
|
||||||
|
mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress +
|
||||||
|
">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", lm.When.Format("2006-01-02 15:04:05")) + msg)
|
||||||
|
|
||||||
|
return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implementing method. empty.
|
||||||
|
func (s *SMTPWriter) Flush() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy implementing method. empty.
|
||||||
|
func (s *SMTPWriter) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(AdapterMail, newSMTPWriter)
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetFuncName get function name
|
||||||
|
func GetFuncName(i interface{}) string {
|
||||||
|
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
||||||
|
}
|
@ -0,0 +1,478 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dunno = []byte("???")
|
||||||
|
centerDot = []byte("·")
|
||||||
|
dot = []byte(".")
|
||||||
|
)
|
||||||
|
|
||||||
|
type pointerInfo struct {
|
||||||
|
prev *pointerInfo
|
||||||
|
n int
|
||||||
|
addr uintptr
|
||||||
|
pos int
|
||||||
|
used []int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display print the data in console
|
||||||
|
func Display(data ...interface{}) {
|
||||||
|
display(true, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDisplayString return data print string
|
||||||
|
func GetDisplayString(data ...interface{}) string {
|
||||||
|
return display(false, data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func display(displayed bool, data ...interface{}) string {
|
||||||
|
pc, file, line, ok := runtime.Caller(2)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
fmt.Fprintf(buf, "[Debug] at %s() [%s:%d]\n", function(pc), file, line)
|
||||||
|
|
||||||
|
fmt.Fprintf(buf, "\n[Variables]\n")
|
||||||
|
|
||||||
|
for i := 0; i < len(data); i += 2 {
|
||||||
|
output := fomateinfo(len(data[i].(string))+3, data[i+1])
|
||||||
|
fmt.Fprintf(buf, "%s = %s", data[i], output)
|
||||||
|
}
|
||||||
|
|
||||||
|
if displayed {
|
||||||
|
log.Print(buf)
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// return data dump and format bytes
|
||||||
|
func fomateinfo(headlen int, data ...interface{}) []byte {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
if len(data) > 1 {
|
||||||
|
fmt.Fprint(buf, " ")
|
||||||
|
|
||||||
|
fmt.Fprint(buf, "[")
|
||||||
|
|
||||||
|
fmt.Fprintln(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range data {
|
||||||
|
buf2 := new(bytes.Buffer)
|
||||||
|
var pointers *pointerInfo
|
||||||
|
interfaces := make([]reflect.Value, 0, 10)
|
||||||
|
|
||||||
|
printKeyValue(buf2, reflect.ValueOf(v), &pointers, &interfaces, nil, true, " ", 1)
|
||||||
|
|
||||||
|
if k < len(data)-1 {
|
||||||
|
fmt.Fprint(buf2, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(buf2)
|
||||||
|
|
||||||
|
buf.Write(buf2.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) > 1 {
|
||||||
|
fmt.Fprintln(buf)
|
||||||
|
|
||||||
|
fmt.Fprint(buf, " ")
|
||||||
|
|
||||||
|
fmt.Fprint(buf, "]")
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// check data is golang basic type
|
||||||
|
func isSimpleType(val reflect.Value, kind reflect.Kind, pointers **pointerInfo, interfaces *[]reflect.Value) bool {
|
||||||
|
switch kind {
|
||||||
|
case reflect.Bool:
|
||||||
|
return true
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return true
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||||
|
return true
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return true
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return true
|
||||||
|
case reflect.String:
|
||||||
|
return true
|
||||||
|
case reflect.Chan:
|
||||||
|
return true
|
||||||
|
case reflect.Invalid:
|
||||||
|
return true
|
||||||
|
case reflect.Interface:
|
||||||
|
for _, in := range *interfaces {
|
||||||
|
if reflect.DeepEqual(in, val) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case reflect.UnsafePointer:
|
||||||
|
if val.IsNil() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
elem := val.Elem()
|
||||||
|
|
||||||
|
if isSimpleType(elem, elem.Kind(), pointers, interfaces) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := val.Elem().UnsafeAddr()
|
||||||
|
|
||||||
|
for p := *pointers; p != nil; p = p.prev {
|
||||||
|
if addr == p.addr {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump value
|
||||||
|
func printKeyValue(buf *bytes.Buffer, val reflect.Value, pointers **pointerInfo, interfaces *[]reflect.Value, structFilter func(string, string) bool, formatOutput bool, indent string, level int) {
|
||||||
|
t := val.Kind()
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case reflect.Bool:
|
||||||
|
fmt.Fprint(buf, val.Bool())
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
fmt.Fprint(buf, val.Int())
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||||
|
fmt.Fprint(buf, val.Uint())
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
fmt.Fprint(buf, val.Float())
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
fmt.Fprint(buf, val.Complex())
|
||||||
|
case reflect.UnsafePointer:
|
||||||
|
fmt.Fprintf(buf, "unsafe.Pointer(0x%X)", val.Pointer())
|
||||||
|
case reflect.Ptr:
|
||||||
|
if val.IsNil() {
|
||||||
|
fmt.Fprint(buf, "nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := val.Elem().UnsafeAddr()
|
||||||
|
|
||||||
|
for p := *pointers; p != nil; p = p.prev {
|
||||||
|
if addr == p.addr {
|
||||||
|
p.used = append(p.used, buf.Len())
|
||||||
|
fmt.Fprintf(buf, "0x%X", addr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*pointers = &pointerInfo{
|
||||||
|
prev: *pointers,
|
||||||
|
addr: addr,
|
||||||
|
pos: buf.Len(),
|
||||||
|
used: make([]int, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(buf, "&")
|
||||||
|
|
||||||
|
printKeyValue(buf, val.Elem(), pointers, interfaces, structFilter, formatOutput, indent, level)
|
||||||
|
case reflect.String:
|
||||||
|
fmt.Fprint(buf, "\"", val.String(), "\"")
|
||||||
|
case reflect.Interface:
|
||||||
|
value := val.Elem()
|
||||||
|
|
||||||
|
if !value.IsValid() {
|
||||||
|
fmt.Fprint(buf, "nil")
|
||||||
|
} else {
|
||||||
|
for _, in := range *interfaces {
|
||||||
|
if reflect.DeepEqual(in, val) {
|
||||||
|
fmt.Fprint(buf, "repeat")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*interfaces = append(*interfaces, val)
|
||||||
|
|
||||||
|
printKeyValue(buf, value, pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
t := val.Type()
|
||||||
|
|
||||||
|
fmt.Fprint(buf, t)
|
||||||
|
fmt.Fprint(buf, "{")
|
||||||
|
|
||||||
|
for i := 0; i < val.NumField(); i++ {
|
||||||
|
if formatOutput {
|
||||||
|
fmt.Fprintln(buf)
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(buf, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
name := t.Field(i).Name
|
||||||
|
|
||||||
|
if formatOutput {
|
||||||
|
for ind := 0; ind < level; ind++ {
|
||||||
|
fmt.Fprint(buf, indent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(buf, name)
|
||||||
|
fmt.Fprint(buf, ": ")
|
||||||
|
|
||||||
|
if structFilter != nil && structFilter(t.String(), name) {
|
||||||
|
fmt.Fprint(buf, "ignore")
|
||||||
|
} else {
|
||||||
|
printKeyValue(buf, val.Field(i), pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(buf, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
if formatOutput {
|
||||||
|
fmt.Fprintln(buf)
|
||||||
|
|
||||||
|
for ind := 0; ind < level-1; ind++ {
|
||||||
|
fmt.Fprint(buf, indent)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(buf, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(buf, "}")
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
fmt.Fprint(buf, val.Type())
|
||||||
|
fmt.Fprint(buf, "{")
|
||||||
|
|
||||||
|
allSimple := true
|
||||||
|
|
||||||
|
for i := 0; i < val.Len(); i++ {
|
||||||
|
elem := val.Index(i)
|
||||||
|
|
||||||
|
isSimple := isSimpleType(elem, elem.Kind(), pointers, interfaces)
|
||||||
|
|
||||||
|
if !isSimple {
|
||||||
|
allSimple = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if formatOutput && !isSimple {
|
||||||
|
fmt.Fprintln(buf)
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(buf, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if formatOutput && !isSimple {
|
||||||
|
for ind := 0; ind < level; ind++ {
|
||||||
|
fmt.Fprint(buf, indent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printKeyValue(buf, elem, pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||||
|
|
||||||
|
if i != val.Len()-1 || !allSimple {
|
||||||
|
fmt.Fprint(buf, ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if formatOutput && !allSimple {
|
||||||
|
fmt.Fprintln(buf)
|
||||||
|
|
||||||
|
for ind := 0; ind < level-1; ind++ {
|
||||||
|
fmt.Fprint(buf, indent)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(buf, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(buf, "}")
|
||||||
|
case reflect.Map:
|
||||||
|
t := val.Type()
|
||||||
|
keys := val.MapKeys()
|
||||||
|
|
||||||
|
fmt.Fprint(buf, t)
|
||||||
|
fmt.Fprint(buf, "{")
|
||||||
|
|
||||||
|
allSimple := true
|
||||||
|
|
||||||
|
for i := 0; i < len(keys); i++ {
|
||||||
|
elem := val.MapIndex(keys[i])
|
||||||
|
|
||||||
|
isSimple := isSimpleType(elem, elem.Kind(), pointers, interfaces)
|
||||||
|
|
||||||
|
if !isSimple {
|
||||||
|
allSimple = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if formatOutput && !isSimple {
|
||||||
|
fmt.Fprintln(buf)
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(buf, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if formatOutput && !isSimple {
|
||||||
|
for ind := 0; ind <= level; ind++ {
|
||||||
|
fmt.Fprint(buf, indent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printKeyValue(buf, keys[i], pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||||
|
fmt.Fprint(buf, ": ")
|
||||||
|
printKeyValue(buf, elem, pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||||
|
|
||||||
|
if i != val.Len()-1 || !allSimple {
|
||||||
|
fmt.Fprint(buf, ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if formatOutput && !allSimple {
|
||||||
|
fmt.Fprintln(buf)
|
||||||
|
|
||||||
|
for ind := 0; ind < level-1; ind++ {
|
||||||
|
fmt.Fprint(buf, indent)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(buf, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(buf, "}")
|
||||||
|
case reflect.Chan:
|
||||||
|
fmt.Fprint(buf, val.Type())
|
||||||
|
case reflect.Invalid:
|
||||||
|
fmt.Fprint(buf, "invalid")
|
||||||
|
default:
|
||||||
|
fmt.Fprint(buf, "unknow")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintPointerInfo dump pointer value
|
||||||
|
func PrintPointerInfo(buf *bytes.Buffer, headlen int, pointers *pointerInfo) {
|
||||||
|
anyused := false
|
||||||
|
pointerNum := 0
|
||||||
|
|
||||||
|
for p := pointers; p != nil; p = p.prev {
|
||||||
|
if len(p.used) > 0 {
|
||||||
|
anyused = true
|
||||||
|
}
|
||||||
|
pointerNum++
|
||||||
|
p.n = pointerNum
|
||||||
|
}
|
||||||
|
|
||||||
|
if anyused {
|
||||||
|
pointerBufs := make([][]rune, pointerNum+1)
|
||||||
|
|
||||||
|
for i := 0; i < len(pointerBufs); i++ {
|
||||||
|
pointerBuf := make([]rune, buf.Len()+headlen)
|
||||||
|
|
||||||
|
for j := 0; j < len(pointerBuf); j++ {
|
||||||
|
pointerBuf[j] = ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
pointerBufs[i] = pointerBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
for pn := 0; pn <= pointerNum; pn++ {
|
||||||
|
for p := pointers; p != nil; p = p.prev {
|
||||||
|
if len(p.used) > 0 && p.n >= pn {
|
||||||
|
if pn == p.n {
|
||||||
|
pointerBufs[pn][p.pos+headlen] = '└'
|
||||||
|
|
||||||
|
maxpos := 0
|
||||||
|
|
||||||
|
for i, pos := range p.used {
|
||||||
|
if i < len(p.used)-1 {
|
||||||
|
pointerBufs[pn][pos+headlen] = '┴'
|
||||||
|
} else {
|
||||||
|
pointerBufs[pn][pos+headlen] = '┘'
|
||||||
|
}
|
||||||
|
|
||||||
|
maxpos = pos
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < maxpos-p.pos-1; i++ {
|
||||||
|
if pointerBufs[pn][i+p.pos+headlen+1] == ' ' {
|
||||||
|
pointerBufs[pn][i+p.pos+headlen+1] = '─'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pointerBufs[pn][p.pos+headlen] = '│'
|
||||||
|
|
||||||
|
for _, pos := range p.used {
|
||||||
|
if pointerBufs[pn][pos+headlen] == ' ' {
|
||||||
|
pointerBufs[pn][pos+headlen] = '│'
|
||||||
|
} else {
|
||||||
|
pointerBufs[pn][pos+headlen] = '┼'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString(string(pointerBufs[pn]) + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack get stack bytes
|
||||||
|
func Stack(skip int, indent string) []byte {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
for i := skip; ; i++ {
|
||||||
|
pc, file, line, ok := runtime.Caller(i)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString(indent)
|
||||||
|
|
||||||
|
fmt.Fprintf(buf, "at %s() [%s:%d]\n", function(pc), file, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the name of the function containing the PC if possible,
|
||||||
|
func function(pc uintptr) []byte {
|
||||||
|
fn := runtime.FuncForPC(pc)
|
||||||
|
if fn == nil {
|
||||||
|
return dunno
|
||||||
|
}
|
||||||
|
name := []byte(fn.Name())
|
||||||
|
// The name includes the path name to the package, which is unnecessary
|
||||||
|
// since the file name is already included. Plus, it has center dots.
|
||||||
|
// That is, we see
|
||||||
|
// runtime/debug.*T·ptrmethod
|
||||||
|
// and want
|
||||||
|
// *T.ptrmethod
|
||||||
|
if period := bytes.Index(name, dot); period >= 0 {
|
||||||
|
name = name[period+1:]
|
||||||
|
}
|
||||||
|
name = bytes.Replace(name, centerDot, dot, -1)
|
||||||
|
return name
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SelfPath gets compiled executable file absolute path
|
||||||
|
func SelfPath() string {
|
||||||
|
path, _ := filepath.Abs(os.Args[0])
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelfDir gets compiled executable file directory
|
||||||
|
func SelfDir() string {
|
||||||
|
return filepath.Dir(SelfPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileExists reports whether the named file or directory exists.
|
||||||
|
func FileExists(name string) bool {
|
||||||
|
if _, err := os.Stat(name); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchFile Search a file in paths.
|
||||||
|
// this is often used in search config file in /etc ~/
|
||||||
|
func SearchFile(filename string, paths ...string) (fullpath string, err error) {
|
||||||
|
for _, path := range paths {
|
||||||
|
if fullpath = filepath.Join(path, filename); FileExists(fullpath) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = errors.New(fullpath + " not found in paths")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GrepFile like command grep -E
|
||||||
|
// for example: GrepFile(`^hello`, "hello.txt")
|
||||||
|
// \n is striped while read
|
||||||
|
func GrepFile(patten string, filename string) (lines []string, err error) {
|
||||||
|
re, err := regexp.Compile(patten)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fd, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lines = make([]string, 0)
|
||||||
|
reader := bufio.NewReader(fd)
|
||||||
|
prefix := ""
|
||||||
|
var isLongLine bool
|
||||||
|
for {
|
||||||
|
byteLine, isPrefix, er := reader.ReadLine()
|
||||||
|
if er != nil && er != io.EOF {
|
||||||
|
return nil, er
|
||||||
|
}
|
||||||
|
if er == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
line := string(byteLine)
|
||||||
|
if isPrefix {
|
||||||
|
prefix += line
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
isLongLine = true
|
||||||
|
}
|
||||||
|
|
||||||
|
line = prefix + line
|
||||||
|
if isLongLine {
|
||||||
|
prefix = ""
|
||||||
|
}
|
||||||
|
if re.MatchString(line) {
|
||||||
|
lines = append(lines, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines, nil
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2020 beego-dev
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
type KV interface {
|
||||||
|
GetKey() interface{}
|
||||||
|
GetValue() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleKV is common structure to store key-value pairs.
|
||||||
|
// When you need something like Pair, you can use this
|
||||||
|
type SimpleKV struct {
|
||||||
|
Key interface{}
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ KV = new(SimpleKV)
|
||||||
|
|
||||||
|
func (s *SimpleKV) GetKey() interface{} {
|
||||||
|
return s.Key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SimpleKV) GetValue() interface{} {
|
||||||
|
return s.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// KVs interface
|
||||||
|
type KVs interface {
|
||||||
|
GetValueOr(key interface{}, defValue interface{}) interface{}
|
||||||
|
Contains(key interface{}) bool
|
||||||
|
IfContains(key interface{}, action func(value interface{})) KVs
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleKVs will store SimpleKV collection as map
|
||||||
|
type SimpleKVs struct {
|
||||||
|
kvs map[interface{}]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ KVs = new(SimpleKVs)
|
||||||
|
|
||||||
|
// GetValueOr returns the value for a given key, if non-existent
|
||||||
|
// it returns defValue
|
||||||
|
func (kvs *SimpleKVs) GetValueOr(key interface{}, defValue interface{}) interface{} {
|
||||||
|
v, ok := kvs.kvs[key]
|
||||||
|
if ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains checks if a key exists
|
||||||
|
func (kvs *SimpleKVs) Contains(key interface{}) bool {
|
||||||
|
_, ok := kvs.kvs[key]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// IfContains invokes the action on a key if it exists
|
||||||
|
func (kvs *SimpleKVs) IfContains(key interface{}, action func(value interface{})) KVs {
|
||||||
|
v, ok := kvs.kvs[key]
|
||||||
|
if ok {
|
||||||
|
action(v)
|
||||||
|
}
|
||||||
|
return kvs
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKVs creates the *KVs instance
|
||||||
|
func NewKVs(kvs ...KV) KVs {
|
||||||
|
res := &SimpleKVs{
|
||||||
|
kvs: make(map[interface{}]interface{}, len(kvs)),
|
||||||
|
}
|
||||||
|
for _, kv := range kvs {
|
||||||
|
res.kvs[kv.GetKey()] = kv.GetValue()
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
@ -0,0 +1,424 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/mail"
|
||||||
|
"net/smtp"
|
||||||
|
"net/textproto"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxLineLength = 76
|
||||||
|
|
||||||
|
upperhex = "0123456789ABCDEF"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Email is the type used for email messages
|
||||||
|
type Email struct {
|
||||||
|
Auth smtp.Auth
|
||||||
|
Identity string `json:"identity"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
From string `json:"from"`
|
||||||
|
To []string
|
||||||
|
Bcc []string
|
||||||
|
Cc []string
|
||||||
|
Subject string
|
||||||
|
Text string // Plaintext message (optional)
|
||||||
|
HTML string // Html message (optional)
|
||||||
|
Headers textproto.MIMEHeader
|
||||||
|
Attachments []*Attachment
|
||||||
|
ReadReceipt []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attachment is a struct representing an email attachment.
|
||||||
|
// Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in question
|
||||||
|
type Attachment struct {
|
||||||
|
Filename string
|
||||||
|
Header textproto.MIMEHeader
|
||||||
|
Content []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEMail create new Email struct with config json.
|
||||||
|
// config json is followed from Email struct fields.
|
||||||
|
func NewEMail(config string) *Email {
|
||||||
|
e := new(Email)
|
||||||
|
e.Headers = textproto.MIMEHeader{}
|
||||||
|
err := json.Unmarshal([]byte(config), e)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes Make all send information to byte
|
||||||
|
func (e *Email) Bytes() ([]byte, error) {
|
||||||
|
buff := &bytes.Buffer{}
|
||||||
|
w := multipart.NewWriter(buff)
|
||||||
|
// Set the appropriate headers (overwriting any conflicts)
|
||||||
|
// Leave out Bcc (only included in envelope headers)
|
||||||
|
e.Headers.Set("To", strings.Join(e.To, ","))
|
||||||
|
if e.Cc != nil {
|
||||||
|
e.Headers.Set("Cc", strings.Join(e.Cc, ","))
|
||||||
|
}
|
||||||
|
e.Headers.Set("From", e.From)
|
||||||
|
e.Headers.Set("Subject", e.Subject)
|
||||||
|
if len(e.ReadReceipt) != 0 {
|
||||||
|
e.Headers.Set("Disposition-Notification-To", strings.Join(e.ReadReceipt, ","))
|
||||||
|
}
|
||||||
|
e.Headers.Set("MIME-Version", "1.0")
|
||||||
|
|
||||||
|
// Write the envelope headers (including any custom headers)
|
||||||
|
if err := headerToBytes(buff, e.Headers); err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to render message headers: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Headers.Set("Content-Type", fmt.Sprintf("multipart/mixed;\r\n boundary=%s\r\n", w.Boundary()))
|
||||||
|
fmt.Fprintf(buff, "%s:", "Content-Type")
|
||||||
|
fmt.Fprintf(buff, " %s\r\n", fmt.Sprintf("multipart/mixed;\r\n boundary=%s\r\n", w.Boundary()))
|
||||||
|
|
||||||
|
// Start the multipart/mixed part
|
||||||
|
fmt.Fprintf(buff, "--%s\r\n", w.Boundary())
|
||||||
|
header := textproto.MIMEHeader{}
|
||||||
|
// Check to see if there is a Text or HTML field
|
||||||
|
if e.Text != "" || e.HTML != "" {
|
||||||
|
subWriter := multipart.NewWriter(buff)
|
||||||
|
// Create the multipart alternative part
|
||||||
|
header.Set("Content-Type", fmt.Sprintf("multipart/alternative;\r\n boundary=%s\r\n", subWriter.Boundary()))
|
||||||
|
// Write the header
|
||||||
|
if err := headerToBytes(buff, header); err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to render multipart message headers: %s", err)
|
||||||
|
}
|
||||||
|
// Create the body sections
|
||||||
|
if e.Text != "" {
|
||||||
|
header.Set("Content-Type", fmt.Sprintf("text/plain; charset=UTF-8"))
|
||||||
|
header.Set("Content-Transfer-Encoding", "quoted-printable")
|
||||||
|
if _, err := subWriter.CreatePart(header); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Write the text
|
||||||
|
if err := quotePrintEncode(buff, e.Text); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if e.HTML != "" {
|
||||||
|
header.Set("Content-Type", fmt.Sprintf("text/html; charset=UTF-8"))
|
||||||
|
header.Set("Content-Transfer-Encoding", "quoted-printable")
|
||||||
|
if _, err := subWriter.CreatePart(header); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Write the text
|
||||||
|
if err := quotePrintEncode(buff, e.HTML); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := subWriter.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Create attachment part, if necessary
|
||||||
|
for _, a := range e.Attachments {
|
||||||
|
ap, err := w.CreatePart(a.Header)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Write the base64Wrapped content to the part
|
||||||
|
base64Wrap(ap, a.Content)
|
||||||
|
}
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buff.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachFile Add attach file to the send mail
|
||||||
|
func (e *Email) AttachFile(args ...string) (a *Attachment, err error) {
|
||||||
|
if len(args) < 1 || len(args) > 2 { // change && to ||
|
||||||
|
err = errors.New("Must specify a file name and number of parameters can not exceed at least two")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filename := args[0]
|
||||||
|
id := ""
|
||||||
|
if len(args) > 1 {
|
||||||
|
id = args[1]
|
||||||
|
}
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
ct := mime.TypeByExtension(filepath.Ext(filename))
|
||||||
|
basename := path.Base(filename)
|
||||||
|
return e.Attach(f, basename, ct, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach is used to attach content from an io.Reader to the email.
|
||||||
|
// Parameters include an io.Reader, the desired filename for the attachment, and the Content-Type.
|
||||||
|
func (e *Email) Attach(r io.Reader, filename string, args ...string) (a *Attachment, err error) {
|
||||||
|
if len(args) < 1 || len(args) > 2 { // change && to ||
|
||||||
|
err = errors.New("Must specify the file type and number of parameters can not exceed at least two")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c := args[0] // Content-Type
|
||||||
|
id := ""
|
||||||
|
if len(args) > 1 {
|
||||||
|
id = args[1] // Content-ID
|
||||||
|
}
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
if _, err = io.Copy(&buffer, r); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
at := &Attachment{
|
||||||
|
Filename: filename,
|
||||||
|
Header: textproto.MIMEHeader{},
|
||||||
|
Content: buffer.Bytes(),
|
||||||
|
}
|
||||||
|
// Get the Content-Type to be used in the MIMEHeader
|
||||||
|
if c != "" {
|
||||||
|
at.Header.Set("Content-Type", c)
|
||||||
|
} else {
|
||||||
|
// If the Content-Type is blank, set the Content-Type to "application/octet-stream"
|
||||||
|
at.Header.Set("Content-Type", "application/octet-stream")
|
||||||
|
}
|
||||||
|
if id != "" {
|
||||||
|
at.Header.Set("Content-Disposition", fmt.Sprintf("inline;\r\n filename=\"%s\"", filename))
|
||||||
|
at.Header.Set("Content-ID", fmt.Sprintf("<%s>", id))
|
||||||
|
} else {
|
||||||
|
at.Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", filename))
|
||||||
|
}
|
||||||
|
at.Header.Set("Content-Transfer-Encoding", "base64")
|
||||||
|
e.Attachments = append(e.Attachments, at)
|
||||||
|
return at, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send will send out the mail
|
||||||
|
func (e *Email) Send() error {
|
||||||
|
if e.Auth == nil {
|
||||||
|
e.Auth = smtp.PlainAuth(e.Identity, e.Username, e.Password, e.Host)
|
||||||
|
}
|
||||||
|
// Merge the To, Cc, and Bcc fields
|
||||||
|
to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))
|
||||||
|
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
|
||||||
|
// Check to make sure there is at least one recipient and one "From" address
|
||||||
|
if len(to) == 0 {
|
||||||
|
return errors.New("Must specify at least one To address")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the username if no From is provided
|
||||||
|
if len(e.From) == 0 {
|
||||||
|
e.From = e.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
from, err := mail.ParseAddress(e.From)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// use mail's RFC 2047 to encode any string
|
||||||
|
e.Subject = qEncode("utf-8", e.Subject)
|
||||||
|
|
||||||
|
raw, err := e.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return smtp.SendMail(e.Host+":"+strconv.Itoa(e.Port), e.Auth, from.Address, to, raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// quotePrintEncode writes the quoted-printable text to the IO Writer (according to RFC 2045)
|
||||||
|
func quotePrintEncode(w io.Writer, s string) error {
|
||||||
|
var buf [3]byte
|
||||||
|
mc := 0
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
// We're assuming Unix style text formats as input (LF line break), and
|
||||||
|
// quoted-printble uses CRLF line breaks. (Literal CRs will become
|
||||||
|
// "=0D", but probably shouldn't be there to begin with!)
|
||||||
|
if c == '\n' {
|
||||||
|
io.WriteString(w, "\r\n")
|
||||||
|
mc = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextOut []byte
|
||||||
|
if isPrintable(c) {
|
||||||
|
nextOut = append(buf[:0], c)
|
||||||
|
} else {
|
||||||
|
nextOut = buf[:]
|
||||||
|
qpEscape(nextOut, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a soft line break if the next (encoded) byte would push this line
|
||||||
|
// to or past the limit.
|
||||||
|
if mc+len(nextOut) >= maxLineLength {
|
||||||
|
if _, err := io.WriteString(w, "=\r\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mc = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := w.Write(nextOut); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mc += len(nextOut)
|
||||||
|
}
|
||||||
|
// No trailing end-of-line?? Soft line break, then. TODO: is this sane?
|
||||||
|
if mc > 0 {
|
||||||
|
io.WriteString(w, "=\r\n")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isPrintable returns true if the rune given is "printable" according to RFC 2045, false otherwise
|
||||||
|
func isPrintable(c byte) bool {
|
||||||
|
return (c >= '!' && c <= '<') || (c >= '>' && c <= '~') || (c == ' ' || c == '\n' || c == '\t')
|
||||||
|
}
|
||||||
|
|
||||||
|
// qpEscape is a helper function for quotePrintEncode which escapes a
|
||||||
|
// non-printable byte. Expects len(dest) == 3.
|
||||||
|
func qpEscape(dest []byte, c byte) {
|
||||||
|
const nums = "0123456789ABCDEF"
|
||||||
|
dest[0] = '='
|
||||||
|
dest[1] = nums[(c&0xf0)>>4]
|
||||||
|
dest[2] = nums[(c & 0xf)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerToBytes enumerates the key and values in the header, and writes the results to the IO Writer
|
||||||
|
func headerToBytes(w io.Writer, t textproto.MIMEHeader) error {
|
||||||
|
for k, v := range t {
|
||||||
|
// Write the header key
|
||||||
|
_, err := fmt.Fprintf(w, "%s:", k)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Write each value in the header
|
||||||
|
for _, c := range v {
|
||||||
|
_, err := fmt.Fprintf(w, " %s\r\n", c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// base64Wrap encodes the attachment content, and wraps it according to RFC 2045 standards (every 76 chars)
|
||||||
|
// The output is then written to the specified io.Writer
|
||||||
|
func base64Wrap(w io.Writer, b []byte) {
|
||||||
|
// 57 raw bytes per 76-byte base64 line.
|
||||||
|
const maxRaw = 57
|
||||||
|
// Buffer for each line, including trailing CRLF.
|
||||||
|
var buffer [maxLineLength + len("\r\n")]byte
|
||||||
|
copy(buffer[maxLineLength:], "\r\n")
|
||||||
|
// Process raw chunks until there's no longer enough to fill a line.
|
||||||
|
for len(b) >= maxRaw {
|
||||||
|
base64.StdEncoding.Encode(buffer[:], b[:maxRaw])
|
||||||
|
w.Write(buffer[:])
|
||||||
|
b = b[maxRaw:]
|
||||||
|
}
|
||||||
|
// Handle the last chunk of bytes.
|
||||||
|
if len(b) > 0 {
|
||||||
|
out := buffer[:base64.StdEncoding.EncodedLen(len(b))]
|
||||||
|
base64.StdEncoding.Encode(out, b)
|
||||||
|
out = append(out, "\r\n"...)
|
||||||
|
w.Write(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode returns the encoded-word form of s. If s is ASCII without special
|
||||||
|
// characters, it is returned unchanged. The provided charset is the IANA
|
||||||
|
// charset name of s. It is case insensitive.
|
||||||
|
// RFC 2047 encoded-word
|
||||||
|
func qEncode(charset, s string) string {
|
||||||
|
if !needsEncoding(s) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return encodeWord(charset, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func needsEncoding(s string) bool {
|
||||||
|
for _, b := range s {
|
||||||
|
if (b < ' ' || b > '~') && b != '\t' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeWord encodes a string into an encoded-word.
|
||||||
|
func encodeWord(charset, s string) string {
|
||||||
|
buf := getBuffer()
|
||||||
|
|
||||||
|
buf.WriteString("=?")
|
||||||
|
buf.WriteString(charset)
|
||||||
|
buf.WriteByte('?')
|
||||||
|
buf.WriteByte('q')
|
||||||
|
buf.WriteByte('?')
|
||||||
|
|
||||||
|
enc := make([]byte, 3)
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
b := s[i]
|
||||||
|
switch {
|
||||||
|
case b == ' ':
|
||||||
|
buf.WriteByte('_')
|
||||||
|
case b <= '~' && b >= '!' && b != '=' && b != '?' && b != '_':
|
||||||
|
buf.WriteByte(b)
|
||||||
|
default:
|
||||||
|
enc[0] = '='
|
||||||
|
enc[1] = upperhex[b>>4]
|
||||||
|
enc[2] = upperhex[b&0x0f]
|
||||||
|
buf.Write(enc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.WriteString("?=")
|
||||||
|
|
||||||
|
es := buf.String()
|
||||||
|
putBuffer(buf)
|
||||||
|
return es
|
||||||
|
}
|
||||||
|
|
||||||
|
var bufPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return new(bytes.Buffer)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBuffer() *bytes.Buffer {
|
||||||
|
return bufPool.Get().(*bytes.Buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func putBuffer(buf *bytes.Buffer) {
|
||||||
|
if buf.Len() > 1024 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
bufPool.Put(buf)
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
r "math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var alphaNum = []byte(`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`)
|
||||||
|
|
||||||
|
// RandomCreateBytes generate random []byte by specify chars.
|
||||||
|
func RandomCreateBytes(n int, alphabets ...byte) []byte {
|
||||||
|
if len(alphabets) == 0 {
|
||||||
|
alphabets = alphaNum
|
||||||
|
}
|
||||||
|
bytes := make([]byte, n)
|
||||||
|
var randBy bool
|
||||||
|
if num, err := rand.Read(bytes); num != n || err != nil {
|
||||||
|
r.Seed(time.Now().UnixNano())
|
||||||
|
randBy = true
|
||||||
|
}
|
||||||
|
for i, b := range bytes {
|
||||||
|
if randBy {
|
||||||
|
bytes[i] = alphabets[r.Intn(len(alphabets))]
|
||||||
|
} else {
|
||||||
|
bytes[i] = alphabets[b%byte(len(alphabets))]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deprecated: using sync.Map
|
||||||
|
type BeeMap struct {
|
||||||
|
lock *sync.RWMutex
|
||||||
|
bm map[interface{}]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBeeMap return new safemap
|
||||||
|
func NewBeeMap() *BeeMap {
|
||||||
|
return &BeeMap{
|
||||||
|
lock: new(sync.RWMutex),
|
||||||
|
bm: make(map[interface{}]interface{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get from maps return the k's value
|
||||||
|
func (m *BeeMap) Get(k interface{}) interface{} {
|
||||||
|
m.lock.RLock()
|
||||||
|
defer m.lock.RUnlock()
|
||||||
|
if val, ok := m.bm[k]; ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Maps the given key and value. Returns false
|
||||||
|
// if the key is already in the map and changes nothing.
|
||||||
|
func (m *BeeMap) Set(k interface{}, v interface{}) bool {
|
||||||
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
if val, ok := m.bm[k]; !ok {
|
||||||
|
m.bm[k] = v
|
||||||
|
} else if val != v {
|
||||||
|
m.bm[k] = v
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Returns true if k is exist in the map.
|
||||||
|
func (m *BeeMap) Check(k interface{}) bool {
|
||||||
|
m.lock.RLock()
|
||||||
|
defer m.lock.RUnlock()
|
||||||
|
_, ok := m.bm[k]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the given key and value.
|
||||||
|
func (m *BeeMap) Delete(k interface{}) {
|
||||||
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
delete(m.bm, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items returns all items in safemap.
|
||||||
|
func (m *BeeMap) Items() map[interface{}]interface{} {
|
||||||
|
m.lock.RLock()
|
||||||
|
defer m.lock.RUnlock()
|
||||||
|
r := make(map[interface{}]interface{})
|
||||||
|
for k, v := range m.bm {
|
||||||
|
r[k] = v
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the number of items within the map.
|
||||||
|
func (m *BeeMap) Count() int {
|
||||||
|
m.lock.RLock()
|
||||||
|
defer m.lock.RUnlock()
|
||||||
|
return len(m.bm)
|
||||||
|
}
|
@ -0,0 +1,171 @@
|
|||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type reducetype func(interface{}) interface{}
|
||||||
|
|
||||||
|
type filtertype func(interface{}) bool
|
||||||
|
|
||||||
|
// InSlice checks given string in string slice or not.
|
||||||
|
func InSlice(v string, sl []string) bool {
|
||||||
|
for _, vv := range sl {
|
||||||
|
if vv == v {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// InSliceIface checks given interface in interface slice.
|
||||||
|
func InSliceIface(v interface{}, sl []interface{}) bool {
|
||||||
|
for _, vv := range sl {
|
||||||
|
if vv == v {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceRandList generate an int slice from min to max.
|
||||||
|
func SliceRandList(min, max int) []int {
|
||||||
|
if max < min {
|
||||||
|
min, max = max, min
|
||||||
|
}
|
||||||
|
length := max - min + 1
|
||||||
|
t0 := time.Now()
|
||||||
|
rand.Seed(int64(t0.Nanosecond()))
|
||||||
|
list := rand.Perm(length)
|
||||||
|
for index := range list {
|
||||||
|
list[index] += min
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceMerge merges interface slices to one slice.
|
||||||
|
func SliceMerge(slice1, slice2 []interface{}) (c []interface{}) {
|
||||||
|
c = append(slice1, slice2...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceReduce generates a new slice after parsing every value by reduce function
|
||||||
|
func SliceReduce(slice []interface{}, a reducetype) (dslice []interface{}) {
|
||||||
|
for _, v := range slice {
|
||||||
|
dslice = append(dslice, a(v))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceRand returns random one from slice.
|
||||||
|
func SliceRand(a []interface{}) (b interface{}) {
|
||||||
|
randnum := rand.Intn(len(a))
|
||||||
|
b = a[randnum]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceSum sums all values in int64 slice.
|
||||||
|
func SliceSum(intslice []int64) (sum int64) {
|
||||||
|
for _, v := range intslice {
|
||||||
|
sum += v
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceFilter generates a new slice after filter function.
|
||||||
|
func SliceFilter(slice []interface{}, a filtertype) (ftslice []interface{}) {
|
||||||
|
for _, v := range slice {
|
||||||
|
if a(v) {
|
||||||
|
ftslice = append(ftslice, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceDiff returns diff slice of slice1 - slice2.
|
||||||
|
func SliceDiff(slice1, slice2 []interface{}) (diffslice []interface{}) {
|
||||||
|
for _, v := range slice1 {
|
||||||
|
if !InSliceIface(v, slice2) {
|
||||||
|
diffslice = append(diffslice, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceIntersect returns slice that are present in all the slice1 and slice2.
|
||||||
|
func SliceIntersect(slice1, slice2 []interface{}) (diffslice []interface{}) {
|
||||||
|
for _, v := range slice1 {
|
||||||
|
if InSliceIface(v, slice2) {
|
||||||
|
diffslice = append(diffslice, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceChunk separates one slice to some sized slice.
|
||||||
|
func SliceChunk(slice []interface{}, size int) (chunkslice [][]interface{}) {
|
||||||
|
if size >= len(slice) {
|
||||||
|
chunkslice = append(chunkslice, slice)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
end := size
|
||||||
|
for i := 0; i <= (len(slice) - size); i += size {
|
||||||
|
chunkslice = append(chunkslice, slice[i:end])
|
||||||
|
end += size
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceRange generates a new slice from begin to end with step duration of int64 number.
|
||||||
|
func SliceRange(start, end, step int64) (intslice []int64) {
|
||||||
|
for i := start; i <= end; i += step {
|
||||||
|
intslice = append(intslice, i)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SlicePad prepends size number of val into slice.
|
||||||
|
func SlicePad(slice []interface{}, size int, val interface{}) []interface{} {
|
||||||
|
if size <= len(slice) {
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
for i := 0; i < (size - len(slice)); i++ {
|
||||||
|
slice = append(slice, val)
|
||||||
|
}
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceUnique cleans repeated values in slice.
|
||||||
|
func SliceUnique(slice []interface{}) (uniqueslice []interface{}) {
|
||||||
|
for _, v := range slice {
|
||||||
|
if !InSliceIface(v, uniqueslice) {
|
||||||
|
uniqueslice = append(uniqueslice, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceShuffle shuffles a slice.
|
||||||
|
func SliceShuffle(slice []interface{}) []interface{} {
|
||||||
|
for i := 0; i < len(slice); i++ {
|
||||||
|
a := rand.Intn(len(slice))
|
||||||
|
b := rand.Intn(len(slice))
|
||||||
|
slice[a], slice[b] = slice[b], slice[a]
|
||||||
|
}
|
||||||
|
return slice
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2020
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToShortTimeFormat short string format
|
||||||
|
func ToShortTimeFormat(d time.Duration) string {
|
||||||
|
u := uint64(d)
|
||||||
|
if u < uint64(time.Second) {
|
||||||
|
switch {
|
||||||
|
case u == 0:
|
||||||
|
return "0"
|
||||||
|
case u < uint64(time.Microsecond):
|
||||||
|
return fmt.Sprintf("%.2fns", float64(u))
|
||||||
|
case u < uint64(time.Millisecond):
|
||||||
|
return fmt.Sprintf("%.2fus", float64(u)/1000)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%.2fms", float64(u)/1000/1000)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch {
|
||||||
|
case u < uint64(time.Minute):
|
||||||
|
return fmt.Sprintf("%.2fs", float64(u)/1000/1000/1000)
|
||||||
|
case u < uint64(time.Hour):
|
||||||
|
return fmt.Sprintf("%.2fm", float64(u)/1000/1000/1000/60)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%.2fh", float64(u)/1000/1000/1000/60/60)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetGOPATHs returns all paths in GOPATH variable.
|
||||||
|
func GetGOPATHs() []string {
|
||||||
|
gopath := os.Getenv("GOPATH")
|
||||||
|
if gopath == "" && compareGoVersion(runtime.Version(), "go1.8") >= 0 {
|
||||||
|
gopath = defaultGOPATH()
|
||||||
|
}
|
||||||
|
return filepath.SplitList(gopath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareGoVersion(a, b string) int {
|
||||||
|
reg := regexp.MustCompile("^\\d*")
|
||||||
|
|
||||||
|
a = strings.TrimPrefix(a, "go")
|
||||||
|
b = strings.TrimPrefix(b, "go")
|
||||||
|
|
||||||
|
versionsA := strings.Split(a, ".")
|
||||||
|
versionsB := strings.Split(b, ".")
|
||||||
|
|
||||||
|
for i := 0; i < len(versionsA) && i < len(versionsB); i++ {
|
||||||
|
versionA := versionsA[i]
|
||||||
|
versionB := versionsB[i]
|
||||||
|
|
||||||
|
vA, err := strconv.Atoi(versionA)
|
||||||
|
if err != nil {
|
||||||
|
str := reg.FindString(versionA)
|
||||||
|
if str != "" {
|
||||||
|
vA, _ = strconv.Atoi(str)
|
||||||
|
} else {
|
||||||
|
vA = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vB, err := strconv.Atoi(versionB)
|
||||||
|
if err != nil {
|
||||||
|
str := reg.FindString(versionB)
|
||||||
|
if str != "" {
|
||||||
|
vB, _ = strconv.Atoi(str)
|
||||||
|
} else {
|
||||||
|
vB = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if vA > vB {
|
||||||
|
// vA = 12, vB = 8
|
||||||
|
return 1
|
||||||
|
} else if vA < vB {
|
||||||
|
// vA = 6, vB = 8
|
||||||
|
return -1
|
||||||
|
} else if vA == -1 {
|
||||||
|
// vA = rc1, vB = rc3
|
||||||
|
return strings.Compare(versionA, versionB)
|
||||||
|
}
|
||||||
|
|
||||||
|
// vA = vB = 8
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(versionsA) > len(versionsB) {
|
||||||
|
return 1
|
||||||
|
} else if len(versionsA) == len(versionsB) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultGOPATH() string {
|
||||||
|
env := "HOME"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
env = "USERPROFILE"
|
||||||
|
} else if runtime.GOOS == "plan9" {
|
||||||
|
env = "home"
|
||||||
|
}
|
||||||
|
if home := os.Getenv(env); home != "" {
|
||||||
|
return filepath.Join(home, "go")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
# xxhash
|
||||||
|
|
||||||
|
[![Go Reference](https://pkg.go.dev/badge/github.com/cespare/xxhash/v2.svg)](https://pkg.go.dev/github.com/cespare/xxhash/v2)
|
||||||
|
[![Test](https://github.com/cespare/xxhash/actions/workflows/test.yml/badge.svg)](https://github.com/cespare/xxhash/actions/workflows/test.yml)
|
||||||
|
|
||||||
|
xxhash is a Go implementation of the 64-bit
|
||||||
|
[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a
|
||||||
|
high-quality hashing algorithm that is much faster than anything in the Go
|
||||||
|
standard library.
|
||||||
|
|
||||||
|
This package provides a straightforward API:
|
||||||
|
|
||||||
|
```
|
||||||
|
func Sum64(b []byte) uint64
|
||||||
|
func Sum64String(s string) uint64
|
||||||
|
type Digest struct{ ... }
|
||||||
|
func New() *Digest
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Digest` type implements hash.Hash64. Its key methods are:
|
||||||
|
|
||||||
|
```
|
||||||
|
func (*Digest) Write([]byte) (int, error)
|
||||||
|
func (*Digest) WriteString(string) (int, error)
|
||||||
|
func (*Digest) Sum64() uint64
|
||||||
|
```
|
||||||
|
|
||||||
|
This implementation provides a fast pure-Go implementation and an even faster
|
||||||
|
assembly implementation for amd64.
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
This package is in a module and the latest code is in version 2 of the module.
|
||||||
|
You need a version of Go with at least "minimal module compatibility" to use
|
||||||
|
github.com/cespare/xxhash/v2:
|
||||||
|
|
||||||
|
* 1.9.7+ for Go 1.9
|
||||||
|
* 1.10.3+ for Go 1.10
|
||||||
|
* Go 1.11 or later
|
||||||
|
|
||||||
|
I recommend using the latest release of Go.
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
Here are some quick benchmarks comparing the pure-Go and assembly
|
||||||
|
implementations of Sum64.
|
||||||
|
|
||||||
|
| input size | purego | asm |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 5 B | 979.66 MB/s | 1291.17 MB/s |
|
||||||
|
| 100 B | 7475.26 MB/s | 7973.40 MB/s |
|
||||||
|
| 4 KB | 17573.46 MB/s | 17602.65 MB/s |
|
||||||
|
| 10 MB | 17131.46 MB/s | 17142.16 MB/s |
|
||||||
|
|
||||||
|
These numbers were generated on Ubuntu 18.04 with an Intel i7-8700K CPU using
|
||||||
|
the following commands under Go 1.11.2:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go test -tags purego -benchtime 10s -bench '/xxhash,direct,bytes'
|
||||||
|
$ go test -benchtime 10s -bench '/xxhash,direct,bytes'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Projects using this package
|
||||||
|
|
||||||
|
- [InfluxDB](https://github.com/influxdata/influxdb)
|
||||||
|
- [Prometheus](https://github.com/prometheus/prometheus)
|
||||||
|
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
|
||||||
|
- [FreeCache](https://github.com/coocood/freecache)
|
||||||
|
- [FastCache](https://github.com/VictoriaMetrics/fastcache)
|
@ -0,0 +1,235 @@
|
|||||||
|
// Package xxhash implements the 64-bit variant of xxHash (XXH64) as described
|
||||||
|
// at http://cyan4973.github.io/xxHash/.
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
prime1 uint64 = 11400714785074694791
|
||||||
|
prime2 uint64 = 14029467366897019727
|
||||||
|
prime3 uint64 = 1609587929392839161
|
||||||
|
prime4 uint64 = 9650029242287828579
|
||||||
|
prime5 uint64 = 2870177450012600261
|
||||||
|
)
|
||||||
|
|
||||||
|
// NOTE(caleb): I'm using both consts and vars of the primes. Using consts where
|
||||||
|
// possible in the Go code is worth a small (but measurable) performance boost
|
||||||
|
// by avoiding some MOVQs. Vars are needed for the asm and also are useful for
|
||||||
|
// convenience in the Go code in a few places where we need to intentionally
|
||||||
|
// avoid constant arithmetic (e.g., v1 := prime1 + prime2 fails because the
|
||||||
|
// result overflows a uint64).
|
||||||
|
var (
|
||||||
|
prime1v = prime1
|
||||||
|
prime2v = prime2
|
||||||
|
prime3v = prime3
|
||||||
|
prime4v = prime4
|
||||||
|
prime5v = prime5
|
||||||
|
)
|
||||||
|
|
||||||
|
// Digest implements hash.Hash64.
|
||||||
|
type Digest struct {
|
||||||
|
v1 uint64
|
||||||
|
v2 uint64
|
||||||
|
v3 uint64
|
||||||
|
v4 uint64
|
||||||
|
total uint64
|
||||||
|
mem [32]byte
|
||||||
|
n int // how much of mem is used
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Digest that computes the 64-bit xxHash algorithm.
|
||||||
|
func New() *Digest {
|
||||||
|
var d Digest
|
||||||
|
d.Reset()
|
||||||
|
return &d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset clears the Digest's state so that it can be reused.
|
||||||
|
func (d *Digest) Reset() {
|
||||||
|
d.v1 = prime1v + prime2
|
||||||
|
d.v2 = prime2
|
||||||
|
d.v3 = 0
|
||||||
|
d.v4 = -prime1v
|
||||||
|
d.total = 0
|
||||||
|
d.n = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size always returns 8 bytes.
|
||||||
|
func (d *Digest) Size() int { return 8 }
|
||||||
|
|
||||||
|
// BlockSize always returns 32 bytes.
|
||||||
|
func (d *Digest) BlockSize() int { return 32 }
|
||||||
|
|
||||||
|
// Write adds more data to d. It always returns len(b), nil.
|
||||||
|
func (d *Digest) Write(b []byte) (n int, err error) {
|
||||||
|
n = len(b)
|
||||||
|
d.total += uint64(n)
|
||||||
|
|
||||||
|
if d.n+n < 32 {
|
||||||
|
// This new data doesn't even fill the current block.
|
||||||
|
copy(d.mem[d.n:], b)
|
||||||
|
d.n += n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.n > 0 {
|
||||||
|
// Finish off the partial block.
|
||||||
|
copy(d.mem[d.n:], b)
|
||||||
|
d.v1 = round(d.v1, u64(d.mem[0:8]))
|
||||||
|
d.v2 = round(d.v2, u64(d.mem[8:16]))
|
||||||
|
d.v3 = round(d.v3, u64(d.mem[16:24]))
|
||||||
|
d.v4 = round(d.v4, u64(d.mem[24:32]))
|
||||||
|
b = b[32-d.n:]
|
||||||
|
d.n = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) >= 32 {
|
||||||
|
// One or more full blocks left.
|
||||||
|
nw := writeBlocks(d, b)
|
||||||
|
b = b[nw:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store any remaining partial block.
|
||||||
|
copy(d.mem[:], b)
|
||||||
|
d.n = len(b)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum appends the current hash to b and returns the resulting slice.
|
||||||
|
func (d *Digest) Sum(b []byte) []byte {
|
||||||
|
s := d.Sum64()
|
||||||
|
return append(
|
||||||
|
b,
|
||||||
|
byte(s>>56),
|
||||||
|
byte(s>>48),
|
||||||
|
byte(s>>40),
|
||||||
|
byte(s>>32),
|
||||||
|
byte(s>>24),
|
||||||
|
byte(s>>16),
|
||||||
|
byte(s>>8),
|
||||||
|
byte(s),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum64 returns the current hash.
|
||||||
|
func (d *Digest) Sum64() uint64 {
|
||||||
|
var h uint64
|
||||||
|
|
||||||
|
if d.total >= 32 {
|
||||||
|
v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4
|
||||||
|
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
||||||
|
h = mergeRound(h, v1)
|
||||||
|
h = mergeRound(h, v2)
|
||||||
|
h = mergeRound(h, v3)
|
||||||
|
h = mergeRound(h, v4)
|
||||||
|
} else {
|
||||||
|
h = d.v3 + prime5
|
||||||
|
}
|
||||||
|
|
||||||
|
h += d.total
|
||||||
|
|
||||||
|
i, end := 0, d.n
|
||||||
|
for ; i+8 <= end; i += 8 {
|
||||||
|
k1 := round(0, u64(d.mem[i:i+8]))
|
||||||
|
h ^= k1
|
||||||
|
h = rol27(h)*prime1 + prime4
|
||||||
|
}
|
||||||
|
if i+4 <= end {
|
||||||
|
h ^= uint64(u32(d.mem[i:i+4])) * prime1
|
||||||
|
h = rol23(h)*prime2 + prime3
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
for i < end {
|
||||||
|
h ^= uint64(d.mem[i]) * prime5
|
||||||
|
h = rol11(h) * prime1
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
h ^= h >> 33
|
||||||
|
h *= prime2
|
||||||
|
h ^= h >> 29
|
||||||
|
h *= prime3
|
||||||
|
h ^= h >> 32
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
magic = "xxh\x06"
|
||||||
|
marshaledSize = len(magic) + 8*5 + 32
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarshalBinary implements the encoding.BinaryMarshaler interface.
|
||||||
|
func (d *Digest) MarshalBinary() ([]byte, error) {
|
||||||
|
b := make([]byte, 0, marshaledSize)
|
||||||
|
b = append(b, magic...)
|
||||||
|
b = appendUint64(b, d.v1)
|
||||||
|
b = appendUint64(b, d.v2)
|
||||||
|
b = appendUint64(b, d.v3)
|
||||||
|
b = appendUint64(b, d.v4)
|
||||||
|
b = appendUint64(b, d.total)
|
||||||
|
b = append(b, d.mem[:d.n]...)
|
||||||
|
b = b[:len(b)+len(d.mem)-d.n]
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
|
||||||
|
func (d *Digest) UnmarshalBinary(b []byte) error {
|
||||||
|
if len(b) < len(magic) || string(b[:len(magic)]) != magic {
|
||||||
|
return errors.New("xxhash: invalid hash state identifier")
|
||||||
|
}
|
||||||
|
if len(b) != marshaledSize {
|
||||||
|
return errors.New("xxhash: invalid hash state size")
|
||||||
|
}
|
||||||
|
b = b[len(magic):]
|
||||||
|
b, d.v1 = consumeUint64(b)
|
||||||
|
b, d.v2 = consumeUint64(b)
|
||||||
|
b, d.v3 = consumeUint64(b)
|
||||||
|
b, d.v4 = consumeUint64(b)
|
||||||
|
b, d.total = consumeUint64(b)
|
||||||
|
copy(d.mem[:], b)
|
||||||
|
d.n = int(d.total % uint64(len(d.mem)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUint64(b []byte, x uint64) []byte {
|
||||||
|
var a [8]byte
|
||||||
|
binary.LittleEndian.PutUint64(a[:], x)
|
||||||
|
return append(b, a[:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeUint64(b []byte) ([]byte, uint64) {
|
||||||
|
x := u64(b)
|
||||||
|
return b[8:], x
|
||||||
|
}
|
||||||
|
|
||||||
|
func u64(b []byte) uint64 { return binary.LittleEndian.Uint64(b) }
|
||||||
|
func u32(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
|
||||||
|
|
||||||
|
func round(acc, input uint64) uint64 {
|
||||||
|
acc += input * prime2
|
||||||
|
acc = rol31(acc)
|
||||||
|
acc *= prime1
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeRound(acc, val uint64) uint64 {
|
||||||
|
val = round(0, val)
|
||||||
|
acc ^= val
|
||||||
|
acc = acc*prime1 + prime4
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
func rol1(x uint64) uint64 { return bits.RotateLeft64(x, 1) }
|
||||||
|
func rol7(x uint64) uint64 { return bits.RotateLeft64(x, 7) }
|
||||||
|
func rol11(x uint64) uint64 { return bits.RotateLeft64(x, 11) }
|
||||||
|
func rol12(x uint64) uint64 { return bits.RotateLeft64(x, 12) }
|
||||||
|
func rol18(x uint64) uint64 { return bits.RotateLeft64(x, 18) }
|
||||||
|
func rol23(x uint64) uint64 { return bits.RotateLeft64(x, 23) }
|
||||||
|
func rol27(x uint64) uint64 { return bits.RotateLeft64(x, 27) }
|
||||||
|
func rol31(x uint64) uint64 { return bits.RotateLeft64(x, 31) }
|
@ -0,0 +1,13 @@
|
|||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !purego
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
// Sum64 computes the 64-bit xxHash digest of b.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func Sum64(b []byte) uint64
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
func writeBlocks(d *Digest, b []byte) int
|
@ -0,0 +1,215 @@
|
|||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !purego
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// Register allocation:
|
||||||
|
// AX h
|
||||||
|
// SI pointer to advance through b
|
||||||
|
// DX n
|
||||||
|
// BX loop end
|
||||||
|
// R8 v1, k1
|
||||||
|
// R9 v2
|
||||||
|
// R10 v3
|
||||||
|
// R11 v4
|
||||||
|
// R12 tmp
|
||||||
|
// R13 prime1v
|
||||||
|
// R14 prime2v
|
||||||
|
// DI prime4v
|
||||||
|
|
||||||
|
// round reads from and advances the buffer pointer in SI.
|
||||||
|
// It assumes that R13 has prime1v and R14 has prime2v.
|
||||||
|
#define round(r) \
|
||||||
|
MOVQ (SI), R12 \
|
||||||
|
ADDQ $8, SI \
|
||||||
|
IMULQ R14, R12 \
|
||||||
|
ADDQ R12, r \
|
||||||
|
ROLQ $31, r \
|
||||||
|
IMULQ R13, r
|
||||||
|
|
||||||
|
// mergeRound applies a merge round on the two registers acc and val.
|
||||||
|
// It assumes that R13 has prime1v, R14 has prime2v, and DI has prime4v.
|
||||||
|
#define mergeRound(acc, val) \
|
||||||
|
IMULQ R14, val \
|
||||||
|
ROLQ $31, val \
|
||||||
|
IMULQ R13, val \
|
||||||
|
XORQ val, acc \
|
||||||
|
IMULQ R13, acc \
|
||||||
|
ADDQ DI, acc
|
||||||
|
|
||||||
|
// func Sum64(b []byte) uint64
|
||||||
|
TEXT ·Sum64(SB), NOSPLIT, $0-32
|
||||||
|
// Load fixed primes.
|
||||||
|
MOVQ ·prime1v(SB), R13
|
||||||
|
MOVQ ·prime2v(SB), R14
|
||||||
|
MOVQ ·prime4v(SB), DI
|
||||||
|
|
||||||
|
// Load slice.
|
||||||
|
MOVQ b_base+0(FP), SI
|
||||||
|
MOVQ b_len+8(FP), DX
|
||||||
|
LEAQ (SI)(DX*1), BX
|
||||||
|
|
||||||
|
// The first loop limit will be len(b)-32.
|
||||||
|
SUBQ $32, BX
|
||||||
|
|
||||||
|
// Check whether we have at least one block.
|
||||||
|
CMPQ DX, $32
|
||||||
|
JLT noBlocks
|
||||||
|
|
||||||
|
// Set up initial state (v1, v2, v3, v4).
|
||||||
|
MOVQ R13, R8
|
||||||
|
ADDQ R14, R8
|
||||||
|
MOVQ R14, R9
|
||||||
|
XORQ R10, R10
|
||||||
|
XORQ R11, R11
|
||||||
|
SUBQ R13, R11
|
||||||
|
|
||||||
|
// Loop until SI > BX.
|
||||||
|
blockLoop:
|
||||||
|
round(R8)
|
||||||
|
round(R9)
|
||||||
|
round(R10)
|
||||||
|
round(R11)
|
||||||
|
|
||||||
|
CMPQ SI, BX
|
||||||
|
JLE blockLoop
|
||||||
|
|
||||||
|
MOVQ R8, AX
|
||||||
|
ROLQ $1, AX
|
||||||
|
MOVQ R9, R12
|
||||||
|
ROLQ $7, R12
|
||||||
|
ADDQ R12, AX
|
||||||
|
MOVQ R10, R12
|
||||||
|
ROLQ $12, R12
|
||||||
|
ADDQ R12, AX
|
||||||
|
MOVQ R11, R12
|
||||||
|
ROLQ $18, R12
|
||||||
|
ADDQ R12, AX
|
||||||
|
|
||||||
|
mergeRound(AX, R8)
|
||||||
|
mergeRound(AX, R9)
|
||||||
|
mergeRound(AX, R10)
|
||||||
|
mergeRound(AX, R11)
|
||||||
|
|
||||||
|
JMP afterBlocks
|
||||||
|
|
||||||
|
noBlocks:
|
||||||
|
MOVQ ·prime5v(SB), AX
|
||||||
|
|
||||||
|
afterBlocks:
|
||||||
|
ADDQ DX, AX
|
||||||
|
|
||||||
|
// Right now BX has len(b)-32, and we want to loop until SI > len(b)-8.
|
||||||
|
ADDQ $24, BX
|
||||||
|
|
||||||
|
CMPQ SI, BX
|
||||||
|
JG fourByte
|
||||||
|
|
||||||
|
wordLoop:
|
||||||
|
// Calculate k1.
|
||||||
|
MOVQ (SI), R8
|
||||||
|
ADDQ $8, SI
|
||||||
|
IMULQ R14, R8
|
||||||
|
ROLQ $31, R8
|
||||||
|
IMULQ R13, R8
|
||||||
|
|
||||||
|
XORQ R8, AX
|
||||||
|
ROLQ $27, AX
|
||||||
|
IMULQ R13, AX
|
||||||
|
ADDQ DI, AX
|
||||||
|
|
||||||
|
CMPQ SI, BX
|
||||||
|
JLE wordLoop
|
||||||
|
|
||||||
|
fourByte:
|
||||||
|
ADDQ $4, BX
|
||||||
|
CMPQ SI, BX
|
||||||
|
JG singles
|
||||||
|
|
||||||
|
MOVL (SI), R8
|
||||||
|
ADDQ $4, SI
|
||||||
|
IMULQ R13, R8
|
||||||
|
XORQ R8, AX
|
||||||
|
|
||||||
|
ROLQ $23, AX
|
||||||
|
IMULQ R14, AX
|
||||||
|
ADDQ ·prime3v(SB), AX
|
||||||
|
|
||||||
|
singles:
|
||||||
|
ADDQ $4, BX
|
||||||
|
CMPQ SI, BX
|
||||||
|
JGE finalize
|
||||||
|
|
||||||
|
singlesLoop:
|
||||||
|
MOVBQZX (SI), R12
|
||||||
|
ADDQ $1, SI
|
||||||
|
IMULQ ·prime5v(SB), R12
|
||||||
|
XORQ R12, AX
|
||||||
|
|
||||||
|
ROLQ $11, AX
|
||||||
|
IMULQ R13, AX
|
||||||
|
|
||||||
|
CMPQ SI, BX
|
||||||
|
JL singlesLoop
|
||||||
|
|
||||||
|
finalize:
|
||||||
|
MOVQ AX, R12
|
||||||
|
SHRQ $33, R12
|
||||||
|
XORQ R12, AX
|
||||||
|
IMULQ R14, AX
|
||||||
|
MOVQ AX, R12
|
||||||
|
SHRQ $29, R12
|
||||||
|
XORQ R12, AX
|
||||||
|
IMULQ ·prime3v(SB), AX
|
||||||
|
MOVQ AX, R12
|
||||||
|
SHRQ $32, R12
|
||||||
|
XORQ R12, AX
|
||||||
|
|
||||||
|
MOVQ AX, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// writeBlocks uses the same registers as above except that it uses AX to store
|
||||||
|
// the d pointer.
|
||||||
|
|
||||||
|
// func writeBlocks(d *Digest, b []byte) int
|
||||||
|
TEXT ·writeBlocks(SB), NOSPLIT, $0-40
|
||||||
|
// Load fixed primes needed for round.
|
||||||
|
MOVQ ·prime1v(SB), R13
|
||||||
|
MOVQ ·prime2v(SB), R14
|
||||||
|
|
||||||
|
// Load slice.
|
||||||
|
MOVQ b_base+8(FP), SI
|
||||||
|
MOVQ b_len+16(FP), DX
|
||||||
|
LEAQ (SI)(DX*1), BX
|
||||||
|
SUBQ $32, BX
|
||||||
|
|
||||||
|
// Load vN from d.
|
||||||
|
MOVQ d+0(FP), AX
|
||||||
|
MOVQ 0(AX), R8 // v1
|
||||||
|
MOVQ 8(AX), R9 // v2
|
||||||
|
MOVQ 16(AX), R10 // v3
|
||||||
|
MOVQ 24(AX), R11 // v4
|
||||||
|
|
||||||
|
// We don't need to check the loop condition here; this function is
|
||||||
|
// always called with at least one block of data to process.
|
||||||
|
blockLoop:
|
||||||
|
round(R8)
|
||||||
|
round(R9)
|
||||||
|
round(R10)
|
||||||
|
round(R11)
|
||||||
|
|
||||||
|
CMPQ SI, BX
|
||||||
|
JLE blockLoop
|
||||||
|
|
||||||
|
// Copy vN back to d.
|
||||||
|
MOVQ R8, 0(AX)
|
||||||
|
MOVQ R9, 8(AX)
|
||||||
|
MOVQ R10, 16(AX)
|
||||||
|
MOVQ R11, 24(AX)
|
||||||
|
|
||||||
|
// The number of bytes written is SI minus the old base pointer.
|
||||||
|
SUBQ b_base+8(FP), SI
|
||||||
|
MOVQ SI, ret+32(FP)
|
||||||
|
|
||||||
|
RET
|
@ -0,0 +1,76 @@
|
|||||||
|
// +build !amd64 appengine !gc purego
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
// Sum64 computes the 64-bit xxHash digest of b.
|
||||||
|
func Sum64(b []byte) uint64 {
|
||||||
|
// A simpler version would be
|
||||||
|
// d := New()
|
||||||
|
// d.Write(b)
|
||||||
|
// return d.Sum64()
|
||||||
|
// but this is faster, particularly for small inputs.
|
||||||
|
|
||||||
|
n := len(b)
|
||||||
|
var h uint64
|
||||||
|
|
||||||
|
if n >= 32 {
|
||||||
|
v1 := prime1v + prime2
|
||||||
|
v2 := prime2
|
||||||
|
v3 := uint64(0)
|
||||||
|
v4 := -prime1v
|
||||||
|
for len(b) >= 32 {
|
||||||
|
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||||
|
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||||
|
v3 = round(v3, u64(b[16:24:len(b)]))
|
||||||
|
v4 = round(v4, u64(b[24:32:len(b)]))
|
||||||
|
b = b[32:len(b):len(b)]
|
||||||
|
}
|
||||||
|
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
||||||
|
h = mergeRound(h, v1)
|
||||||
|
h = mergeRound(h, v2)
|
||||||
|
h = mergeRound(h, v3)
|
||||||
|
h = mergeRound(h, v4)
|
||||||
|
} else {
|
||||||
|
h = prime5
|
||||||
|
}
|
||||||
|
|
||||||
|
h += uint64(n)
|
||||||
|
|
||||||
|
i, end := 0, len(b)
|
||||||
|
for ; i+8 <= end; i += 8 {
|
||||||
|
k1 := round(0, u64(b[i:i+8:len(b)]))
|
||||||
|
h ^= k1
|
||||||
|
h = rol27(h)*prime1 + prime4
|
||||||
|
}
|
||||||
|
if i+4 <= end {
|
||||||
|
h ^= uint64(u32(b[i:i+4:len(b)])) * prime1
|
||||||
|
h = rol23(h)*prime2 + prime3
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
for ; i < end; i++ {
|
||||||
|
h ^= uint64(b[i]) * prime5
|
||||||
|
h = rol11(h) * prime1
|
||||||
|
}
|
||||||
|
|
||||||
|
h ^= h >> 33
|
||||||
|
h *= prime2
|
||||||
|
h ^= h >> 29
|
||||||
|
h *= prime3
|
||||||
|
h ^= h >> 32
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeBlocks(d *Digest, b []byte) int {
|
||||||
|
v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4
|
||||||
|
n := len(b)
|
||||||
|
for len(b) >= 32 {
|
||||||
|
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||||
|
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||||
|
v3 = round(v3, u64(b[16:24:len(b)]))
|
||||||
|
v4 = round(v4, u64(b[24:32:len(b)]))
|
||||||
|
b = b[32:len(b):len(b)]
|
||||||
|
}
|
||||||
|
d.v1, d.v2, d.v3, d.v4 = v1, v2, v3, v4
|
||||||
|
return n - len(b)
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
// +build appengine
|
||||||
|
|
||||||
|
// This file contains the safe implementations of otherwise unsafe-using code.
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
// Sum64String computes the 64-bit xxHash digest of s.
|
||||||
|
func Sum64String(s string) uint64 {
|
||||||
|
return Sum64([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString adds more data to d. It always returns len(s), nil.
|
||||||
|
func (d *Digest) WriteString(s string) (n int, err error) {
|
||||||
|
return d.Write([]byte(s))
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
// This file encapsulates usage of unsafe.
|
||||||
|
// xxhash_safe.go contains the safe implementations.
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// In the future it's possible that compiler optimizations will make these
|
||||||
|
// XxxString functions unnecessary by realizing that calls such as
|
||||||
|
// Sum64([]byte(s)) don't need to copy s. See https://golang.org/issue/2205.
|
||||||
|
// If that happens, even if we keep these functions they can be replaced with
|
||||||
|
// the trivial safe code.
|
||||||
|
|
||||||
|
// NOTE: The usual way of doing an unsafe string-to-[]byte conversion is:
|
||||||
|
//
|
||||||
|
// var b []byte
|
||||||
|
// bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||||
|
// bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
|
||||||
|
// bh.Len = len(s)
|
||||||
|
// bh.Cap = len(s)
|
||||||
|
//
|
||||||
|
// Unfortunately, as of Go 1.15.3 the inliner's cost model assigns a high enough
|
||||||
|
// weight to this sequence of expressions that any function that uses it will
|
||||||
|
// not be inlined. Instead, the functions below use a different unsafe
|
||||||
|
// conversion designed to minimize the inliner weight and allow both to be
|
||||||
|
// inlined. There is also a test (TestInlining) which verifies that these are
|
||||||
|
// inlined.
|
||||||
|
//
|
||||||
|
// See https://github.com/golang/go/issues/42739 for discussion.
|
||||||
|
|
||||||
|
// Sum64String computes the 64-bit xxHash digest of s.
|
||||||
|
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
|
||||||
|
func Sum64String(s string) uint64 {
|
||||||
|
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))
|
||||||
|
return Sum64(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString adds more data to d. It always returns len(s), nil.
|
||||||
|
// It may be faster than Write([]byte(s)) by avoiding a copy.
|
||||||
|
func (d *Digest) WriteString(s string) (n int, err error) {
|
||||||
|
d.Write(*(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)})))
|
||||||
|
// d.Write always returns len(s), nil.
|
||||||
|
// Ignoring the return output and returning these fixed values buys a
|
||||||
|
// savings of 6 in the inliner's cost model.
|
||||||
|
return len(s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sliceHeader is similar to reflect.SliceHeader, but it assumes that the layout
|
||||||
|
// of the first two words is the same as the layout of a string.
|
||||||
|
type sliceHeader struct {
|
||||||
|
s string
|
||||||
|
cap int
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
ISC License
|
||||||
|
|
||||||
|
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
@ -0,0 +1,145 @@
|
|||||||
|
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||||
|
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// Go versions prior to 1.4 are disabled because they use a different layout
|
||||||
|
// for interfaces which make the implementation of unsafeReflectValue more complex.
|
||||||
|
// +build !js,!appengine,!safe,!disableunsafe,go1.4
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||||
|
// not access to the unsafe package is available.
|
||||||
|
UnsafeDisabled = false
|
||||||
|
|
||||||
|
// ptrSize is the size of a pointer on the current arch.
|
||||||
|
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||||
|
)
|
||||||
|
|
||||||
|
type flag uintptr
|
||||||
|
|
||||||
|
var (
|
||||||
|
// flagRO indicates whether the value field of a reflect.Value
|
||||||
|
// is read-only.
|
||||||
|
flagRO flag
|
||||||
|
|
||||||
|
// flagAddr indicates whether the address of the reflect.Value's
|
||||||
|
// value may be taken.
|
||||||
|
flagAddr flag
|
||||||
|
)
|
||||||
|
|
||||||
|
// flagKindMask holds the bits that make up the kind
|
||||||
|
// part of the flags field. In all the supported versions,
|
||||||
|
// it is in the lower 5 bits.
|
||||||
|
const flagKindMask = flag(0x1f)
|
||||||
|
|
||||||
|
// Different versions of Go have used different
|
||||||
|
// bit layouts for the flags type. This table
|
||||||
|
// records the known combinations.
|
||||||
|
var okFlags = []struct {
|
||||||
|
ro, addr flag
|
||||||
|
}{{
|
||||||
|
// From Go 1.4 to 1.5
|
||||||
|
ro: 1 << 5,
|
||||||
|
addr: 1 << 7,
|
||||||
|
}, {
|
||||||
|
// Up to Go tip.
|
||||||
|
ro: 1<<5 | 1<<6,
|
||||||
|
addr: 1 << 8,
|
||||||
|
}}
|
||||||
|
|
||||||
|
var flagValOffset = func() uintptr {
|
||||||
|
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||||
|
if !ok {
|
||||||
|
panic("reflect.Value has no flag field")
|
||||||
|
}
|
||||||
|
return field.Offset
|
||||||
|
}()
|
||||||
|
|
||||||
|
// flagField returns a pointer to the flag field of a reflect.Value.
|
||||||
|
func flagField(v *reflect.Value) *flag {
|
||||||
|
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||||
|
// the typical safety restrictions preventing access to unaddressable and
|
||||||
|
// unexported data. It works by digging the raw pointer to the underlying
|
||||||
|
// value out of the protected value and generating a new unprotected (unsafe)
|
||||||
|
// reflect.Value to it.
|
||||||
|
//
|
||||||
|
// This allows us to check for implementations of the Stringer and error
|
||||||
|
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||||
|
// inaccessible values such as unexported struct fields.
|
||||||
|
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||||
|
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
flagFieldPtr := flagField(&v)
|
||||||
|
*flagFieldPtr &^= flagRO
|
||||||
|
*flagFieldPtr |= flagAddr
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity checks against future reflect package changes
|
||||||
|
// to the type or semantics of the Value.flag field.
|
||||||
|
func init() {
|
||||||
|
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||||
|
if !ok {
|
||||||
|
panic("reflect.Value has no flag field")
|
||||||
|
}
|
||||||
|
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
|
||||||
|
panic("reflect.Value flag field has changed kind")
|
||||||
|
}
|
||||||
|
type t0 int
|
||||||
|
var t struct {
|
||||||
|
A t0
|
||||||
|
// t0 will have flagEmbedRO set.
|
||||||
|
t0
|
||||||
|
// a will have flagStickyRO set
|
||||||
|
a t0
|
||||||
|
}
|
||||||
|
vA := reflect.ValueOf(t).FieldByName("A")
|
||||||
|
va := reflect.ValueOf(t).FieldByName("a")
|
||||||
|
vt0 := reflect.ValueOf(t).FieldByName("t0")
|
||||||
|
|
||||||
|
// Infer flagRO from the difference between the flags
|
||||||
|
// for the (otherwise identical) fields in t.
|
||||||
|
flagPublic := *flagField(&vA)
|
||||||
|
flagWithRO := *flagField(&va) | *flagField(&vt0)
|
||||||
|
flagRO = flagPublic ^ flagWithRO
|
||||||
|
|
||||||
|
// Infer flagAddr from the difference between a value
|
||||||
|
// taken from a pointer and not.
|
||||||
|
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
|
||||||
|
flagNoPtr := *flagField(&vA)
|
||||||
|
flagPtr := *flagField(&vPtrA)
|
||||||
|
flagAddr = flagNoPtr ^ flagPtr
|
||||||
|
|
||||||
|
// Check that the inferred flags tally with one of the known versions.
|
||||||
|
for _, f := range okFlags {
|
||||||
|
if flagRO == f.ro && flagAddr == f.addr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("reflect.Value read-only flag has changed semantics")
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||||
|
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// +build js appengine safe disableunsafe !go1.4
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||||
|
// not access to the unsafe package is available.
|
||||||
|
UnsafeDisabled = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||||
|
// that bypasses the typical safety restrictions preventing access to
|
||||||
|
// unaddressable and unexported data. However, doing this relies on access to
|
||||||
|
// the unsafe package. This is a stub version which simply returns the passed
|
||||||
|
// reflect.Value when the unsafe package is not available.
|
||||||
|
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||||
|
return v
|
||||||
|
}
|
@ -0,0 +1,341 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||||
|
// the technique used in the fmt package.
|
||||||
|
var (
|
||||||
|
panicBytes = []byte("(PANIC=")
|
||||||
|
plusBytes = []byte("+")
|
||||||
|
iBytes = []byte("i")
|
||||||
|
trueBytes = []byte("true")
|
||||||
|
falseBytes = []byte("false")
|
||||||
|
interfaceBytes = []byte("(interface {})")
|
||||||
|
commaNewlineBytes = []byte(",\n")
|
||||||
|
newlineBytes = []byte("\n")
|
||||||
|
openBraceBytes = []byte("{")
|
||||||
|
openBraceNewlineBytes = []byte("{\n")
|
||||||
|
closeBraceBytes = []byte("}")
|
||||||
|
asteriskBytes = []byte("*")
|
||||||
|
colonBytes = []byte(":")
|
||||||
|
colonSpaceBytes = []byte(": ")
|
||||||
|
openParenBytes = []byte("(")
|
||||||
|
closeParenBytes = []byte(")")
|
||||||
|
spaceBytes = []byte(" ")
|
||||||
|
pointerChainBytes = []byte("->")
|
||||||
|
nilAngleBytes = []byte("<nil>")
|
||||||
|
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||||
|
maxShortBytes = []byte("<max>")
|
||||||
|
circularBytes = []byte("<already shown>")
|
||||||
|
circularShortBytes = []byte("<shown>")
|
||||||
|
invalidAngleBytes = []byte("<invalid>")
|
||||||
|
openBracketBytes = []byte("[")
|
||||||
|
closeBracketBytes = []byte("]")
|
||||||
|
percentBytes = []byte("%")
|
||||||
|
precisionBytes = []byte(".")
|
||||||
|
openAngleBytes = []byte("<")
|
||||||
|
closeAngleBytes = []byte(">")
|
||||||
|
openMapBytes = []byte("map[")
|
||||||
|
closeMapBytes = []byte("]")
|
||||||
|
lenEqualsBytes = []byte("len=")
|
||||||
|
capEqualsBytes = []byte("cap=")
|
||||||
|
)
|
||||||
|
|
||||||
|
// hexDigits is used to map a decimal value to a hex digit.
|
||||||
|
var hexDigits = "0123456789abcdef"
|
||||||
|
|
||||||
|
// catchPanic handles any panics that might occur during the handleMethods
|
||||||
|
// calls.
|
||||||
|
func catchPanic(w io.Writer, v reflect.Value) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
w.Write(panicBytes)
|
||||||
|
fmt.Fprintf(w, "%v", err)
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleMethods attempts to call the Error and String methods on the underlying
|
||||||
|
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||||
|
//
|
||||||
|
// It handles panics in any called methods by catching and displaying the error
|
||||||
|
// as the formatted value.
|
||||||
|
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||||
|
// We need an interface to check if the type implements the error or
|
||||||
|
// Stringer interface. However, the reflect package won't give us an
|
||||||
|
// interface on certain things like unexported struct fields in order
|
||||||
|
// to enforce visibility rules. We use unsafe, when it's available,
|
||||||
|
// to bypass these restrictions since this package does not mutate the
|
||||||
|
// values.
|
||||||
|
if !v.CanInterface() {
|
||||||
|
if UnsafeDisabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
v = unsafeReflectValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose whether or not to do error and Stringer interface lookups against
|
||||||
|
// the base type or a pointer to the base type depending on settings.
|
||||||
|
// Technically calling one of these methods with a pointer receiver can
|
||||||
|
// mutate the value, however, types which choose to satisify an error or
|
||||||
|
// Stringer interface with a pointer receiver should not be mutating their
|
||||||
|
// state inside these interface methods.
|
||||||
|
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||||
|
v = unsafeReflectValue(v)
|
||||||
|
}
|
||||||
|
if v.CanAddr() {
|
||||||
|
v = v.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it an error or Stringer?
|
||||||
|
switch iface := v.Interface().(type) {
|
||||||
|
case error:
|
||||||
|
defer catchPanic(w, v)
|
||||||
|
if cs.ContinueOnMethod {
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(iface.Error()))
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(iface.Error()))
|
||||||
|
return true
|
||||||
|
|
||||||
|
case fmt.Stringer:
|
||||||
|
defer catchPanic(w, v)
|
||||||
|
if cs.ContinueOnMethod {
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(iface.String()))
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
w.Write([]byte(iface.String()))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// printBool outputs a boolean value as true or false to Writer w.
|
||||||
|
func printBool(w io.Writer, val bool) {
|
||||||
|
if val {
|
||||||
|
w.Write(trueBytes)
|
||||||
|
} else {
|
||||||
|
w.Write(falseBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printInt outputs a signed integer value to Writer w.
|
||||||
|
func printInt(w io.Writer, val int64, base int) {
|
||||||
|
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printUint outputs an unsigned integer value to Writer w.
|
||||||
|
func printUint(w io.Writer, val uint64, base int) {
|
||||||
|
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printFloat outputs a floating point value using the specified precision,
|
||||||
|
// which is expected to be 32 or 64bit, to Writer w.
|
||||||
|
func printFloat(w io.Writer, val float64, precision int) {
|
||||||
|
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printComplex outputs a complex value using the specified float precision
|
||||||
|
// for the real and imaginary parts to Writer w.
|
||||||
|
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||||
|
r := real(c)
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||||
|
i := imag(c)
|
||||||
|
if i >= 0 {
|
||||||
|
w.Write(plusBytes)
|
||||||
|
}
|
||||||
|
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||||
|
w.Write(iBytes)
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
|
||||||
|
// prefix to Writer w.
|
||||||
|
func printHexPtr(w io.Writer, p uintptr) {
|
||||||
|
// Null pointer.
|
||||||
|
num := uint64(p)
|
||||||
|
if num == 0 {
|
||||||
|
w.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||||
|
buf := make([]byte, 18)
|
||||||
|
|
||||||
|
// It's simpler to construct the hex string right to left.
|
||||||
|
base := uint64(16)
|
||||||
|
i := len(buf) - 1
|
||||||
|
for num >= base {
|
||||||
|
buf[i] = hexDigits[num%base]
|
||||||
|
num /= base
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
buf[i] = hexDigits[num]
|
||||||
|
|
||||||
|
// Add '0x' prefix.
|
||||||
|
i--
|
||||||
|
buf[i] = 'x'
|
||||||
|
i--
|
||||||
|
buf[i] = '0'
|
||||||
|
|
||||||
|
// Strip unused leading bytes.
|
||||||
|
buf = buf[i:]
|
||||||
|
w.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||||
|
// elements to be sorted.
|
||||||
|
type valuesSorter struct {
|
||||||
|
values []reflect.Value
|
||||||
|
strings []string // either nil or same len and values
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||||
|
// surrogate keys on which the data should be sorted. It uses flags in
|
||||||
|
// ConfigState to decide if and how to populate those surrogate keys.
|
||||||
|
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||||
|
vs := &valuesSorter{values: values, cs: cs}
|
||||||
|
if canSortSimply(vs.values[0].Kind()) {
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
if !cs.DisableMethods {
|
||||||
|
vs.strings = make([]string, len(values))
|
||||||
|
for i := range vs.values {
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
if !handleMethods(cs, &b, vs.values[i]) {
|
||||||
|
vs.strings = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
vs.strings[i] = b.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vs.strings == nil && cs.SpewKeys {
|
||||||
|
vs.strings = make([]string, len(values))
|
||||||
|
for i := range vs.values {
|
||||||
|
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||||
|
// directly, or whether it should be considered for sorting by surrogate keys
|
||||||
|
// (if the ConfigState allows it).
|
||||||
|
func canSortSimply(kind reflect.Kind) bool {
|
||||||
|
// This switch parallels valueSortLess, except for the default case.
|
||||||
|
switch kind {
|
||||||
|
case reflect.Bool:
|
||||||
|
return true
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return true
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return true
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return true
|
||||||
|
case reflect.String:
|
||||||
|
return true
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return true
|
||||||
|
case reflect.Array:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of values in the slice. It is part of the
|
||||||
|
// sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Len() int {
|
||||||
|
return len(s.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap swaps the values at the passed indices. It is part of the
|
||||||
|
// sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Swap(i, j int) {
|
||||||
|
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||||
|
if s.strings != nil {
|
||||||
|
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// valueSortLess returns whether the first value should sort before the second
|
||||||
|
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||||
|
// implementation.
|
||||||
|
func valueSortLess(a, b reflect.Value) bool {
|
||||||
|
switch a.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return !a.Bool() && b.Bool()
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return a.Int() < b.Int()
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return a.Float() < b.Float()
|
||||||
|
case reflect.String:
|
||||||
|
return a.String() < b.String()
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Array:
|
||||||
|
// Compare the contents of both arrays.
|
||||||
|
l := a.Len()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
av := a.Index(i)
|
||||||
|
bv := b.Index(i)
|
||||||
|
if av.Interface() == bv.Interface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return valueSortLess(av, bv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.String() < b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less returns whether the value at index i should sort before the
|
||||||
|
// value at index j. It is part of the sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Less(i, j int) bool {
|
||||||
|
if s.strings == nil {
|
||||||
|
return valueSortLess(s.values[i], s.values[j])
|
||||||
|
}
|
||||||
|
return s.strings[i] < s.strings[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortValues is a sort function that handles both native types and any type that
|
||||||
|
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||||
|
// their Value.String() value to ensure display stability.
|
||||||
|
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sort.Sort(newValuesSorter(values, cs))
|
||||||
|
}
|
@ -0,0 +1,306 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigState houses the configuration options used by spew to format and
|
||||||
|
// display values. There is a global instance, Config, that is used to control
|
||||||
|
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||||
|
// provides methods equivalent to the top-level functions.
|
||||||
|
//
|
||||||
|
// The zero value for ConfigState provides no indentation. You would typically
|
||||||
|
// want to set it to a space or a tab.
|
||||||
|
//
|
||||||
|
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||||
|
// with default settings. See the documentation of NewDefaultConfig for default
|
||||||
|
// values.
|
||||||
|
type ConfigState struct {
|
||||||
|
// Indent specifies the string to use for each indentation level. The
|
||||||
|
// global config instance that all top-level functions use set this to a
|
||||||
|
// single space by default. If you would like more indentation, you might
|
||||||
|
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||||
|
Indent string
|
||||||
|
|
||||||
|
// MaxDepth controls the maximum number of levels to descend into nested
|
||||||
|
// data structures. The default, 0, means there is no limit.
|
||||||
|
//
|
||||||
|
// NOTE: Circular data structures are properly detected, so it is not
|
||||||
|
// necessary to set this value unless you specifically want to limit deeply
|
||||||
|
// nested data structures.
|
||||||
|
MaxDepth int
|
||||||
|
|
||||||
|
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||||
|
// invoked for types that implement them.
|
||||||
|
DisableMethods bool
|
||||||
|
|
||||||
|
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||||
|
// error and Stringer interfaces on types which only accept a pointer
|
||||||
|
// receiver when the current type is not a pointer.
|
||||||
|
//
|
||||||
|
// NOTE: This might be an unsafe action since calling one of these methods
|
||||||
|
// with a pointer receiver could technically mutate the value, however,
|
||||||
|
// in practice, types which choose to satisify an error or Stringer
|
||||||
|
// interface with a pointer receiver should not be mutating their state
|
||||||
|
// inside these interface methods. As a result, this option relies on
|
||||||
|
// access to the unsafe package, so it will not have any effect when
|
||||||
|
// running in environments without access to the unsafe package such as
|
||||||
|
// Google App Engine or with the "safe" build tag specified.
|
||||||
|
DisablePointerMethods bool
|
||||||
|
|
||||||
|
// DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
// pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
DisablePointerAddresses bool
|
||||||
|
|
||||||
|
// DisableCapacities specifies whether to disable the printing of capacities
|
||||||
|
// for arrays, slices, maps and channels. This is useful when diffing
|
||||||
|
// data structures in tests.
|
||||||
|
DisableCapacities bool
|
||||||
|
|
||||||
|
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||||
|
// a custom error or Stringer interface is invoked. The default, false,
|
||||||
|
// means it will print the results of invoking the custom error or Stringer
|
||||||
|
// interface and return immediately instead of continuing to recurse into
|
||||||
|
// the internals of the data type.
|
||||||
|
//
|
||||||
|
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||||
|
// via the DisableMethods or DisablePointerMethods options.
|
||||||
|
ContinueOnMethod bool
|
||||||
|
|
||||||
|
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||||
|
// this to have a more deterministic, diffable output. Note that only
|
||||||
|
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||||
|
// that support the error or Stringer interfaces (if methods are
|
||||||
|
// enabled) are supported, with other types sorted according to the
|
||||||
|
// reflect.Value.String() output which guarantees display stability.
|
||||||
|
SortKeys bool
|
||||||
|
|
||||||
|
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||||
|
// be spewed to strings and sorted by those strings. This is only
|
||||||
|
// considered if SortKeys is true.
|
||||||
|
SpewKeys bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the active configuration of the top-level functions.
|
||||||
|
// The configuration can be changed by modifying the contents of spew.Config.
|
||||||
|
var Config = ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the formatted string as a value that satisfies error. See NewFormatter
|
||||||
|
// for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||||
|
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Print(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Printf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Println(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||||
|
return fmt.Sprint(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||||
|
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||||
|
return fmt.Sprintln(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||||
|
interface. As a result, it integrates cleanly with standard fmt package
|
||||||
|
printing functions. The formatter is useful for inline printing of smaller data
|
||||||
|
types similar to the standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Typically this function shouldn't be called directly. It is much easier to make
|
||||||
|
use of the custom formatter by calling one of the convenience functions such as
|
||||||
|
c.Printf, c.Println, or c.Printf.
|
||||||
|
*/
|
||||||
|
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||||
|
return newFormatter(c, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||||
|
// exactly the same as Dump.
|
||||||
|
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||||
|
fdump(c, w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump displays the passed parameters to standard out with newlines, customizable
|
||||||
|
indentation, and additional debug information such as complete types and all
|
||||||
|
pointer addresses used to indirect to the final value. It provides the
|
||||||
|
following features over the built-in printing facilities provided by the fmt
|
||||||
|
package:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output
|
||||||
|
|
||||||
|
The configuration options are controlled by modifying the public members
|
||||||
|
of c. See ConfigState for options documentation.
|
||||||
|
|
||||||
|
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||||
|
get the formatted result as a string.
|
||||||
|
*/
|
||||||
|
func (c *ConfigState) Dump(a ...interface{}) {
|
||||||
|
fdump(c, os.Stdout, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||||
|
// as Dump.
|
||||||
|
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fdump(c, &buf, a...)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||||
|
// length with each argument converted to a spew Formatter interface using
|
||||||
|
// the ConfigState associated with s.
|
||||||
|
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||||
|
formatters = make([]interface{}, len(args))
|
||||||
|
for index, arg := range args {
|
||||||
|
formatters[index] = newFormatter(c, arg)
|
||||||
|
}
|
||||||
|
return formatters
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||||
|
//
|
||||||
|
// Indent: " "
|
||||||
|
// MaxDepth: 0
|
||||||
|
// DisableMethods: false
|
||||||
|
// DisablePointerMethods: false
|
||||||
|
// ContinueOnMethod: false
|
||||||
|
// SortKeys: false
|
||||||
|
func NewDefaultConfig() *ConfigState {
|
||||||
|
return &ConfigState{Indent: " "}
|
||||||
|
}
|
@ -0,0 +1,211 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||||
|
debugging.
|
||||||
|
|
||||||
|
A quick overview of the additional features spew provides over the built-in
|
||||||
|
printing facilities for Go data types are as follows:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output (only when using
|
||||||
|
Dump style)
|
||||||
|
|
||||||
|
There are two different approaches spew allows for dumping Go data structures:
|
||||||
|
|
||||||
|
* Dump style which prints with newlines, customizable indentation,
|
||||||
|
and additional debug information such as types and all pointer addresses
|
||||||
|
used to indirect to the final value
|
||||||
|
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||||
|
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||||
|
similar to the default %v while providing the additional functionality
|
||||||
|
outlined above and passing unsupported format verbs such as %x and %q
|
||||||
|
along to fmt
|
||||||
|
|
||||||
|
Quick Start
|
||||||
|
|
||||||
|
This section demonstrates how to quickly get started with spew. See the
|
||||||
|
sections below for further details on formatting and configuration options.
|
||||||
|
|
||||||
|
To dump a variable with full newlines, indentation, type, and pointer
|
||||||
|
information use Dump, Fdump, or Sdump:
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||||
|
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||||
|
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||||
|
%#+v (adds types and pointer addresses):
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
|
||||||
|
Configuration Options
|
||||||
|
|
||||||
|
Configuration of spew is handled by fields in the ConfigState type. For
|
||||||
|
convenience, all of the top-level functions use a global state available
|
||||||
|
via the spew.Config global.
|
||||||
|
|
||||||
|
It is also possible to create a ConfigState instance that provides methods
|
||||||
|
equivalent to the top-level functions. This allows concurrent configuration
|
||||||
|
options. See the ConfigState documentation for more details.
|
||||||
|
|
||||||
|
The following configuration options are available:
|
||||||
|
* Indent
|
||||||
|
String to use for each indentation level for Dump functions.
|
||||||
|
It is a single space by default. A popular alternative is "\t".
|
||||||
|
|
||||||
|
* MaxDepth
|
||||||
|
Maximum number of levels to descend into nested data structures.
|
||||||
|
There is no limit by default.
|
||||||
|
|
||||||
|
* DisableMethods
|
||||||
|
Disables invocation of error and Stringer interface methods.
|
||||||
|
Method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerMethods
|
||||||
|
Disables invocation of error and Stringer interface methods on types
|
||||||
|
which only accept pointer receivers from non-pointer variables.
|
||||||
|
Pointer method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerAddresses
|
||||||
|
DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
|
||||||
|
* DisableCapacities
|
||||||
|
DisableCapacities specifies whether to disable the printing of
|
||||||
|
capacities for arrays, slices, maps and channels. This is useful when
|
||||||
|
diffing data structures in tests.
|
||||||
|
|
||||||
|
* ContinueOnMethod
|
||||||
|
Enables recursion into types after invoking error and Stringer interface
|
||||||
|
methods. Recursion after method invocation is disabled by default.
|
||||||
|
|
||||||
|
* SortKeys
|
||||||
|
Specifies map keys should be sorted before being printed. Use
|
||||||
|
this to have a more deterministic, diffable output. Note that
|
||||||
|
only native types (bool, int, uint, floats, uintptr and string)
|
||||||
|
and types which implement error or Stringer interfaces are
|
||||||
|
supported with other types sorted according to the
|
||||||
|
reflect.Value.String() output which guarantees display
|
||||||
|
stability. Natural map order is used by default.
|
||||||
|
|
||||||
|
* SpewKeys
|
||||||
|
Specifies that, as a last resort attempt, map keys should be
|
||||||
|
spewed to strings and sorted by those strings. This is only
|
||||||
|
considered if SortKeys is true.
|
||||||
|
|
||||||
|
Dump Usage
|
||||||
|
|
||||||
|
Simply call spew.Dump with a list of variables you want to dump:
|
||||||
|
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||||
|
io.Writer. For example, to dump to standard error:
|
||||||
|
|
||||||
|
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||||
|
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
Sample Dump Output
|
||||||
|
|
||||||
|
See the Dump example for details on the setup of the types and variables being
|
||||||
|
shown here.
|
||||||
|
|
||||||
|
(main.Foo) {
|
||||||
|
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||||
|
flag: (main.Flag) flagTwo,
|
||||||
|
data: (uintptr) <nil>
|
||||||
|
}),
|
||||||
|
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
(string) (len=3) "one": (bool) true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||||
|
command as shown.
|
||||||
|
([]uint8) (len=32 cap=32) {
|
||||||
|
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
00000020 31 32 |12|
|
||||||
|
}
|
||||||
|
|
||||||
|
Custom Formatter
|
||||||
|
|
||||||
|
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||||
|
so that it integrates cleanly with standard fmt package printing functions. The
|
||||||
|
formatter is useful for inline printing of smaller data types similar to the
|
||||||
|
standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Custom Formatter Usage
|
||||||
|
|
||||||
|
The simplest way to make use of the spew custom formatter is to call one of the
|
||||||
|
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||||
|
functions have syntax you are most likely already familiar with:
|
||||||
|
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Println(myVar, myVar2)
|
||||||
|
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
|
||||||
|
See the Index for the full list convenience functions.
|
||||||
|
|
||||||
|
Sample Formatter Output
|
||||||
|
|
||||||
|
Double pointer to a uint8:
|
||||||
|
%v: <**>5
|
||||||
|
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||||
|
%#v: (**uint8)5
|
||||||
|
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||||
|
|
||||||
|
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||||
|
%v: <*>{1 <*><shown>}
|
||||||
|
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||||
|
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||||
|
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||||
|
|
||||||
|
See the Printf example for details on the setup of variables being shown
|
||||||
|
here.
|
||||||
|
|
||||||
|
Errors
|
||||||
|
|
||||||
|
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||||
|
detects them and handles them internally by printing the panic information
|
||||||
|
inline with the output. Since spew is intended to provide deep pretty printing
|
||||||
|
capabilities on structures, it intentionally does not return any errors.
|
||||||
|
*/
|
||||||
|
package spew
|
@ -0,0 +1,509 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||||
|
// convert cgo types to uint8 slices for hexdumping.
|
||||||
|
uint8Type = reflect.TypeOf(uint8(0))
|
||||||
|
|
||||||
|
// cCharRE is a regular expression that matches a cgo char.
|
||||||
|
// It is used to detect character arrays to hexdump them.
|
||||||
|
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
|
||||||
|
|
||||||
|
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||||
|
// char. It is used to detect unsigned character arrays to hexdump
|
||||||
|
// them.
|
||||||
|
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
|
||||||
|
|
||||||
|
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||||
|
// It is used to detect uint8_t arrays to hexdump them.
|
||||||
|
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// dumpState contains information about the state of a dump operation.
|
||||||
|
type dumpState struct {
|
||||||
|
w io.Writer
|
||||||
|
depth int
|
||||||
|
pointers map[uintptr]int
|
||||||
|
ignoreNextType bool
|
||||||
|
ignoreNextIndent bool
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// indent performs indentation according to the depth level and cs.Indent
|
||||||
|
// option.
|
||||||
|
func (d *dumpState) indent() {
|
||||||
|
if d.ignoreNextIndent {
|
||||||
|
d.ignoreNextIndent = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||||
|
// This is useful for data types like structs, arrays, slices, and maps which
|
||||||
|
// can contain varying types packed inside an interface.
|
||||||
|
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
|
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||||
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
// circular refs.
|
||||||
|
for k, depth := range d.pointers {
|
||||||
|
if depth >= d.depth {
|
||||||
|
delete(d.pointers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep list of all dereferenced pointers to show later.
|
||||||
|
pointerChain := make([]uintptr, 0)
|
||||||
|
|
||||||
|
// Figure out how many levels of indirection there are by dereferencing
|
||||||
|
// pointers and unpacking interfaces down the chain while detecting circular
|
||||||
|
// references.
|
||||||
|
nilFound := false
|
||||||
|
cycleFound := false
|
||||||
|
indirects := 0
|
||||||
|
ve := v
|
||||||
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indirects++
|
||||||
|
addr := ve.Pointer()
|
||||||
|
pointerChain = append(pointerChain, addr)
|
||||||
|
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||||
|
cycleFound = true
|
||||||
|
indirects--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d.pointers[addr] = d.depth
|
||||||
|
|
||||||
|
ve = ve.Elem()
|
||||||
|
if ve.Kind() == reflect.Interface {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ve = ve.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display type information.
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||||
|
d.w.Write([]byte(ve.Type().String()))
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
|
||||||
|
// Display pointer information.
|
||||||
|
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
for i, addr := range pointerChain {
|
||||||
|
if i > 0 {
|
||||||
|
d.w.Write(pointerChainBytes)
|
||||||
|
}
|
||||||
|
printHexPtr(d.w, addr)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display dereferenced value.
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
switch {
|
||||||
|
case nilFound:
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
|
||||||
|
case cycleFound:
|
||||||
|
d.w.Write(circularBytes)
|
||||||
|
|
||||||
|
default:
|
||||||
|
d.ignoreNextType = true
|
||||||
|
d.dump(ve)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||||
|
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||||
|
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||||
|
// Determine whether this type should be hex dumped or not. Also,
|
||||||
|
// for types which should be hexdumped, try to use the underlying data
|
||||||
|
// first, then fall back to trying to convert them to a uint8 slice.
|
||||||
|
var buf []uint8
|
||||||
|
doConvert := false
|
||||||
|
doHexDump := false
|
||||||
|
numEntries := v.Len()
|
||||||
|
if numEntries > 0 {
|
||||||
|
vt := v.Index(0).Type()
|
||||||
|
vts := vt.String()
|
||||||
|
switch {
|
||||||
|
// C types that need to be converted.
|
||||||
|
case cCharRE.MatchString(vts):
|
||||||
|
fallthrough
|
||||||
|
case cUnsignedCharRE.MatchString(vts):
|
||||||
|
fallthrough
|
||||||
|
case cUint8tCharRE.MatchString(vts):
|
||||||
|
doConvert = true
|
||||||
|
|
||||||
|
// Try to use existing uint8 slices and fall back to converting
|
||||||
|
// and copying if that fails.
|
||||||
|
case vt.Kind() == reflect.Uint8:
|
||||||
|
// We need an addressable interface to convert the type
|
||||||
|
// to a byte slice. However, the reflect package won't
|
||||||
|
// give us an interface on certain things like
|
||||||
|
// unexported struct fields in order to enforce
|
||||||
|
// visibility rules. We use unsafe, when available, to
|
||||||
|
// bypass these restrictions since this package does not
|
||||||
|
// mutate the values.
|
||||||
|
vs := v
|
||||||
|
if !vs.CanInterface() || !vs.CanAddr() {
|
||||||
|
vs = unsafeReflectValue(vs)
|
||||||
|
}
|
||||||
|
if !UnsafeDisabled {
|
||||||
|
vs = vs.Slice(0, numEntries)
|
||||||
|
|
||||||
|
// Use the existing uint8 slice if it can be
|
||||||
|
// type asserted.
|
||||||
|
iface := vs.Interface()
|
||||||
|
if slice, ok := iface.([]uint8); ok {
|
||||||
|
buf = slice
|
||||||
|
doHexDump = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The underlying data needs to be converted if it can't
|
||||||
|
// be type asserted to a uint8 slice.
|
||||||
|
doConvert = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy and convert the underlying type if needed.
|
||||||
|
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||||
|
// Convert and copy each element into a uint8 byte
|
||||||
|
// slice.
|
||||||
|
buf = make([]uint8, numEntries)
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
vv := v.Index(i)
|
||||||
|
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||||
|
}
|
||||||
|
doHexDump = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hexdump the entire slice as needed.
|
||||||
|
if doHexDump {
|
||||||
|
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||||
|
str := indent + hex.Dump(buf)
|
||||||
|
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||||
|
str = strings.TrimRight(str, d.cs.Indent)
|
||||||
|
d.w.Write([]byte(str))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively call dump for each item.
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
d.dump(d.unpackValue(v.Index(i)))
|
||||||
|
if i < (numEntries - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||||
|
// value to figure out what kind of object we are dealing with and formats it
|
||||||
|
// appropriately. It is a recursive function, however circular data structures
|
||||||
|
// are detected and handled properly.
|
||||||
|
func (d *dumpState) dump(v reflect.Value) {
|
||||||
|
// Handle invalid reflect values immediately.
|
||||||
|
kind := v.Kind()
|
||||||
|
if kind == reflect.Invalid {
|
||||||
|
d.w.Write(invalidAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointers specially.
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
d.indent()
|
||||||
|
d.dumpPtr(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print type information unless already handled elsewhere.
|
||||||
|
if !d.ignoreNextType {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
d.w.Write([]byte(v.Type().String()))
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
d.ignoreNextType = false
|
||||||
|
|
||||||
|
// Display length and capacity if the built-in len and cap functions
|
||||||
|
// work with the value's kind and the len/cap itself is non-zero.
|
||||||
|
valueLen, valueCap := 0, 0
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||||
|
valueLen, valueCap = v.Len(), v.Cap()
|
||||||
|
case reflect.Map, reflect.String:
|
||||||
|
valueLen = v.Len()
|
||||||
|
}
|
||||||
|
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
if valueLen != 0 {
|
||||||
|
d.w.Write(lenEqualsBytes)
|
||||||
|
printInt(d.w, int64(valueLen), 10)
|
||||||
|
}
|
||||||
|
if !d.cs.DisableCapacities && valueCap != 0 {
|
||||||
|
if valueLen != 0 {
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
d.w.Write(capEqualsBytes)
|
||||||
|
printInt(d.w, int64(valueCap), 10)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||||
|
// is enabled
|
||||||
|
if !d.cs.DisableMethods {
|
||||||
|
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||||
|
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// Do nothing. We should never get here since invalid has already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
printBool(d.w, v.Bool())
|
||||||
|
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
printInt(d.w, v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
printUint(d.w, v.Uint(), 10)
|
||||||
|
|
||||||
|
case reflect.Float32:
|
||||||
|
printFloat(d.w, v.Float(), 32)
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
printFloat(d.w, v.Float(), 64)
|
||||||
|
|
||||||
|
case reflect.Complex64:
|
||||||
|
printComplex(d.w, v.Complex(), 32)
|
||||||
|
|
||||||
|
case reflect.Complex128:
|
||||||
|
printComplex(d.w, v.Complex(), 64)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.dumpSlice(v)
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// The only time we should get here is for nil interfaces due to
|
||||||
|
// unpackValue calls.
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Do nothing. We should never get here since pointers have already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// nil maps should be indicated as different than empty maps
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
numEntries := v.Len()
|
||||||
|
keys := v.MapKeys()
|
||||||
|
if d.cs.SortKeys {
|
||||||
|
sortValues(keys, d.cs)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
d.dump(d.unpackValue(key))
|
||||||
|
d.w.Write(colonSpaceBytes)
|
||||||
|
d.ignoreNextIndent = true
|
||||||
|
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||||
|
if i < (numEntries - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
vt := v.Type()
|
||||||
|
numFields := v.NumField()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
d.indent()
|
||||||
|
vtf := vt.Field(i)
|
||||||
|
d.w.Write([]byte(vtf.Name))
|
||||||
|
d.w.Write(colonSpaceBytes)
|
||||||
|
d.ignoreNextIndent = true
|
||||||
|
d.dump(d.unpackValue(v.Field(i)))
|
||||||
|
if i < (numFields - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Uintptr:
|
||||||
|
printHexPtr(d.w, uintptr(v.Uint()))
|
||||||
|
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
printHexPtr(d.w, v.Pointer())
|
||||||
|
|
||||||
|
// There were not any other types at the time this code was written, but
|
||||||
|
// fall back to letting the default fmt package handle it in case any new
|
||||||
|
// types are added.
|
||||||
|
default:
|
||||||
|
if v.CanInterface() {
|
||||||
|
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(d.w, "%v", v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fdump is a helper function to consolidate the logic from the various public
|
||||||
|
// methods which take varying writers and config states.
|
||||||
|
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||||
|
for _, arg := range a {
|
||||||
|
if arg == nil {
|
||||||
|
w.Write(interfaceBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
w.Write(nilAngleBytes)
|
||||||
|
w.Write(newlineBytes)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d := dumpState{w: w, cs: cs}
|
||||||
|
d.pointers = make(map[uintptr]int)
|
||||||
|
d.dump(reflect.ValueOf(arg))
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||||
|
// exactly the same as Dump.
|
||||||
|
func Fdump(w io.Writer, a ...interface{}) {
|
||||||
|
fdump(&Config, w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||||
|
// as Dump.
|
||||||
|
func Sdump(a ...interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fdump(&Config, &buf, a...)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump displays the passed parameters to standard out with newlines, customizable
|
||||||
|
indentation, and additional debug information such as complete types and all
|
||||||
|
pointer addresses used to indirect to the final value. It provides the
|
||||||
|
following features over the built-in printing facilities provided by the fmt
|
||||||
|
package:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output
|
||||||
|
|
||||||
|
The configuration options are controlled by an exported package global,
|
||||||
|
spew.Config. See ConfigState for options documentation.
|
||||||
|
|
||||||
|
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||||
|
get the formatted result as a string.
|
||||||
|
*/
|
||||||
|
func Dump(a ...interface{}) {
|
||||||
|
fdump(&Config, os.Stdout, a...)
|
||||||
|
}
|
@ -0,0 +1,419 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// supportedFlags is a list of all the character flags supported by fmt package.
|
||||||
|
const supportedFlags = "0-+# "
|
||||||
|
|
||||||
|
// formatState implements the fmt.Formatter interface and contains information
|
||||||
|
// about the state of a formatting operation. The NewFormatter function can
|
||||||
|
// be used to get a new Formatter which can be used directly as arguments
|
||||||
|
// in standard fmt package printing calls.
|
||||||
|
type formatState struct {
|
||||||
|
value interface{}
|
||||||
|
fs fmt.State
|
||||||
|
depth int
|
||||||
|
pointers map[uintptr]int
|
||||||
|
ignoreNextType bool
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildDefaultFormat recreates the original format string without precision
|
||||||
|
// and width information to pass in to fmt.Sprintf in the case of an
|
||||||
|
// unrecognized type. Unless new types are added to the language, this
|
||||||
|
// function won't ever be called.
|
||||||
|
func (f *formatState) buildDefaultFormat() (format string) {
|
||||||
|
buf := bytes.NewBuffer(percentBytes)
|
||||||
|
|
||||||
|
for _, flag := range supportedFlags {
|
||||||
|
if f.fs.Flag(int(flag)) {
|
||||||
|
buf.WriteRune(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteRune('v')
|
||||||
|
|
||||||
|
format = buf.String()
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructOrigFormat recreates the original format string including precision
|
||||||
|
// and width information to pass along to the standard fmt package. This allows
|
||||||
|
// automatic deferral of all format strings this package doesn't support.
|
||||||
|
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
||||||
|
buf := bytes.NewBuffer(percentBytes)
|
||||||
|
|
||||||
|
for _, flag := range supportedFlags {
|
||||||
|
if f.fs.Flag(int(flag)) {
|
||||||
|
buf.WriteRune(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if width, ok := f.fs.Width(); ok {
|
||||||
|
buf.WriteString(strconv.Itoa(width))
|
||||||
|
}
|
||||||
|
|
||||||
|
if precision, ok := f.fs.Precision(); ok {
|
||||||
|
buf.Write(precisionBytes)
|
||||||
|
buf.WriteString(strconv.Itoa(precision))
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteRune(verb)
|
||||||
|
|
||||||
|
format = buf.String()
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackValue returns values inside of non-nil interfaces when possible and
|
||||||
|
// ensures that types for values which have been unpacked from an interface
|
||||||
|
// are displayed when the show types flag is also set.
|
||||||
|
// This is useful for data types like structs, arrays, slices, and maps which
|
||||||
|
// can contain varying types packed inside an interface.
|
||||||
|
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() == reflect.Interface {
|
||||||
|
f.ignoreNextType = false
|
||||||
|
if !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
|
func (f *formatState) formatPtr(v reflect.Value) {
|
||||||
|
// Display nil if top level pointer is nil.
|
||||||
|
showTypes := f.fs.Flag('#')
|
||||||
|
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
// circular refs.
|
||||||
|
for k, depth := range f.pointers {
|
||||||
|
if depth >= f.depth {
|
||||||
|
delete(f.pointers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep list of all dereferenced pointers to possibly show later.
|
||||||
|
pointerChain := make([]uintptr, 0)
|
||||||
|
|
||||||
|
// Figure out how many levels of indirection there are by derferencing
|
||||||
|
// pointers and unpacking interfaces down the chain while detecting circular
|
||||||
|
// references.
|
||||||
|
nilFound := false
|
||||||
|
cycleFound := false
|
||||||
|
indirects := 0
|
||||||
|
ve := v
|
||||||
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indirects++
|
||||||
|
addr := ve.Pointer()
|
||||||
|
pointerChain = append(pointerChain, addr)
|
||||||
|
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
||||||
|
cycleFound = true
|
||||||
|
indirects--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
f.pointers[addr] = f.depth
|
||||||
|
|
||||||
|
ve = ve.Elem()
|
||||||
|
if ve.Kind() == reflect.Interface {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ve = ve.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display type or indirection level depending on flags.
|
||||||
|
if showTypes && !f.ignoreNextType {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||||
|
f.fs.Write([]byte(ve.Type().String()))
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
} else {
|
||||||
|
if nilFound || cycleFound {
|
||||||
|
indirects += strings.Count(ve.Type().String(), "*")
|
||||||
|
}
|
||||||
|
f.fs.Write(openAngleBytes)
|
||||||
|
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
||||||
|
f.fs.Write(closeAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display pointer information depending on flags.
|
||||||
|
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
for i, addr := range pointerChain {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(pointerChainBytes)
|
||||||
|
}
|
||||||
|
printHexPtr(f.fs, addr)
|
||||||
|
}
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display dereferenced value.
|
||||||
|
switch {
|
||||||
|
case nilFound:
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
|
||||||
|
case cycleFound:
|
||||||
|
f.fs.Write(circularShortBytes)
|
||||||
|
|
||||||
|
default:
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(ve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// format is the main workhorse for providing the Formatter interface. It
|
||||||
|
// uses the passed reflect value to figure out what kind of object we are
|
||||||
|
// dealing with and formats it appropriately. It is a recursive function,
|
||||||
|
// however circular data structures are detected and handled properly.
|
||||||
|
func (f *formatState) format(v reflect.Value) {
|
||||||
|
// Handle invalid reflect values immediately.
|
||||||
|
kind := v.Kind()
|
||||||
|
if kind == reflect.Invalid {
|
||||||
|
f.fs.Write(invalidAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointers specially.
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
f.formatPtr(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print type information unless already handled elsewhere.
|
||||||
|
if !f.ignoreNextType && f.fs.Flag('#') {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
f.fs.Write([]byte(v.Type().String()))
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = false
|
||||||
|
|
||||||
|
// Call Stringer/error interfaces if they exist and the handle methods
|
||||||
|
// flag is enabled.
|
||||||
|
if !f.cs.DisableMethods {
|
||||||
|
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||||
|
if handled := handleMethods(f.cs, f.fs, v); handled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// Do nothing. We should never get here since invalid has already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
printBool(f.fs, v.Bool())
|
||||||
|
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
printInt(f.fs, v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
printUint(f.fs, v.Uint(), 10)
|
||||||
|
|
||||||
|
case reflect.Float32:
|
||||||
|
printFloat(f.fs, v.Float(), 32)
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
printFloat(f.fs, v.Float(), 64)
|
||||||
|
|
||||||
|
case reflect.Complex64:
|
||||||
|
printComplex(f.fs, v.Complex(), 32)
|
||||||
|
|
||||||
|
case reflect.Complex128:
|
||||||
|
printComplex(f.fs, v.Complex(), 64)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
f.fs.Write(openBracketBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
numEntries := v.Len()
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(v.Index(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeBracketBytes)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
f.fs.Write([]byte(v.String()))
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// The only time we should get here is for nil interfaces due to
|
||||||
|
// unpackValue calls.
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Do nothing. We should never get here since pointers have already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// nil maps should be indicated as different than empty maps
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
f.fs.Write(openMapBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
keys := v.MapKeys()
|
||||||
|
if f.cs.SortKeys {
|
||||||
|
sortValues(keys, f.cs)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(key))
|
||||||
|
f.fs.Write(colonBytes)
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(v.MapIndex(key)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeMapBytes)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
numFields := v.NumField()
|
||||||
|
f.fs.Write(openBraceBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
vt := v.Type()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
vtf := vt.Field(i)
|
||||||
|
if f.fs.Flag('+') || f.fs.Flag('#') {
|
||||||
|
f.fs.Write([]byte(vtf.Name))
|
||||||
|
f.fs.Write(colonBytes)
|
||||||
|
}
|
||||||
|
f.format(f.unpackValue(v.Field(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Uintptr:
|
||||||
|
printHexPtr(f.fs, uintptr(v.Uint()))
|
||||||
|
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
printHexPtr(f.fs, v.Pointer())
|
||||||
|
|
||||||
|
// There were not any other types at the time this code was written, but
|
||||||
|
// fall back to letting the default fmt package handle it if any get added.
|
||||||
|
default:
|
||||||
|
format := f.buildDefaultFormat()
|
||||||
|
if v.CanInterface() {
|
||||||
|
fmt.Fprintf(f.fs, format, v.Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(f.fs, format, v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
||||||
|
// details.
|
||||||
|
func (f *formatState) Format(fs fmt.State, verb rune) {
|
||||||
|
f.fs = fs
|
||||||
|
|
||||||
|
// Use standard formatting for verbs that are not v.
|
||||||
|
if verb != 'v' {
|
||||||
|
format := f.constructOrigFormat(verb)
|
||||||
|
fmt.Fprintf(fs, format, f.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.value == nil {
|
||||||
|
if fs.Flag('#') {
|
||||||
|
fs.Write(interfaceBytes)
|
||||||
|
}
|
||||||
|
fs.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.format(reflect.ValueOf(f.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFormatter is a helper function to consolidate the logic from the various
|
||||||
|
// public methods which take varying config states.
|
||||||
|
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
||||||
|
fs := &formatState{value: v, cs: cs}
|
||||||
|
fs.pointers = make(map[uintptr]int)
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||||
|
interface. As a result, it integrates cleanly with standard fmt package
|
||||||
|
printing functions. The formatter is useful for inline printing of smaller data
|
||||||
|
types similar to the standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Typically this function shouldn't be called directly. It is much easier to make
|
||||||
|
use of the custom formatter by calling one of the convenience functions such as
|
||||||
|
Printf, Println, or Fprintf.
|
||||||
|
*/
|
||||||
|
func NewFormatter(v interface{}) fmt.Formatter {
|
||||||
|
return newFormatter(&Config, v)
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the formatted string as a value that satisfies error. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Errorf(format string, a ...interface{}) (err error) {
|
||||||
|
return fmt.Errorf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprint(w, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintf(w, format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Print(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Print(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Printf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Println(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprint(a ...interface{}) string {
|
||||||
|
return fmt.Sprint(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprintf(format string, a ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||||
|
// were passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprintln(a ...interface{}) string {
|
||||||
|
return fmt.Sprintln(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||||
|
// length with each argument converted to a default spew Formatter interface.
|
||||||
|
func convertArgs(args []interface{}) (formatters []interface{}) {
|
||||||
|
formatters = make([]interface{}, len(args))
|
||||||
|
for index, arg := range args {
|
||||||
|
formatters[index] = NewFormatter(arg)
|
||||||
|
}
|
||||||
|
return formatters
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017-2020 Damian Gryski <damian@gryski.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,79 @@
|
|||||||
|
package rendezvous
|
||||||
|
|
||||||
|
type Rendezvous struct {
|
||||||
|
nodes map[string]int
|
||||||
|
nstr []string
|
||||||
|
nhash []uint64
|
||||||
|
hash Hasher
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hasher func(s string) uint64
|
||||||
|
|
||||||
|
func New(nodes []string, hash Hasher) *Rendezvous {
|
||||||
|
r := &Rendezvous{
|
||||||
|
nodes: make(map[string]int, len(nodes)),
|
||||||
|
nstr: make([]string, len(nodes)),
|
||||||
|
nhash: make([]uint64, len(nodes)),
|
||||||
|
hash: hash,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, n := range nodes {
|
||||||
|
r.nodes[n] = i
|
||||||
|
r.nstr[i] = n
|
||||||
|
r.nhash[i] = hash(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rendezvous) Lookup(k string) string {
|
||||||
|
// short-circuit if we're empty
|
||||||
|
if len(r.nodes) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
khash := r.hash(k)
|
||||||
|
|
||||||
|
var midx int
|
||||||
|
var mhash = xorshiftMult64(khash ^ r.nhash[0])
|
||||||
|
|
||||||
|
for i, nhash := range r.nhash[1:] {
|
||||||
|
if h := xorshiftMult64(khash ^ nhash); h > mhash {
|
||||||
|
midx = i + 1
|
||||||
|
mhash = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.nstr[midx]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rendezvous) Add(node string) {
|
||||||
|
r.nodes[node] = len(r.nstr)
|
||||||
|
r.nstr = append(r.nstr, node)
|
||||||
|
r.nhash = append(r.nhash, r.hash(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rendezvous) Remove(node string) {
|
||||||
|
// find index of node to remove
|
||||||
|
nidx := r.nodes[node]
|
||||||
|
|
||||||
|
// remove from the slices
|
||||||
|
l := len(r.nstr)
|
||||||
|
r.nstr[nidx] = r.nstr[l]
|
||||||
|
r.nstr = r.nstr[:l]
|
||||||
|
|
||||||
|
r.nhash[nidx] = r.nhash[l]
|
||||||
|
r.nhash = r.nhash[:l]
|
||||||
|
|
||||||
|
// update the map
|
||||||
|
delete(r.nodes, node)
|
||||||
|
moved := r.nstr[nidx]
|
||||||
|
r.nodes[moved] = nidx
|
||||||
|
}
|
||||||
|
|
||||||
|
func xorshiftMult64(x uint64) uint64 {
|
||||||
|
x ^= x >> 12 // a
|
||||||
|
x ^= x << 25 // b
|
||||||
|
x ^= x >> 27 // c
|
||||||
|
return x * 2685821657736338717
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
@ -0,0 +1,26 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.13.1
|
||||||
|
- tip
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email:
|
||||||
|
recipients: dean.karn@gmail.com
|
||||||
|
on_success: change
|
||||||
|
on_failure: always
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go install github.com/mattn/goveralls
|
||||||
|
|
||||||
|
# Only clone the most recent commit.
|
||||||
|
git:
|
||||||
|
depth: 1
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v -race -covermode=atomic -coverprofile=coverage.coverprofile ./...
|
||||||
|
|
||||||
|
after_success: |
|
||||||
|
goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN
|
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Go Playground
|
||||||
|
|
||||||
|
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,172 @@
|
|||||||
|
## locales
|
||||||
|
<img align="right" src="https://raw.githubusercontent.com/go-playground/locales/master/logo.png">![Project status](https://img.shields.io/badge/version-0.14.0-green.svg)
|
||||||
|
[![Build Status](https://travis-ci.org/go-playground/locales.svg?branch=master)](https://travis-ci.org/go-playground/locales)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/locales)](https://goreportcard.com/report/github.com/go-playground/locales)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/go-playground/locales?status.svg)](https://godoc.org/github.com/go-playground/locales)
|
||||||
|
![License](https://img.shields.io/dub/l/vibe-d.svg)
|
||||||
|
[![Gitter](https://badges.gitter.im/go-playground/locales.svg)](https://gitter.im/go-playground/locales?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
|
|
||||||
|
Locales is a set of locales generated from the [Unicode CLDR Project](http://cldr.unicode.org/) which can be used independently or within
|
||||||
|
an i18n package; these were built for use with, but not exclusive to, [Universal Translator](https://github.com/go-playground/universal-translator).
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
- [x] Rules generated from the latest [CLDR](http://cldr.unicode.org/index/downloads) data, v36.0.1
|
||||||
|
- [x] Contains Cardinal, Ordinal and Range Plural Rules
|
||||||
|
- [x] Contains Month, Weekday and Timezone translations built in
|
||||||
|
- [x] Contains Date & Time formatting functions
|
||||||
|
- [x] Contains Number, Currency, Accounting and Percent formatting functions
|
||||||
|
- [x] Supports the "Gregorian" calendar only ( my time isn't unlimited, had to draw the line somewhere )
|
||||||
|
|
||||||
|
Full Tests
|
||||||
|
--------------------
|
||||||
|
I could sure use your help adding tests for every locale, it is a huge undertaking and I just don't have the free time to do it all at the moment;
|
||||||
|
any help would be **greatly appreciated!!!!** please see [issue](https://github.com/go-playground/locales/issues/1) for details.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Use go get
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go get github.com/go-playground/locales
|
||||||
|
```
|
||||||
|
|
||||||
|
NOTES
|
||||||
|
--------
|
||||||
|
You'll notice most return types are []byte, this is because most of the time the results will be concatenated with a larger body
|
||||||
|
of text and can avoid some allocations if already appending to a byte array, otherwise just cast as string.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-------
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-playground/locales/currency"
|
||||||
|
"github.com/go-playground/locales/en_CA"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
loc, _ := time.LoadLocation("America/Toronto")
|
||||||
|
datetime := time.Date(2016, 02, 03, 9, 0, 1, 0, loc)
|
||||||
|
|
||||||
|
l := en_CA.New()
|
||||||
|
|
||||||
|
// Dates
|
||||||
|
fmt.Println(l.FmtDateFull(datetime))
|
||||||
|
fmt.Println(l.FmtDateLong(datetime))
|
||||||
|
fmt.Println(l.FmtDateMedium(datetime))
|
||||||
|
fmt.Println(l.FmtDateShort(datetime))
|
||||||
|
|
||||||
|
// Times
|
||||||
|
fmt.Println(l.FmtTimeFull(datetime))
|
||||||
|
fmt.Println(l.FmtTimeLong(datetime))
|
||||||
|
fmt.Println(l.FmtTimeMedium(datetime))
|
||||||
|
fmt.Println(l.FmtTimeShort(datetime))
|
||||||
|
|
||||||
|
// Months Wide
|
||||||
|
fmt.Println(l.MonthWide(time.January))
|
||||||
|
fmt.Println(l.MonthWide(time.February))
|
||||||
|
fmt.Println(l.MonthWide(time.March))
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Months Abbreviated
|
||||||
|
fmt.Println(l.MonthAbbreviated(time.January))
|
||||||
|
fmt.Println(l.MonthAbbreviated(time.February))
|
||||||
|
fmt.Println(l.MonthAbbreviated(time.March))
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Months Narrow
|
||||||
|
fmt.Println(l.MonthNarrow(time.January))
|
||||||
|
fmt.Println(l.MonthNarrow(time.February))
|
||||||
|
fmt.Println(l.MonthNarrow(time.March))
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Weekdays Wide
|
||||||
|
fmt.Println(l.WeekdayWide(time.Sunday))
|
||||||
|
fmt.Println(l.WeekdayWide(time.Monday))
|
||||||
|
fmt.Println(l.WeekdayWide(time.Tuesday))
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Weekdays Abbreviated
|
||||||
|
fmt.Println(l.WeekdayAbbreviated(time.Sunday))
|
||||||
|
fmt.Println(l.WeekdayAbbreviated(time.Monday))
|
||||||
|
fmt.Println(l.WeekdayAbbreviated(time.Tuesday))
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Weekdays Short
|
||||||
|
fmt.Println(l.WeekdayShort(time.Sunday))
|
||||||
|
fmt.Println(l.WeekdayShort(time.Monday))
|
||||||
|
fmt.Println(l.WeekdayShort(time.Tuesday))
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Weekdays Narrow
|
||||||
|
fmt.Println(l.WeekdayNarrow(time.Sunday))
|
||||||
|
fmt.Println(l.WeekdayNarrow(time.Monday))
|
||||||
|
fmt.Println(l.WeekdayNarrow(time.Tuesday))
|
||||||
|
// ...
|
||||||
|
|
||||||
|
var f64 float64
|
||||||
|
|
||||||
|
f64 = -10356.4523
|
||||||
|
|
||||||
|
// Number
|
||||||
|
fmt.Println(l.FmtNumber(f64, 2))
|
||||||
|
|
||||||
|
// Currency
|
||||||
|
fmt.Println(l.FmtCurrency(f64, 2, currency.CAD))
|
||||||
|
fmt.Println(l.FmtCurrency(f64, 2, currency.USD))
|
||||||
|
|
||||||
|
// Accounting
|
||||||
|
fmt.Println(l.FmtAccounting(f64, 2, currency.CAD))
|
||||||
|
fmt.Println(l.FmtAccounting(f64, 2, currency.USD))
|
||||||
|
|
||||||
|
f64 = 78.12
|
||||||
|
|
||||||
|
// Percent
|
||||||
|
fmt.Println(l.FmtPercent(f64, 0))
|
||||||
|
|
||||||
|
// Plural Rules for locale, so you know what rules you must cover
|
||||||
|
fmt.Println(l.PluralsCardinal())
|
||||||
|
fmt.Println(l.PluralsOrdinal())
|
||||||
|
|
||||||
|
// Cardinal Plural Rules
|
||||||
|
fmt.Println(l.CardinalPluralRule(1, 0))
|
||||||
|
fmt.Println(l.CardinalPluralRule(1.0, 0))
|
||||||
|
fmt.Println(l.CardinalPluralRule(1.0, 1))
|
||||||
|
fmt.Println(l.CardinalPluralRule(3, 0))
|
||||||
|
|
||||||
|
// Ordinal Plural Rules
|
||||||
|
fmt.Println(l.OrdinalPluralRule(21, 0)) // 21st
|
||||||
|
fmt.Println(l.OrdinalPluralRule(22, 0)) // 22nd
|
||||||
|
fmt.Println(l.OrdinalPluralRule(33, 0)) // 33rd
|
||||||
|
fmt.Println(l.OrdinalPluralRule(34, 0)) // 34th
|
||||||
|
|
||||||
|
// Range Plural Rules
|
||||||
|
fmt.Println(l.RangePluralRule(1, 0, 1, 0)) // 1-1
|
||||||
|
fmt.Println(l.RangePluralRule(1, 0, 2, 0)) // 1-2
|
||||||
|
fmt.Println(l.RangePluralRule(5, 0, 8, 0)) // 5-8
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
NOTES:
|
||||||
|
-------
|
||||||
|
These rules were generated from the [Unicode CLDR Project](http://cldr.unicode.org/), if you encounter any issues
|
||||||
|
I strongly encourage contributing to the CLDR project to get the locale information corrected and the next time
|
||||||
|
these locales are regenerated the fix will come with.
|
||||||
|
|
||||||
|
I do however realize that time constraints are often important and so there are two options:
|
||||||
|
|
||||||
|
1. Create your own locale, copy, paste and modify, and ensure it complies with the `Translator` interface.
|
||||||
|
2. Add an exception in the locale generation code directly and once regenerated, fix will be in place.
|
||||||
|
|
||||||
|
Please to not make fixes inside the locale files, they WILL get overwritten when the locales are regenerated.
|
||||||
|
|
||||||
|
License
|
||||||
|
------
|
||||||
|
Distributed under MIT License, please see license file in code for more details.
|
@ -0,0 +1,311 @@
|
|||||||
|
package currency
|
||||||
|
|
||||||
|
// Type is the currency type associated with the locales currency enum
|
||||||
|
type Type int
|
||||||
|
|
||||||
|
// locale currencies
|
||||||
|
const (
|
||||||
|
ADP Type = iota
|
||||||
|
AED
|
||||||
|
AFA
|
||||||
|
AFN
|
||||||
|
ALK
|
||||||
|
ALL
|
||||||
|
AMD
|
||||||
|
ANG
|
||||||
|
AOA
|
||||||
|
AOK
|
||||||
|
AON
|
||||||
|
AOR
|
||||||
|
ARA
|
||||||
|
ARL
|
||||||
|
ARM
|
||||||
|
ARP
|
||||||
|
ARS
|
||||||
|
ATS
|
||||||
|
AUD
|
||||||
|
AWG
|
||||||
|
AZM
|
||||||
|
AZN
|
||||||
|
BAD
|
||||||
|
BAM
|
||||||
|
BAN
|
||||||
|
BBD
|
||||||
|
BDT
|
||||||
|
BEC
|
||||||
|
BEF
|
||||||
|
BEL
|
||||||
|
BGL
|
||||||
|
BGM
|
||||||
|
BGN
|
||||||
|
BGO
|
||||||
|
BHD
|
||||||
|
BIF
|
||||||
|
BMD
|
||||||
|
BND
|
||||||
|
BOB
|
||||||
|
BOL
|
||||||
|
BOP
|
||||||
|
BOV
|
||||||
|
BRB
|
||||||
|
BRC
|
||||||
|
BRE
|
||||||
|
BRL
|
||||||
|
BRN
|
||||||
|
BRR
|
||||||
|
BRZ
|
||||||
|
BSD
|
||||||
|
BTN
|
||||||
|
BUK
|
||||||
|
BWP
|
||||||
|
BYB
|
||||||
|
BYN
|
||||||
|
BYR
|
||||||
|
BZD
|
||||||
|
CAD
|
||||||
|
CDF
|
||||||
|
CHE
|
||||||
|
CHF
|
||||||
|
CHW
|
||||||
|
CLE
|
||||||
|
CLF
|
||||||
|
CLP
|
||||||
|
CNH
|
||||||
|
CNX
|
||||||
|
CNY
|
||||||
|
COP
|
||||||
|
COU
|
||||||
|
CRC
|
||||||
|
CSD
|
||||||
|
CSK
|
||||||
|
CUC
|
||||||
|
CUP
|
||||||
|
CVE
|
||||||
|
CYP
|
||||||
|
CZK
|
||||||
|
DDM
|
||||||
|
DEM
|
||||||
|
DJF
|
||||||
|
DKK
|
||||||
|
DOP
|
||||||
|
DZD
|
||||||
|
ECS
|
||||||
|
ECV
|
||||||
|
EEK
|
||||||
|
EGP
|
||||||
|
ERN
|
||||||
|
ESA
|
||||||
|
ESB
|
||||||
|
ESP
|
||||||
|
ETB
|
||||||
|
EUR
|
||||||
|
FIM
|
||||||
|
FJD
|
||||||
|
FKP
|
||||||
|
FRF
|
||||||
|
GBP
|
||||||
|
GEK
|
||||||
|
GEL
|
||||||
|
GHC
|
||||||
|
GHS
|
||||||
|
GIP
|
||||||
|
GMD
|
||||||
|
GNF
|
||||||
|
GNS
|
||||||
|
GQE
|
||||||
|
GRD
|
||||||
|
GTQ
|
||||||
|
GWE
|
||||||
|
GWP
|
||||||
|
GYD
|
||||||
|
HKD
|
||||||
|
HNL
|
||||||
|
HRD
|
||||||
|
HRK
|
||||||
|
HTG
|
||||||
|
HUF
|
||||||
|
IDR
|
||||||
|
IEP
|
||||||
|
ILP
|
||||||
|
ILR
|
||||||
|
ILS
|
||||||
|
INR
|
||||||
|
IQD
|
||||||
|
IRR
|
||||||
|
ISJ
|
||||||
|
ISK
|
||||||
|
ITL
|
||||||
|
JMD
|
||||||
|
JOD
|
||||||
|
JPY
|
||||||
|
KES
|
||||||
|
KGS
|
||||||
|
KHR
|
||||||
|
KMF
|
||||||
|
KPW
|
||||||
|
KRH
|
||||||
|
KRO
|
||||||
|
KRW
|
||||||
|
KWD
|
||||||
|
KYD
|
||||||
|
KZT
|
||||||
|
LAK
|
||||||
|
LBP
|
||||||
|
LKR
|
||||||
|
LRD
|
||||||
|
LSL
|
||||||
|
LTL
|
||||||
|
LTT
|
||||||
|
LUC
|
||||||
|
LUF
|
||||||
|
LUL
|
||||||
|
LVL
|
||||||
|
LVR
|
||||||
|
LYD
|
||||||
|
MAD
|
||||||
|
MAF
|
||||||
|
MCF
|
||||||
|
MDC
|
||||||
|
MDL
|
||||||
|
MGA
|
||||||
|
MGF
|
||||||
|
MKD
|
||||||
|
MKN
|
||||||
|
MLF
|
||||||
|
MMK
|
||||||
|
MNT
|
||||||
|
MOP
|
||||||
|
MRO
|
||||||
|
MRU
|
||||||
|
MTL
|
||||||
|
MTP
|
||||||
|
MUR
|
||||||
|
MVP
|
||||||
|
MVR
|
||||||
|
MWK
|
||||||
|
MXN
|
||||||
|
MXP
|
||||||
|
MXV
|
||||||
|
MYR
|
||||||
|
MZE
|
||||||
|
MZM
|
||||||
|
MZN
|
||||||
|
NAD
|
||||||
|
NGN
|
||||||
|
NIC
|
||||||
|
NIO
|
||||||
|
NLG
|
||||||
|
NOK
|
||||||
|
NPR
|
||||||
|
NZD
|
||||||
|
OMR
|
||||||
|
PAB
|
||||||
|
PEI
|
||||||
|
PEN
|
||||||
|
PES
|
||||||
|
PGK
|
||||||
|
PHP
|
||||||
|
PKR
|
||||||
|
PLN
|
||||||
|
PLZ
|
||||||
|
PTE
|
||||||
|
PYG
|
||||||
|
QAR
|
||||||
|
RHD
|
||||||
|
ROL
|
||||||
|
RON
|
||||||
|
RSD
|
||||||
|
RUB
|
||||||
|
RUR
|
||||||
|
RWF
|
||||||
|
SAR
|
||||||
|
SBD
|
||||||
|
SCR
|
||||||
|
SDD
|
||||||
|
SDG
|
||||||
|
SDP
|
||||||
|
SEK
|
||||||
|
SGD
|
||||||
|
SHP
|
||||||
|
SIT
|
||||||
|
SKK
|
||||||
|
SLL
|
||||||
|
SOS
|
||||||
|
SRD
|
||||||
|
SRG
|
||||||
|
SSP
|
||||||
|
STD
|
||||||
|
STN
|
||||||
|
SUR
|
||||||
|
SVC
|
||||||
|
SYP
|
||||||
|
SZL
|
||||||
|
THB
|
||||||
|
TJR
|
||||||
|
TJS
|
||||||
|
TMM
|
||||||
|
TMT
|
||||||
|
TND
|
||||||
|
TOP
|
||||||
|
TPE
|
||||||
|
TRL
|
||||||
|
TRY
|
||||||
|
TTD
|
||||||
|
TWD
|
||||||
|
TZS
|
||||||
|
UAH
|
||||||
|
UAK
|
||||||
|
UGS
|
||||||
|
UGX
|
||||||
|
USD
|
||||||
|
USN
|
||||||
|
USS
|
||||||
|
UYI
|
||||||
|
UYP
|
||||||
|
UYU
|
||||||
|
UYW
|
||||||
|
UZS
|
||||||
|
VEB
|
||||||
|
VEF
|
||||||
|
VES
|
||||||
|
VND
|
||||||
|
VNN
|
||||||
|
VUV
|
||||||
|
WST
|
||||||
|
XAF
|
||||||
|
XAG
|
||||||
|
XAU
|
||||||
|
XBA
|
||||||
|
XBB
|
||||||
|
XBC
|
||||||
|
XBD
|
||||||
|
XCD
|
||||||
|
XDR
|
||||||
|
XEU
|
||||||
|
XFO
|
||||||
|
XFU
|
||||||
|
XOF
|
||||||
|
XPD
|
||||||
|
XPF
|
||||||
|
XPT
|
||||||
|
XRE
|
||||||
|
XSU
|
||||||
|
XTS
|
||||||
|
XUA
|
||||||
|
XXX
|
||||||
|
YDD
|
||||||
|
YER
|
||||||
|
YUD
|
||||||
|
YUM
|
||||||
|
YUN
|
||||||
|
YUR
|
||||||
|
ZAL
|
||||||
|
ZAR
|
||||||
|
ZMK
|
||||||
|
ZMW
|
||||||
|
ZRN
|
||||||
|
ZRZ
|
||||||
|
ZWD
|
||||||
|
ZWL
|
||||||
|
ZWR
|
||||||
|
)
|
After Width: | Height: | Size: 36 KiB |
@ -0,0 +1,293 @@
|
|||||||
|
package locales
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-playground/locales/currency"
|
||||||
|
)
|
||||||
|
|
||||||
|
// // ErrBadNumberValue is returned when the number passed for
|
||||||
|
// // plural rule determination cannot be parsed
|
||||||
|
// type ErrBadNumberValue struct {
|
||||||
|
// NumberValue string
|
||||||
|
// InnerError error
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Error returns ErrBadNumberValue error string
|
||||||
|
// func (e *ErrBadNumberValue) Error() string {
|
||||||
|
// return fmt.Sprintf("Invalid Number Value '%s' %s", e.NumberValue, e.InnerError)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var _ error = new(ErrBadNumberValue)
|
||||||
|
|
||||||
|
// PluralRule denotes the type of plural rules
|
||||||
|
type PluralRule int
|
||||||
|
|
||||||
|
// PluralRule's
|
||||||
|
const (
|
||||||
|
PluralRuleUnknown PluralRule = iota
|
||||||
|
PluralRuleZero // zero
|
||||||
|
PluralRuleOne // one - singular
|
||||||
|
PluralRuleTwo // two - dual
|
||||||
|
PluralRuleFew // few - paucal
|
||||||
|
PluralRuleMany // many - also used for fractions if they have a separate class
|
||||||
|
PluralRuleOther // other - required—general plural form—also used if the language only has a single form
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
pluralsString = "UnknownZeroOneTwoFewManyOther"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Translator encapsulates an instance of a locale
|
||||||
|
// NOTE: some values are returned as a []byte just in case the caller
|
||||||
|
// wishes to add more and can help avoid allocations; otherwise just cast as string
|
||||||
|
type Translator interface {
|
||||||
|
|
||||||
|
// The following Functions are for overriding, debugging or developing
|
||||||
|
// with a Translator Locale
|
||||||
|
|
||||||
|
// Locale returns the string value of the translator
|
||||||
|
Locale() string
|
||||||
|
|
||||||
|
// returns an array of cardinal plural rules associated
|
||||||
|
// with this translator
|
||||||
|
PluralsCardinal() []PluralRule
|
||||||
|
|
||||||
|
// returns an array of ordinal plural rules associated
|
||||||
|
// with this translator
|
||||||
|
PluralsOrdinal() []PluralRule
|
||||||
|
|
||||||
|
// returns an array of range plural rules associated
|
||||||
|
// with this translator
|
||||||
|
PluralsRange() []PluralRule
|
||||||
|
|
||||||
|
// returns the cardinal PluralRule given 'num' and digits/precision of 'v' for locale
|
||||||
|
CardinalPluralRule(num float64, v uint64) PluralRule
|
||||||
|
|
||||||
|
// returns the ordinal PluralRule given 'num' and digits/precision of 'v' for locale
|
||||||
|
OrdinalPluralRule(num float64, v uint64) PluralRule
|
||||||
|
|
||||||
|
// returns the ordinal PluralRule given 'num1', 'num2' and digits/precision of 'v1' and 'v2' for locale
|
||||||
|
RangePluralRule(num1 float64, v1 uint64, num2 float64, v2 uint64) PluralRule
|
||||||
|
|
||||||
|
// returns the locales abbreviated month given the 'month' provided
|
||||||
|
MonthAbbreviated(month time.Month) string
|
||||||
|
|
||||||
|
// returns the locales abbreviated months
|
||||||
|
MonthsAbbreviated() []string
|
||||||
|
|
||||||
|
// returns the locales narrow month given the 'month' provided
|
||||||
|
MonthNarrow(month time.Month) string
|
||||||
|
|
||||||
|
// returns the locales narrow months
|
||||||
|
MonthsNarrow() []string
|
||||||
|
|
||||||
|
// returns the locales wide month given the 'month' provided
|
||||||
|
MonthWide(month time.Month) string
|
||||||
|
|
||||||
|
// returns the locales wide months
|
||||||
|
MonthsWide() []string
|
||||||
|
|
||||||
|
// returns the locales abbreviated weekday given the 'weekday' provided
|
||||||
|
WeekdayAbbreviated(weekday time.Weekday) string
|
||||||
|
|
||||||
|
// returns the locales abbreviated weekdays
|
||||||
|
WeekdaysAbbreviated() []string
|
||||||
|
|
||||||
|
// returns the locales narrow weekday given the 'weekday' provided
|
||||||
|
WeekdayNarrow(weekday time.Weekday) string
|
||||||
|
|
||||||
|
// WeekdaysNarrowreturns the locales narrow weekdays
|
||||||
|
WeekdaysNarrow() []string
|
||||||
|
|
||||||
|
// returns the locales short weekday given the 'weekday' provided
|
||||||
|
WeekdayShort(weekday time.Weekday) string
|
||||||
|
|
||||||
|
// returns the locales short weekdays
|
||||||
|
WeekdaysShort() []string
|
||||||
|
|
||||||
|
// returns the locales wide weekday given the 'weekday' provided
|
||||||
|
WeekdayWide(weekday time.Weekday) string
|
||||||
|
|
||||||
|
// returns the locales wide weekdays
|
||||||
|
WeekdaysWide() []string
|
||||||
|
|
||||||
|
// The following Functions are common Formatting functionsfor the Translator's Locale
|
||||||
|
|
||||||
|
// returns 'num' with digits/precision of 'v' for locale and handles both Whole and Real numbers based on 'v'
|
||||||
|
FmtNumber(num float64, v uint64) string
|
||||||
|
|
||||||
|
// returns 'num' with digits/precision of 'v' for locale and handles both Whole and Real numbers based on 'v'
|
||||||
|
// NOTE: 'num' passed into FmtPercent is assumed to be in percent already
|
||||||
|
FmtPercent(num float64, v uint64) string
|
||||||
|
|
||||||
|
// returns the currency representation of 'num' with digits/precision of 'v' for locale
|
||||||
|
FmtCurrency(num float64, v uint64, currency currency.Type) string
|
||||||
|
|
||||||
|
// returns the currency representation of 'num' with digits/precision of 'v' for locale
|
||||||
|
// in accounting notation.
|
||||||
|
FmtAccounting(num float64, v uint64, currency currency.Type) string
|
||||||
|
|
||||||
|
// returns the short date representation of 't' for locale
|
||||||
|
FmtDateShort(t time.Time) string
|
||||||
|
|
||||||
|
// returns the medium date representation of 't' for locale
|
||||||
|
FmtDateMedium(t time.Time) string
|
||||||
|
|
||||||
|
// returns the long date representation of 't' for locale
|
||||||
|
FmtDateLong(t time.Time) string
|
||||||
|
|
||||||
|
// returns the full date representation of 't' for locale
|
||||||
|
FmtDateFull(t time.Time) string
|
||||||
|
|
||||||
|
// returns the short time representation of 't' for locale
|
||||||
|
FmtTimeShort(t time.Time) string
|
||||||
|
|
||||||
|
// returns the medium time representation of 't' for locale
|
||||||
|
FmtTimeMedium(t time.Time) string
|
||||||
|
|
||||||
|
// returns the long time representation of 't' for locale
|
||||||
|
FmtTimeLong(t time.Time) string
|
||||||
|
|
||||||
|
// returns the full time representation of 't' for locale
|
||||||
|
FmtTimeFull(t time.Time) string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string value of PluralRule
|
||||||
|
func (p PluralRule) String() string {
|
||||||
|
|
||||||
|
switch p {
|
||||||
|
case PluralRuleZero:
|
||||||
|
return pluralsString[7:11]
|
||||||
|
case PluralRuleOne:
|
||||||
|
return pluralsString[11:14]
|
||||||
|
case PluralRuleTwo:
|
||||||
|
return pluralsString[14:17]
|
||||||
|
case PluralRuleFew:
|
||||||
|
return pluralsString[17:20]
|
||||||
|
case PluralRuleMany:
|
||||||
|
return pluralsString[20:24]
|
||||||
|
case PluralRuleOther:
|
||||||
|
return pluralsString[24:]
|
||||||
|
default:
|
||||||
|
return pluralsString[:7]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Precision Notes:
|
||||||
|
//
|
||||||
|
// must specify a precision >= 0, and here is why https://play.golang.org/p/LyL90U0Vyh
|
||||||
|
//
|
||||||
|
// v := float64(3.141)
|
||||||
|
// i := float64(int64(v))
|
||||||
|
//
|
||||||
|
// fmt.Println(v - i)
|
||||||
|
//
|
||||||
|
// or
|
||||||
|
//
|
||||||
|
// s := strconv.FormatFloat(v-i, 'f', -1, 64)
|
||||||
|
// fmt.Println(s)
|
||||||
|
//
|
||||||
|
// these will not print what you'd expect: 0.14100000000000001
|
||||||
|
// and so this library requires a precision to be specified, or
|
||||||
|
// inaccurate plural rules could be applied.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// n - absolute value of the source number (integer and decimals).
|
||||||
|
// i - integer digits of n.
|
||||||
|
// v - number of visible fraction digits in n, with trailing zeros.
|
||||||
|
// w - number of visible fraction digits in n, without trailing zeros.
|
||||||
|
// f - visible fractional digits in n, with trailing zeros.
|
||||||
|
// t - visible fractional digits in n, without trailing zeros.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Func(num float64, v uint64) // v = digits/precision and prevents -1 as a special case as this can lead to very unexpected behaviour, see precision note's above.
|
||||||
|
//
|
||||||
|
// n := math.Abs(num)
|
||||||
|
// i := int64(n)
|
||||||
|
// v := v
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// w := strconv.FormatFloat(num-float64(i), 'f', int(v), 64) // then parse backwards on string until no more zero's....
|
||||||
|
// f := strconv.FormatFloat(n, 'f', int(v), 64) // then turn everything after decimal into an int64
|
||||||
|
// t := strconv.FormatFloat(n, 'f', int(v), 64) // then parse backwards on string until no more zero's....
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// General Inclusion Rules
|
||||||
|
// - v will always be available inherently
|
||||||
|
// - all require n
|
||||||
|
// - w requires i
|
||||||
|
//
|
||||||
|
|
||||||
|
// W returns the number of visible fraction digits in N, without trailing zeros.
|
||||||
|
func W(n float64, v uint64) (w int64) {
|
||||||
|
|
||||||
|
s := strconv.FormatFloat(n-float64(int64(n)), 'f', int(v), 64)
|
||||||
|
|
||||||
|
// with either be '0' or '0.xxxx', so if 1 then w will be zero
|
||||||
|
// otherwise need to parse
|
||||||
|
if len(s) != 1 {
|
||||||
|
|
||||||
|
s = s[2:]
|
||||||
|
end := len(s) + 1
|
||||||
|
|
||||||
|
for i := end; i >= 0; i-- {
|
||||||
|
if s[i] != '0' {
|
||||||
|
end = i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w = int64(len(s[:end]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// F returns the visible fractional digits in N, with trailing zeros.
|
||||||
|
func F(n float64, v uint64) (f int64) {
|
||||||
|
|
||||||
|
s := strconv.FormatFloat(n-float64(int64(n)), 'f', int(v), 64)
|
||||||
|
|
||||||
|
// with either be '0' or '0.xxxx', so if 1 then f will be zero
|
||||||
|
// otherwise need to parse
|
||||||
|
if len(s) != 1 {
|
||||||
|
|
||||||
|
// ignoring error, because it can't fail as we generated
|
||||||
|
// the string internally from a real number
|
||||||
|
f, _ = strconv.ParseInt(s[2:], 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// T returns the visible fractional digits in N, without trailing zeros.
|
||||||
|
func T(n float64, v uint64) (t int64) {
|
||||||
|
|
||||||
|
s := strconv.FormatFloat(n-float64(int64(n)), 'f', int(v), 64)
|
||||||
|
|
||||||
|
// with either be '0' or '0.xxxx', so if 1 then t will be zero
|
||||||
|
// otherwise need to parse
|
||||||
|
if len(s) != 1 {
|
||||||
|
|
||||||
|
s = s[2:]
|
||||||
|
end := len(s) + 1
|
||||||
|
|
||||||
|
for i := end; i >= 0; i-- {
|
||||||
|
if s[i] != '0' {
|
||||||
|
end = i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignoring error, because it can't fail as we generated
|
||||||
|
// the string internally from a real number
|
||||||
|
t, _ = strconv.ParseInt(s[:end], 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
*.coverprofile
|
@ -0,0 +1,27 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.13.4
|
||||||
|
- tip
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email:
|
||||||
|
recipients: dean.karn@gmail.com
|
||||||
|
on_success: change
|
||||||
|
on_failure: always
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go install github.com/mattn/goveralls
|
||||||
|
|
||||||
|
# Only clone the most recent commit.
|
||||||
|
git:
|
||||||
|
depth: 1
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v -race -covermode=atomic -coverprofile=coverage.coverprofile ./...
|
||||||
|
|
||||||
|
after_success: |
|
||||||
|
[ $TRAVIS_GO_VERSION = 1.13.4 ] &&
|
||||||
|
goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN
|
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Go Playground
|
||||||
|
|
||||||
|
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,18 @@
|
|||||||
|
GOCMD=GO111MODULE=on go
|
||||||
|
|
||||||
|
linters-install:
|
||||||
|
@golangci-lint --version >/dev/null 2>&1 || { \
|
||||||
|
echo "installing linting tools..."; \
|
||||||
|
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.41.1; \
|
||||||
|
}
|
||||||
|
|
||||||
|
lint: linters-install
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
|
test:
|
||||||
|
$(GOCMD) test -cover -race ./...
|
||||||
|
|
||||||
|
bench:
|
||||||
|
$(GOCMD) test -bench=. -benchmem ./...
|
||||||
|
|
||||||
|
.PHONY: test lint linters-install
|
@ -0,0 +1,89 @@
|
|||||||
|
## universal-translator
|
||||||
|
<img align="right" src="https://raw.githubusercontent.com/go-playground/universal-translator/master/logo.png">![Project status](https://img.shields.io/badge/version-0.18.0-green.svg)
|
||||||
|
[![Build Status](https://travis-ci.org/go-playground/universal-translator.svg?branch=master)](https://travis-ci.org/go-playground/universal-translator)
|
||||||
|
[![Coverage Status](https://coveralls.io/repos/github/go-playground/universal-translator/badge.svg)](https://coveralls.io/github/go-playground/universal-translator)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/universal-translator)](https://goreportcard.com/report/github.com/go-playground/universal-translator)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/go-playground/universal-translator?status.svg)](https://godoc.org/github.com/go-playground/universal-translator)
|
||||||
|
![License](https://img.shields.io/dub/l/vibe-d.svg)
|
||||||
|
[![Gitter](https://badges.gitter.im/go-playground/universal-translator.svg)](https://gitter.im/go-playground/universal-translator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
|
|
||||||
|
Universal Translator is an i18n Translator for Go/Golang using CLDR data + pluralization rules
|
||||||
|
|
||||||
|
Why another i18n library?
|
||||||
|
--------------------------
|
||||||
|
Because none of the plural rules seem to be correct out there, including the previous implementation of this package,
|
||||||
|
so I took it upon myself to create [locales](https://github.com/go-playground/locales) for everyone to use; this package
|
||||||
|
is a thin wrapper around [locales](https://github.com/go-playground/locales) in order to store and translate text for
|
||||||
|
use in your applications.
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
- [x] Rules generated from the [CLDR](http://cldr.unicode.org/index/downloads) data, v36.0.1
|
||||||
|
- [x] Contains Cardinal, Ordinal and Range Plural Rules
|
||||||
|
- [x] Contains Month, Weekday and Timezone translations built in
|
||||||
|
- [x] Contains Date & Time formatting functions
|
||||||
|
- [x] Contains Number, Currency, Accounting and Percent formatting functions
|
||||||
|
- [x] Supports the "Gregorian" calendar only ( my time isn't unlimited, had to draw the line somewhere )
|
||||||
|
- [x] Support loading translations from files
|
||||||
|
- [x] Exporting translations to file(s), mainly for getting them professionally translated
|
||||||
|
- [ ] Code Generation for translation files -> Go code.. i.e. after it has been professionally translated
|
||||||
|
- [ ] Tests for all languages, I need help with this, please see [here](https://github.com/go-playground/locales/issues/1)
|
||||||
|
|
||||||
|
Installation
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Use go get
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go get github.com/go-playground/universal-translator
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage & Documentation
|
||||||
|
-------
|
||||||
|
|
||||||
|
Please see https://godoc.org/github.com/go-playground/universal-translator for usage docs
|
||||||
|
|
||||||
|
##### Examples:
|
||||||
|
|
||||||
|
- [Basic](https://github.com/go-playground/universal-translator/tree/master/_examples/basic)
|
||||||
|
- [Full - no files](https://github.com/go-playground/universal-translator/tree/master/_examples/full-no-files)
|
||||||
|
- [Full - with files](https://github.com/go-playground/universal-translator/tree/master/_examples/full-with-files)
|
||||||
|
|
||||||
|
File formatting
|
||||||
|
--------------
|
||||||
|
All types, Plain substitution, Cardinal, Ordinal and Range translations can all be contained within the same file(s);
|
||||||
|
they are only separated for easy viewing.
|
||||||
|
|
||||||
|
##### Examples:
|
||||||
|
|
||||||
|
- [Formats](https://github.com/go-playground/universal-translator/tree/master/_examples/file-formats)
|
||||||
|
|
||||||
|
##### Basic Makeup
|
||||||
|
NOTE: not all fields are needed for all translation types, see [examples](https://github.com/go-playground/universal-translator/tree/master/_examples/file-formats)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"locale": "en",
|
||||||
|
"key": "days-left",
|
||||||
|
"trans": "You have {0} day left.",
|
||||||
|
"type": "Cardinal",
|
||||||
|
"rule": "One",
|
||||||
|
"override": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|Field|Description|
|
||||||
|
|---|---|
|
||||||
|
|locale|The locale for which the translation is for.|
|
||||||
|
|key|The translation key that will be used to store and lookup each translation; normally it is a string or integer.|
|
||||||
|
|trans|The actual translation text.|
|
||||||
|
|type|The type of translation Cardinal, Ordinal, Range or "" for a plain substitution(not required to be defined if plain used)|
|
||||||
|
|rule|The plural rule for which the translation is for eg. One, Two, Few, Many or Other.(not required to be defined if plain used)|
|
||||||
|
|override|If you wish to override an existing translation that has already been registered, set this to 'true'. 99% of the time there is no need to define it.|
|
||||||
|
|
||||||
|
Help With Tests
|
||||||
|
---------------
|
||||||
|
To anyone interesting in helping or contributing, I sure could use some help creating tests for each language.
|
||||||
|
Please see issue [here](https://github.com/go-playground/locales/issues/1) for details.
|
||||||
|
|
||||||
|
License
|
||||||
|
------
|
||||||
|
Distributed under MIT License, please see license file in code for more details.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue