36
Gopkg.lock
generated
36
Gopkg.lock
generated
@@ -19,8 +19,8 @@
|
||||
"pkg/transport",
|
||||
"pkg/types"
|
||||
]
|
||||
revision = "56536de5511128d0c7997aa2b792a4de575da536"
|
||||
version = "v3.3.7"
|
||||
revision = "33245c6b5b49130ca99280408fadfab01aac0e48"
|
||||
version = "v3.3.8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -53,7 +53,7 @@
|
||||
"api/types/volume",
|
||||
"client"
|
||||
]
|
||||
revision = "692df4699c3dcbe4645e9b90cc40091b1f2519d4"
|
||||
revision = "de0abf4315fd1d737aedc2e9f410c8125ad1a7fc"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -95,6 +95,12 @@
|
||||
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-sql-driver/mysql"
|
||||
packages = ["."]
|
||||
revision = "d523deb1b23d913de5bdada721a6071e71283618"
|
||||
version = "v1.4.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = [
|
||||
@@ -158,6 +164,18 @@
|
||||
revision = "9316a62528ac99aaecb4e47eadd6dc8aa6533d58"
|
||||
version = "v0.3.5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/jinzhu/gorm"
|
||||
packages = ["."]
|
||||
revision = "6ed508ec6a4ecb3531899a69cbc746ccf65a4166"
|
||||
version = "v1.9.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/jinzhu/inflection"
|
||||
packages = ["."]
|
||||
revision = "04140366298a54a039076d798123ffa108fff46c"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/json-iterator/go"
|
||||
packages = ["."]
|
||||
@@ -207,7 +225,7 @@
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["ssh/terminal"]
|
||||
revision = "8ac0e0d97ce45cd83d1d7243c060cb8461dda5e9"
|
||||
revision = "a8fb68e7206f8c78be19b432c58eb52a6aa34462"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -233,7 +251,7 @@
|
||||
"unix",
|
||||
"windows"
|
||||
]
|
||||
revision = "8ee9f3e146b708d082f4bab861e5759d1edf8c00"
|
||||
revision = "6c888cc515d3ed83fc103cf1d84468aad274b0a7"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/text"
|
||||
@@ -262,6 +280,12 @@
|
||||
packages = ["rate"]
|
||||
revision = "fbb02b2291d28baffd63558aa44b4b56f178d650"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/appengine"
|
||||
packages = ["cloudsql"]
|
||||
revision = "b1f26356af11148e710935ed1ac8a7f5702c7612"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "google.golang.org/genproto"
|
||||
@@ -468,6 +492,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "3a835656d614522efea125721424042ef0f7159446907bedccef8fd249a69e0d"
|
||||
inputs-digest = "cd655ecbe7567a163c75d4b4fb9f0e51f1b6a8c94cfe28a2542a82990e610741"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
@@ -18,9 +18,16 @@
|
||||
package iam
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/rbac/v1"
|
||||
)
|
||||
|
||||
const rulesConfigPath = "/etc/kubesphere/rules.json"
|
||||
const clusterRulesConfigPath = "/etc/kubesphere/clusterrules.json"
|
||||
|
||||
type roleList struct {
|
||||
ClusterRoles []v1.ClusterRole `json:"clusterRoles" protobuf:"bytes,2,rep,name=clusterRoles"`
|
||||
Roles []v1.Role `json:"roles" protobuf:"bytes,2,rep,name=roles"`
|
||||
@@ -41,14 +48,35 @@ type userRuleList struct {
|
||||
Rules map[string][]rule `json:"rules"`
|
||||
}
|
||||
|
||||
// TODO stored in etcd, allow updates
|
||||
func init() {
|
||||
rulesConfig, err := ioutil.ReadFile(rulesConfigPath)
|
||||
if err == nil {
|
||||
config := &[]rule{}
|
||||
json.Unmarshal(rulesConfig, config)
|
||||
if len(*config) > 0 {
|
||||
roleRuleGroup = *config
|
||||
glog.Info("rules config load success")
|
||||
}
|
||||
}
|
||||
|
||||
clusterRulesConfig, err := ioutil.ReadFile(clusterRulesConfigPath)
|
||||
|
||||
if err == nil {
|
||||
config := &[]rule{}
|
||||
json.Unmarshal(clusterRulesConfig, config)
|
||||
if len(*config) > 0 {
|
||||
clusterRoleRuleGroup = *config
|
||||
glog.Info("cluster rules config load success")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
clusterRoleRuleGroup = []rule{projects, users, roles, images,
|
||||
volumes, storageclasses, nodes, appCatalog, apps, components,
|
||||
deployments, statefulsets, daemonsets, services, routes}
|
||||
volumes, storageclasses, nodes, appCatalog, apps, components, deployments, statefulsets, daemonsets, pods, services, routes}
|
||||
|
||||
roleRuleGroup = []rule{project, deployments, statefulsets, daemonsets,
|
||||
services, routes}
|
||||
roleRuleGroup = []rule{project, deployments, statefulsets, daemonsets, pods,
|
||||
services, routes, volumes}
|
||||
|
||||
components = rule{
|
||||
Name: "components",
|
||||
@@ -86,6 +114,15 @@ var (
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "edit",
|
||||
Rules: []v1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"update", "patch"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"namespaces"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "delete",
|
||||
Rules: []v1.PolicyRule{
|
||||
{
|
||||
@@ -95,12 +132,6 @@ var (
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
project = rule{
|
||||
Name: "project",
|
||||
Actions: []action{
|
||||
{Name: "members",
|
||||
Rules: []v1.PolicyRule{
|
||||
{
|
||||
@@ -121,6 +152,48 @@ var (
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
project = rule{
|
||||
Name: "projects",
|
||||
Actions: []action{
|
||||
{Name: "members",
|
||||
Rules: []v1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get", "list", "create", "delete"},
|
||||
APIGroups: []string{"rbac.authorization.k8s.io"},
|
||||
Resources: []string{"rolebindings"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "member_roles",
|
||||
Rules: []v1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get", "list", "create", "delete", "patch", "update"},
|
||||
APIGroups: []string{"rbac.authorization.k8s.io"},
|
||||
Resources: []string{"roles"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "edit",
|
||||
Rules: []v1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"update", "patch"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"namespaces"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "delete",
|
||||
Rules: []v1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"delete"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"namespaces"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
users = rule{
|
||||
Name: "users",
|
||||
Actions: []action{
|
||||
@@ -134,7 +207,7 @@ var (
|
||||
{
|
||||
Verbs: []string{"get", "list"},
|
||||
APIGroups: []string{"rbac.authorization.k8s.io"},
|
||||
Resources: []string{"rolebindings", "clusterrolebindings"},
|
||||
Resources: []string{"clusterrolebindings"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -145,6 +218,11 @@ var (
|
||||
APIGroups: []string{"kubesphere.io"},
|
||||
Resources: []string{"users"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"create", "delete", "deletecollection"},
|
||||
APIGroups: []string{"rbac.authorization.k8s.io"},
|
||||
Resources: []string{"clusterrolebindings"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "edit",
|
||||
@@ -154,6 +232,11 @@ var (
|
||||
APIGroups: []string{"kubesphere.io"},
|
||||
Resources: []string{"users"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"create", "delete", "deletecollection"},
|
||||
APIGroups: []string{"rbac.authorization.k8s.io"},
|
||||
Resources: []string{"clusterrolebindings"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "delete",
|
||||
@@ -176,7 +259,7 @@ var (
|
||||
{
|
||||
Verbs: []string{"get", "list"},
|
||||
APIGroups: []string{"rbac.authorization.k8s.io"},
|
||||
Resources: []string{"roles", "clusterroles"},
|
||||
Resources: []string{"clusterroles"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -186,7 +269,7 @@ var (
|
||||
{
|
||||
Verbs: []string{"create"},
|
||||
APIGroups: []string{"rbac.authorization.k8s.io"},
|
||||
Resources: []string{"roles", "clusterroles"},
|
||||
Resources: []string{"clusterroles"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -195,7 +278,7 @@ var (
|
||||
{
|
||||
Verbs: []string{"update", "patch"},
|
||||
APIGroups: []string{"rbac.authorization.k8s.io"},
|
||||
Resources: []string{"roles", "clusterroles"},
|
||||
Resources: []string{"clusterroles"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -204,16 +287,7 @@ var (
|
||||
{
|
||||
Verbs: []string{"delete", "deletecollection"},
|
||||
APIGroups: []string{"rbac.authorization.k8s.io"},
|
||||
Resources: []string{"roles", "clusterroles"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "role_binding",
|
||||
Rules: []v1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"create", "delete", "deletecollection"},
|
||||
APIGroups: []string{"rbac.authorization.k8s.io"},
|
||||
Resources: []string{"rolebindings", "clusterrolebindings"},
|
||||
Resources: []string{"clusterroles"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -232,6 +306,24 @@ var (
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "edit",
|
||||
Rules: []v1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"update", "patch"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"nodes"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "drain",
|
||||
Rules: []v1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"*"},
|
||||
APIGroups: []string{"kubesphere.io"},
|
||||
Resources: []string{"nodes"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -245,18 +337,14 @@ var (
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"persistentvolumes"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "create",
|
||||
Rules: []v1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get", "list"},
|
||||
Verbs: []string{"list"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"persistentvolumes"},
|
||||
Resources: []string{"namespaces"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "delete",
|
||||
{Name: "create",
|
||||
Rules: []v1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get", "list"},
|
||||
@@ -274,6 +362,15 @@ var (
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "delete",
|
||||
Rules: []v1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"get", "list"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"persistentvolumes"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -331,6 +428,11 @@ var (
|
||||
"secrets",
|
||||
},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"list"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"namespaces"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "create",
|
||||
@@ -436,6 +538,16 @@ var (
|
||||
APIGroups: []string{"apps"},
|
||||
Resources: []string{"statefulsets"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"list"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"namespaces"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"get", "list"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"pods", "pods/log", "pods/status"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "create",
|
||||
@@ -478,6 +590,16 @@ var (
|
||||
APIGroups: []string{"apps", "extensions"},
|
||||
Resources: []string{"daemonsets"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"list"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"namespaces"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"get", "list"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"pods", "pods/log", "pods/status"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "create",
|
||||
@@ -520,6 +642,11 @@ var (
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"services"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"list"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"namespaces"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "create",
|
||||
@@ -563,6 +690,11 @@ var (
|
||||
APIGroups: []string{"extensions"},
|
||||
Resources: []string{"ingresses"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"list"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"namespaces"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "create",
|
||||
@@ -603,11 +735,17 @@ var (
|
||||
{
|
||||
Verbs: []string{"get", "list"},
|
||||
APIGroups: []string{"apps", "extensions"},
|
||||
Resources: []string{
|
||||
"deployments",
|
||||
"deployments/rollback",
|
||||
"deployments/scale",
|
||||
},
|
||||
Resources: []string{"deployments", "deployments/scale"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"list"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"namespaces"},
|
||||
},
|
||||
{
|
||||
Verbs: []string{"get", "list"},
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"pods", "pods/log", "pods/status"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -651,4 +789,19 @@ var (
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pods = rule{
|
||||
Name: "pods",
|
||||
Actions: []action{
|
||||
{Name: "terminal",
|
||||
Rules: []v1.PolicyRule{
|
||||
{
|
||||
Verbs: []string{"*"},
|
||||
APIGroups: []string{"kubesphere.io"},
|
||||
Resources: []string{"terminal"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"kubesphere.io/kubesphere/pkg/apis/v1alpha/pods"
|
||||
"kubesphere.io/kubesphere/pkg/apis/v1alpha/quota"
|
||||
"kubesphere.io/kubesphere/pkg/apis/v1alpha/registries"
|
||||
"kubesphere.io/kubesphere/pkg/apis/v1alpha/resources"
|
||||
"kubesphere.io/kubesphere/pkg/apis/v1alpha/routes"
|
||||
"kubesphere.io/kubesphere/pkg/apis/v1alpha/storage"
|
||||
"kubesphere.io/kubesphere/pkg/apis/v1alpha/terminal"
|
||||
@@ -52,6 +53,7 @@ func init() {
|
||||
terminal.Register(ws, "/namespaces/{namespace}/pod/{pod}/shell/{container}")
|
||||
workloadstatus.Register(ws, "/status")
|
||||
quota.Register(ws, "/quota")
|
||||
resources.Register(ws, "/resources")
|
||||
|
||||
// add webservice to default container
|
||||
restful.Add(ws)
|
||||
|
||||
46
pkg/apis/v1alpha/resources/resources.go
Normal file
46
pkg/apis/v1alpha/resources/resources.go
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 resources
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/models"
|
||||
)
|
||||
|
||||
func Register(ws *restful.WebService, subPath string) {
|
||||
|
||||
ws.Route(ws.GET(subPath + "/{resource}").To(listResource).Produces(restful.MIME_JSON))
|
||||
|
||||
}
|
||||
|
||||
func listResource(req *restful.Request, resp *restful.Response) {
|
||||
|
||||
resource := req.PathParameter("resource")
|
||||
conditions := req.QueryParameter("conditions")
|
||||
paging := req.QueryParameter("paging")
|
||||
|
||||
res, err := models.ListResource(resource, conditions, paging)
|
||||
if err != nil {
|
||||
resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()})
|
||||
return
|
||||
}
|
||||
resp.WriteEntity(res)
|
||||
}
|
||||
@@ -15,10 +15,10 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
"net/http"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/models"
|
||||
)
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/models"
|
||||
)
|
||||
@@ -60,13 +62,14 @@ func delUser(req *restful.Request, resp *restful.Response) {
|
||||
|
||||
err := models.DelKubectlPod(user)
|
||||
|
||||
if err != nil {
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
err = models.DelKubeConfig(user)
|
||||
if err != nil {
|
||||
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -20,19 +20,18 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
_ "kubesphere.io/kubesphere/pkg/apis/v1alpha"
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/models/jobs/cronjobs"
|
||||
"kubesphere.io/kubesphere/pkg/models/jobs/resources"
|
||||
"kubesphere.io/kubesphere/pkg/models/controllers"
|
||||
"kubesphere.io/kubesphere/pkg/options"
|
||||
)
|
||||
|
||||
@@ -81,8 +80,8 @@ func (server *kubeSphereServer) run() {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
go resources.Run()
|
||||
go cronjobs.Run()
|
||||
|
||||
go controllers.Run()
|
||||
|
||||
if len(server.certFile) > 0 && len(server.keyFile) > 0 {
|
||||
servingCert, err := tls.LoadX509KeyPair(server.certFile, server.keyFile)
|
||||
|
||||
62
pkg/client/dbclient.go
Normal file
62
pkg/client/dbclient.go
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 client
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/gorm"
|
||||
//_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/golang/glog"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/options"
|
||||
)
|
||||
|
||||
var dbClient *gorm.DB
|
||||
|
||||
const database = "kubesphere"
|
||||
|
||||
func NewDBClient() *gorm.DB {
|
||||
|
||||
if dbClient != nil {
|
||||
return dbClient
|
||||
}
|
||||
|
||||
user := options.ServerOptions.GetMysqlUser()
|
||||
passwd := options.ServerOptions.GetMysqlPassword()
|
||||
addr := options.ServerOptions.GetMysqlAddr()
|
||||
conn := fmt.Sprintf("%s:%s@tcp(%s)/mysql?charset=utf8mb4&parseTime=True&loc=Local", user, passwd, addr)
|
||||
db, err := gorm.Open("mysql", conn)
|
||||
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
db.Exec(fmt.Sprintf("create database if not exists %s;", database))
|
||||
|
||||
conn = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, passwd, addr, database)
|
||||
db, err = gorm.Open("mysql", conn)
|
||||
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
panic(err)
|
||||
}
|
||||
dbClient = db
|
||||
return dbClient
|
||||
}
|
||||
@@ -28,10 +28,6 @@ type PageableResponse struct {
|
||||
const (
|
||||
APIVERSION = "v1alpha1"
|
||||
KIND = "kubesphere"
|
||||
Root = "/kubesphere"
|
||||
UpdateCircle = 60
|
||||
QuotaKey = "resource-quota"
|
||||
WorkloadStatusKey = "workload-status"
|
||||
NameSpace = "kubesphere"
|
||||
DATA_HOME = "/etc/kubesphere"
|
||||
INGRESS_CONTROLLER_FOLDER = DATA_HOME + "/ingress-controller"
|
||||
|
||||
140
pkg/models/controllers/clusterRoles.go
Normal file
140
pkg/models/controllers/clusterRoles.go
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/rbac/v1"
|
||||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
func (ctl *ClusterRoleCtl) generateObjec(item v1.ClusterRole) *ClusterRole {
|
||||
name := item.Name
|
||||
if strings.HasPrefix(name, "system:") {
|
||||
return nil
|
||||
}
|
||||
|
||||
createTime := item.CreationTimestamp.Time
|
||||
if createTime.IsZero() {
|
||||
createTime = time.Now()
|
||||
}
|
||||
|
||||
annotation, _ := json.Marshal(item.Annotations)
|
||||
|
||||
object := &ClusterRole{Name: name, CreateTime: createTime, AnnotationStr: string(annotation)}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func (ctl *ClusterRoleCtl) listAndWatch() {
|
||||
defer func() {
|
||||
close(ctl.aliveChan)
|
||||
if err := recover(); err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
db := ctl.DB
|
||||
|
||||
if db.HasTable(&ClusterRole{}) {
|
||||
db.DropTable(&ClusterRole{})
|
||||
|
||||
}
|
||||
|
||||
db = db.CreateTable(&ClusterRole{})
|
||||
|
||||
k8sClient := ctl.K8sClient
|
||||
|
||||
clusterRoleList, err := k8sClient.RbacV1().ClusterRoles().List(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range clusterRoleList.Items {
|
||||
obj := ctl.generateObjec(item)
|
||||
if obj != nil {
|
||||
db.Create(obj)
|
||||
}
|
||||
}
|
||||
|
||||
clusterRoleWatcher, err := k8sClient.RbacV1().ClusterRoles().Watch(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctl.stopChan:
|
||||
return
|
||||
case event := <-clusterRoleWatcher.ResultChan():
|
||||
var role ClusterRole
|
||||
if event.Object == nil {
|
||||
break
|
||||
}
|
||||
object := event.Object.(*v1.ClusterRole)
|
||||
if event.Type == watch.Deleted {
|
||||
db.Where("name=? And namespace=?", object.Name, "\"\"").Find(&role)
|
||||
db.Delete(role)
|
||||
break
|
||||
}
|
||||
obj := ctl.generateObjec(*object)
|
||||
if obj != nil {
|
||||
db.Save(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *ClusterRoleCtl) CountWithConditions(conditions string) int {
|
||||
var object ClusterRole
|
||||
|
||||
return countWithConditions(ctl.DB, conditions, &object)
|
||||
}
|
||||
|
||||
func (ctl *ClusterRoleCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) {
|
||||
var object ClusterRole
|
||||
var list []ClusterRole
|
||||
var total int
|
||||
|
||||
order := "createTime desc"
|
||||
db := ctl.DB
|
||||
|
||||
listWithConditions(db, &total, &object, &list, conditions, paging, order)
|
||||
|
||||
for index, item := range list {
|
||||
annotation := make(map[string]string)
|
||||
json.Unmarshal([]byte(item.AnnotationStr), &annotation)
|
||||
list[index].Annotation = annotation
|
||||
list[index].AnnotationStr = ""
|
||||
}
|
||||
return total, list, nil
|
||||
}
|
||||
|
||||
func (ctl *ClusterRoleCtl) Count(namespace string) int {
|
||||
var count int
|
||||
db := ctl.DB
|
||||
db.Model(&ClusterRole{}).Count(&count)
|
||||
return count
|
||||
}
|
||||
161
pkg/models/controllers/daemonsets.go
Normal file
161
pkg/models/controllers/daemonsets.go
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/apps/v1beta2"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
)
|
||||
|
||||
func (ctl *DaemonsetCtl) generateObject(item v1beta2.DaemonSet) *Daemonset {
|
||||
var app string
|
||||
var status string
|
||||
name := item.Name
|
||||
namespace := item.Namespace
|
||||
availablePodNum := item.Status.CurrentNumberScheduled
|
||||
desirePodNum := item.Status.DesiredNumberScheduled
|
||||
createTime := item.CreationTimestamp.Time
|
||||
release := item.ObjectMeta.Labels["release"]
|
||||
nodeSelector := item.Spec.Template.Spec.NodeSelector
|
||||
nodeSelectorStr, _ := json.Marshal(nodeSelector)
|
||||
|
||||
chart := item.ObjectMeta.Labels["chart"]
|
||||
|
||||
if len(release) > 0 && len(chart) > 0 {
|
||||
app = release + "/" + chart
|
||||
} else {
|
||||
app = "-"
|
||||
}
|
||||
|
||||
if createTime.IsZero() {
|
||||
createTime = time.Now()
|
||||
}
|
||||
|
||||
if availablePodNum >= desirePodNum {
|
||||
status = running
|
||||
} else {
|
||||
status = updating
|
||||
}
|
||||
|
||||
annotation, _ := json.Marshal(item.Annotations)
|
||||
|
||||
object := &Daemonset{Namespace: namespace, Name: name, Available: availablePodNum, Desire: desirePodNum,
|
||||
App: app, CreateTime: createTime, Status: status, NodeSelector: string(nodeSelectorStr), AnnotationStr: string(annotation)}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func (ctl *DaemonsetCtl) listAndWatch() {
|
||||
defer func() {
|
||||
|
||||
close(ctl.aliveChan)
|
||||
|
||||
if err := recover(); err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
db := ctl.DB
|
||||
|
||||
if db.HasTable(&Daemonset{}) {
|
||||
db.DropTable(&Daemonset{})
|
||||
|
||||
}
|
||||
|
||||
db = db.CreateTable(&Daemonset{})
|
||||
|
||||
k8sClient := client.NewK8sClient()
|
||||
deoloyList, err := k8sClient.AppsV1beta2().DaemonSets("").List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range deoloyList.Items {
|
||||
obj := ctl.generateObject(item)
|
||||
db.Create(obj)
|
||||
}
|
||||
|
||||
watcher, err := k8sClient.AppsV1beta2().DaemonSets("").Watch(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctl.stopChan:
|
||||
return
|
||||
case event := <-watcher.ResultChan():
|
||||
var ss Daemonset
|
||||
if event.Object == nil {
|
||||
break
|
||||
}
|
||||
object := event.Object.(*v1beta2.DaemonSet)
|
||||
if event.Type == watch.Deleted {
|
||||
db.Where("name=? And namespace=?", object.Name, object.Namespace).Find(&ss)
|
||||
db.Delete(ss)
|
||||
break
|
||||
}
|
||||
obj := ctl.generateObject(*object)
|
||||
db.Save(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *DaemonsetCtl) CountWithConditions(conditions string) int {
|
||||
var object Daemonset
|
||||
|
||||
return countWithConditions(ctl.DB, conditions, &object)
|
||||
}
|
||||
|
||||
func (ctl *DaemonsetCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) {
|
||||
var list []Daemonset
|
||||
var object Daemonset
|
||||
var total int
|
||||
|
||||
order := "createTime desc"
|
||||
|
||||
listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order)
|
||||
|
||||
for index, item := range list {
|
||||
annotation := make(map[string]string)
|
||||
json.Unmarshal([]byte(item.AnnotationStr), &annotation)
|
||||
list[index].Annotation = annotation
|
||||
list[index].AnnotationStr = ""
|
||||
}
|
||||
return total, list, nil
|
||||
}
|
||||
|
||||
func (ctl *DaemonsetCtl) Count(namespace string) int {
|
||||
var count int
|
||||
db := ctl.DB
|
||||
if len(namespace) == 0 {
|
||||
db.Model(&Daemonset{}).Count(&count)
|
||||
} else {
|
||||
db.Model(&Daemonset{}).Where("namespace = ?", namespace).Count(&count)
|
||||
}
|
||||
return count
|
||||
}
|
||||
161
pkg/models/controllers/deployments.go
Normal file
161
pkg/models/controllers/deployments.go
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/apps/v1beta2"
|
||||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
)
|
||||
|
||||
func (ctl *DeploymentCtl) generateObject(item v1beta2.Deployment) *Deployment {
|
||||
var app string
|
||||
var status string
|
||||
var updateTime time.Time
|
||||
name := item.Name
|
||||
namespace := item.Namespace
|
||||
availablePodNum := item.Status.AvailableReplicas
|
||||
desirePodNum := *item.Spec.Replicas
|
||||
release := item.ObjectMeta.Labels["release"]
|
||||
chart := item.ObjectMeta.Labels["chart"]
|
||||
|
||||
if len(release) > 0 && len(chart) > 0 {
|
||||
app = release + "/" + chart
|
||||
} else {
|
||||
app = "-"
|
||||
}
|
||||
|
||||
for _, conditon := range item.Status.Conditions {
|
||||
if conditon.Type == "Progressing" {
|
||||
updateTime = conditon.LastUpdateTime.Time
|
||||
}
|
||||
}
|
||||
if updateTime.IsZero() {
|
||||
updateTime = time.Now()
|
||||
}
|
||||
|
||||
if item.Annotations["state"] == "stop" {
|
||||
status = stopping
|
||||
} else {
|
||||
if availablePodNum >= desirePodNum {
|
||||
status = running
|
||||
} else {
|
||||
status = updating
|
||||
}
|
||||
}
|
||||
|
||||
annotation, _ := json.Marshal(item.Annotations)
|
||||
|
||||
return &Deployment{Namespace: namespace, Name: name, Available: availablePodNum, Desire: desirePodNum,
|
||||
App: app, UpdateTime: updateTime, Status: status, AnnotationStr: string(annotation)}
|
||||
}
|
||||
|
||||
func (ctl *DeploymentCtl) listAndWatch() {
|
||||
defer func() {
|
||||
close(ctl.aliveChan)
|
||||
if err := recover(); err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
db := ctl.DB
|
||||
if db.HasTable(&Deployment{}) {
|
||||
db.DropTable(&Deployment{})
|
||||
}
|
||||
|
||||
db = db.CreateTable(&Deployment{})
|
||||
|
||||
k8sClient := client.NewK8sClient()
|
||||
deoloyList, err := k8sClient.AppsV1beta2().Deployments("").List(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range deoloyList.Items {
|
||||
obj := ctl.generateObject(item)
|
||||
db.Create(obj)
|
||||
}
|
||||
|
||||
watcher, err := k8sClient.AppsV1beta2().Deployments("").Watch(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctl.stopChan:
|
||||
return
|
||||
case event := <-watcher.ResultChan():
|
||||
var deploy Deployment
|
||||
if event.Object == nil {
|
||||
break
|
||||
}
|
||||
object := event.Object.(*v1beta2.Deployment)
|
||||
if event.Type == watch.Deleted {
|
||||
db.Where("name=? And namespace=?", object.Name, object.Namespace).Find(&deploy)
|
||||
db.Delete(deploy)
|
||||
break
|
||||
}
|
||||
obj := ctl.generateObject(*object)
|
||||
db.Save(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *DeploymentCtl) CountWithConditions(conditions string) int {
|
||||
var object Deployment
|
||||
|
||||
return countWithConditions(ctl.DB, conditions, &object)
|
||||
}
|
||||
|
||||
func (ctl *DeploymentCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) {
|
||||
var list []Deployment
|
||||
var object Deployment
|
||||
var total int
|
||||
|
||||
order := "updateTime desc"
|
||||
|
||||
listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order)
|
||||
|
||||
for index, item := range list {
|
||||
annotation := make(map[string]string)
|
||||
json.Unmarshal([]byte(item.AnnotationStr), &annotation)
|
||||
list[index].Annotation = annotation
|
||||
list[index].AnnotationStr = ""
|
||||
}
|
||||
return total, list, nil
|
||||
}
|
||||
|
||||
func (ctl *DeploymentCtl) Count(namespace string) int {
|
||||
var count int
|
||||
db := ctl.DB
|
||||
if len(namespace) == 0 {
|
||||
db.Model(&Deployment{}).Count(&count)
|
||||
} else {
|
||||
db.Model(&Deployment{}).Where("namespace = ?", namespace).Count(&count)
|
||||
}
|
||||
return count
|
||||
}
|
||||
148
pkg/models/controllers/ingresses.go
Normal file
148
pkg/models/controllers/ingresses.go
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
)
|
||||
|
||||
func (ctl *IngressCtl) generateObject(item v1beta1.Ingress) *Ingress {
|
||||
name := item.Name
|
||||
namespace := item.Namespace
|
||||
ip := "-"
|
||||
tls := "-"
|
||||
createTime := item.CreationTimestamp.Time
|
||||
if createTime.IsZero() {
|
||||
createTime = time.Now()
|
||||
}
|
||||
|
||||
var ipList []string
|
||||
for _, lb := range item.Status.LoadBalancer.Ingress {
|
||||
if len(lb.IP) > 0 {
|
||||
ipList = append(ipList, lb.IP)
|
||||
}
|
||||
}
|
||||
if len(ipList) > 0 {
|
||||
ip = strings.Join(ipList, ",")
|
||||
}
|
||||
|
||||
annotation, _ := json.Marshal(item.Annotations)
|
||||
object := &Ingress{Namespace: namespace, Name: name, TlsTermination: tls, Ip: ip, CreateTime: createTime, AnnotationStr: string(annotation)}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func (ctl *IngressCtl) listAndWatch() {
|
||||
defer func() {
|
||||
defer close(ctl.aliveChan)
|
||||
if err := recover(); err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
db := ctl.DB
|
||||
|
||||
if db.HasTable(&Ingress{}) {
|
||||
db.DropTable(&Ingress{})
|
||||
|
||||
}
|
||||
|
||||
db = db.CreateTable(&Ingress{})
|
||||
|
||||
k8sClient := client.NewK8sClient()
|
||||
list, err := k8sClient.ExtensionsV1beta1().Ingresses("").List(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range list.Items {
|
||||
obj := ctl.generateObject(item)
|
||||
db.Create(obj)
|
||||
}
|
||||
|
||||
watcher, err := k8sClient.ExtensionsV1beta1().Ingresses("").Watch(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctl.stopChan:
|
||||
return
|
||||
case event := <-watcher.ResultChan():
|
||||
var ing Ingress
|
||||
if event.Object == nil {
|
||||
break
|
||||
}
|
||||
object := event.Object.(*v1beta1.Ingress)
|
||||
if event.Type == watch.Deleted {
|
||||
db.Where("name=? And namespace=?", object.Name, object.Namespace).Find(&ing)
|
||||
db.Delete(ing)
|
||||
break
|
||||
}
|
||||
obj := ctl.generateObject(*object)
|
||||
db.Save(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *IngressCtl) CountWithConditions(conditions string) int {
|
||||
var object Ingress
|
||||
|
||||
return countWithConditions(ctl.DB, conditions, &object)
|
||||
}
|
||||
|
||||
func (ctl *IngressCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) {
|
||||
var list []Ingress
|
||||
var object Ingress
|
||||
var total int
|
||||
|
||||
order := "createTime desc"
|
||||
|
||||
listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order)
|
||||
|
||||
for index, item := range list {
|
||||
annotation := make(map[string]string)
|
||||
json.Unmarshal([]byte(item.AnnotationStr), &annotation)
|
||||
list[index].Annotation = annotation
|
||||
list[index].AnnotationStr = ""
|
||||
}
|
||||
return total, list, nil
|
||||
}
|
||||
|
||||
func (ctl *IngressCtl) Count(namespace string) int {
|
||||
var count int
|
||||
db := ctl.DB
|
||||
if len(namespace) == 0 {
|
||||
db.Model(&Ingress{}).Count(&count)
|
||||
} else {
|
||||
db.Model(&Ingress{}).Where("namespace = ?", namespace).Count(&count)
|
||||
}
|
||||
return count
|
||||
}
|
||||
310
pkg/models/controllers/namespaces.go
Normal file
310
pkg/models/controllers/namespaces.go
Normal file
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
rbac "k8s.io/api/rbac/v1"
|
||||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
"kubesphere.io/kubesphere/pkg/options"
|
||||
)
|
||||
|
||||
const (
|
||||
provider = "kubernetes"
|
||||
admin = "admin"
|
||||
normal = "normal"
|
||||
view = "view"
|
||||
kubectlNamespace = "kubesphere"
|
||||
kubectlConfigKey = "config"
|
||||
)
|
||||
|
||||
var adminRules = []rbac.PolicyRule{rbac.PolicyRule{Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}}}
|
||||
var normalRules = []rbac.PolicyRule{rbac.PolicyRule{Verbs: []string{"*"}, APIGroups: []string{"", "apps", "extensions"}, Resources: []string{"*"}}}
|
||||
var viewRules = []rbac.PolicyRule{rbac.PolicyRule{Verbs: []string{"list", "get"}, APIGroups: []string{"", "apps", "extensions"}, Resources: []string{"*"}}}
|
||||
|
||||
type runTime struct {
|
||||
RuntimeId string `json:"runtime_id"`
|
||||
RuntimeUrl string `json:"runtime_url"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
Zone string `json:"zone"`
|
||||
RuntimeCredential string `json:"runtime_credential"`
|
||||
}
|
||||
|
||||
type DeleteRunTime struct {
|
||||
RuntimeId []string `json:"runtime_id"`
|
||||
}
|
||||
|
||||
func makeHttpRequest(method, url, data string) ([]byte, error) {
|
||||
req, err := http.NewRequest(method, url, strings.NewReader(data))
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpClient := &http.Client{}
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
glog.Error(string(body))
|
||||
defer resp.Body.Close()
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func (ctl *NamespaceCtl) getKubeConfig(user string) (string, error) {
|
||||
k8sClient := client.NewK8sClient()
|
||||
configmap, err := k8sClient.CoreV1().ConfigMaps(kubectlNamespace).Get(user, metaV1.GetOptions{})
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
return "", err
|
||||
}
|
||||
return configmap.Data[kubectlConfigKey], nil
|
||||
}
|
||||
|
||||
func (ctl *NamespaceCtl) deleteOpRuntime(item v1.Namespace) {
|
||||
|
||||
runtimeId := item.Annotations["openpitrix_runtime"]
|
||||
if len(runtimeId) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
url := options.ServerOptions.GetOpAddress() + "/v1/runtimes"
|
||||
|
||||
var deleteRuntime = DeleteRunTime{RuntimeId: []string{runtimeId}}
|
||||
|
||||
body, err := json.Marshal(deleteRuntime)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// todo: if delete failed, what's to be done?
|
||||
makeHttpRequest("DELETE", url, string(body))
|
||||
}
|
||||
|
||||
func (ctl *NamespaceCtl) createOpRuntime(namespace, user string) ([]byte, error) {
|
||||
zone := namespace
|
||||
name := namespace
|
||||
kubeConfig, err := ctl.getKubeConfig(user)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := options.ServerOptions.GetOpAddress() + "/v1/runtimes"
|
||||
|
||||
option := runTime{Name: name, Provider: provider, RuntimeCredential: kubeConfig, Zone: zone}
|
||||
body, err := json.Marshal(option)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return makeHttpRequest("POST", url, string(body))
|
||||
}
|
||||
|
||||
func (ctl *NamespaceCtl) createDefaultRole(ns string) error {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
adminRole := &rbac.Role{ObjectMeta: metaV1.ObjectMeta{Name: admin, Namespace: ns}, Rules: adminRules}
|
||||
normalRole := &rbac.Role{ObjectMeta: metaV1.ObjectMeta{Name: normal, Namespace: ns}, Rules: normalRules}
|
||||
viewRole := &rbac.Role{ObjectMeta: metaV1.ObjectMeta{Name: view, Namespace: ns}, Rules: viewRules}
|
||||
|
||||
role, _ := ctl.K8sClient.RbacV1().Roles(ns).Get(admin, metaV1.GetOptions{})
|
||||
|
||||
if role.Name != admin {
|
||||
_, err := ctl.K8sClient.RbacV1().Roles(ns).Create(adminRole)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
role, _ = ctl.K8sClient.RbacV1().Roles(ns).Get(normal, metaV1.GetOptions{})
|
||||
|
||||
if role.Name != normal {
|
||||
_, err := ctl.K8sClient.RbacV1().Roles(ns).Create(normalRole)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
role, _ = ctl.K8sClient.RbacV1().Roles(ns).Get(view, metaV1.GetOptions{})
|
||||
|
||||
if role.Name != view {
|
||||
_, err := ctl.K8sClient.RbacV1().Roles(ns).Create(viewRole)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctl *NamespaceCtl) createRoleAndRuntime(item v1.Namespace) {
|
||||
user := item.Annotations["creator"]
|
||||
ns := item.Name
|
||||
if len(user) > 0 && len(item.Annotations["openpitrix_runtime"]) == 0 {
|
||||
err := ctl.createDefaultRole(ns)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err := ctl.createOpRuntime(ns, user)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var runtime runTime
|
||||
err = json.Unmarshal(resp, &runtime)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
item.Annotations["openpitrix_runtime"] = runtime.RuntimeId
|
||||
ctl.K8sClient.CoreV1().Namespaces().Update(&item)
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *NamespaceCtl) generateObject(item v1.Namespace) *Namespace {
|
||||
|
||||
name := item.Name
|
||||
createTime := item.CreationTimestamp.Time
|
||||
status := fmt.Sprintf("%v", item.Status.Phase)
|
||||
|
||||
if createTime.IsZero() {
|
||||
createTime = time.Now()
|
||||
}
|
||||
|
||||
annotation, _ := json.Marshal(item.Annotations)
|
||||
object := &Namespace{Name: name, CreateTime: createTime, Status: status, AnnotationStr: string(annotation)}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func (ctl *NamespaceCtl) listAndWatch() {
|
||||
defer func() {
|
||||
defer close(ctl.aliveChan)
|
||||
if err := recover(); err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
db := ctl.DB
|
||||
|
||||
if db.HasTable(&Namespace{}) {
|
||||
db.DropTable(&Namespace{})
|
||||
}
|
||||
|
||||
db = db.CreateTable(&Namespace{})
|
||||
|
||||
k8sClient := client.NewK8sClient()
|
||||
list, err := k8sClient.CoreV1().Namespaces().List(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range list.Items {
|
||||
obj := ctl.generateObject(item)
|
||||
db.Create(obj)
|
||||
|
||||
ctl.createRoleAndRuntime(item)
|
||||
}
|
||||
|
||||
watcher, err := k8sClient.CoreV1().Namespaces().Watch(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctl.stopChan:
|
||||
return
|
||||
case event := <-watcher.ResultChan():
|
||||
var ns Namespace
|
||||
if event.Object == nil {
|
||||
break
|
||||
}
|
||||
object := event.Object.(*v1.Namespace)
|
||||
if event.Type == watch.Deleted {
|
||||
db.Where("name=?", object.Name).Find(&ns)
|
||||
db.Delete(ns)
|
||||
|
||||
ctl.deleteOpRuntime(*object)
|
||||
break
|
||||
}
|
||||
|
||||
ctl.createRoleAndRuntime(*object)
|
||||
|
||||
obj := ctl.generateObject(*object)
|
||||
db.Save(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *NamespaceCtl) CountWithConditions(conditions string) int {
|
||||
var object Namespace
|
||||
|
||||
return countWithConditions(ctl.DB, conditions, &object)
|
||||
}
|
||||
|
||||
func (ctl *NamespaceCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) {
|
||||
var list []Namespace
|
||||
var object Namespace
|
||||
var total int
|
||||
|
||||
order := "createTime desc"
|
||||
|
||||
listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order)
|
||||
|
||||
for index, item := range list {
|
||||
annotation := make(map[string]string)
|
||||
json.Unmarshal([]byte(item.AnnotationStr), &annotation)
|
||||
list[index].Annotation = annotation
|
||||
list[index].AnnotationStr = ""
|
||||
}
|
||||
return total, list, nil
|
||||
}
|
||||
|
||||
func (ctl *NamespaceCtl) Count(namespace string) int {
|
||||
var count int
|
||||
db := ctl.DB
|
||||
db.Model(&Namespace{}).Count(&count)
|
||||
return count
|
||||
}
|
||||
161
pkg/models/controllers/pods.go
Normal file
161
pkg/models/controllers/pods.go
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
)
|
||||
|
||||
func (ctl *PodCtl) generateObject(item v1.Pod) *Pod {
|
||||
name := item.Name
|
||||
namespace := item.Namespace
|
||||
podIp := item.Status.PodIP
|
||||
nodeName := item.Spec.NodeName
|
||||
nodeIp := item.Status.HostIP
|
||||
status := string(item.Status.Phase)
|
||||
createTime := item.CreationTimestamp.Time
|
||||
containerStatus := item.Status.ContainerStatuses
|
||||
containerSpecs := item.Spec.Containers
|
||||
var containers []Container
|
||||
|
||||
for _, containerSpec := range containerSpecs {
|
||||
var container Container
|
||||
container.Name = containerSpec.Name
|
||||
container.Image = containerSpec.Image
|
||||
container.Ports = containerSpec.Ports
|
||||
for _, status := range containerStatus {
|
||||
if container.Name == status.Name {
|
||||
container.Ready = status.Ready
|
||||
}
|
||||
}
|
||||
|
||||
containers = append(containers, container)
|
||||
}
|
||||
|
||||
containerStr, _ := json.Marshal(containers)
|
||||
|
||||
annotation, _ := json.Marshal(item.Annotations)
|
||||
|
||||
object := &Pod{Namespace: namespace, Name: name, Node: nodeName, PodIp: podIp, Status: status, NodeIp: nodeIp,
|
||||
CreateTime: createTime, ContainerStr: string(containerStr), AnnotationStr: string(annotation)}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func (ctl *PodCtl) listAndWatch() {
|
||||
defer func() {
|
||||
defer close(ctl.aliveChan)
|
||||
if err := recover(); err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
db := ctl.DB
|
||||
|
||||
if db.HasTable(&Pod{}) {
|
||||
db.DropTable(&Pod{})
|
||||
|
||||
}
|
||||
|
||||
db = db.CreateTable(&Pod{})
|
||||
|
||||
k8sClient := client.NewK8sClient()
|
||||
list, err := k8sClient.CoreV1().Pods("").List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range list.Items {
|
||||
obj := ctl.generateObject(item)
|
||||
db.Create(obj)
|
||||
}
|
||||
|
||||
watcher, err := k8sClient.CoreV1().Pods("").Watch(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctl.stopChan:
|
||||
return
|
||||
case event := <-watcher.ResultChan():
|
||||
var po Pod
|
||||
if event.Object == nil {
|
||||
break
|
||||
}
|
||||
object := event.Object.(*v1.Pod)
|
||||
if event.Type == watch.Deleted {
|
||||
db.Where("name=? And namespace=?", object.Name, object.Namespace).Find(&po)
|
||||
db.Delete(po)
|
||||
break
|
||||
}
|
||||
obj := ctl.generateObject(*object)
|
||||
db.Save(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *PodCtl) CountWithConditions(conditions string) int {
|
||||
var object Pod
|
||||
|
||||
return countWithConditions(ctl.DB, conditions, &object)
|
||||
}
|
||||
|
||||
func (ctl *PodCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) {
|
||||
var list []Pod
|
||||
var object Pod
|
||||
var total int
|
||||
|
||||
order := "createTime desc"
|
||||
|
||||
listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order)
|
||||
|
||||
for index, item := range list {
|
||||
var containers []Container
|
||||
json.Unmarshal([]byte(item.ContainerStr), &containers)
|
||||
list[index].Containers = containers
|
||||
list[index].ContainerStr = ""
|
||||
|
||||
annotation := make(Annotation)
|
||||
json.Unmarshal([]byte(item.AnnotationStr), &annotation)
|
||||
list[index].Annotation = annotation
|
||||
list[index].AnnotationStr = ""
|
||||
}
|
||||
return total, list, nil
|
||||
}
|
||||
|
||||
func (ctl *PodCtl) Count(namespace string) int {
|
||||
var count int
|
||||
db := ctl.DB
|
||||
if len(namespace) == 0 {
|
||||
db.Model(&Pod{}).Count(&count)
|
||||
} else {
|
||||
db.Model(&Pod{}).Where("namespace = ?", namespace).Count(&count)
|
||||
}
|
||||
return count
|
||||
}
|
||||
162
pkg/models/controllers/pvcs.go
Normal file
162
pkg/models/controllers/pvcs.go
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
)
|
||||
|
||||
const creator = "creator"
|
||||
|
||||
func (ctl *PvcCtl) generateObject(item *v1.PersistentVolumeClaim) *Pvc {
|
||||
name := item.Name
|
||||
namespace := item.Namespace
|
||||
status := fmt.Sprintf("%s", item.Status.Phase)
|
||||
createTime := item.CreationTimestamp.Time
|
||||
capacity := "-"
|
||||
|
||||
if createTime.IsZero() {
|
||||
createTime = time.Now()
|
||||
}
|
||||
|
||||
if storage, exist := item.Status.Capacity["storage"]; exist {
|
||||
capacity = storage.String()
|
||||
}
|
||||
|
||||
storageClass := "-"
|
||||
if item.Spec.StorageClassName != nil {
|
||||
storageClass = *item.Spec.StorageClassName
|
||||
}
|
||||
|
||||
accessModeStr := "-"
|
||||
|
||||
var accessModeList []string
|
||||
for _, accessMode := range item.Status.AccessModes {
|
||||
accessModeList = append(accessModeList, string(accessMode))
|
||||
}
|
||||
|
||||
accessModeStr = strings.Join(accessModeList, ",")
|
||||
annotation, _ := json.Marshal(item.Annotations)
|
||||
|
||||
object := &Pvc{Namespace: namespace, Name: name, Status: status, Capacity: capacity,
|
||||
AccessMode: accessModeStr, StorageClassName: storageClass, CreateTime: createTime, AnnotationStr: string(annotation)}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func (ctl *PvcCtl) listAndWatch() {
|
||||
defer func() {
|
||||
defer close(ctl.aliveChan)
|
||||
if err := recover(); err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
db := ctl.DB
|
||||
|
||||
if db.HasTable(&Pvc{}) {
|
||||
db.DropTable(&Pvc{})
|
||||
|
||||
}
|
||||
|
||||
db = db.CreateTable(&Pvc{})
|
||||
|
||||
k8sClient := client.NewK8sClient()
|
||||
pvcList, err := k8sClient.CoreV1().PersistentVolumeClaims("").List(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range pvcList.Items {
|
||||
obj := ctl.generateObject(&item)
|
||||
db.Create(obj)
|
||||
}
|
||||
|
||||
watcher, err := k8sClient.CoreV1().PersistentVolumeClaims("").Watch(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctl.stopChan:
|
||||
return
|
||||
case event := <-watcher.ResultChan():
|
||||
var pvc Pvc
|
||||
if event.Object == nil {
|
||||
break
|
||||
}
|
||||
object := event.Object.(*v1.PersistentVolumeClaim)
|
||||
if event.Type == watch.Deleted {
|
||||
db.Where("name=? And namespace=?", object.Name, object.Namespace).Find(&pvc)
|
||||
db.Delete(pvc)
|
||||
break
|
||||
}
|
||||
obj := ctl.generateObject(object)
|
||||
db.Save(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *PvcCtl) CountWithConditions(conditions string) int {
|
||||
var object Pvc
|
||||
|
||||
return countWithConditions(ctl.DB, conditions, &object)
|
||||
}
|
||||
|
||||
func (ctl *PvcCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) {
|
||||
var list []Pvc
|
||||
var object Pvc
|
||||
var total int
|
||||
|
||||
order := "createTime desc"
|
||||
|
||||
listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order)
|
||||
|
||||
for index, item := range list {
|
||||
annotation := make(map[string]string)
|
||||
json.Unmarshal([]byte(item.AnnotationStr), &annotation)
|
||||
list[index].Annotation = annotation
|
||||
list[index].AnnotationStr = ""
|
||||
}
|
||||
|
||||
return total, list, nil
|
||||
}
|
||||
|
||||
func (ctl *PvcCtl) Count(namespace string) int {
|
||||
var count int
|
||||
db := ctl.DB
|
||||
if len(namespace) == 0 {
|
||||
db.Model(&Pvc{}).Count(&count)
|
||||
} else {
|
||||
db.Model(&Pvc{}).Where("namespace = ?", namespace).Count(&count)
|
||||
}
|
||||
return count
|
||||
}
|
||||
142
pkg/models/controllers/roles.go
Normal file
142
pkg/models/controllers/roles.go
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/rbac/v1"
|
||||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
)
|
||||
|
||||
func (ctl *RoleCtl) generateObject(item v1.Role) *Role {
|
||||
name := item.Name
|
||||
if strings.HasPrefix(name, "system:") {
|
||||
return nil
|
||||
}
|
||||
namespace := item.Namespace
|
||||
createTime := item.CreationTimestamp.Time
|
||||
if createTime.IsZero() {
|
||||
createTime = time.Now()
|
||||
}
|
||||
|
||||
annotation, _ := json.Marshal(item.Annotations)
|
||||
|
||||
object := &Role{Namespace: namespace, Name: name, CreateTime: createTime, AnnotationStr: string(annotation)}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func (ctl *RoleCtl) listAndWatch() {
|
||||
defer func() {
|
||||
close(ctl.aliveChan)
|
||||
if err := recover(); err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
db := ctl.DB
|
||||
|
||||
if db.HasTable(&Role{}) {
|
||||
db.DropTable(&Role{})
|
||||
|
||||
}
|
||||
|
||||
db = db.CreateTable(&Role{})
|
||||
|
||||
k8sClient := client.NewK8sClient()
|
||||
roleList, err := k8sClient.RbacV1().Roles("").List(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range roleList.Items {
|
||||
obj := ctl.generateObject(item)
|
||||
if obj != nil {
|
||||
db.Create(obj)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
roleWatcher, err := k8sClient.RbacV1().Roles("").Watch(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctl.stopChan:
|
||||
return
|
||||
case event := <-roleWatcher.ResultChan():
|
||||
var role Role
|
||||
object := event.Object.(*v1.Role)
|
||||
if event.Object == nil {
|
||||
break
|
||||
}
|
||||
if event.Type == watch.Deleted {
|
||||
db.Where("name=? And namespace=?", object.Name, object.Namespace).Find(&role)
|
||||
db.Delete(role)
|
||||
break
|
||||
}
|
||||
obj := ctl.generateObject(*object)
|
||||
if obj != nil {
|
||||
db.Save(obj)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *RoleCtl) CountWithConditions(conditions string) int {
|
||||
var object Role
|
||||
|
||||
return countWithConditions(ctl.DB, conditions, &object)
|
||||
}
|
||||
|
||||
func (ctl *RoleCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) {
|
||||
var list []Role
|
||||
var object Role
|
||||
var total int
|
||||
|
||||
order := "createTime desc"
|
||||
|
||||
listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order)
|
||||
|
||||
for index, item := range list {
|
||||
annotation := make(map[string]string)
|
||||
json.Unmarshal([]byte(item.AnnotationStr), &annotation)
|
||||
list[index].Annotation = annotation
|
||||
list[index].AnnotationStr = ""
|
||||
}
|
||||
return total, list, nil
|
||||
}
|
||||
|
||||
func (ctl *RoleCtl) Count(namespace string) int {
|
||||
var count int
|
||||
db := ctl.DB
|
||||
db.Model(&Role{}).Where("namespace = ?", namespace).Count(&count)
|
||||
return count
|
||||
}
|
||||
100
pkg/models/controllers/run.go
Normal file
100
pkg/models/controllers/run.go
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/jinzhu/gorm"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
)
|
||||
|
||||
type resourceControllers struct {
|
||||
controllers map[string]Controller
|
||||
db *gorm.DB
|
||||
k8sClient *kubernetes.Clientset
|
||||
}
|
||||
|
||||
var stopChan chan struct{}
|
||||
|
||||
func (rec *resourceControllers) runContoller(name string) {
|
||||
var ctl Controller
|
||||
attr := CommonAttribute{DB: rec.db, K8sClient: rec.k8sClient, stopChan: stopChan, aliveChan: make(chan struct{})}
|
||||
switch name {
|
||||
case Deployments:
|
||||
ctl = &DeploymentCtl{attr}
|
||||
case Statefulsets:
|
||||
ctl = &StatefulsetCtl{attr}
|
||||
case Daemonsets:
|
||||
ctl = &DaemonsetCtl{attr}
|
||||
case Ingresses:
|
||||
ctl = &IngressCtl{attr}
|
||||
case PersistentVolumeClaim:
|
||||
ctl = &PvcCtl{attr}
|
||||
case Roles:
|
||||
ctl = &RoleCtl{attr}
|
||||
case ClusterRoles:
|
||||
ctl = &ClusterRoleCtl{attr}
|
||||
case Services:
|
||||
ctl = &ServiceCtl{attr}
|
||||
case Pods:
|
||||
ctl = &PodCtl{attr}
|
||||
case Namespaces:
|
||||
ctl = &NamespaceCtl{attr}
|
||||
case StorageClasses:
|
||||
ctl = &StorageClassCtl{attr}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
rec.controllers[name] = ctl
|
||||
go ctl.listAndWatch()
|
||||
|
||||
}
|
||||
|
||||
func Run() {
|
||||
db := client.NewDBClient()
|
||||
|
||||
stopChan := make(chan struct{})
|
||||
defer db.Commit()
|
||||
defer db.Close()
|
||||
defer close(stopChan)
|
||||
|
||||
rec := resourceControllers{k8sClient: client.NewK8sClient(), db: db, controllers: make(map[string]Controller)}
|
||||
|
||||
for _, item := range []string{Deployments, Statefulsets, Daemonsets, PersistentVolumeClaim, Pods, Services,
|
||||
Ingresses, Roles, ClusterRoles, Namespaces, StorageClasses} {
|
||||
rec.runContoller(item)
|
||||
}
|
||||
|
||||
for {
|
||||
for ctlName, controller := range rec.controllers {
|
||||
select {
|
||||
case _, isClose := <-controller.chanAlive():
|
||||
if !isClose {
|
||||
glog.Errorf("controller %s have stopped, restart it", ctlName)
|
||||
rec.runContoller(ctlName)
|
||||
}
|
||||
default:
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
218
pkg/models/controllers/services.go
Normal file
218
pkg/models/controllers/services.go
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
)
|
||||
|
||||
const (
|
||||
headlessSelector = "Headless(Selector)"
|
||||
headlessExternal = "Headless(ExternalName)"
|
||||
virtualIp = "Virtual IP"
|
||||
)
|
||||
|
||||
func (ctl *ServiceCtl) loadBalancerStatusStringer(item v1.Service) string {
|
||||
ingress := item.Status.LoadBalancer.Ingress
|
||||
result := sets.NewString()
|
||||
for i := range ingress {
|
||||
if ingress[i].IP != "" {
|
||||
result.Insert(ingress[i].IP)
|
||||
} else if ingress[i].Hostname != "" {
|
||||
result.Insert(ingress[i].Hostname)
|
||||
}
|
||||
}
|
||||
|
||||
r := strings.Join(result.List(), ",")
|
||||
return r
|
||||
}
|
||||
|
||||
func (ctl *ServiceCtl) getExternalIp(item v1.Service) string {
|
||||
switch item.Spec.Type {
|
||||
case "ClusterIP", "NodePort":
|
||||
if len(item.Spec.ExternalIPs) > 0 {
|
||||
return strings.Join(item.Spec.ExternalIPs, ",")
|
||||
}
|
||||
case "ExternalName":
|
||||
return item.Spec.ExternalName
|
||||
|
||||
case "LoadBalancer":
|
||||
lbIps := ctl.loadBalancerStatusStringer(item)
|
||||
if len(item.Spec.ExternalIPs) > 0 {
|
||||
results := []string{}
|
||||
if len(lbIps) > 0 {
|
||||
results = append(results, strings.Split(lbIps, ",")...)
|
||||
}
|
||||
results = append(results, item.Spec.ExternalIPs...)
|
||||
return strings.Join(results, ",")
|
||||
}
|
||||
if len(lbIps) > 0 {
|
||||
return lbIps
|
||||
}
|
||||
return "<pending>"
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
|
||||
func (ctl *ServiceCtl) generateObject(item v1.Service) *Service {
|
||||
|
||||
name := item.Name
|
||||
namespace := item.Namespace
|
||||
createTime := item.CreationTimestamp.Time
|
||||
externalIp := ctl.getExternalIp(item)
|
||||
serviceType := virtualIp
|
||||
vip := item.Spec.ClusterIP
|
||||
ports := ""
|
||||
|
||||
if createTime.IsZero() {
|
||||
createTime = time.Now()
|
||||
}
|
||||
|
||||
if item.Spec.ClusterIP == "None" {
|
||||
serviceType = headlessSelector
|
||||
vip = "-"
|
||||
}
|
||||
|
||||
if len(item.Spec.ExternalName) > 0 {
|
||||
serviceType = headlessExternal
|
||||
vip = "-"
|
||||
}
|
||||
|
||||
if len(item.Spec.ExternalIPs) > 0 {
|
||||
externalIp = strings.Join(item.Spec.ExternalIPs, ",")
|
||||
}
|
||||
|
||||
for _, portItem := range item.Spec.Ports {
|
||||
port := portItem.Port
|
||||
targetPort := portItem.TargetPort.String()
|
||||
protocol := portItem.Protocol
|
||||
ports += fmt.Sprintf("%d:%s/%s,", port, targetPort, protocol)
|
||||
}
|
||||
if len(ports) == 0 {
|
||||
ports = "-"
|
||||
} else {
|
||||
ports = ports[0 : len(ports)-1]
|
||||
}
|
||||
|
||||
annotation, _ := json.Marshal(item.Annotations)
|
||||
object := &Service{Namespace: namespace, Name: name, ServiceType: serviceType, ExternalIp: externalIp,
|
||||
VirtualIp: vip, CreateTime: createTime, Ports: ports, AnnotationStr: string(annotation)}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func (ctl *ServiceCtl) listAndWatch() {
|
||||
defer func() {
|
||||
defer close(ctl.aliveChan)
|
||||
if err := recover(); err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
db := ctl.DB
|
||||
|
||||
if db.HasTable(&Service{}) {
|
||||
db.DropTable(&Service{})
|
||||
}
|
||||
|
||||
db = db.CreateTable(&Service{})
|
||||
|
||||
k8sClient := client.NewK8sClient()
|
||||
svcList, err := k8sClient.CoreV1().Services("").List(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range svcList.Items {
|
||||
obj := ctl.generateObject(item)
|
||||
db.Create(obj)
|
||||
}
|
||||
|
||||
watcher, err := k8sClient.CoreV1().Services("").Watch(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctl.stopChan:
|
||||
return
|
||||
case event := <-watcher.ResultChan():
|
||||
var svc Service
|
||||
object := event.Object.(*v1.Service)
|
||||
if event.Object == nil {
|
||||
break
|
||||
}
|
||||
if event.Type == watch.Deleted {
|
||||
db.Where("name=? And namespace=?", object.Name, object.Namespace).Find(&svc)
|
||||
db.Delete(svc)
|
||||
break
|
||||
}
|
||||
obj := ctl.generateObject(*object)
|
||||
db.Save(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *ServiceCtl) CountWithConditions(conditions string) int {
|
||||
var object Service
|
||||
|
||||
return countWithConditions(ctl.DB, conditions, &object)
|
||||
}
|
||||
|
||||
func (ctl *ServiceCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) {
|
||||
var list []Service
|
||||
var object Service
|
||||
var total int
|
||||
|
||||
order := "createTime desc"
|
||||
|
||||
listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order)
|
||||
|
||||
for index, item := range list {
|
||||
annotation := make(map[string]string)
|
||||
json.Unmarshal([]byte(item.AnnotationStr), &annotation)
|
||||
list[index].Annotation = annotation
|
||||
list[index].AnnotationStr = ""
|
||||
}
|
||||
return total, list, nil
|
||||
}
|
||||
|
||||
func (ctl *ServiceCtl) Count(namespace string) int {
|
||||
var count int
|
||||
db := ctl.DB
|
||||
if len(namespace) == 0 {
|
||||
db.Model(&Service{}).Count(&count)
|
||||
} else {
|
||||
db.Model(&Service{}).Where("namespace = ?", namespace).Count(&count)
|
||||
}
|
||||
return count
|
||||
}
|
||||
156
pkg/models/controllers/statefulsets.go
Normal file
156
pkg/models/controllers/statefulsets.go
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/apps/v1beta2"
|
||||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
)
|
||||
|
||||
func (ctl *StatefulsetCtl) generateObject(item v1beta2.StatefulSet) *Statefulset {
|
||||
var app string
|
||||
var status string
|
||||
name := item.Name
|
||||
namespace := item.Namespace
|
||||
availablePodNum := item.Status.ReadyReplicas
|
||||
desirePodNum := *item.Spec.Replicas
|
||||
createTime := item.CreationTimestamp.Time
|
||||
release := item.ObjectMeta.Labels["release"]
|
||||
chart := item.ObjectMeta.Labels["chart"]
|
||||
|
||||
if createTime.IsZero() {
|
||||
createTime = time.Now()
|
||||
}
|
||||
|
||||
if len(release) > 0 && len(chart) > 0 {
|
||||
app = release + "/" + chart
|
||||
} else {
|
||||
app = "-"
|
||||
}
|
||||
|
||||
if item.Annotations["state"] == "stop" {
|
||||
status = stopping
|
||||
} else {
|
||||
if availablePodNum >= desirePodNum {
|
||||
status = running
|
||||
} else {
|
||||
status = updating
|
||||
}
|
||||
}
|
||||
|
||||
annotation, _ := json.Marshal(item.Annotations)
|
||||
|
||||
statefulSetObject := &Statefulset{Namespace: namespace, Name: name, Available: availablePodNum, Desire: desirePodNum,
|
||||
App: app, CreateTime: createTime, Status: status, AnnotationStr: string(annotation)}
|
||||
|
||||
return statefulSetObject
|
||||
}
|
||||
|
||||
func (ctl *StatefulsetCtl) listAndWatch() {
|
||||
defer func() {
|
||||
defer close(ctl.aliveChan)
|
||||
if err := recover(); err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
db := ctl.DB
|
||||
if db.HasTable(&Statefulset{}) {
|
||||
db.DropTable(&Statefulset{})
|
||||
}
|
||||
|
||||
db = db.CreateTable(&Statefulset{})
|
||||
k8sClient := client.NewK8sClient()
|
||||
deoloyList, err := k8sClient.AppsV1beta2().StatefulSets("").List(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
|
||||
for _, item := range deoloyList.Items {
|
||||
obj := ctl.generateObject(item)
|
||||
db.Create(obj)
|
||||
}
|
||||
|
||||
watcher, err := k8sClient.AppsV1beta2().StatefulSets("").Watch(metaV1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctl.stopChan:
|
||||
return
|
||||
case event := <-watcher.ResultChan():
|
||||
var tmp Statefulset
|
||||
if event.Object == nil {
|
||||
break
|
||||
}
|
||||
object := event.Object.(*v1beta2.StatefulSet)
|
||||
if event.Type == watch.Deleted {
|
||||
db.Where("name=? And namespace=?", object.Name, object.Namespace).Find(&tmp)
|
||||
db.Delete(tmp)
|
||||
break
|
||||
}
|
||||
obj := ctl.generateObject(*object)
|
||||
db.Save(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *StatefulsetCtl) CountWithConditions(conditions string) int {
|
||||
var object Statefulset
|
||||
|
||||
return countWithConditions(ctl.DB, conditions, &object)
|
||||
}
|
||||
|
||||
func (ctl *StatefulsetCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) {
|
||||
var list []Statefulset
|
||||
var object Statefulset
|
||||
var total int
|
||||
|
||||
order := "createTime desc"
|
||||
|
||||
listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order)
|
||||
|
||||
for index, item := range list {
|
||||
annotation := make(map[string]string)
|
||||
json.Unmarshal([]byte(item.AnnotationStr), &annotation)
|
||||
list[index].Annotation = annotation
|
||||
list[index].AnnotationStr = ""
|
||||
}
|
||||
return total, list, nil
|
||||
}
|
||||
|
||||
func (ctl *StatefulsetCtl) Count(namespace string) int {
|
||||
var count int
|
||||
db := ctl.DB
|
||||
if len(namespace) == 0 {
|
||||
db.Model(&Statefulset{}).Count(&count)
|
||||
} else {
|
||||
db.Model(&Statefulset{}).Where("namespace = ?", namespace).Count(&count)
|
||||
}
|
||||
return count
|
||||
}
|
||||
141
pkg/models/controllers/storageclasses.go
Normal file
141
pkg/models/controllers/storageclasses.go
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/storage/v1beta1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
)
|
||||
|
||||
func (ctl *StorageClassCtl) generateObject(item v1beta1.StorageClass) *StorageClass {
|
||||
|
||||
name := item.Name
|
||||
createTime := item.CreationTimestamp.Time
|
||||
isDefault := false
|
||||
if item.Annotations["storageclass.beta.kubernetes.io/is-default-class"] == "true" {
|
||||
isDefault = true
|
||||
}
|
||||
|
||||
if createTime.IsZero() {
|
||||
createTime = time.Now()
|
||||
}
|
||||
|
||||
annotation, _ := json.Marshal(item.Annotations)
|
||||
object := &StorageClass{Name: name, CreateTime: createTime, IsDefault: isDefault, AnnotationStr: string(annotation)}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func (ctl *StorageClassCtl) listAndWatch() {
|
||||
defer func() {
|
||||
defer close(ctl.aliveChan)
|
||||
if err := recover(); err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
db := ctl.DB
|
||||
|
||||
if db.HasTable(&StorageClass{}) {
|
||||
db.DropTable(&StorageClass{})
|
||||
}
|
||||
|
||||
db = db.CreateTable(&StorageClass{})
|
||||
|
||||
k8sClient := client.NewK8sClient()
|
||||
list, err := k8sClient.StorageV1beta1().StorageClasses().List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range list.Items {
|
||||
obj := ctl.generateObject(item)
|
||||
db.Create(obj)
|
||||
}
|
||||
|
||||
watcher, err := k8sClient.StorageV1beta1().StorageClasses().Watch(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctl.stopChan:
|
||||
return
|
||||
case event := <-watcher.ResultChan():
|
||||
var sc StorageClass
|
||||
if event.Object == nil {
|
||||
break
|
||||
}
|
||||
object := event.Object.(*v1beta1.StorageClass)
|
||||
if event.Type == watch.Deleted {
|
||||
db.Where("name=?", object.Name).Find(&sc)
|
||||
db.Delete(sc)
|
||||
break
|
||||
}
|
||||
obj := ctl.generateObject(*object)
|
||||
db.Save(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctl *StorageClassCtl) CountWithConditions(conditions string) int {
|
||||
var object StorageClass
|
||||
|
||||
return countWithConditions(ctl.DB, conditions, &object)
|
||||
}
|
||||
|
||||
func (ctl *StorageClassCtl) ListWithConditions(conditions string, paging *Paging) (int, interface{}, error) {
|
||||
var list []StorageClass
|
||||
var object StorageClass
|
||||
var total int
|
||||
|
||||
order := "createTime desc"
|
||||
|
||||
listWithConditions(ctl.DB, &total, &object, &list, conditions, paging, order)
|
||||
|
||||
for index, storageClass := range list {
|
||||
name := storageClass.Name
|
||||
annotation := make(map[string]string)
|
||||
json.Unmarshal([]byte(storageClass.AnnotationStr), &annotation)
|
||||
list[index].Annotation = annotation
|
||||
list[index].AnnotationStr = ""
|
||||
pvcCtl := PvcCtl{CommonAttribute{K8sClient: ctl.K8sClient, DB: ctl.DB}}
|
||||
|
||||
list[index].Count = pvcCtl.CountWithConditions(fmt.Sprintf("storage_class=\"%s\"", name))
|
||||
}
|
||||
|
||||
return total, list, nil
|
||||
}
|
||||
|
||||
func (ctl *StorageClassCtl) Count(name string) int {
|
||||
var count int
|
||||
db := ctl.DB
|
||||
db.Model(&StorageClass{}).Count(&count)
|
||||
return count
|
||||
}
|
||||
361
pkg/models/controllers/types.go
Normal file
361
pkg/models/controllers/types.go
Normal file
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 controllers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
const (
|
||||
stopping = "stopped"
|
||||
running = "running"
|
||||
updating = "updating"
|
||||
tablePods = "pods"
|
||||
tableDeployments = "deployments"
|
||||
tableDaemonsets = "daemonsets"
|
||||
tableStatefulsets = "statefulsets"
|
||||
tableNamespaces = "namespaces"
|
||||
tableIngresses = "ingresses"
|
||||
tablePersistentVolumeClaim = "pvcs"
|
||||
tableRoles = "roles"
|
||||
tableClusterRoles = "cluster_roles"
|
||||
tableServices = "services"
|
||||
tableStorageClasses = "storage_classes"
|
||||
|
||||
Pods = "pods"
|
||||
Deployments = "deployments"
|
||||
Daemonsets = "daemonsets"
|
||||
Statefulsets = "statefulsets"
|
||||
Namespaces = "namespaces"
|
||||
Ingresses = "ingresses"
|
||||
PersistentVolumeClaim = "persistent-volume-claims"
|
||||
Roles = "roles"
|
||||
ClusterRoles = "cluster-roles"
|
||||
Services = "services"
|
||||
StorageClasses = "storage-classes"
|
||||
)
|
||||
|
||||
var ResourceTable = map[string]string{Deployments: tableDeployments, Statefulsets: tableStatefulsets, Daemonsets: tableDaemonsets,
|
||||
Pods: tablePods, Namespaces: tableNamespaces, Ingresses: tableIngresses, PersistentVolumeClaim: tablePersistentVolumeClaim, Roles: tableRoles,
|
||||
Services: tableServices, StorageClasses: tableStorageClasses, ClusterRoles: tableClusterRoles}
|
||||
|
||||
type Annotation map[string]string
|
||||
|
||||
//
|
||||
//func (annotation *Annotation)Scan(val interface{}) error{
|
||||
// switch val := val.(type){
|
||||
// case string:
|
||||
// return json.Unmarshal([]byte(val), annotation)
|
||||
// case []byte:
|
||||
// return json.Unmarshal(val, annotation)
|
||||
// default:
|
||||
// return errors.New("not support")
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
//
|
||||
//func (annotation *Annotation)Value() (driver.Value, error){
|
||||
// bytes, err := json.Marshal(annotation)
|
||||
// return string(bytes), err
|
||||
//}
|
||||
|
||||
type Deployment struct {
|
||||
Name string `gorm:"primary_key" json:"name"`
|
||||
Namespace string `gorm:"primary_key" json:"namespace"`
|
||||
App string `json:"app,omitempty"`
|
||||
|
||||
Available int32 `json:"available"`
|
||||
Desire int32 `json:"desire"`
|
||||
Status string `json:"status"`
|
||||
AnnotationStr string `gorm:"type:text" json:"annotationStr,omitempty"`
|
||||
Annotation Annotation `gorm:"-" json:"annotation"`
|
||||
UpdateTime time.Time `gorm:"column:updateTime" json:"updateTime,omitempty"`
|
||||
}
|
||||
|
||||
func (Deployment) TableName() string {
|
||||
return tableDeployments
|
||||
}
|
||||
|
||||
type Statefulset struct {
|
||||
Name string `gorm:"primary_key" json:"name,omitempty"`
|
||||
Namespace string `gorm:"primary_key" json:"namespace,omitempty"`
|
||||
App string `json:"app,omitempty"`
|
||||
|
||||
Available int32 `json:"available"`
|
||||
Desire int32 `json:"desire"`
|
||||
Status string `json:"status"`
|
||||
AnnotationStr string `gorm:"type:text" json:"annotationStr,omitempty"`
|
||||
Annotation Annotation `gorm:"-" json:"annotation"`
|
||||
CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"`
|
||||
}
|
||||
|
||||
func (Statefulset) TableName() string {
|
||||
return tableStatefulsets
|
||||
}
|
||||
|
||||
type Daemonset struct {
|
||||
Name string `gorm:"primary_key" json:"name,omitempty"`
|
||||
Namespace string `gorm:"primary_key" json:"namespace,omitempty"`
|
||||
App string `json:"app,omitempty"`
|
||||
|
||||
Available int32 `json:"available"`
|
||||
Desire int32 `json:"desire"`
|
||||
Status string `json:"status"`
|
||||
NodeSelector string `json:"nodeSelector, omitempty"`
|
||||
AnnotationStr string `gorm:"type:text" json:"annotationStr,omitempty"`
|
||||
Annotation Annotation `gorm:"-" json:"annotation"`
|
||||
CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"`
|
||||
}
|
||||
|
||||
func (Daemonset) TableName() string {
|
||||
return tableDaemonsets
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
Name string `gorm:"primary_key" json:"name"`
|
||||
Namespace string `gorm:"primary_key" json:"namespace"`
|
||||
ServiceType string `json:"type,omitempty"`
|
||||
|
||||
VirtualIp string `json:"virtualIp,omitempty"`
|
||||
ExternalIp string `json:"externalIp,omitempty"`
|
||||
|
||||
Ports string `json:"ports,omitempty"`
|
||||
AnnotationStr string `gorm:"type:text" json:"annotationStr,omitempty"`
|
||||
Annotation Annotation `gorm:"-" json:"annotation"`
|
||||
CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"`
|
||||
}
|
||||
|
||||
func (Service) TableName() string {
|
||||
return tableServices
|
||||
}
|
||||
|
||||
type Pvc struct {
|
||||
Name string `gorm:"primary_key" json:"name"`
|
||||
Namespace string `gorm:"primary_key" json:"namespace"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Capacity string `json:"capacity,omitempty"`
|
||||
AccessMode string `json:"accessMode,omitempty"`
|
||||
AnnotationStr string `gorm:"type:text" json:"annotationStr,omitempty"`
|
||||
Annotation Annotation `gorm:"-" json:"annotation"`
|
||||
CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"`
|
||||
StorageClassName string `gorm:"column:storage_class" json:"storage_class,omitempty"`
|
||||
}
|
||||
|
||||
func (Pvc) TableName() string {
|
||||
return tablePersistentVolumeClaim
|
||||
}
|
||||
|
||||
type Ingress struct {
|
||||
Name string `gorm:"primary_key" json:"name"`
|
||||
Namespace string `gorm:"primary_key" json:"namespace"`
|
||||
Ip string `json:"ip,omitempty"`
|
||||
TlsTermination string `json:"tlsTermination,omitempty"`
|
||||
AnnotationStr string `gorm:"type:text" json:"annotationStr,omitempty"`
|
||||
Annotation Annotation `gorm:"-" json:"annotation"`
|
||||
CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"`
|
||||
}
|
||||
|
||||
func (Ingress) TableName() string {
|
||||
return tableIngresses
|
||||
}
|
||||
|
||||
type Pod struct {
|
||||
Name string `gorm:"primary_key" json:"name"`
|
||||
Namespace string `gorm:"primary_key" json:"namespace"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Node string `json:"node,omitempty"`
|
||||
NodeIp string `json:"nodeIp,omitempty"`
|
||||
PodIp string `json:"podIp,omitempty"`
|
||||
ContainerStr string `gorm:"type:text" json:",omitempty"`
|
||||
Containers []Container `json:"containers,omitempty"`
|
||||
AnnotationStr string `gorm:"type:text" json:"annotationStr,omitempty"`
|
||||
Annotation Annotation `gorm:"-" json:"annotation"`
|
||||
CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"`
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
Name string `json:"name"`
|
||||
Ready bool `json:"ready,omitempty"`
|
||||
Image string `json:"image"`
|
||||
Ports []v1.ContainerPort `json:"ports"`
|
||||
}
|
||||
|
||||
func (Pod) TableName() string {
|
||||
return tablePods
|
||||
}
|
||||
|
||||
type Role struct {
|
||||
Name string `gorm:"primary_key" json:"name"`
|
||||
Namespace string `gorm:"primary_key" json:"namespace"`
|
||||
AnnotationStr string `gorm:"type:text" json:"annotationStr,omitempty"`
|
||||
Annotation Annotation `gorm:"-" json:"annotation"`
|
||||
CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"`
|
||||
}
|
||||
|
||||
func (Role) TableName() string {
|
||||
return tableRoles
|
||||
}
|
||||
|
||||
type ClusterRole struct {
|
||||
Name string `gorm:"primary_key" json:"name"`
|
||||
AnnotationStr string `gorm:"type:text" json:"annotationStr,omitempty"`
|
||||
Annotation Annotation `gorm:"-" json:"annotation"`
|
||||
CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"`
|
||||
}
|
||||
|
||||
func (ClusterRole) TableName() string {
|
||||
return tableClusterRoles
|
||||
}
|
||||
|
||||
type Namespace struct {
|
||||
Name string `gorm:"primary_key" json:"name"`
|
||||
Creator string `json:"creator,omitempty"`
|
||||
Status string `json:"status"`
|
||||
|
||||
Descrition string `json:"description,omitempty"`
|
||||
AnnotationStr string `gorm:"type:text" json:"annotationStr,omitempty"`
|
||||
Annotation Annotation `gorm:"-" json:"annotation"`
|
||||
CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"`
|
||||
}
|
||||
|
||||
func (Namespace) TableName() string {
|
||||
return tableNamespaces
|
||||
}
|
||||
|
||||
type StorageClass struct {
|
||||
Name string `gorm:"primary_key" json:"name"`
|
||||
Creator string `json:"creator,omitempty"`
|
||||
AnnotationStr string `gorm:"type:text" json:"annotationStr,omitempty"`
|
||||
Annotation Annotation `gorm:"-" json:"annotation"`
|
||||
CreateTime time.Time `gorm:"column:createTime" json:"createTime,omitempty"`
|
||||
IsDefault bool `json:"default"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
func (StorageClass) TableName() string {
|
||||
return tableStorageClasses
|
||||
}
|
||||
|
||||
type Paging struct {
|
||||
Limit, Offset int
|
||||
}
|
||||
|
||||
type Controller interface {
|
||||
listAndWatch()
|
||||
chanStop() chan struct{}
|
||||
chanAlive() chan struct{}
|
||||
Count(conditions string) int
|
||||
ListWithConditions(condition string, paging *Paging) (int, interface{}, error)
|
||||
}
|
||||
|
||||
type CommonAttribute struct {
|
||||
K8sClient *kubernetes.Clientset
|
||||
DB *gorm.DB
|
||||
stopChan chan struct{}
|
||||
aliveChan chan struct{}
|
||||
}
|
||||
|
||||
func (ca *CommonAttribute) chanStop() chan struct{} {
|
||||
|
||||
return ca.stopChan
|
||||
}
|
||||
|
||||
func (ca *CommonAttribute) chanAlive() chan struct{} {
|
||||
|
||||
return ca.aliveChan
|
||||
}
|
||||
|
||||
type DeploymentCtl struct {
|
||||
CommonAttribute
|
||||
}
|
||||
|
||||
type StatefulsetCtl struct {
|
||||
CommonAttribute
|
||||
}
|
||||
|
||||
type DaemonsetCtl struct {
|
||||
CommonAttribute
|
||||
}
|
||||
|
||||
type ServiceCtl struct {
|
||||
CommonAttribute
|
||||
}
|
||||
|
||||
type PvcCtl struct {
|
||||
CommonAttribute
|
||||
}
|
||||
|
||||
type PodCtl struct {
|
||||
CommonAttribute
|
||||
}
|
||||
|
||||
type IngressCtl struct {
|
||||
CommonAttribute
|
||||
}
|
||||
|
||||
type NamespaceCtl struct {
|
||||
CommonAttribute
|
||||
}
|
||||
|
||||
type StorageClassCtl struct {
|
||||
CommonAttribute
|
||||
}
|
||||
|
||||
type RoleCtl struct {
|
||||
CommonAttribute
|
||||
}
|
||||
|
||||
type ClusterRoleCtl struct {
|
||||
CommonAttribute
|
||||
}
|
||||
|
||||
func listWithConditions(db *gorm.DB, total *int, object, list interface{}, conditions string, paging *Paging, order string) {
|
||||
if len(conditions) == 0 {
|
||||
db.Model(object).Count(total)
|
||||
} else {
|
||||
db.Model(object).Where(conditions).Count(total)
|
||||
}
|
||||
|
||||
if paging != nil {
|
||||
if len(conditions) > 0 {
|
||||
db.Where(conditions).Order(order).Limit(paging.Limit).Offset(paging.Offset).Find(list)
|
||||
} else {
|
||||
db.Order(order).Limit(paging.Limit).Offset(paging.Offset).Find(list)
|
||||
}
|
||||
|
||||
} else {
|
||||
if len(conditions) > 0 {
|
||||
db.Where(conditions).Order(order).Find(list)
|
||||
} else {
|
||||
db.Order(order).Find(list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func countWithConditions(db *gorm.DB, conditions string, object interface{}) int {
|
||||
var count int
|
||||
if len(conditions) == 0 {
|
||||
db.Model(object).Count(&count)
|
||||
} else {
|
||||
db.Model(object).Where(conditions).Count(&count)
|
||||
}
|
||||
return count
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 cronjobs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/models/jobs/resources"
|
||||
)
|
||||
|
||||
const (
|
||||
pods = "count/pods"
|
||||
daemonsets = "count/daemonsets.apps"
|
||||
deployments = "count/deployments.apps"
|
||||
ingress = "count/ingresses.extensions"
|
||||
roles = "count/roles.rbac.authorization.k8s.io"
|
||||
services = "count/services"
|
||||
statefulsets = "count/statefulsets.apps"
|
||||
persistentvolumeclaims = "persistentvolumeclaims"
|
||||
)
|
||||
|
||||
type resourceUsage struct {
|
||||
NameSpace string
|
||||
Data v1.ResourceQuotaStatus
|
||||
UpdateTimeStamp int64
|
||||
}
|
||||
|
||||
type resourceQuotaWorker struct {
|
||||
k8sClient *kubernetes.Clientset
|
||||
resChan chan dataType
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
func (ru resourceUsage) namespace() string {
|
||||
return ru.NameSpace
|
||||
}
|
||||
|
||||
type workloadList map[string][]resources.WorkLoadObject
|
||||
|
||||
type otherResourceList map[string][]resources.OtherResourceObject
|
||||
|
||||
type workload struct {
|
||||
ResourceType string `json:"type"`
|
||||
ResourceList workloadList `json:"lists"`
|
||||
UpdateTimeStamp int64 `json:"updateTimestamp"`
|
||||
}
|
||||
|
||||
type otherResource struct {
|
||||
ResourceType string `json:"type"`
|
||||
ResourceList otherResourceList `json:"lists"`
|
||||
UpdateTimeStamp int64 `json:"updateTimestamp"`
|
||||
}
|
||||
|
||||
var workLoads = []string{"deployments", "daemonsets", "statefulsets"}
|
||||
|
||||
var resourceMap = map[string]string{daemonsets: "daemonsets", deployments: "deployments", ingress: "ingresses",
|
||||
roles: "roles", services: "services", statefulsets: "statefulsets", persistentvolumeclaims: "persistent-volume-claim", pods: "pods"}
|
||||
|
||||
func contain(items []string, item string) bool {
|
||||
for _, v := range items {
|
||||
if v == item {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (rw *resourceQuotaWorker) getResourceusage(namespace, resourceName string) (int, error) {
|
||||
|
||||
etcdcli, err := client.NewEtcdClient()
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
defer etcdcli.Close()
|
||||
key := constants.Root + "/" + resourceName
|
||||
value, err := etcdcli.Get(key)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
|
||||
if contain(workLoads, resourceName) {
|
||||
resourceStatus := workload{ResourceList: make(workloadList)}
|
||||
|
||||
err := json.Unmarshal(value, &resourceStatus)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return len(resourceStatus.ResourceList[namespace]), nil
|
||||
} else {
|
||||
resourceStatus := otherResource{ResourceList: make(otherResourceList)}
|
||||
|
||||
err := json.Unmarshal(value, &resourceStatus)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(resourceStatus.ResourceList[namespace]), nil
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (rw *resourceQuotaWorker) updateNamespaceQuota(tmpResourceList, resourceList v1.ResourceList) {
|
||||
if tmpResourceList == nil {
|
||||
tmpResourceList = resourceList
|
||||
}
|
||||
for resource, usage := range resourceList {
|
||||
tmpUsage, exist := tmpResourceList[resource]
|
||||
if !exist {
|
||||
tmpResourceList[resource] = usage
|
||||
}
|
||||
if tmpUsage.Cmp(usage) == 1 {
|
||||
tmpResourceList[resource] = usage
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (rw *resourceQuotaWorker) getNamespaceResourceUsageByQuota(namespace string) (*v1.ResourceQuotaStatus, error) {
|
||||
quotaList, err := rw.k8sClient.CoreV1().ResourceQuotas(namespace).List(meta_v1.ListOptions{})
|
||||
if err != nil || len(quotaList.Items) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
quotaStatus := v1.ResourceQuotaStatus{Hard: make(v1.ResourceList), Used: make(v1.ResourceList)}
|
||||
|
||||
for _, quota := range quotaList.Items {
|
||||
rw.updateNamespaceQuota(quotaStatus.Hard, quota.Status.Hard)
|
||||
rw.updateNamespaceQuota(quotaStatus.Used, quota.Status.Used)
|
||||
}
|
||||
|
||||
return "aStatus, nil
|
||||
}
|
||||
|
||||
func (rw *resourceQuotaWorker) getNamespaceQuota(namespace string) (v1.ResourceQuotaStatus, error) {
|
||||
quota, err := rw.getNamespaceResourceUsageByQuota(namespace)
|
||||
if err != nil {
|
||||
return v1.ResourceQuotaStatus{}, err
|
||||
}
|
||||
|
||||
if quota == nil {
|
||||
quota = new(v1.ResourceQuotaStatus)
|
||||
quota.Used = make(v1.ResourceList)
|
||||
}
|
||||
|
||||
for k, v := range resourceMap {
|
||||
if _, exist := quota.Used[v1.ResourceName(k)]; !exist {
|
||||
used, err := rw.getResourceusage(namespace, v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var quantity resource.Quantity
|
||||
quantity.Set(int64(used))
|
||||
quota.Used[v1.ResourceName(k)] = quantity
|
||||
}
|
||||
}
|
||||
|
||||
return *quota, nil
|
||||
}
|
||||
|
||||
func (rw *resourceQuotaWorker) workOnce() {
|
||||
clusterQuota := new(v1.ResourceQuotaStatus)
|
||||
clusterQuota.Used = make(v1.ResourceList)
|
||||
namespaces, err := rw.k8sClient.CoreV1().Namespaces().List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, ns := range namespaces.Items {
|
||||
namespace := ns.Name
|
||||
nsquota, err := rw.getNamespaceQuota(namespace)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
res := resourceUsage{NameSpace: namespace, Data: nsquota, UpdateTimeStamp: time.Now().Unix()}
|
||||
rw.resChan <- res
|
||||
|
||||
for k, v := range nsquota.Used {
|
||||
tmp := clusterQuota.Used[k]
|
||||
tmp.Add(v)
|
||||
clusterQuota.Used[k] = tmp
|
||||
}
|
||||
}
|
||||
|
||||
var quantity resource.Quantity
|
||||
quantity.Set(int64(len(namespaces.Items)))
|
||||
|
||||
clusterQuota.Used["count/namespaces"] = quantity
|
||||
res := resourceUsage{NameSpace: "\"\"", Data: *clusterQuota, UpdateTimeStamp: time.Now().Unix()}
|
||||
rw.resChan <- res
|
||||
|
||||
}
|
||||
|
||||
func (rw *resourceQuotaWorker) chanStop() chan struct{} {
|
||||
return rw.stopChan
|
||||
}
|
||||
|
||||
func (rw *resourceQuotaWorker) chanRes() chan dataType {
|
||||
return rw.resChan
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 cronjobs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
)
|
||||
|
||||
var etcdClient *client.EtcdClient
|
||||
|
||||
var stopChan = make(chan struct{})
|
||||
|
||||
type dataType interface {
|
||||
namespace() string
|
||||
}
|
||||
|
||||
type Worker interface {
|
||||
workOnce()
|
||||
chanRes() chan dataType
|
||||
chanStop() chan struct{}
|
||||
}
|
||||
|
||||
func registerWorker(workers map[string]Worker, name string) {
|
||||
|
||||
glog.Infof("Register cronjob: %s", name)
|
||||
k8sClient := client.NewK8sClient()
|
||||
switch name {
|
||||
case constants.WorkloadStatusKey:
|
||||
worker := workloadWorker{k8sClient: k8sClient, stopChan: stopChan, resChan: make(chan dataType, 10)}
|
||||
workers[constants.WorkloadStatusKey] = &worker
|
||||
case constants.QuotaKey:
|
||||
worker := resourceQuotaWorker{k8sClient: k8sClient, stopChan: stopChan, resChan: make(chan dataType, 10)}
|
||||
workers[constants.QuotaKey] = &worker
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func run(worker Worker) {
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
glog.Error(err)
|
||||
close(worker.chanRes())
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-worker.chanStop():
|
||||
return
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
worker.workOnce()
|
||||
time.Sleep(time.Duration(constants.UpdateCircle) * time.Second)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func startWorks(workers map[string]Worker) {
|
||||
for wokername, woker := range workers {
|
||||
glog.Infof("cronjob %s start to work", wokername)
|
||||
go run(woker)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func receiveResourceStatus(workers map[string]Worker) {
|
||||
defer func() {
|
||||
close(stopChan)
|
||||
}()
|
||||
|
||||
for {
|
||||
for name, worker := range workers {
|
||||
select {
|
||||
case res, ok := <-worker.chanRes():
|
||||
if !ok {
|
||||
glog.Errorf("cronjob:%s have stopped", name)
|
||||
registerWorker(workers, name)
|
||||
run(workers[name])
|
||||
} else {
|
||||
value, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
continue
|
||||
}
|
||||
key := constants.Root + "/" + name + "/" + res.namespace()
|
||||
err = etcdClient.Put(key, string(value))
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Run() {
|
||||
glog.Info("Begin to run cronjob")
|
||||
var err error
|
||||
etcdClient, err = client.NewEtcdClient()
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
defer etcdClient.Close()
|
||||
workers := make(map[string]Worker)
|
||||
workerList := []string{constants.QuotaKey, constants.WorkloadStatusKey}
|
||||
for _, name := range workerList {
|
||||
registerWorker(workers, name)
|
||||
}
|
||||
startWorks(workers)
|
||||
receiveResourceStatus(workers)
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package cronjobs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
)
|
||||
|
||||
var workLoadList = []string{"deployments", "daemonsets", "statefulsets"}
|
||||
|
||||
type workLoadStatus struct {
|
||||
NameSpace string
|
||||
Data map[string]int
|
||||
UpdateTimeStamp int64
|
||||
}
|
||||
|
||||
func (ws workLoadStatus) namespace() string {
|
||||
return ws.NameSpace
|
||||
}
|
||||
|
||||
type workloadWorker struct {
|
||||
k8sClient *kubernetes.Clientset
|
||||
resChan chan dataType
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
func (ww *workloadWorker) GetNamespacesResourceStatus(namespace string) (map[string]int, error) {
|
||||
|
||||
cli, err := client.NewEtcdClient()
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
res := make(map[string]int)
|
||||
|
||||
for _, resourceName := range workLoadList {
|
||||
key := constants.Root + "/" + resourceName
|
||||
value, err := cli.Get(key)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
resourceStatus := workload{ResourceList: make(workloadList)}
|
||||
|
||||
err = json.Unmarshal(value, &resourceStatus)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
notReady := 0
|
||||
for _, v := range resourceStatus.ResourceList[namespace] {
|
||||
if !v.Ready {
|
||||
notReady++
|
||||
}
|
||||
}
|
||||
res[resourceName] = notReady
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (ww workloadWorker) workOnce() {
|
||||
namespaces, err := ww.k8sClient.CoreV1().Namespaces().List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
|
||||
resourceStatus := make(map[string]int)
|
||||
for _, item := range namespaces.Items {
|
||||
namespace := item.Name
|
||||
namespacesResourceStatus, err := ww.GetNamespacesResourceStatus(namespace)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
|
||||
var ws = workLoadStatus{UpdateTimeStamp: time.Now().Unix(), Data: namespacesResourceStatus, NameSpace: namespace}
|
||||
ww.resChan <- ws
|
||||
|
||||
for k, v := range namespacesResourceStatus {
|
||||
resourceStatus[k] = v + resourceStatus[k]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var ws = workLoadStatus{UpdateTimeStamp: time.Now().Unix(), Data: resourceStatus, NameSpace: "\"\""}
|
||||
ww.resChan <- ws
|
||||
}
|
||||
|
||||
func (ww workloadWorker) chanRes() chan dataType {
|
||||
return ww.resChan
|
||||
}
|
||||
|
||||
func (ww workloadWorker) chanStop() chan struct{} {
|
||||
return ww.stopChan
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 resources
|
||||
|
||||
import (
|
||||
"k8s.io/api/apps/v1beta2"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type daemonset struct {
|
||||
k8sClient *kubernetes.Clientset
|
||||
}
|
||||
|
||||
func (ds *daemonset) list() (interface{}, error) {
|
||||
daemonsetList, err := ds.k8sClient.AppsV1beta2().DaemonSets("").List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return daemonsetList.Items, nil
|
||||
}
|
||||
|
||||
func (ds *daemonset) getWatcher() (watch.Interface, error) {
|
||||
watcher, err := ds.k8sClient.AppsV1beta2().DaemonSets("").Watch(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return watcher, nil
|
||||
}
|
||||
|
||||
func (ds *daemonset) generateObject(item v1beta2.DaemonSet) WorkLoadObject {
|
||||
var app string
|
||||
var ready bool
|
||||
name := item.Name
|
||||
namespace := item.Namespace
|
||||
availablePodNum := item.Status.CurrentNumberScheduled
|
||||
desirePodNum := item.Status.DesiredNumberScheduled
|
||||
createTime := item.CreationTimestamp
|
||||
release := item.ObjectMeta.Labels["release"]
|
||||
nodeSelector := item.Spec.Template.Spec.NodeSelector
|
||||
chart := item.ObjectMeta.Labels["chart"]
|
||||
|
||||
if len(release) > 0 && len(chart) > 0 {
|
||||
app = release + "/" + chart
|
||||
} else {
|
||||
app = "-"
|
||||
}
|
||||
|
||||
if availablePodNum >= desirePodNum {
|
||||
ready = true
|
||||
} else {
|
||||
ready = false
|
||||
}
|
||||
|
||||
workloadObject := WorkLoadObject{Namespace: namespace, Name: name, Available: availablePodNum, Desire: desirePodNum,
|
||||
App: app, CreateTime: createTime, Ready: ready, NodeSelector: nodeSelector}
|
||||
|
||||
return workloadObject
|
||||
|
||||
}
|
||||
|
||||
func (ds *daemonset) updateWithObject(status *ResourceStatus, item v1beta2.DaemonSet) {
|
||||
namespace := item.Namespace
|
||||
dsObject := ds.generateObject(item)
|
||||
|
||||
status.ResourceList.update(namespace, dsObject)
|
||||
}
|
||||
|
||||
func (ds *daemonset) updateWithObjects(status *ResourceStatus, objects interface{}) {
|
||||
if status.ResourceList == nil {
|
||||
status.ResourceList = make(Resources)
|
||||
}
|
||||
|
||||
items := objects.([]v1beta2.DaemonSet)
|
||||
|
||||
for _, item := range items {
|
||||
ds.updateWithObject(status, item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (ds *daemonset) updateWithEvent(status *ResourceStatus, event watch.Event) {
|
||||
object := event.Object.(*v1beta2.DaemonSet)
|
||||
namespace := object.Namespace
|
||||
daemonsetObject := ds.generateObject(*object)
|
||||
|
||||
if event.Type == watch.Deleted {
|
||||
status.ResourceList.del(namespace, daemonsetObject)
|
||||
return
|
||||
}
|
||||
|
||||
ds.updateWithObject(status, *object)
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 resources
|
||||
|
||||
import (
|
||||
"k8s.io/api/apps/v1beta2"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type deployment struct {
|
||||
k8sClient *kubernetes.Clientset
|
||||
}
|
||||
|
||||
func (deploy *deployment) list() (interface{}, error) {
|
||||
deoloyList, err := deploy.k8sClient.AppsV1beta2().Deployments("").List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return deoloyList.Items, nil
|
||||
}
|
||||
|
||||
func (deploy *deployment) getWatcher() (watch.Interface, error) {
|
||||
watcher, err := deploy.k8sClient.AppsV1beta2().Deployments("").Watch(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return watcher, nil
|
||||
}
|
||||
|
||||
func (deploy *deployment) generateObject(item v1beta2.Deployment) WorkLoadObject {
|
||||
var app string
|
||||
var ready bool
|
||||
var updateTime meta_v1.Time
|
||||
name := item.Name
|
||||
namespace := item.Namespace
|
||||
availablePodNum := item.Status.AvailableReplicas
|
||||
desirePodNum := *item.Spec.Replicas
|
||||
release := item.ObjectMeta.Labels["release"]
|
||||
chart := item.ObjectMeta.Labels["chart"]
|
||||
|
||||
if len(release) > 0 && len(chart) > 0 {
|
||||
app = release + "/" + chart
|
||||
} else {
|
||||
app = "-"
|
||||
}
|
||||
|
||||
for _, conditon := range item.Status.Conditions {
|
||||
if conditon.Type == "Progressing" {
|
||||
updateTime = conditon.LastUpdateTime
|
||||
}
|
||||
}
|
||||
|
||||
if availablePodNum >= desirePodNum {
|
||||
ready = true
|
||||
} else {
|
||||
ready = false
|
||||
}
|
||||
|
||||
deployObject := WorkLoadObject{Namespace: namespace, Name: name, Available: availablePodNum, Desire: desirePodNum,
|
||||
App: app, UpdateTime: updateTime, Ready: ready}
|
||||
|
||||
return deployObject
|
||||
|
||||
}
|
||||
|
||||
func (deploy *deployment) updateWithObject(status *ResourceStatus, item v1beta2.Deployment) {
|
||||
namespace := item.Namespace
|
||||
|
||||
deployObject := deploy.generateObject(item)
|
||||
status.ResourceList.update(namespace, deployObject)
|
||||
}
|
||||
|
||||
func (deploy *deployment) updateWithObjects(status *ResourceStatus, objects interface{}) {
|
||||
if status.ResourceList == nil {
|
||||
status.ResourceList = make(Resources)
|
||||
}
|
||||
|
||||
items := objects.([]v1beta2.Deployment)
|
||||
|
||||
for _, item := range items {
|
||||
deploy.updateWithObject(status, item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (deploy *deployment) updateWithEvent(status *ResourceStatus, event watch.Event) {
|
||||
object := event.Object.(*v1beta2.Deployment)
|
||||
namespace := object.Namespace
|
||||
deployObject := deploy.generateObject(*object)
|
||||
|
||||
if event.Type == watch.Deleted {
|
||||
status.ResourceList.del(namespace, deployObject)
|
||||
return
|
||||
}
|
||||
|
||||
deploy.updateWithObject(status, *object)
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 resources
|
||||
|
||||
import (
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type ingress struct {
|
||||
k8sClient *kubernetes.Clientset
|
||||
}
|
||||
|
||||
func (ing *ingress) list() (interface{}, error) {
|
||||
list, err := ing.k8sClient.ExtensionsV1beta1().Ingresses("").List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list.Items, nil
|
||||
}
|
||||
|
||||
func (ing *ingress) getWatcher() (watch.Interface, error) {
|
||||
watcher, err := ing.k8sClient.ExtensionsV1beta1().Ingresses("").Watch(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return watcher, nil
|
||||
}
|
||||
|
||||
func (ing *ingress) generateObject(item v1beta1.Ingress) OtherResourceObject {
|
||||
name := item.Name
|
||||
ns := item.Namespace
|
||||
|
||||
object := OtherResourceObject{Namespace: ns, Name: name}
|
||||
|
||||
return object
|
||||
|
||||
}
|
||||
|
||||
func (ing *ingress) updateWithObject(status *ResourceStatus, item v1beta1.Ingress) {
|
||||
namespace := item.Namespace
|
||||
|
||||
object := ing.generateObject(item)
|
||||
status.ResourceList.update(namespace, object)
|
||||
}
|
||||
|
||||
func (ing *ingress) updateWithObjects(status *ResourceStatus, objects interface{}) {
|
||||
if status.ResourceList == nil {
|
||||
status.ResourceList = make(Resources)
|
||||
}
|
||||
|
||||
items := objects.([]v1beta1.Ingress)
|
||||
|
||||
for _, item := range items {
|
||||
ing.updateWithObject(status, item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (ing *ingress) updateWithEvent(status *ResourceStatus, event watch.Event) {
|
||||
object := event.Object.(*v1beta1.Ingress)
|
||||
namespace := object.Namespace
|
||||
tmpObject := ing.generateObject(*object)
|
||||
|
||||
if event.Type == watch.Deleted {
|
||||
status.ResourceList.del(namespace, tmpObject)
|
||||
return
|
||||
}
|
||||
|
||||
ing.updateWithObject(status, *object)
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 resources
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type namespace struct {
|
||||
k8sClient *kubernetes.Clientset
|
||||
}
|
||||
|
||||
func (ns *namespace) list() (interface{}, error) {
|
||||
nsList, err := ns.k8sClient.CoreV1().Namespaces().List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nsList.Items, nil
|
||||
}
|
||||
|
||||
func (ns *namespace) getWatcher() (watch.Interface, error) {
|
||||
watcher, err := ns.k8sClient.CoreV1().Namespaces().Watch(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return watcher, nil
|
||||
}
|
||||
|
||||
func (ns *namespace) generateObject(item v1.Namespace) OtherResourceObject {
|
||||
name := item.Name
|
||||
nsp := item.Namespace
|
||||
|
||||
object := OtherResourceObject{Namespace: nsp, Name: name}
|
||||
|
||||
return object
|
||||
|
||||
}
|
||||
|
||||
func (ns *namespace) updateWithObject(status *ResourceStatus, item v1.Namespace) {
|
||||
namespace := item.Namespace
|
||||
|
||||
object := ns.generateObject(item)
|
||||
status.ResourceList.update(namespace, object)
|
||||
}
|
||||
|
||||
func (ns *namespace) updateWithObjects(status *ResourceStatus, objects interface{}) {
|
||||
if status.ResourceList == nil {
|
||||
status.ResourceList = make(Resources)
|
||||
}
|
||||
|
||||
items := objects.([]v1.Namespace)
|
||||
|
||||
for _, item := range items {
|
||||
ns.updateWithObject(status, item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (ns *namespace) updateWithEvent(status *ResourceStatus, event watch.Event) {
|
||||
object := event.Object.(*v1.Namespace)
|
||||
namespace := object.Namespace
|
||||
tmpObject := ns.generateObject(*object)
|
||||
|
||||
if event.Type == watch.Deleted {
|
||||
status.ResourceList.del(namespace, tmpObject)
|
||||
return
|
||||
}
|
||||
|
||||
ns.updateWithObject(status, *object)
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 resources
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type pod struct {
|
||||
k8sClient *kubernetes.Clientset
|
||||
}
|
||||
|
||||
func (po *pod) list() (interface{}, error) {
|
||||
list, err := po.k8sClient.CoreV1().Pods("").List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list.Items, nil
|
||||
}
|
||||
|
||||
func (po *pod) getWatcher() (watch.Interface, error) {
|
||||
watcher, err := po.k8sClient.CoreV1().Pods("").Watch(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return watcher, nil
|
||||
}
|
||||
|
||||
func (po *pod) generateObject(item v1.Pod) OtherResourceObject {
|
||||
name := item.Name
|
||||
ns := item.Namespace
|
||||
|
||||
Object := OtherResourceObject{Namespace: ns, Name: name}
|
||||
|
||||
return Object
|
||||
|
||||
}
|
||||
|
||||
func (po *pod) updateWithObject(status *ResourceStatus, item v1.Pod) {
|
||||
namespace := item.Namespace
|
||||
|
||||
object := po.generateObject(item)
|
||||
status.ResourceList.update(namespace, object)
|
||||
}
|
||||
|
||||
func (po *pod) updateWithObjects(status *ResourceStatus, objects interface{}) {
|
||||
if status.ResourceList == nil {
|
||||
status.ResourceList = make(Resources)
|
||||
}
|
||||
|
||||
items := objects.([]v1.Pod)
|
||||
|
||||
for _, item := range items {
|
||||
po.updateWithObject(status, item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (po *pod) updateWithEvent(status *ResourceStatus, event watch.Event) {
|
||||
object := event.Object.(*v1.Pod)
|
||||
namespace := object.Namespace
|
||||
tmpObject := po.generateObject(*object)
|
||||
|
||||
if event.Type == watch.Deleted {
|
||||
status.ResourceList.del(namespace, tmpObject)
|
||||
return
|
||||
}
|
||||
|
||||
po.updateWithObject(status, *object)
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 resources
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type persistmentVolume struct {
|
||||
k8sClient *kubernetes.Clientset
|
||||
}
|
||||
|
||||
func (pvc *persistmentVolume) list() (interface{}, error) {
|
||||
list, err := pvc.k8sClient.CoreV1().PersistentVolumeClaims("").List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list.Items, nil
|
||||
}
|
||||
|
||||
func (pvc *persistmentVolume) getWatcher() (watch.Interface, error) {
|
||||
watcher, err := pvc.k8sClient.CoreV1().PersistentVolumeClaims("").Watch(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return watcher, nil
|
||||
}
|
||||
|
||||
func (pvc *persistmentVolume) generateObject(item v1.PersistentVolumeClaim) OtherResourceObject {
|
||||
name := item.Name
|
||||
ns := item.Namespace
|
||||
|
||||
object := OtherResourceObject{Namespace: ns, Name: name}
|
||||
|
||||
return object
|
||||
|
||||
}
|
||||
|
||||
func (pvc *persistmentVolume) updateWithObject(status *ResourceStatus, item v1.PersistentVolumeClaim) {
|
||||
namespace := item.Namespace
|
||||
|
||||
object := pvc.generateObject(item)
|
||||
status.ResourceList.update(namespace, object)
|
||||
}
|
||||
|
||||
func (pvc *persistmentVolume) updateWithObjects(status *ResourceStatus, objects interface{}) {
|
||||
if status.ResourceList == nil {
|
||||
status.ResourceList = make(Resources)
|
||||
}
|
||||
|
||||
items := objects.([]v1.PersistentVolumeClaim)
|
||||
|
||||
for _, item := range items {
|
||||
pvc.updateWithObject(status, item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (pvc *persistmentVolume) updateWithEvent(status *ResourceStatus, event watch.Event) {
|
||||
object := event.Object.(*v1.PersistentVolumeClaim)
|
||||
namespace := object.Namespace
|
||||
tmpObject := pvc.generateObject(*object)
|
||||
|
||||
if event.Type == watch.Deleted {
|
||||
status.ResourceList.del(namespace, tmpObject)
|
||||
return
|
||||
}
|
||||
|
||||
pvc.updateWithObject(status, *object)
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 resources
|
||||
|
||||
import (
|
||||
"k8s.io/api/rbac/v1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type role struct {
|
||||
k8sClient *kubernetes.Clientset
|
||||
}
|
||||
|
||||
func (r *role) list() (interface{}, error) {
|
||||
list, err := r.k8sClient.RbacV1().Roles("").List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list.Items, nil
|
||||
}
|
||||
|
||||
func (r *role) getWatcher() (watch.Interface, error) {
|
||||
watcher, err := r.k8sClient.RbacV1().Roles("").Watch(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return watcher, nil
|
||||
}
|
||||
|
||||
func (r *role) generateObject(item v1.Role) OtherResourceObject {
|
||||
name := item.Name
|
||||
ns := item.Namespace
|
||||
|
||||
object := OtherResourceObject{Namespace: ns, Name: name}
|
||||
|
||||
return object
|
||||
|
||||
}
|
||||
|
||||
func (r *role) updateWithObject(status *ResourceStatus, item v1.Role) {
|
||||
namespace := item.Namespace
|
||||
|
||||
object := r.generateObject(item)
|
||||
status.ResourceList.update(namespace, object)
|
||||
}
|
||||
|
||||
func (r *role) updateWithObjects(status *ResourceStatus, objects interface{}) {
|
||||
if status.ResourceList == nil {
|
||||
status.ResourceList = make(Resources)
|
||||
}
|
||||
|
||||
items := objects.([]v1.Role)
|
||||
|
||||
for _, item := range items {
|
||||
r.updateWithObject(status, item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (r *role) updateWithEvent(status *ResourceStatus, event watch.Event) {
|
||||
object := event.Object.(*v1.Role)
|
||||
namespace := object.Namespace
|
||||
tmpObject := r.generateObject(*object)
|
||||
|
||||
if event.Type == watch.Deleted {
|
||||
status.ResourceList.del(namespace, tmpObject)
|
||||
return
|
||||
}
|
||||
|
||||
r.updateWithObject(status, *object)
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 resources
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
)
|
||||
|
||||
var etcdClient *client.EtcdClient
|
||||
|
||||
var stopChan = make(chan struct{})
|
||||
|
||||
const (
|
||||
pods = "pods"
|
||||
deployments = "deployments"
|
||||
daemonsets = "daemonsets"
|
||||
statefulsets = "statefulsets"
|
||||
namespaces = "namespaces"
|
||||
ingresses = "ingresses"
|
||||
persistentVolumeClaim = "persistent-volume-claim"
|
||||
roles = "roles"
|
||||
services = "services"
|
||||
)
|
||||
|
||||
func registerResource(resourceChans map[string]ResourceChan, resourceType string) {
|
||||
resourceChan := ResourceChan{Type: resourceType, StatusChan: make(chan *ResourceStatus), StopChan: stopChan}
|
||||
resourceChans[resourceType] = resourceChan
|
||||
}
|
||||
|
||||
func updateStatus(resource Resource, resourceChan ResourceChan) {
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
glog.Error(err)
|
||||
close(resourceChan.StatusChan)
|
||||
}
|
||||
}()
|
||||
|
||||
var clusterStatus ResourceStatus
|
||||
clusterStatus.UpdateTimeStamp = time.Now().Unix()
|
||||
clusterStatus.ResourceType = resourceChan.Type
|
||||
|
||||
items, err := resource.list()
|
||||
if err != nil {
|
||||
glog.Errorln(err)
|
||||
return
|
||||
}
|
||||
resource.updateWithObjects(&clusterStatus, items)
|
||||
|
||||
watcher, err := resource.getWatcher()
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-resourceChan.StopChan:
|
||||
return
|
||||
case event := <-watcher.ResultChan():
|
||||
resource.updateWithEvent(&clusterStatus, event)
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if time.Now().Unix()-clusterStatus.UpdateTimeStamp > constants.UpdateCircle {
|
||||
clusterStatus.UpdateTimeStamp = time.Now().Unix()
|
||||
resourceChan.StatusChan <- &clusterStatus
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func updateResourceStatus(resourceChan ResourceChan) {
|
||||
glog.Infof("updateResourceStatus:%s", resourceChan.Type)
|
||||
client := client.NewK8sClient()
|
||||
switch resourceChan.Type {
|
||||
case deployments:
|
||||
deploy := deployment{k8sClient: client}
|
||||
go updateStatus(&deploy, resourceChan)
|
||||
case daemonsets:
|
||||
ds := daemonset{k8sClient: client}
|
||||
go updateStatus(&ds, resourceChan)
|
||||
case statefulsets:
|
||||
ss := statefulset{k8sClient: client}
|
||||
go updateStatus(&ss, resourceChan)
|
||||
case namespaces:
|
||||
ns := namespace{k8sClient: client}
|
||||
go updateStatus(&ns, resourceChan)
|
||||
case ingresses:
|
||||
ing := ingress{k8sClient: client}
|
||||
go updateStatus(&ing, resourceChan)
|
||||
case persistentVolumeClaim:
|
||||
pvc := persistmentVolume{k8sClient: client}
|
||||
go updateStatus(&pvc, resourceChan)
|
||||
case roles:
|
||||
r := role{k8sClient: client}
|
||||
go updateStatus(&r, resourceChan)
|
||||
case services:
|
||||
svc := service{k8sClient: client}
|
||||
go updateStatus(&svc, resourceChan)
|
||||
case pods:
|
||||
po := pod{k8sClient: client}
|
||||
go updateStatus(&po, resourceChan)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func updateAllResourceStatus(resourceChans map[string]ResourceChan) {
|
||||
for _, resourceChan := range resourceChans {
|
||||
updateResourceStatus(resourceChan)
|
||||
}
|
||||
}
|
||||
|
||||
func receiveResourceStatus(resourceChans map[string]ResourceChan) {
|
||||
defer func() {
|
||||
close(stopChan)
|
||||
}()
|
||||
|
||||
for {
|
||||
for _, resourceChan := range resourceChans {
|
||||
select {
|
||||
case res, ok := <-resourceChan.StatusChan:
|
||||
if !ok {
|
||||
glog.Errorf("job:calculate %s' status have stopped", resourceChan.Type)
|
||||
registerResource(resourceChans, resourceChan.Type)
|
||||
updateResourceStatus(resourceChans[resourceChan.Type])
|
||||
} else {
|
||||
value, _ := json.Marshal(res)
|
||||
key := constants.Root + "/" + res.ResourceType
|
||||
etcdClient.Put(key, string(value))
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Run() {
|
||||
glog.Info("Begin to submit resource status")
|
||||
var err error
|
||||
etcdClient, err = client.NewEtcdClient()
|
||||
defer etcdClient.Close()
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
resourceChans := make(map[string]ResourceChan)
|
||||
resourceList := []string{statefulsets, deployments, daemonsets, namespaces, ingresses, services, roles, persistentVolumeClaim, pods}
|
||||
for _, resource := range resourceList {
|
||||
registerResource(resourceChans, resource)
|
||||
}
|
||||
updateAllResourceStatus(resourceChans)
|
||||
receiveResourceStatus(resourceChans)
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 resources
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
k8sClient *kubernetes.Clientset
|
||||
}
|
||||
|
||||
func (svc *service) list() (interface{}, error) {
|
||||
list, err := svc.k8sClient.CoreV1().Services("").List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list.Items, nil
|
||||
}
|
||||
|
||||
func (svc *service) getWatcher() (watch.Interface, error) {
|
||||
watcher, err := svc.k8sClient.CoreV1().Services("").Watch(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return watcher, nil
|
||||
}
|
||||
|
||||
func (svc *service) generateObject(item v1.Service) OtherResourceObject {
|
||||
name := item.Name
|
||||
ns := item.Namespace
|
||||
|
||||
object := OtherResourceObject{Namespace: ns, Name: name}
|
||||
|
||||
return object
|
||||
|
||||
}
|
||||
|
||||
func (svc *service) updateWithObject(status *ResourceStatus, item v1.Service) {
|
||||
namespace := item.Namespace
|
||||
|
||||
object := svc.generateObject(item)
|
||||
status.ResourceList.update(namespace, object)
|
||||
}
|
||||
|
||||
func (svc *service) updateWithObjects(status *ResourceStatus, objects interface{}) {
|
||||
if status.ResourceList == nil {
|
||||
status.ResourceList = make(Resources)
|
||||
}
|
||||
|
||||
items := objects.([]v1.Service)
|
||||
|
||||
for _, item := range items {
|
||||
svc.updateWithObject(status, item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (svc *service) updateWithEvent(status *ResourceStatus, event watch.Event) {
|
||||
object := event.Object.(*v1.Service)
|
||||
namespace := object.Namespace
|
||||
tmpObject := svc.generateObject(*object)
|
||||
|
||||
if event.Type == watch.Deleted {
|
||||
status.ResourceList.del(namespace, tmpObject)
|
||||
return
|
||||
}
|
||||
|
||||
svc.updateWithObject(status, *object)
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 resources
|
||||
|
||||
import (
|
||||
"k8s.io/api/apps/v1beta2"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type statefulset struct {
|
||||
k8sClient *kubernetes.Clientset
|
||||
}
|
||||
|
||||
func (ss *statefulset) list() (interface{}, error) {
|
||||
daemonsetList, err := ss.k8sClient.AppsV1beta2().StatefulSets("").List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return daemonsetList.Items, nil
|
||||
}
|
||||
|
||||
func (ss *statefulset) getWatcher() (watch.Interface, error) {
|
||||
watcher, err := ss.k8sClient.AppsV1beta2().StatefulSets("").Watch(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return watcher, nil
|
||||
}
|
||||
|
||||
func (ss *statefulset) generateObject(item v1beta2.StatefulSet) WorkLoadObject {
|
||||
var app string
|
||||
var ready bool
|
||||
name := item.Name
|
||||
namespace := item.Namespace
|
||||
availablePodNum := item.Status.ReadyReplicas
|
||||
desirePodNum := *item.Spec.Replicas
|
||||
createTime := item.CreationTimestamp
|
||||
release := item.ObjectMeta.Labels["release"]
|
||||
chart := item.ObjectMeta.Labels["chart"]
|
||||
|
||||
if len(release) > 0 && len(chart) > 0 {
|
||||
app = release + "/" + chart
|
||||
} else {
|
||||
app = "-"
|
||||
}
|
||||
if availablePodNum >= desirePodNum {
|
||||
ready = true
|
||||
} else {
|
||||
ready = false
|
||||
}
|
||||
|
||||
statefulSetObject := WorkLoadObject{Namespace: namespace, Name: name, Available: availablePodNum, Desire: desirePodNum,
|
||||
App: app, CreateTime: createTime, Ready: ready}
|
||||
|
||||
return statefulSetObject
|
||||
|
||||
}
|
||||
|
||||
func (ss *statefulset) updateWithObject(status *ResourceStatus, item v1beta2.StatefulSet) {
|
||||
namespace := item.Namespace
|
||||
ssObject := ss.generateObject(item)
|
||||
|
||||
status.ResourceList.update(namespace, ssObject)
|
||||
|
||||
}
|
||||
|
||||
func (ss *statefulset) updateWithObjects(status *ResourceStatus, objects interface{}) {
|
||||
if status.ResourceList == nil {
|
||||
status.ResourceList = make(Resources)
|
||||
}
|
||||
|
||||
items := objects.([]v1beta2.StatefulSet)
|
||||
|
||||
for _, item := range items {
|
||||
ss.updateWithObject(status, item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (ss *statefulset) updateWithEvent(status *ResourceStatus, event watch.Event) {
|
||||
object := event.Object.(*v1beta2.StatefulSet)
|
||||
namespace := object.Namespace
|
||||
ssObject := ss.generateObject(*object)
|
||||
|
||||
if event.Type == watch.Deleted {
|
||||
status.ResourceList.del(namespace, ssObject)
|
||||
return
|
||||
}
|
||||
|
||||
ss.updateWithObject(status, *object)
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 resources
|
||||
|
||||
import (
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
type resource interface {
|
||||
equal(object resource) bool
|
||||
}
|
||||
|
||||
type resourcetList interface {
|
||||
update(key string, value resource)
|
||||
del(key string, value resource)
|
||||
}
|
||||
|
||||
type ResourceStatus struct {
|
||||
ResourceType string `json:"type"`
|
||||
ResourceList resourcetList `json:"lists"`
|
||||
UpdateTimeStamp int64 `json:"updateTimestamp"`
|
||||
}
|
||||
|
||||
type ResourceChan struct {
|
||||
Type string
|
||||
StatusChan chan *ResourceStatus
|
||||
StopChan chan struct{}
|
||||
}
|
||||
|
||||
type Resource interface {
|
||||
list() (interface{}, error)
|
||||
getWatcher() (watch.Interface, error)
|
||||
updateWithObjects(workload *ResourceStatus, objects interface{})
|
||||
updateWithEvent(workload *ResourceStatus, event watch.Event)
|
||||
}
|
||||
|
||||
type WorkLoadObject struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
App string `json:"app"`
|
||||
Available int32 `json:"available"`
|
||||
Desire int32 `json:"desire"`
|
||||
Ready bool `json:"ready"`
|
||||
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
|
||||
UpdateTime meta_v1.Time `json:"updateTime,omitempty"`
|
||||
CreateTime meta_v1.Time `json:"createTime,omitempty"`
|
||||
}
|
||||
|
||||
type OtherResourceObject struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
type Resources map[string][]resource
|
||||
|
||||
func (resources Resources) update(namespace string, object resource) {
|
||||
for index, tmpObject := range resources[namespace] {
|
||||
if tmpObject.equal(object) {
|
||||
resources[namespace][index] = object
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resources[namespace] = append(resources[namespace], object)
|
||||
}
|
||||
|
||||
func (resources Resources) del(namespace string, object resource) {
|
||||
for index, tmpObject := range resources[namespace] {
|
||||
if tmpObject.equal(object) {
|
||||
resources[namespace] = append(resources[namespace][:index], resources[namespace][index+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (workLoadObject WorkLoadObject) equal(object resource) bool {
|
||||
tmp := object.(WorkLoadObject)
|
||||
if workLoadObject.Name == tmp.Name && workLoadObject.Namespace == tmp.Namespace {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (otherResourceObject OtherResourceObject) equal(object resource) bool {
|
||||
tmp := object.(OtherResourceObject)
|
||||
if otherResourceObject.Name == tmp.Name && otherResourceObject.Namespace == tmp.Namespace {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -17,50 +17,115 @@ limitations under the License.
|
||||
package models
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/models/controllers"
|
||||
)
|
||||
|
||||
const (
|
||||
podsKey = "count/pods"
|
||||
daemonsetsKey = "count/daemonsets.apps"
|
||||
deploymentsKey = "count/deployments.apps"
|
||||
ingressKey = "count/ingresses.extensions"
|
||||
rolesKey = "count/roles.rbac.authorization.k8s.io"
|
||||
clusterRolesKey = "count/cluster-role"
|
||||
servicesKey = "count/services"
|
||||
statefulsetsKey = "count/statefulsets.apps"
|
||||
persistentvolumeclaimsKey = "persistentvolumeclaims"
|
||||
storageClassesKey = "count/storageClass"
|
||||
namespaceKey = "count/namespace"
|
||||
)
|
||||
|
||||
var resourceMap = map[string]string{daemonsetsKey: controllers.Daemonsets, deploymentsKey: controllers.Deployments,
|
||||
ingressKey: controllers.Ingresses, rolesKey: controllers.Roles, servicesKey: controllers.Services,
|
||||
statefulsetsKey: controllers.Statefulsets, persistentvolumeclaimsKey: controllers.PersistentVolumeClaim, podsKey: controllers.Pods,
|
||||
namespaceKey: controllers.Namespaces, storageClassesKey: controllers.StorageClasses, clusterRolesKey: controllers.ClusterRoles}
|
||||
|
||||
type resourceQuota struct {
|
||||
NameSpace string `json:"namespace"`
|
||||
Data v1.ResourceQuotaStatus `json:"data"`
|
||||
UpdateTimeStamp int64 `json:"updateTimeStamp"`
|
||||
NameSpace string `json:"namespace"`
|
||||
Data v1.ResourceQuotaStatus `json:"data"`
|
||||
}
|
||||
|
||||
func GetNamespaceQuota(namespace string) (*resourceQuota, error) {
|
||||
|
||||
cli, err := client.NewEtcdClient()
|
||||
func getUsage(namespace, resource string) int {
|
||||
ctl, err := getController(resource)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return 0
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
key := constants.Root + "/" + constants.QuotaKey + "/" + namespace
|
||||
value, err := cli.Get(key)
|
||||
var data = v1.ResourceQuotaStatus{make(v1.ResourceList), make(v1.ResourceList)}
|
||||
var res = resourceQuota{Data: data}
|
||||
|
||||
err = json.Unmarshal(value, &res)
|
||||
if time.Now().Unix()-res.UpdateTimeStamp > 5*constants.UpdateCircle {
|
||||
err = errors.New("internal server error")
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
return ctl.Count(namespace)
|
||||
}
|
||||
|
||||
func GetClusterQuota() (*resourceQuota, error) {
|
||||
|
||||
return GetNamespaceQuota("\"\"")
|
||||
quota := v1.ResourceQuotaStatus{Hard: make(v1.ResourceList), Used: make(v1.ResourceList)}
|
||||
for k, v := range resourceMap {
|
||||
used := getUsage("", v)
|
||||
var quantity resource.Quantity
|
||||
quantity.Set(int64(used))
|
||||
quota.Used[v1.ResourceName(k)] = quantity
|
||||
}
|
||||
|
||||
return &resourceQuota{NameSpace: "\"\"", Data: quota}, nil
|
||||
|
||||
}
|
||||
|
||||
func GetNamespaceQuota(namespace string) (*resourceQuota, error) {
|
||||
quota, err := getNamespaceResourceQuota(namespace)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
if quota == nil {
|
||||
quota = &v1.ResourceQuotaStatus{Hard: make(v1.ResourceList), Used: make(v1.ResourceList)}
|
||||
}
|
||||
|
||||
for k, v := range resourceMap {
|
||||
if _, exist := quota.Used[v1.ResourceName(k)]; !exist {
|
||||
if k == namespaceKey || k == storageClassesKey {
|
||||
continue
|
||||
}
|
||||
|
||||
used := getUsage(namespace, v)
|
||||
var quantity resource.Quantity
|
||||
quantity.Set(int64(used))
|
||||
quota.Used[v1.ResourceName(k)] = quantity
|
||||
}
|
||||
}
|
||||
|
||||
return &resourceQuota{NameSpace: namespace, Data: *quota}, nil
|
||||
}
|
||||
|
||||
func updateNamespaceQuota(tmpResourceList, resourceList v1.ResourceList) {
|
||||
if tmpResourceList == nil {
|
||||
tmpResourceList = resourceList
|
||||
}
|
||||
for resource, usage := range resourceList {
|
||||
tmpUsage, exist := tmpResourceList[resource]
|
||||
if !exist {
|
||||
tmpResourceList[resource] = usage
|
||||
}
|
||||
if tmpUsage.Cmp(usage) == 1 {
|
||||
tmpResourceList[resource] = usage
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getNamespaceResourceQuota(namespace string) (*v1.ResourceQuotaStatus, error) {
|
||||
quotaList, err := client.NewK8sClient().CoreV1().ResourceQuotas(namespace).List(metaV1.ListOptions{})
|
||||
if err != nil || len(quotaList.Items) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
quotaStatus := v1.ResourceQuotaStatus{Hard: make(v1.ResourceList), Used: make(v1.ResourceList)}
|
||||
|
||||
for _, quota := range quotaList.Items {
|
||||
updateNamespaceQuota(quotaStatus.Hard, quota.Status.Hard)
|
||||
updateNamespaceQuota(quotaStatus.Used, quota.Status.Used)
|
||||
}
|
||||
|
||||
return "aStatus, nil
|
||||
}
|
||||
|
||||
163
pkg/models/resources.go
Normal file
163
pkg/models/resources.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
"kubesphere.io/kubesphere/pkg/models/controllers"
|
||||
)
|
||||
|
||||
type ResourceList struct {
|
||||
Total int `json:"total,omitempty"`
|
||||
Page int `json:"page,omitempty"`
|
||||
Limit int `json:"limit,omitempty"`
|
||||
Items interface{} `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
func getController(resource string) (controllers.Controller, error) {
|
||||
var ctl controllers.Controller
|
||||
attr := controllers.CommonAttribute{DB: client.NewDBClient()}
|
||||
switch resource {
|
||||
case controllers.Deployments:
|
||||
ctl = &controllers.DeploymentCtl{attr}
|
||||
case controllers.Statefulsets:
|
||||
ctl = &controllers.StatefulsetCtl{attr}
|
||||
case controllers.Daemonsets:
|
||||
ctl = &controllers.DaemonsetCtl{attr}
|
||||
case controllers.Ingresses:
|
||||
ctl = &controllers.IngressCtl{attr}
|
||||
case controllers.PersistentVolumeClaim:
|
||||
ctl = &controllers.PvcCtl{attr}
|
||||
case controllers.Roles:
|
||||
ctl = &controllers.RoleCtl{attr}
|
||||
case controllers.ClusterRoles:
|
||||
ctl = &controllers.ClusterRoleCtl{attr}
|
||||
case controllers.Services:
|
||||
ctl = &controllers.ServiceCtl{attr}
|
||||
case controllers.Pods:
|
||||
ctl = &controllers.PodCtl{attr}
|
||||
case controllers.Namespaces:
|
||||
ctl = &controllers.NamespaceCtl{attr}
|
||||
case controllers.StorageClasses:
|
||||
ctl = &controllers.StorageClassCtl{attr}
|
||||
default:
|
||||
return nil, errors.New("invalid resource type")
|
||||
}
|
||||
return ctl, nil
|
||||
|
||||
}
|
||||
|
||||
func getConditions(str string) (map[string]string, error) {
|
||||
dict := make(map[string]string)
|
||||
if len(str) == 0 {
|
||||
return dict, nil
|
||||
}
|
||||
list := strings.Split(str, ",")
|
||||
for _, item := range list {
|
||||
kvs := strings.Split(item, "=")
|
||||
if len(kvs) < 2 {
|
||||
return nil, errors.New("invalid condition input")
|
||||
}
|
||||
dict[kvs[0]] = kvs[1]
|
||||
}
|
||||
return dict, nil
|
||||
}
|
||||
|
||||
func getPaging(str string) (map[string]int, error) {
|
||||
paging := make(map[string]int)
|
||||
if len(str) == 0 {
|
||||
return paging, nil
|
||||
}
|
||||
list := strings.Split(str, ",")
|
||||
for _, item := range list {
|
||||
kvs := strings.Split(item, "=")
|
||||
if len(kvs) < 2 {
|
||||
return nil, errors.New("invalid Paging input")
|
||||
}
|
||||
|
||||
value, err := strconv.Atoi(kvs[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
paging[kvs[0]] = value
|
||||
}
|
||||
return paging, nil
|
||||
}
|
||||
|
||||
func ListResource(resourceName, conditonSrt, pagingStr string) (*ResourceList, error) {
|
||||
conditions, err := getConditions(conditonSrt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pagingMap, err := getPaging(pagingStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conditionStr, paging := generateConditionAndPaging(conditions, pagingMap)
|
||||
|
||||
ctl, err := getController(resourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
total, items, err := ctl.ListWithConditions(conditionStr, paging)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ResourceList{Total: total, Items: items, Page: pagingMap["page"], Limit: pagingMap["limit"]}, nil
|
||||
}
|
||||
|
||||
func generateConditionAndPaging(conditions map[string]string, paging map[string]int) (string, *controllers.Paging) {
|
||||
conditionStr := ""
|
||||
|
||||
for k, v := range conditions {
|
||||
if len(conditionStr) == 0 {
|
||||
conditionStr = fmt.Sprintf("%s = \"%s\" ", k, v)
|
||||
} else {
|
||||
conditionStr = fmt.Sprintf("%s AND %s = \"%s\" ", conditionStr, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
if paging["limit"] > 0 && paging["page"] >= 0 {
|
||||
offset := (paging["page"] - 1) * paging["limit"]
|
||||
return conditionStr, &controllers.Paging{Limit: paging["limit"], Offset: offset}
|
||||
}
|
||||
|
||||
return conditionStr, nil
|
||||
}
|
||||
|
||||
type workLoadStatus struct {
|
||||
NameSpace string `json:"namespace"`
|
||||
Count map[string]int `json:"data"`
|
||||
Items map[string]interface{}
|
||||
}
|
||||
|
||||
func GetNamespacesResourceStatus(namespace string) (*workLoadStatus, error) {
|
||||
|
||||
res := workLoadStatus{Count: make(map[string]int), NameSpace: namespace, Items: make(map[string]interface{})}
|
||||
for _, resource := range []string{controllers.Deployments, controllers.Statefulsets, controllers.Daemonsets} {
|
||||
status, err := ListResource(resource, "status=updating", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
count := status.Total
|
||||
items := status.Items
|
||||
res.Count[resource] = count
|
||||
res.Items[resource] = items
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func GetClusterResourceStatus() (*workLoadStatus, error) {
|
||||
|
||||
return GetNamespacesResourceStatus("")
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"k8s.io/api/rbac/v1"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/rbac/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
)
|
||||
@@ -143,7 +141,7 @@ func GetRoles(username string) ([]v1.Role, error) {
|
||||
role.Namespace = roleBinding.Namespace
|
||||
roles = append(roles, role)
|
||||
break
|
||||
} else if strings.HasSuffix(err.Error(), "not found") {
|
||||
} else if apierrors.IsNotFound(err) {
|
||||
glog.Infoln(err.Error())
|
||||
break
|
||||
} else {
|
||||
@@ -156,7 +154,7 @@ func GetRoles(username string) ([]v1.Role, error) {
|
||||
if err == nil {
|
||||
roles = append(roles, *rule)
|
||||
break
|
||||
} else if strings.HasSuffix(err.Error(), "not found") {
|
||||
} else if apierrors.IsNotFound(err) {
|
||||
glog.Infoln(err.Error())
|
||||
break
|
||||
} else {
|
||||
@@ -193,7 +191,7 @@ func GetClusterRoles(username string) ([]v1.ClusterRole, error) {
|
||||
if err == nil {
|
||||
roles = append(roles, *rule)
|
||||
break
|
||||
} else if strings.HasSuffix(err.Error(), "not found") {
|
||||
} else if apierrors.IsNotFound(err) {
|
||||
glog.Infoln(err.Error())
|
||||
break
|
||||
} else {
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 The KubeSphere Authors.
|
||||
|
||||
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 models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/client"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
)
|
||||
|
||||
type workLoadStatus struct {
|
||||
NameSpace string `json:"namespace"`
|
||||
Data map[string]int `json:"data"`
|
||||
UpdateTimeStamp int64 `json:"updateTimeStamp"`
|
||||
}
|
||||
|
||||
var resourceList = []string{"deployments", "daemonsets", "statefulsets"}
|
||||
|
||||
func GetNamespacesResourceStatus(namespace string) (*workLoadStatus, error) {
|
||||
|
||||
cli, err := client.NewEtcdClient()
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
res := workLoadStatus{Data: make(map[string]int)}
|
||||
|
||||
key := constants.Root + "/" + constants.WorkloadStatusKey + "/" + namespace
|
||||
value, err := cli.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(value, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if time.Now().Unix()-res.UpdateTimeStamp > 5*constants.UpdateCircle {
|
||||
err = errors.New("data in etcd is too old")
|
||||
return nil, err
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func GetClusterResourceStatus() (*workLoadStatus, error) {
|
||||
|
||||
return GetNamespacesResourceStatus("\"\"")
|
||||
}
|
||||
@@ -51,6 +51,10 @@ type ServerRunOptions struct {
|
||||
etcdKeyFile string
|
||||
etcdCaFile string
|
||||
kubectlImage string
|
||||
mysqlUser string
|
||||
mysqlPasswd string
|
||||
mysqlAddress string
|
||||
opAddress string
|
||||
}
|
||||
|
||||
// NewServerRunOptions creates a new ServerRunOptions object with default parameters
|
||||
@@ -97,6 +101,17 @@ func (s *ServerRunOptions) addFlags(fs *pflag.FlagSet) {
|
||||
|
||||
fs.StringVar(&s.kubectlImage, "kubectl-image", "kubectl:1.0",
|
||||
"kubectl pod's image")
|
||||
fs.StringVar(&s.mysqlAddress, "mysql-addr", "127.0.0.1:3306",
|
||||
"Address of mysql, exp:127.0.0.1:3306.")
|
||||
|
||||
fs.StringVar(&s.mysqlPasswd, "mysql-password", "123456",
|
||||
"Password of mysql")
|
||||
|
||||
fs.StringVar(&s.mysqlUser, "mysql-user", "mysql",
|
||||
"User of mysql.")
|
||||
|
||||
fs.StringVar(&s.opAddress, "openpitrix-address", "http://openpitrix-api-gateway.openpitrix-system.svc",
|
||||
"Address of openPitrix")
|
||||
}
|
||||
|
||||
func (s *ServerRunOptions) GetApiServerHost() string {
|
||||
@@ -181,6 +196,22 @@ func (s *ServerRunOptions) GetKubectlImage() string {
|
||||
return s.kubectlImage
|
||||
}
|
||||
|
||||
func (s *ServerRunOptions) GetMysqlAddr() string {
|
||||
return s.mysqlAddress
|
||||
}
|
||||
|
||||
func (s *ServerRunOptions) GetMysqlUser() string {
|
||||
return s.mysqlUser
|
||||
}
|
||||
|
||||
func (s *ServerRunOptions) GetMysqlPassword() string {
|
||||
return s.mysqlPasswd
|
||||
}
|
||||
|
||||
func (s *ServerRunOptions) GetOpAddress() string {
|
||||
return s.opAddress
|
||||
}
|
||||
|
||||
var ServerOptions = NewServerRunOptions()
|
||||
|
||||
func AddFlags(fs *pflag.FlagSet) {
|
||||
|
||||
9
vendor/github.com/go-sql-driver/mysql/.gitignore
generated
vendored
Normal file
9
vendor/github.com/go-sql-driver/mysql/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
Icon?
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
.idea
|
||||
107
vendor/github.com/go-sql-driver/mysql/.travis.yml
generated
vendored
Normal file
107
vendor/github.com/go-sql-driver/mysql/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- master
|
||||
|
||||
before_install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
|
||||
before_script:
|
||||
- echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB" | sudo tee -a /etc/mysql/my.cnf
|
||||
- sudo service mysql restart
|
||||
- .travis/wait_mysql.sh
|
||||
- mysql -e 'create database gotest;'
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- env: DB=MYSQL8
|
||||
sudo: required
|
||||
dist: trusty
|
||||
go: 1.10.x
|
||||
services:
|
||||
- docker
|
||||
before_install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
- docker pull mysql:8.0
|
||||
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
||||
mysql:8.0 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
|
||||
- cp .travis/docker.cnf ~/.my.cnf
|
||||
- .travis/wait_mysql.sh
|
||||
before_script:
|
||||
- export MYSQL_TEST_USER=gotest
|
||||
- export MYSQL_TEST_PASS=secret
|
||||
- export MYSQL_TEST_ADDR=127.0.0.1:3307
|
||||
- export MYSQL_TEST_CONCURRENT=1
|
||||
|
||||
- env: DB=MYSQL57
|
||||
sudo: required
|
||||
dist: trusty
|
||||
go: 1.10.x
|
||||
services:
|
||||
- docker
|
||||
before_install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
- docker pull mysql:5.7
|
||||
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
||||
mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
|
||||
- cp .travis/docker.cnf ~/.my.cnf
|
||||
- .travis/wait_mysql.sh
|
||||
before_script:
|
||||
- export MYSQL_TEST_USER=gotest
|
||||
- export MYSQL_TEST_PASS=secret
|
||||
- export MYSQL_TEST_ADDR=127.0.0.1:3307
|
||||
- export MYSQL_TEST_CONCURRENT=1
|
||||
|
||||
- env: DB=MARIA55
|
||||
sudo: required
|
||||
dist: trusty
|
||||
go: 1.10.x
|
||||
services:
|
||||
- docker
|
||||
before_install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
- docker pull mariadb:5.5
|
||||
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
||||
mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
|
||||
- cp .travis/docker.cnf ~/.my.cnf
|
||||
- .travis/wait_mysql.sh
|
||||
before_script:
|
||||
- export MYSQL_TEST_USER=gotest
|
||||
- export MYSQL_TEST_PASS=secret
|
||||
- export MYSQL_TEST_ADDR=127.0.0.1:3307
|
||||
- export MYSQL_TEST_CONCURRENT=1
|
||||
|
||||
- env: DB=MARIA10_1
|
||||
sudo: required
|
||||
dist: trusty
|
||||
go: 1.10.x
|
||||
services:
|
||||
- docker
|
||||
before_install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
- docker pull mariadb:10.1
|
||||
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
||||
mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
|
||||
- cp .travis/docker.cnf ~/.my.cnf
|
||||
- .travis/wait_mysql.sh
|
||||
before_script:
|
||||
- export MYSQL_TEST_USER=gotest
|
||||
- export MYSQL_TEST_PASS=secret
|
||||
- export MYSQL_TEST_ADDR=127.0.0.1:3307
|
||||
- export MYSQL_TEST_CONCURRENT=1
|
||||
|
||||
script:
|
||||
- go test -v -covermode=count -coverprofile=coverage.out
|
||||
- go vet ./...
|
||||
- .travis/gofmt.sh
|
||||
after_script:
|
||||
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci
|
||||
89
vendor/github.com/go-sql-driver/mysql/AUTHORS
generated
vendored
Normal file
89
vendor/github.com/go-sql-driver/mysql/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
# This is the official list of Go-MySQL-Driver authors for copyright purposes.
|
||||
|
||||
# If you are submitting a patch, please add your name or the name of the
|
||||
# organization which holds the copyright to this list in alphabetical order.
|
||||
|
||||
# Names should be added to this file as
|
||||
# Name <email address>
|
||||
# The email address is not required for organizations.
|
||||
# Please keep the list sorted.
|
||||
|
||||
|
||||
# Individual Persons
|
||||
|
||||
Aaron Hopkins <go-sql-driver at die.net>
|
||||
Achille Roussel <achille.roussel at gmail.com>
|
||||
Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
|
||||
Andrew Reid <andrew.reid at tixtrack.com>
|
||||
Arne Hormann <arnehormann at gmail.com>
|
||||
Asta Xie <xiemengjun at gmail.com>
|
||||
Bulat Gaifullin <gaifullinbf at gmail.com>
|
||||
Carlos Nieto <jose.carlos at menteslibres.net>
|
||||
Chris Moos <chris at tech9computers.com>
|
||||
Craig Wilson <craiggwilson at gmail.com>
|
||||
Daniel Montoya <dsmontoyam at gmail.com>
|
||||
Daniel Nichter <nil at codenode.com>
|
||||
Daniël van Eeden <git at myname.nl>
|
||||
Dave Protasowski <dprotaso at gmail.com>
|
||||
DisposaBoy <disposaboy at dby.me>
|
||||
Egor Smolyakov <egorsmkv at gmail.com>
|
||||
Evan Shaw <evan at vendhq.com>
|
||||
Frederick Mayle <frederickmayle at gmail.com>
|
||||
Gustavo Kristic <gkristic at gmail.com>
|
||||
Hajime Nakagami <nakagami at gmail.com>
|
||||
Hanno Braun <mail at hannobraun.com>
|
||||
Henri Yandell <flamefew at gmail.com>
|
||||
Hirotaka Yamamoto <ymmt2005 at gmail.com>
|
||||
ICHINOSE Shogo <shogo82148 at gmail.com>
|
||||
INADA Naoki <songofacandy at gmail.com>
|
||||
Jacek Szwec <szwec.jacek at gmail.com>
|
||||
James Harr <james.harr at gmail.com>
|
||||
Jeff Hodges <jeff at somethingsimilar.com>
|
||||
Jeffrey Charles <jeffreycharles at gmail.com>
|
||||
Jian Zhen <zhenjl at gmail.com>
|
||||
Joshua Prunier <joshua.prunier at gmail.com>
|
||||
Julien Lefevre <julien.lefevr at gmail.com>
|
||||
Julien Schmidt <go-sql-driver at julienschmidt.com>
|
||||
Justin Li <jli at j-li.net>
|
||||
Justin Nuß <nuss.justin at gmail.com>
|
||||
Kamil Dziedzic <kamil at klecza.pl>
|
||||
Kevin Malachowski <kevin at chowski.com>
|
||||
Kieron Woodhouse <kieron.woodhouse at infosum.com>
|
||||
Lennart Rudolph <lrudolph at hmc.edu>
|
||||
Leonardo YongUk Kim <dalinaum at gmail.com>
|
||||
Linh Tran Tuan <linhduonggnu at gmail.com>
|
||||
Lion Yang <lion at aosc.xyz>
|
||||
Luca Looz <luca.looz92 at gmail.com>
|
||||
Lucas Liu <extrafliu at gmail.com>
|
||||
Luke Scott <luke at webconnex.com>
|
||||
Maciej Zimnoch <maciej.zimnoch at codilime.com>
|
||||
Michael Woolnough <michael.woolnough at gmail.com>
|
||||
Nicola Peduzzi <thenikso at gmail.com>
|
||||
Olivier Mengué <dolmen at cpan.org>
|
||||
oscarzhao <oscarzhaosl at gmail.com>
|
||||
Paul Bonser <misterpib at gmail.com>
|
||||
Peter Schultz <peter.schultz at classmarkets.com>
|
||||
Rebecca Chin <rchin at pivotal.io>
|
||||
Reed Allman <rdallman10 at gmail.com>
|
||||
Richard Wilkes <wilkes at me.com>
|
||||
Robert Russell <robert at rrbrussell.com>
|
||||
Runrioter Wung <runrioter at gmail.com>
|
||||
Shuode Li <elemount at qq.com>
|
||||
Soroush Pour <me at soroushjp.com>
|
||||
Stan Putrya <root.vagner at gmail.com>
|
||||
Stanley Gunawan <gunawan.stanley at gmail.com>
|
||||
Xiangyu Hu <xiangyu.hu at outlook.com>
|
||||
Xiaobing Jiang <s7v7nislands at gmail.com>
|
||||
Xiuming Chen <cc at cxm.cc>
|
||||
Zhenye Xie <xiezhenye at gmail.com>
|
||||
|
||||
# Organizations
|
||||
|
||||
Barracuda Networks, Inc.
|
||||
Counting Ltd.
|
||||
Google Inc.
|
||||
InfoSum Ltd.
|
||||
Keybase Inc.
|
||||
Percona LLC
|
||||
Pivotal Inc.
|
||||
Stripe Inc.
|
||||
167
vendor/github.com/go-sql-driver/mysql/CHANGELOG.md
generated
vendored
Normal file
167
vendor/github.com/go-sql-driver/mysql/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
## Version 1.4 (2018-06-03)
|
||||
|
||||
Changes:
|
||||
|
||||
- Documentation fixes (#530, #535, #567)
|
||||
- Refactoring (#575, #579, #580, #581, #603, #615, #704)
|
||||
- Cache column names (#444)
|
||||
- Sort the DSN parameters in DSNs generated from a config (#637)
|
||||
- Allow native password authentication by default (#644)
|
||||
- Use the default port if it is missing in the DSN (#668)
|
||||
- Removed the `strict` mode (#676)
|
||||
- Do not query `max_allowed_packet` by default (#680)
|
||||
- Dropped support Go 1.6 and lower (#696)
|
||||
- Updated `ConvertValue()` to match the database/sql/driver implementation (#760)
|
||||
- Document the usage of `0000-00-00T00:00:00` as the time.Time zero value (#783)
|
||||
- Improved the compatibility of the authentication system (#807)
|
||||
|
||||
New Features:
|
||||
|
||||
- Multi-Results support (#537)
|
||||
- `rejectReadOnly` DSN option (#604)
|
||||
- `context.Context` support (#608, #612, #627, #761)
|
||||
- Transaction isolation level support (#619, #744)
|
||||
- Read-Only transactions support (#618, #634)
|
||||
- `NewConfig` function which initializes a config with default values (#679)
|
||||
- Implemented the `ColumnType` interfaces (#667, #724)
|
||||
- Support for custom string types in `ConvertValue` (#623)
|
||||
- Implemented `NamedValueChecker`, improving support for uint64 with high bit set (#690, #709, #710)
|
||||
- `caching_sha2_password` authentication plugin support (#794, #800, #801, #802)
|
||||
- Implemented `driver.SessionResetter` (#779)
|
||||
- `sha256_password` authentication plugin support (#808)
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Use the DSN hostname as TLS default ServerName if `tls=true` (#564, #718)
|
||||
- Fixed LOAD LOCAL DATA INFILE for empty files (#590)
|
||||
- Removed columns definition cache since it sometimes cached invalid data (#592)
|
||||
- Don't mutate registered TLS configs (#600)
|
||||
- Make RegisterTLSConfig concurrency-safe (#613)
|
||||
- Handle missing auth data in the handshake packet correctly (#646)
|
||||
- Do not retry queries when data was written to avoid data corruption (#302, #736)
|
||||
- Cache the connection pointer for error handling before invalidating it (#678)
|
||||
- Fixed imports for appengine/cloudsql (#700)
|
||||
- Fix sending STMT_LONG_DATA for 0 byte data (#734)
|
||||
- Set correct capacity for []bytes read from length-encoded strings (#766)
|
||||
- Make RegisterDial concurrency-safe (#773)
|
||||
|
||||
|
||||
## Version 1.3 (2016-12-01)
|
||||
|
||||
Changes:
|
||||
|
||||
- Go 1.1 is no longer supported
|
||||
- Use decimals fields in MySQL to format time types (#249)
|
||||
- Buffer optimizations (#269)
|
||||
- TLS ServerName defaults to the host (#283)
|
||||
- Refactoring (#400, #410, #437)
|
||||
- Adjusted documentation for second generation CloudSQL (#485)
|
||||
- Documented DSN system var quoting rules (#502)
|
||||
- Made statement.Close() calls idempotent to avoid errors in Go 1.6+ (#512)
|
||||
|
||||
New Features:
|
||||
|
||||
- Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249)
|
||||
- Support for returning table alias on Columns() (#289, #359, #382)
|
||||
- Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318, #490)
|
||||
- Support for uint64 parameters with high bit set (#332, #345)
|
||||
- Cleartext authentication plugin support (#327)
|
||||
- Exported ParseDSN function and the Config struct (#403, #419, #429)
|
||||
- Read / Write timeouts (#401)
|
||||
- Support for JSON field type (#414)
|
||||
- Support for multi-statements and multi-results (#411, #431)
|
||||
- DSN parameter to set the driver-side max_allowed_packet value manually (#489)
|
||||
- Native password authentication plugin support (#494, #524)
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Fixed handling of queries without columns and rows (#255)
|
||||
- Fixed a panic when SetKeepAlive() failed (#298)
|
||||
- Handle ERR packets while reading rows (#321)
|
||||
- Fixed reading NULL length-encoded integers in MySQL 5.6+ (#349)
|
||||
- Fixed absolute paths support in LOAD LOCAL DATA INFILE (#356)
|
||||
- Actually zero out bytes in handshake response (#378)
|
||||
- Fixed race condition in registering LOAD DATA INFILE handler (#383)
|
||||
- Fixed tests with MySQL 5.7.9+ (#380)
|
||||
- QueryUnescape TLS config names (#397)
|
||||
- Fixed "broken pipe" error by writing to closed socket (#390)
|
||||
- Fixed LOAD LOCAL DATA INFILE buffering (#424)
|
||||
- Fixed parsing of floats into float64 when placeholders are used (#434)
|
||||
- Fixed DSN tests with Go 1.7+ (#459)
|
||||
- Handle ERR packets while waiting for EOF (#473)
|
||||
- Invalidate connection on error while discarding additional results (#513)
|
||||
- Allow terminating packets of length 0 (#516)
|
||||
|
||||
|
||||
## Version 1.2 (2014-06-03)
|
||||
|
||||
Changes:
|
||||
|
||||
- We switched back to a "rolling release". `go get` installs the current master branch again
|
||||
- Version v1 of the driver will not be maintained anymore. Go 1.0 is no longer supported by this driver
|
||||
- Exported errors to allow easy checking from application code
|
||||
- Enabled TCP Keepalives on TCP connections
|
||||
- Optimized INFILE handling (better buffer size calculation, lazy init, ...)
|
||||
- The DSN parser also checks for a missing separating slash
|
||||
- Faster binary date / datetime to string formatting
|
||||
- Also exported the MySQLWarning type
|
||||
- mysqlConn.Close returns the first error encountered instead of ignoring all errors
|
||||
- writePacket() automatically writes the packet size to the header
|
||||
- readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets
|
||||
|
||||
New Features:
|
||||
|
||||
- `RegisterDial` allows the usage of a custom dial function to establish the network connection
|
||||
- Setting the connection collation is possible with the `collation` DSN parameter. This parameter should be preferred over the `charset` parameter
|
||||
- Logging of critical errors is configurable with `SetLogger`
|
||||
- Google CloudSQL support
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Allow more than 32 parameters in prepared statements
|
||||
- Various old_password fixes
|
||||
- Fixed TestConcurrent test to pass Go's race detection
|
||||
- Fixed appendLengthEncodedInteger for large numbers
|
||||
- Renamed readLengthEnodedString to readLengthEncodedString and skipLengthEnodedString to skipLengthEncodedString (fixed typo)
|
||||
|
||||
|
||||
## Version 1.1 (2013-11-02)
|
||||
|
||||
Changes:
|
||||
|
||||
- Go-MySQL-Driver now requires Go 1.1
|
||||
- Connections now use the collation `utf8_general_ci` by default. Adding `&charset=UTF8` to the DSN should not be necessary anymore
|
||||
- Made closing rows and connections error tolerant. This allows for example deferring rows.Close() without checking for errors
|
||||
- `[]byte(nil)` is now treated as a NULL value. Before, it was treated like an empty string / `[]byte("")`
|
||||
- DSN parameter values must now be url.QueryEscape'ed. This allows text values to contain special characters, such as '&'.
|
||||
- Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries
|
||||
- Optimized the buffer for reading
|
||||
- stmt.Query now caches column metadata
|
||||
- New Logo
|
||||
- Changed the copyright header to include all contributors
|
||||
- Improved the LOAD INFILE documentation
|
||||
- The driver struct is now exported to make the driver directly accessible
|
||||
- Refactored the driver tests
|
||||
- Added more benchmarks and moved all to a separate file
|
||||
- Other small refactoring
|
||||
|
||||
New Features:
|
||||
|
||||
- Added *old_passwords* support: Required in some cases, but must be enabled by adding `allowOldPasswords=true` to the DSN since it is insecure
|
||||
- Added a `clientFoundRows` parameter: Return the number of matching rows instead of the number of rows changed on UPDATEs
|
||||
- Added TLS/SSL support: Use a TLS/SSL encrypted connection to the server. Custom TLS configs can be registered and used
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification
|
||||
- Convert to DB timezone when inserting `time.Time`
|
||||
- Splitted packets (more than 16MB) are now merged correctly
|
||||
- Fixed false positive `io.EOF` errors when the data was fully read
|
||||
- Avoid panics on reuse of closed connections
|
||||
- Fixed empty string producing false nil values
|
||||
- Fixed sign byte for positive TIME fields
|
||||
|
||||
|
||||
## Version 1.0 (2013-05-14)
|
||||
|
||||
Initial Release
|
||||
23
vendor/github.com/go-sql-driver/mysql/CONTRIBUTING.md
generated
vendored
Normal file
23
vendor/github.com/go-sql-driver/mysql/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Contributing Guidelines
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed).
|
||||
|
||||
## Contributing Code
|
||||
|
||||
By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file.
|
||||
Don't forget to add yourself to the AUTHORS file.
|
||||
|
||||
### Code Review
|
||||
|
||||
Everyone is invited to review and comment on pull requests.
|
||||
If it looks fine to you, comment with "LGTM" (Looks good to me).
|
||||
|
||||
If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes.
|
||||
|
||||
Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM".
|
||||
|
||||
## Development Ideas
|
||||
|
||||
If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page.
|
||||
373
vendor/github.com/go-sql-driver/mysql/LICENSE
generated
vendored
Normal file
373
vendor/github.com/go-sql-driver/mysql/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
490
vendor/github.com/go-sql-driver/mysql/README.md
generated
vendored
Normal file
490
vendor/github.com/go-sql-driver/mysql/README.md
generated
vendored
Normal file
@@ -0,0 +1,490 @@
|
||||
# Go-MySQL-Driver
|
||||
|
||||
A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) package
|
||||
|
||||

|
||||
|
||||
---------------------------------------
|
||||
* [Features](#features)
|
||||
* [Requirements](#requirements)
|
||||
* [Installation](#installation)
|
||||
* [Usage](#usage)
|
||||
* [DSN (Data Source Name)](#dsn-data-source-name)
|
||||
* [Password](#password)
|
||||
* [Protocol](#protocol)
|
||||
* [Address](#address)
|
||||
* [Parameters](#parameters)
|
||||
* [Examples](#examples)
|
||||
* [Connection pool and timeouts](#connection-pool-and-timeouts)
|
||||
* [context.Context Support](#contextcontext-support)
|
||||
* [ColumnType Support](#columntype-support)
|
||||
* [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
|
||||
* [time.Time support](#timetime-support)
|
||||
* [Unicode support](#unicode-support)
|
||||
* [Testing / Development](#testing--development)
|
||||
* [License](#license)
|
||||
|
||||
---------------------------------------
|
||||
|
||||
## Features
|
||||
* Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
|
||||
* Native Go implementation. No C-bindings, just pure Go
|
||||
* Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](https://godoc.org/github.com/go-sql-driver/mysql#DialFunc)
|
||||
* Automatic handling of broken connections
|
||||
* Automatic Connection Pooling *(by database/sql package)*
|
||||
* Supports queries larger than 16MB
|
||||
* Full [`sql.RawBytes`](https://golang.org/pkg/database/sql/#RawBytes) support.
|
||||
* Intelligent `LONG DATA` handling in prepared statements
|
||||
* Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
|
||||
* Optional `time.Time` parsing
|
||||
* Optional placeholder interpolation
|
||||
|
||||
## Requirements
|
||||
* Go 1.7 or higher. We aim to support the 3 latest versions of Go.
|
||||
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
|
||||
|
||||
---------------------------------------
|
||||
|
||||
## Installation
|
||||
Simple install the package to your [$GOPATH](https://github.com/golang/go/wiki/GOPATH "GOPATH") with the [go tool](https://golang.org/cmd/go/ "go command") from shell:
|
||||
```bash
|
||||
$ go get -u github.com/go-sql-driver/mysql
|
||||
```
|
||||
Make sure [Git is installed](https://git-scm.com/downloads) on your machine and in your system's `PATH`.
|
||||
|
||||
## Usage
|
||||
_Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](https://golang.org/pkg/database/sql/) API then.
|
||||
|
||||
Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`:
|
||||
```go
|
||||
import "database/sql"
|
||||
import _ "github.com/go-sql-driver/mysql"
|
||||
|
||||
db, err := sql.Open("mysql", "user:password@/dbname")
|
||||
```
|
||||
|
||||
[Examples are available in our Wiki](https://github.com/go-sql-driver/mysql/wiki/Examples "Go-MySQL-Driver Examples").
|
||||
|
||||
|
||||
### DSN (Data Source Name)
|
||||
|
||||
The Data Source Name has a common format, like e.g. [PEAR DB](http://pear.php.net/manual/en/package.database.db.intro-dsn.php) uses it, but without type-prefix (optional parts marked by squared brackets):
|
||||
```
|
||||
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
|
||||
```
|
||||
|
||||
A DSN in its fullest form:
|
||||
```
|
||||
username:password@protocol(address)/dbname?param=value
|
||||
```
|
||||
|
||||
Except for the databasename, all values are optional. So the minimal DSN is:
|
||||
```
|
||||
/dbname
|
||||
```
|
||||
|
||||
If you do not want to preselect a database, leave `dbname` empty:
|
||||
```
|
||||
/
|
||||
```
|
||||
This has the same effect as an empty DSN string:
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
Alternatively, [Config.FormatDSN](https://godoc.org/github.com/go-sql-driver/mysql#Config.FormatDSN) can be used to create a DSN string by filling a struct.
|
||||
|
||||
#### Password
|
||||
Passwords can consist of any character. Escaping is **not** necessary.
|
||||
|
||||
#### Protocol
|
||||
See [net.Dial](https://golang.org/pkg/net/#Dial) for more information which networks are available.
|
||||
In general you should use an Unix domain socket if available and TCP otherwise for best performance.
|
||||
|
||||
#### Address
|
||||
For TCP and UDP networks, addresses have the form `host[:port]`.
|
||||
If `port` is omitted, the default port will be used.
|
||||
If `host` is a literal IPv6 address, it must be enclosed in square brackets.
|
||||
The functions [net.JoinHostPort](https://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](https://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
|
||||
|
||||
For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`.
|
||||
|
||||
#### Parameters
|
||||
*Parameters are case-sensitive!*
|
||||
|
||||
Notice that any of `true`, `TRUE`, `True` or `1` is accepted to stand for a true boolean value. Not surprisingly, false can be specified as any of: `false`, `FALSE`, `False` or `0`.
|
||||
|
||||
##### `allowAllFiles`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files.
|
||||
[*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
|
||||
|
||||
##### `allowCleartextPasswords`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`allowCleartextPasswords=true` allows using the [cleartext client side plugin](http://dev.mysql.com/doc/en/cleartext-authentication-plugin.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network.
|
||||
|
||||
##### `allowNativePasswords`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: true
|
||||
```
|
||||
`allowNativePasswords=false` disallows the usage of MySQL native password method.
|
||||
|
||||
##### `allowOldPasswords`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
`allowOldPasswords=true` allows the usage of the insecure old password method. This should be avoided, but is necessary in some cases. See also [the old_passwords wiki page](https://github.com/go-sql-driver/mysql/wiki/old_passwords).
|
||||
|
||||
##### `charset`
|
||||
|
||||
```
|
||||
Type: string
|
||||
Valid Values: <name>
|
||||
Default: none
|
||||
```
|
||||
|
||||
Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
|
||||
|
||||
Usage of the `charset` parameter is discouraged because it issues additional queries to the server.
|
||||
Unless you need the fallback behavior, please use `collation` instead.
|
||||
|
||||
##### `collation`
|
||||
|
||||
```
|
||||
Type: string
|
||||
Valid Values: <name>
|
||||
Default: utf8_general_ci
|
||||
```
|
||||
|
||||
Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail.
|
||||
|
||||
A list of valid charsets for a server is retrievable with `SHOW COLLATION`.
|
||||
|
||||
##### `clientFoundRows`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed.
|
||||
|
||||
##### `columnsWithAlias`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
When `columnsWithAlias` is true, calls to `sql.Rows.Columns()` will return the table alias and the column name separated by a dot. For example:
|
||||
|
||||
```
|
||||
SELECT u.id FROM users as u
|
||||
```
|
||||
|
||||
will return `u.id` instead of just `id` if `columnsWithAlias=true`.
|
||||
|
||||
##### `interpolateParams`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
If `interpolateParams` is true, placeholders (`?`) in calls to `db.Query()` and `db.Exec()` are interpolated into a single query string with given parameters. This reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with `interpolateParams=false`.
|
||||
|
||||
*This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are blacklisted as they may [introduce a SQL injection vulnerability](http://stackoverflow.com/a/12118602/3430118)!*
|
||||
|
||||
##### `loc`
|
||||
|
||||
```
|
||||
Type: string
|
||||
Valid Values: <escaped name>
|
||||
Default: UTC
|
||||
```
|
||||
|
||||
Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](https://golang.org/pkg/time/#LoadLocation) for details.
|
||||
|
||||
Note that this sets the location for time.Time values but does not change MySQL's [time_zone setting](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html). For that see the [time_zone system variable](#system-variables), which can also be set as a DSN parameter.
|
||||
|
||||
Please keep in mind, that param values must be [url.QueryEscape](https://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
|
||||
|
||||
##### `maxAllowedPacket`
|
||||
```
|
||||
Type: decimal number
|
||||
Default: 4194304
|
||||
```
|
||||
|
||||
Max packet size allowed in bytes. The default value is 4 MiB and should be adjusted to match the server settings. `maxAllowedPacket=0` can be used to automatically fetch the `max_allowed_packet` variable from server *on every connection*.
|
||||
|
||||
##### `multiStatements`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
Allow multiple statements in one query. While this allows batch queries, it also greatly increases the risk of SQL injections. Only the result of the first query is returned, all other results are silently discarded.
|
||||
|
||||
When `multiStatements` is used, `?` parameters must only be used in the first statement.
|
||||
|
||||
##### `parseTime`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
|
||||
The date or datetime like `0000-00-00 00:00:00` is converted into zero value of `time.Time`.
|
||||
|
||||
|
||||
##### `readTimeout`
|
||||
|
||||
```
|
||||
Type: duration
|
||||
Default: 0
|
||||
```
|
||||
|
||||
I/O read timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
|
||||
|
||||
##### `rejectReadOnly`
|
||||
|
||||
```
|
||||
Type: bool
|
||||
Valid Values: true, false
|
||||
Default: false
|
||||
```
|
||||
|
||||
|
||||
`rejectReadOnly=true` causes the driver to reject read-only connections. This
|
||||
is for a possible race condition during an automatic failover, where the mysql
|
||||
client gets connected to a read-only replica after the failover.
|
||||
|
||||
Note that this should be a fairly rare case, as an automatic failover normally
|
||||
happens when the primary is down, and the race condition shouldn't happen
|
||||
unless it comes back up online as soon as the failover is kicked off. On the
|
||||
other hand, when this happens, a MySQL application can get stuck on a
|
||||
read-only connection until restarted. It is however fairly easy to reproduce,
|
||||
for example, using a manual failover on AWS Aurora's MySQL-compatible cluster.
|
||||
|
||||
If you are not relying on read-only transactions to reject writes that aren't
|
||||
supposed to happen, setting this on some MySQL providers (such as AWS Aurora)
|
||||
is safer for failovers.
|
||||
|
||||
Note that ERROR 1290 can be returned for a `read-only` server and this option will
|
||||
cause a retry for that error. However the same error number is used for some
|
||||
other cases. You should ensure your application will never cause an ERROR 1290
|
||||
except for `read-only` mode when enabling this option.
|
||||
|
||||
|
||||
##### `serverPubKey`
|
||||
|
||||
```
|
||||
Type: string
|
||||
Valid Values: <name>
|
||||
Default: none
|
||||
```
|
||||
|
||||
Server public keys can be registered with [`mysql.RegisterServerPubKey`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterServerPubKey), which can then be used by the assigned name in the DSN.
|
||||
Public keys are used to transmit encrypted data, e.g. for authentication.
|
||||
If the server's public key is known, it should be set manually to avoid expensive and potentially insecure transmissions of the public key from the server to the client each time it is required.
|
||||
|
||||
|
||||
##### `timeout`
|
||||
|
||||
```
|
||||
Type: duration
|
||||
Default: OS default
|
||||
```
|
||||
|
||||
Timeout for establishing connections, aka dial timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
|
||||
|
||||
|
||||
##### `tls`
|
||||
|
||||
```
|
||||
Type: bool / string
|
||||
Valid Values: true, false, skip-verify, <name>
|
||||
Default: false
|
||||
```
|
||||
|
||||
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
|
||||
|
||||
|
||||
##### `writeTimeout`
|
||||
|
||||
```
|
||||
Type: duration
|
||||
Default: 0
|
||||
```
|
||||
|
||||
I/O write timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
|
||||
|
||||
|
||||
##### System Variables
|
||||
|
||||
Any other parameters are interpreted as system variables:
|
||||
* `<boolean_var>=<value>`: `SET <boolean_var>=<value>`
|
||||
* `<enum_var>=<value>`: `SET <enum_var>=<value>`
|
||||
* `<string_var>=%27<value>%27`: `SET <string_var>='<value>'`
|
||||
|
||||
Rules:
|
||||
* The values for string variables must be quoted with `'`.
|
||||
* The values must also be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!
|
||||
(which implies values of string variables must be wrapped with `%27`).
|
||||
|
||||
Examples:
|
||||
* `autocommit=1`: `SET autocommit=1`
|
||||
* [`time_zone=%27Europe%2FParis%27`](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html): `SET time_zone='Europe/Paris'`
|
||||
* [`tx_isolation=%27REPEATABLE-READ%27`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `SET tx_isolation='REPEATABLE-READ'`
|
||||
|
||||
|
||||
#### Examples
|
||||
```
|
||||
user@unix(/path/to/socket)/dbname
|
||||
```
|
||||
|
||||
```
|
||||
root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local
|
||||
```
|
||||
|
||||
```
|
||||
user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true
|
||||
```
|
||||
|
||||
Treat warnings as errors by setting the system variable [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html):
|
||||
```
|
||||
user:password@/dbname?sql_mode=TRADITIONAL
|
||||
```
|
||||
|
||||
TCP via IPv6:
|
||||
```
|
||||
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s&collation=utf8mb4_unicode_ci
|
||||
```
|
||||
|
||||
TCP on a remote host, e.g. Amazon RDS:
|
||||
```
|
||||
id:password@tcp(your-amazonaws-uri.com:3306)/dbname
|
||||
```
|
||||
|
||||
Google Cloud SQL on App Engine (First Generation MySQL Server):
|
||||
```
|
||||
user@cloudsql(project-id:instance-name)/dbname
|
||||
```
|
||||
|
||||
Google Cloud SQL on App Engine (Second Generation MySQL Server):
|
||||
```
|
||||
user@cloudsql(project-id:regionname:instance-name)/dbname
|
||||
```
|
||||
|
||||
TCP using default port (3306) on localhost:
|
||||
```
|
||||
user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped
|
||||
```
|
||||
|
||||
Use the default protocol (tcp) and host (localhost:3306):
|
||||
```
|
||||
user:password@/dbname
|
||||
```
|
||||
|
||||
No Database preselected:
|
||||
```
|
||||
user:password@/
|
||||
```
|
||||
|
||||
|
||||
### Connection pool and timeouts
|
||||
The connection pool is managed by Go's database/sql package. For details on how to configure the size of the pool and how long connections stay in the pool see `*DB.SetMaxOpenConns`, `*DB.SetMaxIdleConns`, and `*DB.SetConnMaxLifetime` in the [database/sql documentation](https://golang.org/pkg/database/sql/). The read, write, and dial timeouts for each individual connection are configured with the DSN parameters [`readTimeout`](#readtimeout), [`writeTimeout`](#writetimeout), and [`timeout`](#timeout), respectively.
|
||||
|
||||
## `ColumnType` Support
|
||||
This driver supports the [`ColumnType` interface](https://golang.org/pkg/database/sql/#ColumnType) introduced in Go 1.8, with the exception of [`ColumnType.Length()`](https://golang.org/pkg/database/sql/#ColumnType.Length), which is currently not supported.
|
||||
|
||||
## `context.Context` Support
|
||||
Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts.
|
||||
See [context support in the database/sql package](https://golang.org/doc/go1.8#database_sql) for more details.
|
||||
|
||||
|
||||
### `LOAD DATA LOCAL INFILE` support
|
||||
For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
|
||||
```go
|
||||
import "github.com/go-sql-driver/mysql"
|
||||
```
|
||||
|
||||
Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
|
||||
|
||||
To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore.
|
||||
|
||||
See the [godoc of Go-MySQL-Driver](https://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
|
||||
|
||||
|
||||
### `time.Time` support
|
||||
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your program.
|
||||
|
||||
However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
|
||||
|
||||
**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
|
||||
|
||||
Alternatively you can use the [`NullTime`](https://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
|
||||
|
||||
|
||||
### Unicode support
|
||||
Since version 1.1 Go-MySQL-Driver automatically uses the collation `utf8_general_ci` by default.
|
||||
|
||||
Other collations / charsets can be set using the [`collation`](#collation) DSN parameter.
|
||||
|
||||
Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default.
|
||||
|
||||
See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
|
||||
|
||||
## Testing / Development
|
||||
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
|
||||
|
||||
Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated.
|
||||
If you want to contribute, you can work on an [open issue](https://github.com/go-sql-driver/mysql/issues?state=open) or review a [pull request](https://github.com/go-sql-driver/mysql/pulls).
|
||||
|
||||
See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CONTRIBUTING.md) for details.
|
||||
|
||||
---------------------------------------
|
||||
|
||||
## License
|
||||
Go-MySQL-Driver is licensed under the [Mozilla Public License Version 2.0](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
|
||||
|
||||
Mozilla summarizes the license scope as follows:
|
||||
> MPL: The copyleft applies to any files containing MPLed code.
|
||||
|
||||
|
||||
That means:
|
||||
* You can **use** the **unchanged** source code both in private and commercially.
|
||||
* When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0).
|
||||
* You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**.
|
||||
|
||||
Please read the [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/) if you have further questions regarding the license.
|
||||
|
||||
You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE).
|
||||
|
||||

|
||||
|
||||
19
vendor/github.com/go-sql-driver/mysql/appengine.go
generated
vendored
Normal file
19
vendor/github.com/go-sql-driver/mysql/appengine.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build appengine
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"google.golang.org/appengine/cloudsql"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterDial("cloudsql", cloudsql.Dial)
|
||||
}
|
||||
420
vendor/github.com/go-sql-driver/mysql/auth.go
generated
vendored
Normal file
420
vendor/github.com/go-sql-driver/mysql/auth.go
generated
vendored
Normal file
@@ -0,0 +1,420 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2018 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// server pub keys registry
|
||||
var (
|
||||
serverPubKeyLock sync.RWMutex
|
||||
serverPubKeyRegistry map[string]*rsa.PublicKey
|
||||
)
|
||||
|
||||
// RegisterServerPubKey registers a server RSA public key which can be used to
|
||||
// send data in a secure manner to the server without receiving the public key
|
||||
// in a potentially insecure way from the server first.
|
||||
// Registered keys can afterwards be used adding serverPubKey=<name> to the DSN.
|
||||
//
|
||||
// Note: The provided rsa.PublicKey instance is exclusively owned by the driver
|
||||
// after registering it and may not be modified.
|
||||
//
|
||||
// data, err := ioutil.ReadFile("mykey.pem")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// block, _ := pem.Decode(data)
|
||||
// if block == nil || block.Type != "PUBLIC KEY" {
|
||||
// log.Fatal("failed to decode PEM block containing public key")
|
||||
// }
|
||||
//
|
||||
// pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// if rsaPubKey, ok := pub.(*rsa.PublicKey); ok {
|
||||
// mysql.RegisterServerPubKey("mykey", rsaPubKey)
|
||||
// } else {
|
||||
// log.Fatal("not a RSA public key")
|
||||
// }
|
||||
//
|
||||
func RegisterServerPubKey(name string, pubKey *rsa.PublicKey) {
|
||||
serverPubKeyLock.Lock()
|
||||
if serverPubKeyRegistry == nil {
|
||||
serverPubKeyRegistry = make(map[string]*rsa.PublicKey)
|
||||
}
|
||||
|
||||
serverPubKeyRegistry[name] = pubKey
|
||||
serverPubKeyLock.Unlock()
|
||||
}
|
||||
|
||||
// DeregisterServerPubKey removes the public key registered with the given name.
|
||||
func DeregisterServerPubKey(name string) {
|
||||
serverPubKeyLock.Lock()
|
||||
if serverPubKeyRegistry != nil {
|
||||
delete(serverPubKeyRegistry, name)
|
||||
}
|
||||
serverPubKeyLock.Unlock()
|
||||
}
|
||||
|
||||
func getServerPubKey(name string) (pubKey *rsa.PublicKey) {
|
||||
serverPubKeyLock.RLock()
|
||||
if v, ok := serverPubKeyRegistry[name]; ok {
|
||||
pubKey = v
|
||||
}
|
||||
serverPubKeyLock.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Hash password using pre 4.1 (old password) method
|
||||
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
|
||||
type myRnd struct {
|
||||
seed1, seed2 uint32
|
||||
}
|
||||
|
||||
const myRndMaxVal = 0x3FFFFFFF
|
||||
|
||||
// Pseudo random number generator
|
||||
func newMyRnd(seed1, seed2 uint32) *myRnd {
|
||||
return &myRnd{
|
||||
seed1: seed1 % myRndMaxVal,
|
||||
seed2: seed2 % myRndMaxVal,
|
||||
}
|
||||
}
|
||||
|
||||
// Tested to be equivalent to MariaDB's floating point variant
|
||||
// http://play.golang.org/p/QHvhd4qved
|
||||
// http://play.golang.org/p/RG0q4ElWDx
|
||||
func (r *myRnd) NextByte() byte {
|
||||
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
|
||||
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
|
||||
|
||||
return byte(uint64(r.seed1) * 31 / myRndMaxVal)
|
||||
}
|
||||
|
||||
// Generate binary hash from byte string using insecure pre 4.1 method
|
||||
func pwHash(password []byte) (result [2]uint32) {
|
||||
var add uint32 = 7
|
||||
var tmp uint32
|
||||
|
||||
result[0] = 1345345333
|
||||
result[1] = 0x12345671
|
||||
|
||||
for _, c := range password {
|
||||
// skip spaces and tabs in password
|
||||
if c == ' ' || c == '\t' {
|
||||
continue
|
||||
}
|
||||
|
||||
tmp = uint32(c)
|
||||
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
|
||||
result[1] += (result[1] << 8) ^ result[0]
|
||||
add += tmp
|
||||
}
|
||||
|
||||
// Remove sign bit (1<<31)-1)
|
||||
result[0] &= 0x7FFFFFFF
|
||||
result[1] &= 0x7FFFFFFF
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Hash password using insecure pre 4.1 method
|
||||
func scrambleOldPassword(scramble []byte, password string) []byte {
|
||||
if len(password) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
scramble = scramble[:8]
|
||||
|
||||
hashPw := pwHash([]byte(password))
|
||||
hashSc := pwHash(scramble)
|
||||
|
||||
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
|
||||
|
||||
var out [8]byte
|
||||
for i := range out {
|
||||
out[i] = r.NextByte() + 64
|
||||
}
|
||||
|
||||
mask := r.NextByte()
|
||||
for i := range out {
|
||||
out[i] ^= mask
|
||||
}
|
||||
|
||||
return out[:]
|
||||
}
|
||||
|
||||
// Hash password using 4.1+ method (SHA1)
|
||||
func scramblePassword(scramble []byte, password string) []byte {
|
||||
if len(password) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// stage1Hash = SHA1(password)
|
||||
crypt := sha1.New()
|
||||
crypt.Write([]byte(password))
|
||||
stage1 := crypt.Sum(nil)
|
||||
|
||||
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
|
||||
// inner Hash
|
||||
crypt.Reset()
|
||||
crypt.Write(stage1)
|
||||
hash := crypt.Sum(nil)
|
||||
|
||||
// outer Hash
|
||||
crypt.Reset()
|
||||
crypt.Write(scramble)
|
||||
crypt.Write(hash)
|
||||
scramble = crypt.Sum(nil)
|
||||
|
||||
// token = scrambleHash XOR stage1Hash
|
||||
for i := range scramble {
|
||||
scramble[i] ^= stage1[i]
|
||||
}
|
||||
return scramble
|
||||
}
|
||||
|
||||
// Hash password using MySQL 8+ method (SHA256)
|
||||
func scrambleSHA256Password(scramble []byte, password string) []byte {
|
||||
if len(password) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
|
||||
|
||||
crypt := sha256.New()
|
||||
crypt.Write([]byte(password))
|
||||
message1 := crypt.Sum(nil)
|
||||
|
||||
crypt.Reset()
|
||||
crypt.Write(message1)
|
||||
message1Hash := crypt.Sum(nil)
|
||||
|
||||
crypt.Reset()
|
||||
crypt.Write(message1Hash)
|
||||
crypt.Write(scramble)
|
||||
message2 := crypt.Sum(nil)
|
||||
|
||||
for i := range message1 {
|
||||
message1[i] ^= message2[i]
|
||||
}
|
||||
|
||||
return message1
|
||||
}
|
||||
|
||||
func encryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte, error) {
|
||||
plain := make([]byte, len(password)+1)
|
||||
copy(plain, password)
|
||||
for i := range plain {
|
||||
j := i % len(seed)
|
||||
plain[i] ^= seed[j]
|
||||
}
|
||||
sha1 := sha1.New()
|
||||
return rsa.EncryptOAEP(sha1, rand.Reader, pub, plain, nil)
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) error {
|
||||
enc, err := encryptPassword(mc.cfg.Passwd, seed, pub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mc.writeAuthSwitchPacket(enc, false)
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, bool, error) {
|
||||
switch plugin {
|
||||
case "caching_sha2_password":
|
||||
authResp := scrambleSHA256Password(authData, mc.cfg.Passwd)
|
||||
return authResp, (authResp == nil), nil
|
||||
|
||||
case "mysql_old_password":
|
||||
if !mc.cfg.AllowOldPasswords {
|
||||
return nil, false, ErrOldPassword
|
||||
}
|
||||
// Note: there are edge cases where this should work but doesn't;
|
||||
// this is currently "wontfix":
|
||||
// https://github.com/go-sql-driver/mysql/issues/184
|
||||
authResp := scrambleOldPassword(authData[:8], mc.cfg.Passwd)
|
||||
return authResp, true, nil
|
||||
|
||||
case "mysql_clear_password":
|
||||
if !mc.cfg.AllowCleartextPasswords {
|
||||
return nil, false, ErrCleartextPassword
|
||||
}
|
||||
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
|
||||
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
|
||||
return []byte(mc.cfg.Passwd), true, nil
|
||||
|
||||
case "mysql_native_password":
|
||||
if !mc.cfg.AllowNativePasswords {
|
||||
return nil, false, ErrNativePassword
|
||||
}
|
||||
// https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
|
||||
// Native password authentication only need and will need 20-byte challenge.
|
||||
authResp := scramblePassword(authData[:20], mc.cfg.Passwd)
|
||||
return authResp, false, nil
|
||||
|
||||
case "sha256_password":
|
||||
if len(mc.cfg.Passwd) == 0 {
|
||||
return nil, true, nil
|
||||
}
|
||||
if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
|
||||
// write cleartext auth packet
|
||||
return []byte(mc.cfg.Passwd), true, nil
|
||||
}
|
||||
|
||||
pubKey := mc.cfg.pubKey
|
||||
if pubKey == nil {
|
||||
// request public key from server
|
||||
return []byte{1}, false, nil
|
||||
}
|
||||
|
||||
// encrypted password
|
||||
enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey)
|
||||
return enc, false, err
|
||||
|
||||
default:
|
||||
errLog.Print("unknown auth plugin:", plugin)
|
||||
return nil, false, ErrUnknownPlugin
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
|
||||
// Read Result Packet
|
||||
authData, newPlugin, err := mc.readAuthResult()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// handle auth plugin switch, if requested
|
||||
if newPlugin != "" {
|
||||
// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
|
||||
// sent and we have to keep using the cipher sent in the init packet.
|
||||
if authData == nil {
|
||||
authData = oldAuthData
|
||||
} else {
|
||||
// copy data from read buffer to owned slice
|
||||
copy(oldAuthData, authData)
|
||||
}
|
||||
|
||||
plugin = newPlugin
|
||||
|
||||
authResp, addNUL, err := mc.auth(authData, plugin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = mc.writeAuthSwitchPacket(authResp, addNUL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read Result Packet
|
||||
authData, newPlugin, err = mc.readAuthResult()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Do not allow to change the auth plugin more than once
|
||||
if newPlugin != "" {
|
||||
return ErrMalformPkt
|
||||
}
|
||||
}
|
||||
|
||||
switch plugin {
|
||||
|
||||
// https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
|
||||
case "caching_sha2_password":
|
||||
switch len(authData) {
|
||||
case 0:
|
||||
return nil // auth successful
|
||||
case 1:
|
||||
switch authData[0] {
|
||||
case cachingSha2PasswordFastAuthSuccess:
|
||||
if err = mc.readResultOK(); err == nil {
|
||||
return nil // auth successful
|
||||
}
|
||||
|
||||
case cachingSha2PasswordPerformFullAuthentication:
|
||||
if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
|
||||
// write cleartext auth packet
|
||||
err = mc.writeAuthSwitchPacket([]byte(mc.cfg.Passwd), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
pubKey := mc.cfg.pubKey
|
||||
if pubKey == nil {
|
||||
// request public key from server
|
||||
data := mc.buf.takeSmallBuffer(4 + 1)
|
||||
data[4] = cachingSha2PasswordRequestPublicKey
|
||||
mc.writePacket(data)
|
||||
|
||||
// parse public key
|
||||
data, err := mc.readPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(data[1:])
|
||||
pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pubKey = pkix.(*rsa.PublicKey)
|
||||
}
|
||||
|
||||
// send encrypted password
|
||||
err = mc.sendEncryptedPassword(oldAuthData, pubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return mc.readResultOK()
|
||||
|
||||
default:
|
||||
return ErrMalformPkt
|
||||
}
|
||||
default:
|
||||
return ErrMalformPkt
|
||||
}
|
||||
|
||||
case "sha256_password":
|
||||
switch len(authData) {
|
||||
case 0:
|
||||
return nil // auth successful
|
||||
default:
|
||||
block, _ := pem.Decode(authData)
|
||||
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// send encrypted password
|
||||
err = mc.sendEncryptedPassword(oldAuthData, pub.(*rsa.PublicKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mc.readResultOK()
|
||||
}
|
||||
|
||||
default:
|
||||
return nil // auth successful
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
147
vendor/github.com/go-sql-driver/mysql/buffer.go
generated
vendored
Normal file
147
vendor/github.com/go-sql-driver/mysql/buffer.go
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultBufSize = 4096
|
||||
|
||||
// A buffer which is used for both reading and writing.
|
||||
// This is possible since communication on each connection is synchronous.
|
||||
// In other words, we can't write and read simultaneously on the same connection.
|
||||
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
|
||||
// Also highly optimized for this particular use case.
|
||||
type buffer struct {
|
||||
buf []byte
|
||||
nc net.Conn
|
||||
idx int
|
||||
length int
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func newBuffer(nc net.Conn) buffer {
|
||||
var b [defaultBufSize]byte
|
||||
return buffer{
|
||||
buf: b[:],
|
||||
nc: nc,
|
||||
}
|
||||
}
|
||||
|
||||
// fill reads into the buffer until at least _need_ bytes are in it
|
||||
func (b *buffer) fill(need int) error {
|
||||
n := b.length
|
||||
|
||||
// move existing data to the beginning
|
||||
if n > 0 && b.idx > 0 {
|
||||
copy(b.buf[0:n], b.buf[b.idx:])
|
||||
}
|
||||
|
||||
// grow buffer if necessary
|
||||
// TODO: let the buffer shrink again at some point
|
||||
// Maybe keep the org buf slice and swap back?
|
||||
if need > len(b.buf) {
|
||||
// Round up to the next multiple of the default size
|
||||
newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize)
|
||||
copy(newBuf, b.buf)
|
||||
b.buf = newBuf
|
||||
}
|
||||
|
||||
b.idx = 0
|
||||
|
||||
for {
|
||||
if b.timeout > 0 {
|
||||
if err := b.nc.SetReadDeadline(time.Now().Add(b.timeout)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
nn, err := b.nc.Read(b.buf[n:])
|
||||
n += nn
|
||||
|
||||
switch err {
|
||||
case nil:
|
||||
if n < need {
|
||||
continue
|
||||
}
|
||||
b.length = n
|
||||
return nil
|
||||
|
||||
case io.EOF:
|
||||
if n >= need {
|
||||
b.length = n
|
||||
return nil
|
||||
}
|
||||
return io.ErrUnexpectedEOF
|
||||
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns next N bytes from buffer.
|
||||
// The returned slice is only guaranteed to be valid until the next read
|
||||
func (b *buffer) readNext(need int) ([]byte, error) {
|
||||
if b.length < need {
|
||||
// refill
|
||||
if err := b.fill(need); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
offset := b.idx
|
||||
b.idx += need
|
||||
b.length -= need
|
||||
return b.buf[offset:b.idx], nil
|
||||
}
|
||||
|
||||
// returns a buffer with the requested size.
|
||||
// If possible, a slice from the existing buffer is returned.
|
||||
// Otherwise a bigger buffer is made.
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeBuffer(length int) []byte {
|
||||
if b.length > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// test (cheap) general case first
|
||||
if length <= defaultBufSize || length <= cap(b.buf) {
|
||||
return b.buf[:length]
|
||||
}
|
||||
|
||||
if length < maxPacketSize {
|
||||
b.buf = make([]byte, length)
|
||||
return b.buf
|
||||
}
|
||||
return make([]byte, length)
|
||||
}
|
||||
|
||||
// shortcut which can be used if the requested buffer is guaranteed to be
|
||||
// smaller than defaultBufSize
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeSmallBuffer(length int) []byte {
|
||||
if b.length > 0 {
|
||||
return nil
|
||||
}
|
||||
return b.buf[:length]
|
||||
}
|
||||
|
||||
// takeCompleteBuffer returns the complete existing buffer.
|
||||
// This can be used if the necessary buffer size is unknown.
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeCompleteBuffer() []byte {
|
||||
if b.length > 0 {
|
||||
return nil
|
||||
}
|
||||
return b.buf
|
||||
}
|
||||
251
vendor/github.com/go-sql-driver/mysql/collations.go
generated
vendored
Normal file
251
vendor/github.com/go-sql-driver/mysql/collations.go
generated
vendored
Normal file
@@ -0,0 +1,251 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
const defaultCollation = "utf8_general_ci"
|
||||
const binaryCollation = "binary"
|
||||
|
||||
// A list of available collations mapped to the internal ID.
|
||||
// To update this map use the following MySQL query:
|
||||
// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS
|
||||
var collations = map[string]byte{
|
||||
"big5_chinese_ci": 1,
|
||||
"latin2_czech_cs": 2,
|
||||
"dec8_swedish_ci": 3,
|
||||
"cp850_general_ci": 4,
|
||||
"latin1_german1_ci": 5,
|
||||
"hp8_english_ci": 6,
|
||||
"koi8r_general_ci": 7,
|
||||
"latin1_swedish_ci": 8,
|
||||
"latin2_general_ci": 9,
|
||||
"swe7_swedish_ci": 10,
|
||||
"ascii_general_ci": 11,
|
||||
"ujis_japanese_ci": 12,
|
||||
"sjis_japanese_ci": 13,
|
||||
"cp1251_bulgarian_ci": 14,
|
||||
"latin1_danish_ci": 15,
|
||||
"hebrew_general_ci": 16,
|
||||
"tis620_thai_ci": 18,
|
||||
"euckr_korean_ci": 19,
|
||||
"latin7_estonian_cs": 20,
|
||||
"latin2_hungarian_ci": 21,
|
||||
"koi8u_general_ci": 22,
|
||||
"cp1251_ukrainian_ci": 23,
|
||||
"gb2312_chinese_ci": 24,
|
||||
"greek_general_ci": 25,
|
||||
"cp1250_general_ci": 26,
|
||||
"latin2_croatian_ci": 27,
|
||||
"gbk_chinese_ci": 28,
|
||||
"cp1257_lithuanian_ci": 29,
|
||||
"latin5_turkish_ci": 30,
|
||||
"latin1_german2_ci": 31,
|
||||
"armscii8_general_ci": 32,
|
||||
"utf8_general_ci": 33,
|
||||
"cp1250_czech_cs": 34,
|
||||
"ucs2_general_ci": 35,
|
||||
"cp866_general_ci": 36,
|
||||
"keybcs2_general_ci": 37,
|
||||
"macce_general_ci": 38,
|
||||
"macroman_general_ci": 39,
|
||||
"cp852_general_ci": 40,
|
||||
"latin7_general_ci": 41,
|
||||
"latin7_general_cs": 42,
|
||||
"macce_bin": 43,
|
||||
"cp1250_croatian_ci": 44,
|
||||
"utf8mb4_general_ci": 45,
|
||||
"utf8mb4_bin": 46,
|
||||
"latin1_bin": 47,
|
||||
"latin1_general_ci": 48,
|
||||
"latin1_general_cs": 49,
|
||||
"cp1251_bin": 50,
|
||||
"cp1251_general_ci": 51,
|
||||
"cp1251_general_cs": 52,
|
||||
"macroman_bin": 53,
|
||||
"utf16_general_ci": 54,
|
||||
"utf16_bin": 55,
|
||||
"utf16le_general_ci": 56,
|
||||
"cp1256_general_ci": 57,
|
||||
"cp1257_bin": 58,
|
||||
"cp1257_general_ci": 59,
|
||||
"utf32_general_ci": 60,
|
||||
"utf32_bin": 61,
|
||||
"utf16le_bin": 62,
|
||||
"binary": 63,
|
||||
"armscii8_bin": 64,
|
||||
"ascii_bin": 65,
|
||||
"cp1250_bin": 66,
|
||||
"cp1256_bin": 67,
|
||||
"cp866_bin": 68,
|
||||
"dec8_bin": 69,
|
||||
"greek_bin": 70,
|
||||
"hebrew_bin": 71,
|
||||
"hp8_bin": 72,
|
||||
"keybcs2_bin": 73,
|
||||
"koi8r_bin": 74,
|
||||
"koi8u_bin": 75,
|
||||
"latin2_bin": 77,
|
||||
"latin5_bin": 78,
|
||||
"latin7_bin": 79,
|
||||
"cp850_bin": 80,
|
||||
"cp852_bin": 81,
|
||||
"swe7_bin": 82,
|
||||
"utf8_bin": 83,
|
||||
"big5_bin": 84,
|
||||
"euckr_bin": 85,
|
||||
"gb2312_bin": 86,
|
||||
"gbk_bin": 87,
|
||||
"sjis_bin": 88,
|
||||
"tis620_bin": 89,
|
||||
"ucs2_bin": 90,
|
||||
"ujis_bin": 91,
|
||||
"geostd8_general_ci": 92,
|
||||
"geostd8_bin": 93,
|
||||
"latin1_spanish_ci": 94,
|
||||
"cp932_japanese_ci": 95,
|
||||
"cp932_bin": 96,
|
||||
"eucjpms_japanese_ci": 97,
|
||||
"eucjpms_bin": 98,
|
||||
"cp1250_polish_ci": 99,
|
||||
"utf16_unicode_ci": 101,
|
||||
"utf16_icelandic_ci": 102,
|
||||
"utf16_latvian_ci": 103,
|
||||
"utf16_romanian_ci": 104,
|
||||
"utf16_slovenian_ci": 105,
|
||||
"utf16_polish_ci": 106,
|
||||
"utf16_estonian_ci": 107,
|
||||
"utf16_spanish_ci": 108,
|
||||
"utf16_swedish_ci": 109,
|
||||
"utf16_turkish_ci": 110,
|
||||
"utf16_czech_ci": 111,
|
||||
"utf16_danish_ci": 112,
|
||||
"utf16_lithuanian_ci": 113,
|
||||
"utf16_slovak_ci": 114,
|
||||
"utf16_spanish2_ci": 115,
|
||||
"utf16_roman_ci": 116,
|
||||
"utf16_persian_ci": 117,
|
||||
"utf16_esperanto_ci": 118,
|
||||
"utf16_hungarian_ci": 119,
|
||||
"utf16_sinhala_ci": 120,
|
||||
"utf16_german2_ci": 121,
|
||||
"utf16_croatian_ci": 122,
|
||||
"utf16_unicode_520_ci": 123,
|
||||
"utf16_vietnamese_ci": 124,
|
||||
"ucs2_unicode_ci": 128,
|
||||
"ucs2_icelandic_ci": 129,
|
||||
"ucs2_latvian_ci": 130,
|
||||
"ucs2_romanian_ci": 131,
|
||||
"ucs2_slovenian_ci": 132,
|
||||
"ucs2_polish_ci": 133,
|
||||
"ucs2_estonian_ci": 134,
|
||||
"ucs2_spanish_ci": 135,
|
||||
"ucs2_swedish_ci": 136,
|
||||
"ucs2_turkish_ci": 137,
|
||||
"ucs2_czech_ci": 138,
|
||||
"ucs2_danish_ci": 139,
|
||||
"ucs2_lithuanian_ci": 140,
|
||||
"ucs2_slovak_ci": 141,
|
||||
"ucs2_spanish2_ci": 142,
|
||||
"ucs2_roman_ci": 143,
|
||||
"ucs2_persian_ci": 144,
|
||||
"ucs2_esperanto_ci": 145,
|
||||
"ucs2_hungarian_ci": 146,
|
||||
"ucs2_sinhala_ci": 147,
|
||||
"ucs2_german2_ci": 148,
|
||||
"ucs2_croatian_ci": 149,
|
||||
"ucs2_unicode_520_ci": 150,
|
||||
"ucs2_vietnamese_ci": 151,
|
||||
"ucs2_general_mysql500_ci": 159,
|
||||
"utf32_unicode_ci": 160,
|
||||
"utf32_icelandic_ci": 161,
|
||||
"utf32_latvian_ci": 162,
|
||||
"utf32_romanian_ci": 163,
|
||||
"utf32_slovenian_ci": 164,
|
||||
"utf32_polish_ci": 165,
|
||||
"utf32_estonian_ci": 166,
|
||||
"utf32_spanish_ci": 167,
|
||||
"utf32_swedish_ci": 168,
|
||||
"utf32_turkish_ci": 169,
|
||||
"utf32_czech_ci": 170,
|
||||
"utf32_danish_ci": 171,
|
||||
"utf32_lithuanian_ci": 172,
|
||||
"utf32_slovak_ci": 173,
|
||||
"utf32_spanish2_ci": 174,
|
||||
"utf32_roman_ci": 175,
|
||||
"utf32_persian_ci": 176,
|
||||
"utf32_esperanto_ci": 177,
|
||||
"utf32_hungarian_ci": 178,
|
||||
"utf32_sinhala_ci": 179,
|
||||
"utf32_german2_ci": 180,
|
||||
"utf32_croatian_ci": 181,
|
||||
"utf32_unicode_520_ci": 182,
|
||||
"utf32_vietnamese_ci": 183,
|
||||
"utf8_unicode_ci": 192,
|
||||
"utf8_icelandic_ci": 193,
|
||||
"utf8_latvian_ci": 194,
|
||||
"utf8_romanian_ci": 195,
|
||||
"utf8_slovenian_ci": 196,
|
||||
"utf8_polish_ci": 197,
|
||||
"utf8_estonian_ci": 198,
|
||||
"utf8_spanish_ci": 199,
|
||||
"utf8_swedish_ci": 200,
|
||||
"utf8_turkish_ci": 201,
|
||||
"utf8_czech_ci": 202,
|
||||
"utf8_danish_ci": 203,
|
||||
"utf8_lithuanian_ci": 204,
|
||||
"utf8_slovak_ci": 205,
|
||||
"utf8_spanish2_ci": 206,
|
||||
"utf8_roman_ci": 207,
|
||||
"utf8_persian_ci": 208,
|
||||
"utf8_esperanto_ci": 209,
|
||||
"utf8_hungarian_ci": 210,
|
||||
"utf8_sinhala_ci": 211,
|
||||
"utf8_german2_ci": 212,
|
||||
"utf8_croatian_ci": 213,
|
||||
"utf8_unicode_520_ci": 214,
|
||||
"utf8_vietnamese_ci": 215,
|
||||
"utf8_general_mysql500_ci": 223,
|
||||
"utf8mb4_unicode_ci": 224,
|
||||
"utf8mb4_icelandic_ci": 225,
|
||||
"utf8mb4_latvian_ci": 226,
|
||||
"utf8mb4_romanian_ci": 227,
|
||||
"utf8mb4_slovenian_ci": 228,
|
||||
"utf8mb4_polish_ci": 229,
|
||||
"utf8mb4_estonian_ci": 230,
|
||||
"utf8mb4_spanish_ci": 231,
|
||||
"utf8mb4_swedish_ci": 232,
|
||||
"utf8mb4_turkish_ci": 233,
|
||||
"utf8mb4_czech_ci": 234,
|
||||
"utf8mb4_danish_ci": 235,
|
||||
"utf8mb4_lithuanian_ci": 236,
|
||||
"utf8mb4_slovak_ci": 237,
|
||||
"utf8mb4_spanish2_ci": 238,
|
||||
"utf8mb4_roman_ci": 239,
|
||||
"utf8mb4_persian_ci": 240,
|
||||
"utf8mb4_esperanto_ci": 241,
|
||||
"utf8mb4_hungarian_ci": 242,
|
||||
"utf8mb4_sinhala_ci": 243,
|
||||
"utf8mb4_german2_ci": 244,
|
||||
"utf8mb4_croatian_ci": 245,
|
||||
"utf8mb4_unicode_520_ci": 246,
|
||||
"utf8mb4_vietnamese_ci": 247,
|
||||
}
|
||||
|
||||
// A blacklist of collations which is unsafe to interpolate parameters.
|
||||
// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
|
||||
var unsafeCollations = map[string]bool{
|
||||
"big5_chinese_ci": true,
|
||||
"sjis_japanese_ci": true,
|
||||
"gbk_chinese_ci": true,
|
||||
"big5_bin": true,
|
||||
"gb2312_bin": true,
|
||||
"gbk_bin": true,
|
||||
"sjis_bin": true,
|
||||
"cp932_japanese_ci": true,
|
||||
"cp932_bin": true,
|
||||
}
|
||||
461
vendor/github.com/go-sql-driver/mysql/connection.go
generated
vendored
Normal file
461
vendor/github.com/go-sql-driver/mysql/connection.go
generated
vendored
Normal file
@@ -0,0 +1,461 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// a copy of context.Context for Go 1.7 and earlier
|
||||
type mysqlContext interface {
|
||||
Done() <-chan struct{}
|
||||
Err() error
|
||||
|
||||
// defined in context.Context, but not used in this driver:
|
||||
// Deadline() (deadline time.Time, ok bool)
|
||||
// Value(key interface{}) interface{}
|
||||
}
|
||||
|
||||
type mysqlConn struct {
|
||||
buf buffer
|
||||
netConn net.Conn
|
||||
affectedRows uint64
|
||||
insertId uint64
|
||||
cfg *Config
|
||||
maxAllowedPacket int
|
||||
maxWriteSize int
|
||||
writeTimeout time.Duration
|
||||
flags clientFlag
|
||||
status statusFlag
|
||||
sequence uint8
|
||||
parseTime bool
|
||||
|
||||
// for context support (Go 1.8+)
|
||||
watching bool
|
||||
watcher chan<- mysqlContext
|
||||
closech chan struct{}
|
||||
finished chan<- struct{}
|
||||
canceled atomicError // set non-nil if conn is canceled
|
||||
closed atomicBool // set when conn is closed, before closech is closed
|
||||
}
|
||||
|
||||
// Handles parameters set in DSN after the connection is established
|
||||
func (mc *mysqlConn) handleParams() (err error) {
|
||||
for param, val := range mc.cfg.Params {
|
||||
switch param {
|
||||
// Charset
|
||||
case "charset":
|
||||
charsets := strings.Split(val, ",")
|
||||
for i := range charsets {
|
||||
// ignore errors here - a charset may not exist
|
||||
err = mc.exec("SET NAMES " + charsets[i])
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// System Vars
|
||||
default:
|
||||
err = mc.exec("SET " + param + "=" + val + "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) markBadConn(err error) error {
|
||||
if mc == nil {
|
||||
return err
|
||||
}
|
||||
if err != errBadConnNoWrite {
|
||||
return err
|
||||
}
|
||||
return driver.ErrBadConn
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Begin() (driver.Tx, error) {
|
||||
return mc.begin(false)
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
|
||||
if mc.closed.IsSet() {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
var q string
|
||||
if readOnly {
|
||||
q = "START TRANSACTION READ ONLY"
|
||||
} else {
|
||||
q = "START TRANSACTION"
|
||||
}
|
||||
err := mc.exec(q)
|
||||
if err == nil {
|
||||
return &mysqlTx{mc}, err
|
||||
}
|
||||
return nil, mc.markBadConn(err)
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Close() (err error) {
|
||||
// Makes Close idempotent
|
||||
if !mc.closed.IsSet() {
|
||||
err = mc.writeCommandPacket(comQuit)
|
||||
}
|
||||
|
||||
mc.cleanup()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Closes the network connection and unsets internal variables. Do not call this
|
||||
// function after successfully authentication, call Close instead. This function
|
||||
// is called before auth or on auth failure because MySQL will have already
|
||||
// closed the network connection.
|
||||
func (mc *mysqlConn) cleanup() {
|
||||
if !mc.closed.TrySet(true) {
|
||||
return
|
||||
}
|
||||
|
||||
// Makes cleanup idempotent
|
||||
close(mc.closech)
|
||||
if mc.netConn == nil {
|
||||
return
|
||||
}
|
||||
if err := mc.netConn.Close(); err != nil {
|
||||
errLog.Print(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) error() error {
|
||||
if mc.closed.IsSet() {
|
||||
if err := mc.canceled.Value(); err != nil {
|
||||
return err
|
||||
}
|
||||
return ErrInvalidConn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
|
||||
if mc.closed.IsSet() {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
// Send command
|
||||
err := mc.writeCommandPacketStr(comStmtPrepare, query)
|
||||
if err != nil {
|
||||
return nil, mc.markBadConn(err)
|
||||
}
|
||||
|
||||
stmt := &mysqlStmt{
|
||||
mc: mc,
|
||||
}
|
||||
|
||||
// Read Result
|
||||
columnCount, err := stmt.readPrepareResultPacket()
|
||||
if err == nil {
|
||||
if stmt.paramCount > 0 {
|
||||
if err = mc.readUntilEOF(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if columnCount > 0 {
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
}
|
||||
|
||||
return stmt, err
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
|
||||
// Number of ? should be same to len(args)
|
||||
if strings.Count(query, "?") != len(args) {
|
||||
return "", driver.ErrSkip
|
||||
}
|
||||
|
||||
buf := mc.buf.takeCompleteBuffer()
|
||||
if buf == nil {
|
||||
// can not take the buffer. Something must be wrong with the connection
|
||||
errLog.Print(ErrBusyBuffer)
|
||||
return "", ErrInvalidConn
|
||||
}
|
||||
buf = buf[:0]
|
||||
argPos := 0
|
||||
|
||||
for i := 0; i < len(query); i++ {
|
||||
q := strings.IndexByte(query[i:], '?')
|
||||
if q == -1 {
|
||||
buf = append(buf, query[i:]...)
|
||||
break
|
||||
}
|
||||
buf = append(buf, query[i:i+q]...)
|
||||
i += q
|
||||
|
||||
arg := args[argPos]
|
||||
argPos++
|
||||
|
||||
if arg == nil {
|
||||
buf = append(buf, "NULL"...)
|
||||
continue
|
||||
}
|
||||
|
||||
switch v := arg.(type) {
|
||||
case int64:
|
||||
buf = strconv.AppendInt(buf, v, 10)
|
||||
case float64:
|
||||
buf = strconv.AppendFloat(buf, v, 'g', -1, 64)
|
||||
case bool:
|
||||
if v {
|
||||
buf = append(buf, '1')
|
||||
} else {
|
||||
buf = append(buf, '0')
|
||||
}
|
||||
case time.Time:
|
||||
if v.IsZero() {
|
||||
buf = append(buf, "'0000-00-00'"...)
|
||||
} else {
|
||||
v := v.In(mc.cfg.Loc)
|
||||
v = v.Add(time.Nanosecond * 500) // To round under microsecond
|
||||
year := v.Year()
|
||||
year100 := year / 100
|
||||
year1 := year % 100
|
||||
month := v.Month()
|
||||
day := v.Day()
|
||||
hour := v.Hour()
|
||||
minute := v.Minute()
|
||||
second := v.Second()
|
||||
micro := v.Nanosecond() / 1000
|
||||
|
||||
buf = append(buf, []byte{
|
||||
'\'',
|
||||
digits10[year100], digits01[year100],
|
||||
digits10[year1], digits01[year1],
|
||||
'-',
|
||||
digits10[month], digits01[month],
|
||||
'-',
|
||||
digits10[day], digits01[day],
|
||||
' ',
|
||||
digits10[hour], digits01[hour],
|
||||
':',
|
||||
digits10[minute], digits01[minute],
|
||||
':',
|
||||
digits10[second], digits01[second],
|
||||
}...)
|
||||
|
||||
if micro != 0 {
|
||||
micro10000 := micro / 10000
|
||||
micro100 := micro / 100 % 100
|
||||
micro1 := micro % 100
|
||||
buf = append(buf, []byte{
|
||||
'.',
|
||||
digits10[micro10000], digits01[micro10000],
|
||||
digits10[micro100], digits01[micro100],
|
||||
digits10[micro1], digits01[micro1],
|
||||
}...)
|
||||
}
|
||||
buf = append(buf, '\'')
|
||||
}
|
||||
case []byte:
|
||||
if v == nil {
|
||||
buf = append(buf, "NULL"...)
|
||||
} else {
|
||||
buf = append(buf, "_binary'"...)
|
||||
if mc.status&statusNoBackslashEscapes == 0 {
|
||||
buf = escapeBytesBackslash(buf, v)
|
||||
} else {
|
||||
buf = escapeBytesQuotes(buf, v)
|
||||
}
|
||||
buf = append(buf, '\'')
|
||||
}
|
||||
case string:
|
||||
buf = append(buf, '\'')
|
||||
if mc.status&statusNoBackslashEscapes == 0 {
|
||||
buf = escapeStringBackslash(buf, v)
|
||||
} else {
|
||||
buf = escapeStringQuotes(buf, v)
|
||||
}
|
||||
buf = append(buf, '\'')
|
||||
default:
|
||||
return "", driver.ErrSkip
|
||||
}
|
||||
|
||||
if len(buf)+4 > mc.maxAllowedPacket {
|
||||
return "", driver.ErrSkip
|
||||
}
|
||||
}
|
||||
if argPos != len(args) {
|
||||
return "", driver.ErrSkip
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
||||
if mc.closed.IsSet() {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
if len(args) != 0 {
|
||||
if !mc.cfg.InterpolateParams {
|
||||
return nil, driver.ErrSkip
|
||||
}
|
||||
// try to interpolate the parameters to save extra roundtrips for preparing and closing a statement
|
||||
prepared, err := mc.interpolateParams(query, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query = prepared
|
||||
}
|
||||
mc.affectedRows = 0
|
||||
mc.insertId = 0
|
||||
|
||||
err := mc.exec(query)
|
||||
if err == nil {
|
||||
return &mysqlResult{
|
||||
affectedRows: int64(mc.affectedRows),
|
||||
insertId: int64(mc.insertId),
|
||||
}, err
|
||||
}
|
||||
return nil, mc.markBadConn(err)
|
||||
}
|
||||
|
||||
// Internal function to execute commands
|
||||
func (mc *mysqlConn) exec(query string) error {
|
||||
// Send command
|
||||
if err := mc.writeCommandPacketStr(comQuery, query); err != nil {
|
||||
return mc.markBadConn(err)
|
||||
}
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resLen > 0 {
|
||||
// columns
|
||||
if err := mc.readUntilEOF(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// rows
|
||||
if err := mc.readUntilEOF(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return mc.discardResults()
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
||||
return mc.query(query, args)
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
|
||||
if mc.closed.IsSet() {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
if len(args) != 0 {
|
||||
if !mc.cfg.InterpolateParams {
|
||||
return nil, driver.ErrSkip
|
||||
}
|
||||
// try client-side prepare to reduce roundtrip
|
||||
prepared, err := mc.interpolateParams(query, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query = prepared
|
||||
}
|
||||
// Send command
|
||||
err := mc.writeCommandPacketStr(comQuery, query)
|
||||
if err == nil {
|
||||
// Read Result
|
||||
var resLen int
|
||||
resLen, err = mc.readResultSetHeaderPacket()
|
||||
if err == nil {
|
||||
rows := new(textRows)
|
||||
rows.mc = mc
|
||||
|
||||
if resLen == 0 {
|
||||
rows.rs.done = true
|
||||
|
||||
switch err := rows.NextResultSet(); err {
|
||||
case nil, io.EOF:
|
||||
return rows, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Columns
|
||||
rows.rs.columns, err = mc.readColumns(resLen)
|
||||
return rows, err
|
||||
}
|
||||
}
|
||||
return nil, mc.markBadConn(err)
|
||||
}
|
||||
|
||||
// Gets the value of the given MySQL System Variable
|
||||
// The returned byte slice is only valid until the next read
|
||||
func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
|
||||
// Send command
|
||||
if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err == nil {
|
||||
rows := new(textRows)
|
||||
rows.mc = mc
|
||||
rows.rs.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
|
||||
|
||||
if resLen > 0 {
|
||||
// Columns
|
||||
if err := mc.readUntilEOF(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
dest := make([]driver.Value, resLen)
|
||||
if err = rows.readRow(dest); err == nil {
|
||||
return dest[0].([]byte), mc.readUntilEOF()
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// finish is called when the query has canceled.
|
||||
func (mc *mysqlConn) cancel(err error) {
|
||||
mc.canceled.Set(err)
|
||||
mc.cleanup()
|
||||
}
|
||||
|
||||
// finish is called when the query has succeeded.
|
||||
func (mc *mysqlConn) finish() {
|
||||
if !mc.watching || mc.finished == nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case mc.finished <- struct{}{}:
|
||||
mc.watching = false
|
||||
case <-mc.closech:
|
||||
}
|
||||
}
|
||||
208
vendor/github.com/go-sql-driver/mysql/connection_go18.go
generated
vendored
Normal file
208
vendor/github.com/go-sql-driver/mysql/connection_go18.go
generated
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
)
|
||||
|
||||
// Ping implements driver.Pinger interface
|
||||
func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
|
||||
if mc.closed.IsSet() {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return driver.ErrBadConn
|
||||
}
|
||||
|
||||
if err = mc.watchCancel(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
defer mc.finish()
|
||||
|
||||
if err = mc.writeCommandPacket(comPing); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return mc.readResultOK()
|
||||
}
|
||||
|
||||
// BeginTx implements driver.ConnBeginTx interface
|
||||
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
||||
if err := mc.watchCancel(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer mc.finish()
|
||||
|
||||
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
|
||||
level, err := mapIsolationLevel(opts.Isolation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return mc.begin(opts.ReadOnly)
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
||||
dargs, err := namedValueToValue(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mc.watchCancel(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := mc.query(query, dargs)
|
||||
if err != nil {
|
||||
mc.finish()
|
||||
return nil, err
|
||||
}
|
||||
rows.finish = mc.finish
|
||||
return rows, err
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||
dargs, err := namedValueToValue(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mc.watchCancel(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer mc.finish()
|
||||
|
||||
return mc.Exec(query, dargs)
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
||||
if err := mc.watchCancel(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stmt, err := mc.Prepare(query)
|
||||
mc.finish()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
select {
|
||||
default:
|
||||
case <-ctx.Done():
|
||||
stmt.Close()
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
return stmt, nil
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||
dargs, err := namedValueToValue(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := stmt.mc.watchCancel(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := stmt.query(dargs)
|
||||
if err != nil {
|
||||
stmt.mc.finish()
|
||||
return nil, err
|
||||
}
|
||||
rows.finish = stmt.mc.finish
|
||||
return rows, err
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
||||
dargs, err := namedValueToValue(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := stmt.mc.watchCancel(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.mc.finish()
|
||||
|
||||
return stmt.Exec(dargs)
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) watchCancel(ctx context.Context) error {
|
||||
if mc.watching {
|
||||
// Reach here if canceled,
|
||||
// so the connection is already invalid
|
||||
mc.cleanup()
|
||||
return nil
|
||||
}
|
||||
if ctx.Done() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mc.watching = true
|
||||
select {
|
||||
default:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
if mc.watcher == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mc.watcher <- ctx
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) startWatcher() {
|
||||
watcher := make(chan mysqlContext, 1)
|
||||
mc.watcher = watcher
|
||||
finished := make(chan struct{})
|
||||
mc.finished = finished
|
||||
go func() {
|
||||
for {
|
||||
var ctx mysqlContext
|
||||
select {
|
||||
case ctx = <-watcher:
|
||||
case <-mc.closech:
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
mc.cancel(ctx.Err())
|
||||
case <-finished:
|
||||
case <-mc.closech:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
|
||||
nv.Value, err = converter{}.ConvertValue(nv.Value)
|
||||
return
|
||||
}
|
||||
|
||||
// ResetSession implements driver.SessionResetter.
|
||||
// (From Go 1.10)
|
||||
func (mc *mysqlConn) ResetSession(ctx context.Context) error {
|
||||
if mc.closed.IsSet() {
|
||||
return driver.ErrBadConn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
174
vendor/github.com/go-sql-driver/mysql/const.go
generated
vendored
Normal file
174
vendor/github.com/go-sql-driver/mysql/const.go
generated
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
const (
|
||||
defaultAuthPlugin = "mysql_native_password"
|
||||
defaultMaxAllowedPacket = 4 << 20 // 4 MiB
|
||||
minProtocolVersion = 10
|
||||
maxPacketSize = 1<<24 - 1
|
||||
timeFormat = "2006-01-02 15:04:05.999999"
|
||||
)
|
||||
|
||||
// MySQL constants documentation:
|
||||
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
|
||||
|
||||
const (
|
||||
iOK byte = 0x00
|
||||
iAuthMoreData byte = 0x01
|
||||
iLocalInFile byte = 0xfb
|
||||
iEOF byte = 0xfe
|
||||
iERR byte = 0xff
|
||||
)
|
||||
|
||||
// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
|
||||
type clientFlag uint32
|
||||
|
||||
const (
|
||||
clientLongPassword clientFlag = 1 << iota
|
||||
clientFoundRows
|
||||
clientLongFlag
|
||||
clientConnectWithDB
|
||||
clientNoSchema
|
||||
clientCompress
|
||||
clientODBC
|
||||
clientLocalFiles
|
||||
clientIgnoreSpace
|
||||
clientProtocol41
|
||||
clientInteractive
|
||||
clientSSL
|
||||
clientIgnoreSIGPIPE
|
||||
clientTransactions
|
||||
clientReserved
|
||||
clientSecureConn
|
||||
clientMultiStatements
|
||||
clientMultiResults
|
||||
clientPSMultiResults
|
||||
clientPluginAuth
|
||||
clientConnectAttrs
|
||||
clientPluginAuthLenEncClientData
|
||||
clientCanHandleExpiredPasswords
|
||||
clientSessionTrack
|
||||
clientDeprecateEOF
|
||||
)
|
||||
|
||||
const (
|
||||
comQuit byte = iota + 1
|
||||
comInitDB
|
||||
comQuery
|
||||
comFieldList
|
||||
comCreateDB
|
||||
comDropDB
|
||||
comRefresh
|
||||
comShutdown
|
||||
comStatistics
|
||||
comProcessInfo
|
||||
comConnect
|
||||
comProcessKill
|
||||
comDebug
|
||||
comPing
|
||||
comTime
|
||||
comDelayedInsert
|
||||
comChangeUser
|
||||
comBinlogDump
|
||||
comTableDump
|
||||
comConnectOut
|
||||
comRegisterSlave
|
||||
comStmtPrepare
|
||||
comStmtExecute
|
||||
comStmtSendLongData
|
||||
comStmtClose
|
||||
comStmtReset
|
||||
comSetOption
|
||||
comStmtFetch
|
||||
)
|
||||
|
||||
// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
|
||||
type fieldType byte
|
||||
|
||||
const (
|
||||
fieldTypeDecimal fieldType = iota
|
||||
fieldTypeTiny
|
||||
fieldTypeShort
|
||||
fieldTypeLong
|
||||
fieldTypeFloat
|
||||
fieldTypeDouble
|
||||
fieldTypeNULL
|
||||
fieldTypeTimestamp
|
||||
fieldTypeLongLong
|
||||
fieldTypeInt24
|
||||
fieldTypeDate
|
||||
fieldTypeTime
|
||||
fieldTypeDateTime
|
||||
fieldTypeYear
|
||||
fieldTypeNewDate
|
||||
fieldTypeVarChar
|
||||
fieldTypeBit
|
||||
)
|
||||
const (
|
||||
fieldTypeJSON fieldType = iota + 0xf5
|
||||
fieldTypeNewDecimal
|
||||
fieldTypeEnum
|
||||
fieldTypeSet
|
||||
fieldTypeTinyBLOB
|
||||
fieldTypeMediumBLOB
|
||||
fieldTypeLongBLOB
|
||||
fieldTypeBLOB
|
||||
fieldTypeVarString
|
||||
fieldTypeString
|
||||
fieldTypeGeometry
|
||||
)
|
||||
|
||||
type fieldFlag uint16
|
||||
|
||||
const (
|
||||
flagNotNULL fieldFlag = 1 << iota
|
||||
flagPriKey
|
||||
flagUniqueKey
|
||||
flagMultipleKey
|
||||
flagBLOB
|
||||
flagUnsigned
|
||||
flagZeroFill
|
||||
flagBinary
|
||||
flagEnum
|
||||
flagAutoIncrement
|
||||
flagTimestamp
|
||||
flagSet
|
||||
flagUnknown1
|
||||
flagUnknown2
|
||||
flagUnknown3
|
||||
flagUnknown4
|
||||
)
|
||||
|
||||
// http://dev.mysql.com/doc/internals/en/status-flags.html
|
||||
type statusFlag uint16
|
||||
|
||||
const (
|
||||
statusInTrans statusFlag = 1 << iota
|
||||
statusInAutocommit
|
||||
statusReserved // Not in documentation
|
||||
statusMoreResultsExists
|
||||
statusNoGoodIndexUsed
|
||||
statusNoIndexUsed
|
||||
statusCursorExists
|
||||
statusLastRowSent
|
||||
statusDbDropped
|
||||
statusNoBackslashEscapes
|
||||
statusMetadataChanged
|
||||
statusQueryWasSlow
|
||||
statusPsOutParams
|
||||
statusInTransReadonly
|
||||
statusSessionStateChanged
|
||||
)
|
||||
|
||||
const (
|
||||
cachingSha2PasswordRequestPublicKey = 2
|
||||
cachingSha2PasswordFastAuthSuccess = 3
|
||||
cachingSha2PasswordPerformFullAuthentication = 4
|
||||
)
|
||||
169
vendor/github.com/go-sql-driver/mysql/driver.go
generated
vendored
Normal file
169
vendor/github.com/go-sql-driver/mysql/driver.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package mysql provides a MySQL driver for Go's database/sql package.
|
||||
//
|
||||
// The driver should be used via the database/sql package:
|
||||
//
|
||||
// import "database/sql"
|
||||
// import _ "github.com/go-sql-driver/mysql"
|
||||
//
|
||||
// db, err := sql.Open("mysql", "user:password@/dbname")
|
||||
//
|
||||
// See https://github.com/go-sql-driver/mysql#usage for details
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// watcher interface is used for context support (From Go 1.8)
|
||||
type watcher interface {
|
||||
startWatcher()
|
||||
}
|
||||
|
||||
// MySQLDriver is exported to make the driver directly accessible.
|
||||
// In general the driver is used via the database/sql package.
|
||||
type MySQLDriver struct{}
|
||||
|
||||
// DialFunc is a function which can be used to establish the network connection.
|
||||
// Custom dial functions must be registered with RegisterDial
|
||||
type DialFunc func(addr string) (net.Conn, error)
|
||||
|
||||
var (
|
||||
dialsLock sync.RWMutex
|
||||
dials map[string]DialFunc
|
||||
)
|
||||
|
||||
// RegisterDial registers a custom dial function. It can then be used by the
|
||||
// network address mynet(addr), where mynet is the registered new network.
|
||||
// addr is passed as a parameter to the dial function.
|
||||
func RegisterDial(net string, dial DialFunc) {
|
||||
dialsLock.Lock()
|
||||
defer dialsLock.Unlock()
|
||||
if dials == nil {
|
||||
dials = make(map[string]DialFunc)
|
||||
}
|
||||
dials[net] = dial
|
||||
}
|
||||
|
||||
// Open new Connection.
|
||||
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
|
||||
// the DSN string is formated
|
||||
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
|
||||
var err error
|
||||
|
||||
// New mysqlConn
|
||||
mc := &mysqlConn{
|
||||
maxAllowedPacket: maxPacketSize,
|
||||
maxWriteSize: maxPacketSize - 1,
|
||||
closech: make(chan struct{}),
|
||||
}
|
||||
mc.cfg, err = ParseDSN(dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mc.parseTime = mc.cfg.ParseTime
|
||||
|
||||
// Connect to Server
|
||||
dialsLock.RLock()
|
||||
dial, ok := dials[mc.cfg.Net]
|
||||
dialsLock.RUnlock()
|
||||
if ok {
|
||||
mc.netConn, err = dial(mc.cfg.Addr)
|
||||
} else {
|
||||
nd := net.Dialer{Timeout: mc.cfg.Timeout}
|
||||
mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Enable TCP Keepalives on TCP connections
|
||||
if tc, ok := mc.netConn.(*net.TCPConn); ok {
|
||||
if err := tc.SetKeepAlive(true); err != nil {
|
||||
// Don't send COM_QUIT before handshake.
|
||||
mc.netConn.Close()
|
||||
mc.netConn = nil
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Call startWatcher for context support (From Go 1.8)
|
||||
if s, ok := interface{}(mc).(watcher); ok {
|
||||
s.startWatcher()
|
||||
}
|
||||
|
||||
mc.buf = newBuffer(mc.netConn)
|
||||
|
||||
// Set I/O timeouts
|
||||
mc.buf.timeout = mc.cfg.ReadTimeout
|
||||
mc.writeTimeout = mc.cfg.WriteTimeout
|
||||
|
||||
// Reading Handshake Initialization Packet
|
||||
authData, plugin, err := mc.readHandshakePacket()
|
||||
if err != nil {
|
||||
mc.cleanup()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Send Client Authentication Packet
|
||||
authResp, addNUL, err := mc.auth(authData, plugin)
|
||||
if err != nil {
|
||||
// try the default auth plugin, if using the requested plugin failed
|
||||
errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
|
||||
plugin = defaultAuthPlugin
|
||||
authResp, addNUL, err = mc.auth(authData, plugin)
|
||||
if err != nil {
|
||||
mc.cleanup()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = mc.writeHandshakeResponsePacket(authResp, addNUL, plugin); err != nil {
|
||||
mc.cleanup()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handle response to auth packet, switch methods if possible
|
||||
if err = mc.handleAuthResult(authData, plugin); err != nil {
|
||||
// Authentication failed and MySQL has already closed the connection
|
||||
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
|
||||
// Do not send COM_QUIT, just cleanup and return the error.
|
||||
mc.cleanup()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if mc.cfg.MaxAllowedPacket > 0 {
|
||||
mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
|
||||
} else {
|
||||
// Get max allowed packet size
|
||||
maxap, err := mc.getSystemVar("max_allowed_packet")
|
||||
if err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
mc.maxAllowedPacket = stringToInt(maxap) - 1
|
||||
}
|
||||
if mc.maxAllowedPacket < maxPacketSize {
|
||||
mc.maxWriteSize = mc.maxAllowedPacket
|
||||
}
|
||||
|
||||
// Handle DSN Params
|
||||
err = mc.handleParams()
|
||||
if err != nil {
|
||||
mc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mc, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
sql.Register("mysql", &MySQLDriver{})
|
||||
}
|
||||
611
vendor/github.com/go-sql-driver/mysql/dsn.go
generated
vendored
Normal file
611
vendor/github.com/go-sql-driver/mysql/dsn.go
generated
vendored
Normal file
@@ -0,0 +1,611 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidDSNUnescaped = errors.New("invalid DSN: did you forget to escape a param value?")
|
||||
errInvalidDSNAddr = errors.New("invalid DSN: network address not terminated (missing closing brace)")
|
||||
errInvalidDSNNoSlash = errors.New("invalid DSN: missing the slash separating the database name")
|
||||
errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
|
||||
)
|
||||
|
||||
// Config is a configuration parsed from a DSN string.
|
||||
// If a new Config is created instead of being parsed from a DSN string,
|
||||
// the NewConfig function should be used, which sets default values.
|
||||
type Config struct {
|
||||
User string // Username
|
||||
Passwd string // Password (requires User)
|
||||
Net string // Network type
|
||||
Addr string // Network address (requires Net)
|
||||
DBName string // Database name
|
||||
Params map[string]string // Connection parameters
|
||||
Collation string // Connection collation
|
||||
Loc *time.Location // Location for time.Time values
|
||||
MaxAllowedPacket int // Max packet size allowed
|
||||
ServerPubKey string // Server public key name
|
||||
pubKey *rsa.PublicKey // Server public key
|
||||
TLSConfig string // TLS configuration name
|
||||
tls *tls.Config // TLS configuration
|
||||
Timeout time.Duration // Dial timeout
|
||||
ReadTimeout time.Duration // I/O read timeout
|
||||
WriteTimeout time.Duration // I/O write timeout
|
||||
|
||||
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
|
||||
AllowCleartextPasswords bool // Allows the cleartext client side plugin
|
||||
AllowNativePasswords bool // Allows the native password authentication method
|
||||
AllowOldPasswords bool // Allows the old insecure password method
|
||||
ClientFoundRows bool // Return number of matching rows instead of rows changed
|
||||
ColumnsWithAlias bool // Prepend table alias to column names
|
||||
InterpolateParams bool // Interpolate placeholders into query string
|
||||
MultiStatements bool // Allow multiple statements in one query
|
||||
ParseTime bool // Parse time values to time.Time
|
||||
RejectReadOnly bool // Reject read-only connections
|
||||
}
|
||||
|
||||
// NewConfig creates a new Config and sets default values.
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
Collation: defaultCollation,
|
||||
Loc: time.UTC,
|
||||
MaxAllowedPacket: defaultMaxAllowedPacket,
|
||||
AllowNativePasswords: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *Config) normalize() error {
|
||||
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
|
||||
return errInvalidDSNUnsafeCollation
|
||||
}
|
||||
|
||||
// Set default network if empty
|
||||
if cfg.Net == "" {
|
||||
cfg.Net = "tcp"
|
||||
}
|
||||
|
||||
// Set default address if empty
|
||||
if cfg.Addr == "" {
|
||||
switch cfg.Net {
|
||||
case "tcp":
|
||||
cfg.Addr = "127.0.0.1:3306"
|
||||
case "unix":
|
||||
cfg.Addr = "/tmp/mysql.sock"
|
||||
default:
|
||||
return errors.New("default addr for network '" + cfg.Net + "' unknown")
|
||||
}
|
||||
|
||||
} else if cfg.Net == "tcp" {
|
||||
cfg.Addr = ensureHavePort(cfg.Addr)
|
||||
}
|
||||
|
||||
if cfg.tls != nil {
|
||||
if cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify {
|
||||
host, _, err := net.SplitHostPort(cfg.Addr)
|
||||
if err == nil {
|
||||
cfg.tls.ServerName = host
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormatDSN formats the given Config into a DSN string which can be passed to
|
||||
// the driver.
|
||||
func (cfg *Config) FormatDSN() string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// [username[:password]@]
|
||||
if len(cfg.User) > 0 {
|
||||
buf.WriteString(cfg.User)
|
||||
if len(cfg.Passwd) > 0 {
|
||||
buf.WriteByte(':')
|
||||
buf.WriteString(cfg.Passwd)
|
||||
}
|
||||
buf.WriteByte('@')
|
||||
}
|
||||
|
||||
// [protocol[(address)]]
|
||||
if len(cfg.Net) > 0 {
|
||||
buf.WriteString(cfg.Net)
|
||||
if len(cfg.Addr) > 0 {
|
||||
buf.WriteByte('(')
|
||||
buf.WriteString(cfg.Addr)
|
||||
buf.WriteByte(')')
|
||||
}
|
||||
}
|
||||
|
||||
// /dbname
|
||||
buf.WriteByte('/')
|
||||
buf.WriteString(cfg.DBName)
|
||||
|
||||
// [?param1=value1&...¶mN=valueN]
|
||||
hasParam := false
|
||||
|
||||
if cfg.AllowAllFiles {
|
||||
hasParam = true
|
||||
buf.WriteString("?allowAllFiles=true")
|
||||
}
|
||||
|
||||
if cfg.AllowCleartextPasswords {
|
||||
if hasParam {
|
||||
buf.WriteString("&allowCleartextPasswords=true")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?allowCleartextPasswords=true")
|
||||
}
|
||||
}
|
||||
|
||||
if !cfg.AllowNativePasswords {
|
||||
if hasParam {
|
||||
buf.WriteString("&allowNativePasswords=false")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?allowNativePasswords=false")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.AllowOldPasswords {
|
||||
if hasParam {
|
||||
buf.WriteString("&allowOldPasswords=true")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?allowOldPasswords=true")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.ClientFoundRows {
|
||||
if hasParam {
|
||||
buf.WriteString("&clientFoundRows=true")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?clientFoundRows=true")
|
||||
}
|
||||
}
|
||||
|
||||
if col := cfg.Collation; col != defaultCollation && len(col) > 0 {
|
||||
if hasParam {
|
||||
buf.WriteString("&collation=")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?collation=")
|
||||
}
|
||||
buf.WriteString(col)
|
||||
}
|
||||
|
||||
if cfg.ColumnsWithAlias {
|
||||
if hasParam {
|
||||
buf.WriteString("&columnsWithAlias=true")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?columnsWithAlias=true")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.InterpolateParams {
|
||||
if hasParam {
|
||||
buf.WriteString("&interpolateParams=true")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?interpolateParams=true")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.Loc != time.UTC && cfg.Loc != nil {
|
||||
if hasParam {
|
||||
buf.WriteString("&loc=")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?loc=")
|
||||
}
|
||||
buf.WriteString(url.QueryEscape(cfg.Loc.String()))
|
||||
}
|
||||
|
||||
if cfg.MultiStatements {
|
||||
if hasParam {
|
||||
buf.WriteString("&multiStatements=true")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?multiStatements=true")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.ParseTime {
|
||||
if hasParam {
|
||||
buf.WriteString("&parseTime=true")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?parseTime=true")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.ReadTimeout > 0 {
|
||||
if hasParam {
|
||||
buf.WriteString("&readTimeout=")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?readTimeout=")
|
||||
}
|
||||
buf.WriteString(cfg.ReadTimeout.String())
|
||||
}
|
||||
|
||||
if cfg.RejectReadOnly {
|
||||
if hasParam {
|
||||
buf.WriteString("&rejectReadOnly=true")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?rejectReadOnly=true")
|
||||
}
|
||||
}
|
||||
|
||||
if len(cfg.ServerPubKey) > 0 {
|
||||
if hasParam {
|
||||
buf.WriteString("&serverPubKey=")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?serverPubKey=")
|
||||
}
|
||||
buf.WriteString(url.QueryEscape(cfg.ServerPubKey))
|
||||
}
|
||||
|
||||
if cfg.Timeout > 0 {
|
||||
if hasParam {
|
||||
buf.WriteString("&timeout=")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?timeout=")
|
||||
}
|
||||
buf.WriteString(cfg.Timeout.String())
|
||||
}
|
||||
|
||||
if len(cfg.TLSConfig) > 0 {
|
||||
if hasParam {
|
||||
buf.WriteString("&tls=")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?tls=")
|
||||
}
|
||||
buf.WriteString(url.QueryEscape(cfg.TLSConfig))
|
||||
}
|
||||
|
||||
if cfg.WriteTimeout > 0 {
|
||||
if hasParam {
|
||||
buf.WriteString("&writeTimeout=")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?writeTimeout=")
|
||||
}
|
||||
buf.WriteString(cfg.WriteTimeout.String())
|
||||
}
|
||||
|
||||
if cfg.MaxAllowedPacket != defaultMaxAllowedPacket {
|
||||
if hasParam {
|
||||
buf.WriteString("&maxAllowedPacket=")
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteString("?maxAllowedPacket=")
|
||||
}
|
||||
buf.WriteString(strconv.Itoa(cfg.MaxAllowedPacket))
|
||||
|
||||
}
|
||||
|
||||
// other params
|
||||
if cfg.Params != nil {
|
||||
var params []string
|
||||
for param := range cfg.Params {
|
||||
params = append(params, param)
|
||||
}
|
||||
sort.Strings(params)
|
||||
for _, param := range params {
|
||||
if hasParam {
|
||||
buf.WriteByte('&')
|
||||
} else {
|
||||
hasParam = true
|
||||
buf.WriteByte('?')
|
||||
}
|
||||
|
||||
buf.WriteString(param)
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(url.QueryEscape(cfg.Params[param]))
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// ParseDSN parses the DSN string to a Config
|
||||
func ParseDSN(dsn string) (cfg *Config, err error) {
|
||||
// New config with some default values
|
||||
cfg = NewConfig()
|
||||
|
||||
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN]
|
||||
// Find the last '/' (since the password or the net addr might contain a '/')
|
||||
foundSlash := false
|
||||
for i := len(dsn) - 1; i >= 0; i-- {
|
||||
if dsn[i] == '/' {
|
||||
foundSlash = true
|
||||
var j, k int
|
||||
|
||||
// left part is empty if i <= 0
|
||||
if i > 0 {
|
||||
// [username[:password]@][protocol[(address)]]
|
||||
// Find the last '@' in dsn[:i]
|
||||
for j = i; j >= 0; j-- {
|
||||
if dsn[j] == '@' {
|
||||
// username[:password]
|
||||
// Find the first ':' in dsn[:j]
|
||||
for k = 0; k < j; k++ {
|
||||
if dsn[k] == ':' {
|
||||
cfg.Passwd = dsn[k+1 : j]
|
||||
break
|
||||
}
|
||||
}
|
||||
cfg.User = dsn[:k]
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// [protocol[(address)]]
|
||||
// Find the first '(' in dsn[j+1:i]
|
||||
for k = j + 1; k < i; k++ {
|
||||
if dsn[k] == '(' {
|
||||
// dsn[i-1] must be == ')' if an address is specified
|
||||
if dsn[i-1] != ')' {
|
||||
if strings.ContainsRune(dsn[k+1:i], ')') {
|
||||
return nil, errInvalidDSNUnescaped
|
||||
}
|
||||
return nil, errInvalidDSNAddr
|
||||
}
|
||||
cfg.Addr = dsn[k+1 : i-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
cfg.Net = dsn[j+1 : k]
|
||||
}
|
||||
|
||||
// dbname[?param1=value1&...¶mN=valueN]
|
||||
// Find the first '?' in dsn[i+1:]
|
||||
for j = i + 1; j < len(dsn); j++ {
|
||||
if dsn[j] == '?' {
|
||||
if err = parseDSNParams(cfg, dsn[j+1:]); err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
cfg.DBName = dsn[i+1 : j]
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !foundSlash && len(dsn) > 0 {
|
||||
return nil, errInvalidDSNNoSlash
|
||||
}
|
||||
|
||||
if err = cfg.normalize(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// parseDSNParams parses the DSN "query string"
|
||||
// Values must be url.QueryEscape'ed
|
||||
func parseDSNParams(cfg *Config, params string) (err error) {
|
||||
for _, v := range strings.Split(params, "&") {
|
||||
param := strings.SplitN(v, "=", 2)
|
||||
if len(param) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// cfg params
|
||||
switch value := param[1]; param[0] {
|
||||
// Disable INFILE whitelist / enable all files
|
||||
case "allowAllFiles":
|
||||
var isBool bool
|
||||
cfg.AllowAllFiles, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return errors.New("invalid bool value: " + value)
|
||||
}
|
||||
|
||||
// Use cleartext authentication mode (MySQL 5.5.10+)
|
||||
case "allowCleartextPasswords":
|
||||
var isBool bool
|
||||
cfg.AllowCleartextPasswords, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return errors.New("invalid bool value: " + value)
|
||||
}
|
||||
|
||||
// Use native password authentication
|
||||
case "allowNativePasswords":
|
||||
var isBool bool
|
||||
cfg.AllowNativePasswords, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return errors.New("invalid bool value: " + value)
|
||||
}
|
||||
|
||||
// Use old authentication mode (pre MySQL 4.1)
|
||||
case "allowOldPasswords":
|
||||
var isBool bool
|
||||
cfg.AllowOldPasswords, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return errors.New("invalid bool value: " + value)
|
||||
}
|
||||
|
||||
// Switch "rowsAffected" mode
|
||||
case "clientFoundRows":
|
||||
var isBool bool
|
||||
cfg.ClientFoundRows, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return errors.New("invalid bool value: " + value)
|
||||
}
|
||||
|
||||
// Collation
|
||||
case "collation":
|
||||
cfg.Collation = value
|
||||
break
|
||||
|
||||
case "columnsWithAlias":
|
||||
var isBool bool
|
||||
cfg.ColumnsWithAlias, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return errors.New("invalid bool value: " + value)
|
||||
}
|
||||
|
||||
// Compression
|
||||
case "compress":
|
||||
return errors.New("compression not implemented yet")
|
||||
|
||||
// Enable client side placeholder substitution
|
||||
case "interpolateParams":
|
||||
var isBool bool
|
||||
cfg.InterpolateParams, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return errors.New("invalid bool value: " + value)
|
||||
}
|
||||
|
||||
// Time Location
|
||||
case "loc":
|
||||
if value, err = url.QueryUnescape(value); err != nil {
|
||||
return
|
||||
}
|
||||
cfg.Loc, err = time.LoadLocation(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// multiple statements in one query
|
||||
case "multiStatements":
|
||||
var isBool bool
|
||||
cfg.MultiStatements, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return errors.New("invalid bool value: " + value)
|
||||
}
|
||||
|
||||
// time.Time parsing
|
||||
case "parseTime":
|
||||
var isBool bool
|
||||
cfg.ParseTime, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return errors.New("invalid bool value: " + value)
|
||||
}
|
||||
|
||||
// I/O read Timeout
|
||||
case "readTimeout":
|
||||
cfg.ReadTimeout, err = time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Reject read-only connections
|
||||
case "rejectReadOnly":
|
||||
var isBool bool
|
||||
cfg.RejectReadOnly, isBool = readBool(value)
|
||||
if !isBool {
|
||||
return errors.New("invalid bool value: " + value)
|
||||
}
|
||||
|
||||
// Server public key
|
||||
case "serverPubKey":
|
||||
name, err := url.QueryUnescape(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for server pub key name: %v", err)
|
||||
}
|
||||
|
||||
if pubKey := getServerPubKey(name); pubKey != nil {
|
||||
cfg.ServerPubKey = name
|
||||
cfg.pubKey = pubKey
|
||||
} else {
|
||||
return errors.New("invalid value / unknown server pub key name: " + name)
|
||||
}
|
||||
|
||||
// Strict mode
|
||||
case "strict":
|
||||
panic("strict mode has been removed. See https://github.com/go-sql-driver/mysql/wiki/strict-mode")
|
||||
|
||||
// Dial Timeout
|
||||
case "timeout":
|
||||
cfg.Timeout, err = time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TLS-Encryption
|
||||
case "tls":
|
||||
boolValue, isBool := readBool(value)
|
||||
if isBool {
|
||||
if boolValue {
|
||||
cfg.TLSConfig = "true"
|
||||
cfg.tls = &tls.Config{}
|
||||
} else {
|
||||
cfg.TLSConfig = "false"
|
||||
}
|
||||
} else if vl := strings.ToLower(value); vl == "skip-verify" {
|
||||
cfg.TLSConfig = vl
|
||||
cfg.tls = &tls.Config{InsecureSkipVerify: true}
|
||||
} else {
|
||||
name, err := url.QueryUnescape(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for TLS config name: %v", err)
|
||||
}
|
||||
|
||||
if tlsConfig := getTLSConfigClone(name); tlsConfig != nil {
|
||||
cfg.TLSConfig = name
|
||||
cfg.tls = tlsConfig
|
||||
} else {
|
||||
return errors.New("invalid value / unknown config name: " + name)
|
||||
}
|
||||
}
|
||||
|
||||
// I/O write Timeout
|
||||
case "writeTimeout":
|
||||
cfg.WriteTimeout, err = time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case "maxAllowedPacket":
|
||||
cfg.MaxAllowedPacket, err = strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
default:
|
||||
// lazy init
|
||||
if cfg.Params == nil {
|
||||
cfg.Params = make(map[string]string)
|
||||
}
|
||||
|
||||
if cfg.Params[param[0]], err = url.QueryUnescape(value); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ensureHavePort(addr string) string {
|
||||
if _, _, err := net.SplitHostPort(addr); err != nil {
|
||||
return net.JoinHostPort(addr, "3306")
|
||||
}
|
||||
return addr
|
||||
}
|
||||
65
vendor/github.com/go-sql-driver/mysql/errors.go
generated
vendored
Normal file
65
vendor/github.com/go-sql-driver/mysql/errors.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Various errors the driver might return. Can change between driver versions.
|
||||
var (
|
||||
ErrInvalidConn = errors.New("invalid connection")
|
||||
ErrMalformPkt = errors.New("malformed packet")
|
||||
ErrNoTLS = errors.New("TLS requested but server does not support TLS")
|
||||
ErrCleartextPassword = errors.New("this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN")
|
||||
ErrNativePassword = errors.New("this user requires mysql native password authentication.")
|
||||
ErrOldPassword = errors.New("this user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
|
||||
ErrUnknownPlugin = errors.New("this authentication plugin is not supported")
|
||||
ErrOldProtocol = errors.New("MySQL server does not support required protocol 41+")
|
||||
ErrPktSync = errors.New("commands out of sync. You can't run this command now")
|
||||
ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?")
|
||||
ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
|
||||
ErrBusyBuffer = errors.New("busy buffer")
|
||||
|
||||
// errBadConnNoWrite is used for connection errors where nothing was sent to the database yet.
|
||||
// If this happens first in a function starting a database interaction, it should be replaced by driver.ErrBadConn
|
||||
// to trigger a resend.
|
||||
// See https://github.com/go-sql-driver/mysql/pull/302
|
||||
errBadConnNoWrite = errors.New("bad connection")
|
||||
)
|
||||
|
||||
var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
|
||||
|
||||
// Logger is used to log critical error messages.
|
||||
type Logger interface {
|
||||
Print(v ...interface{})
|
||||
}
|
||||
|
||||
// SetLogger is used to set the logger for critical errors.
|
||||
// The initial logger is os.Stderr.
|
||||
func SetLogger(logger Logger) error {
|
||||
if logger == nil {
|
||||
return errors.New("logger is nil")
|
||||
}
|
||||
errLog = logger
|
||||
return nil
|
||||
}
|
||||
|
||||
// MySQLError is an error type which represents a single MySQL error
|
||||
type MySQLError struct {
|
||||
Number uint16
|
||||
Message string
|
||||
}
|
||||
|
||||
func (me *MySQLError) Error() string {
|
||||
return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
|
||||
}
|
||||
194
vendor/github.com/go-sql-driver/mysql/fields.go
generated
vendored
Normal file
194
vendor/github.com/go-sql-driver/mysql/fields.go
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func (mf *mysqlField) typeDatabaseName() string {
|
||||
switch mf.fieldType {
|
||||
case fieldTypeBit:
|
||||
return "BIT"
|
||||
case fieldTypeBLOB:
|
||||
if mf.charSet != collations[binaryCollation] {
|
||||
return "TEXT"
|
||||
}
|
||||
return "BLOB"
|
||||
case fieldTypeDate:
|
||||
return "DATE"
|
||||
case fieldTypeDateTime:
|
||||
return "DATETIME"
|
||||
case fieldTypeDecimal:
|
||||
return "DECIMAL"
|
||||
case fieldTypeDouble:
|
||||
return "DOUBLE"
|
||||
case fieldTypeEnum:
|
||||
return "ENUM"
|
||||
case fieldTypeFloat:
|
||||
return "FLOAT"
|
||||
case fieldTypeGeometry:
|
||||
return "GEOMETRY"
|
||||
case fieldTypeInt24:
|
||||
return "MEDIUMINT"
|
||||
case fieldTypeJSON:
|
||||
return "JSON"
|
||||
case fieldTypeLong:
|
||||
return "INT"
|
||||
case fieldTypeLongBLOB:
|
||||
if mf.charSet != collations[binaryCollation] {
|
||||
return "LONGTEXT"
|
||||
}
|
||||
return "LONGBLOB"
|
||||
case fieldTypeLongLong:
|
||||
return "BIGINT"
|
||||
case fieldTypeMediumBLOB:
|
||||
if mf.charSet != collations[binaryCollation] {
|
||||
return "MEDIUMTEXT"
|
||||
}
|
||||
return "MEDIUMBLOB"
|
||||
case fieldTypeNewDate:
|
||||
return "DATE"
|
||||
case fieldTypeNewDecimal:
|
||||
return "DECIMAL"
|
||||
case fieldTypeNULL:
|
||||
return "NULL"
|
||||
case fieldTypeSet:
|
||||
return "SET"
|
||||
case fieldTypeShort:
|
||||
return "SMALLINT"
|
||||
case fieldTypeString:
|
||||
if mf.charSet == collations[binaryCollation] {
|
||||
return "BINARY"
|
||||
}
|
||||
return "CHAR"
|
||||
case fieldTypeTime:
|
||||
return "TIME"
|
||||
case fieldTypeTimestamp:
|
||||
return "TIMESTAMP"
|
||||
case fieldTypeTiny:
|
||||
return "TINYINT"
|
||||
case fieldTypeTinyBLOB:
|
||||
if mf.charSet != collations[binaryCollation] {
|
||||
return "TINYTEXT"
|
||||
}
|
||||
return "TINYBLOB"
|
||||
case fieldTypeVarChar:
|
||||
if mf.charSet == collations[binaryCollation] {
|
||||
return "VARBINARY"
|
||||
}
|
||||
return "VARCHAR"
|
||||
case fieldTypeVarString:
|
||||
if mf.charSet == collations[binaryCollation] {
|
||||
return "VARBINARY"
|
||||
}
|
||||
return "VARCHAR"
|
||||
case fieldTypeYear:
|
||||
return "YEAR"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
scanTypeFloat32 = reflect.TypeOf(float32(0))
|
||||
scanTypeFloat64 = reflect.TypeOf(float64(0))
|
||||
scanTypeInt8 = reflect.TypeOf(int8(0))
|
||||
scanTypeInt16 = reflect.TypeOf(int16(0))
|
||||
scanTypeInt32 = reflect.TypeOf(int32(0))
|
||||
scanTypeInt64 = reflect.TypeOf(int64(0))
|
||||
scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
|
||||
scanTypeNullInt = reflect.TypeOf(sql.NullInt64{})
|
||||
scanTypeNullTime = reflect.TypeOf(NullTime{})
|
||||
scanTypeUint8 = reflect.TypeOf(uint8(0))
|
||||
scanTypeUint16 = reflect.TypeOf(uint16(0))
|
||||
scanTypeUint32 = reflect.TypeOf(uint32(0))
|
||||
scanTypeUint64 = reflect.TypeOf(uint64(0))
|
||||
scanTypeRawBytes = reflect.TypeOf(sql.RawBytes{})
|
||||
scanTypeUnknown = reflect.TypeOf(new(interface{}))
|
||||
)
|
||||
|
||||
type mysqlField struct {
|
||||
tableName string
|
||||
name string
|
||||
length uint32
|
||||
flags fieldFlag
|
||||
fieldType fieldType
|
||||
decimals byte
|
||||
charSet uint8
|
||||
}
|
||||
|
||||
func (mf *mysqlField) scanType() reflect.Type {
|
||||
switch mf.fieldType {
|
||||
case fieldTypeTiny:
|
||||
if mf.flags&flagNotNULL != 0 {
|
||||
if mf.flags&flagUnsigned != 0 {
|
||||
return scanTypeUint8
|
||||
}
|
||||
return scanTypeInt8
|
||||
}
|
||||
return scanTypeNullInt
|
||||
|
||||
case fieldTypeShort, fieldTypeYear:
|
||||
if mf.flags&flagNotNULL != 0 {
|
||||
if mf.flags&flagUnsigned != 0 {
|
||||
return scanTypeUint16
|
||||
}
|
||||
return scanTypeInt16
|
||||
}
|
||||
return scanTypeNullInt
|
||||
|
||||
case fieldTypeInt24, fieldTypeLong:
|
||||
if mf.flags&flagNotNULL != 0 {
|
||||
if mf.flags&flagUnsigned != 0 {
|
||||
return scanTypeUint32
|
||||
}
|
||||
return scanTypeInt32
|
||||
}
|
||||
return scanTypeNullInt
|
||||
|
||||
case fieldTypeLongLong:
|
||||
if mf.flags&flagNotNULL != 0 {
|
||||
if mf.flags&flagUnsigned != 0 {
|
||||
return scanTypeUint64
|
||||
}
|
||||
return scanTypeInt64
|
||||
}
|
||||
return scanTypeNullInt
|
||||
|
||||
case fieldTypeFloat:
|
||||
if mf.flags&flagNotNULL != 0 {
|
||||
return scanTypeFloat32
|
||||
}
|
||||
return scanTypeNullFloat
|
||||
|
||||
case fieldTypeDouble:
|
||||
if mf.flags&flagNotNULL != 0 {
|
||||
return scanTypeFloat64
|
||||
}
|
||||
return scanTypeNullFloat
|
||||
|
||||
case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
|
||||
fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
|
||||
fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
|
||||
fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON,
|
||||
fieldTypeTime:
|
||||
return scanTypeRawBytes
|
||||
|
||||
case fieldTypeDate, fieldTypeNewDate,
|
||||
fieldTypeTimestamp, fieldTypeDateTime:
|
||||
// NullTime is always returned for more consistent behavior as it can
|
||||
// handle both cases of parseTime regardless if the field is nullable.
|
||||
return scanTypeNullTime
|
||||
|
||||
default:
|
||||
return scanTypeUnknown
|
||||
}
|
||||
}
|
||||
182
vendor/github.com/go-sql-driver/mysql/infile.go
generated
vendored
Normal file
182
vendor/github.com/go-sql-driver/mysql/infile.go
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
fileRegister map[string]bool
|
||||
fileRegisterLock sync.RWMutex
|
||||
readerRegister map[string]func() io.Reader
|
||||
readerRegisterLock sync.RWMutex
|
||||
)
|
||||
|
||||
// RegisterLocalFile adds the given file to the file whitelist,
|
||||
// so that it can be used by "LOAD DATA LOCAL INFILE <filepath>".
|
||||
// Alternatively you can allow the use of all local files with
|
||||
// the DSN parameter 'allowAllFiles=true'
|
||||
//
|
||||
// filePath := "/home/gopher/data.csv"
|
||||
// mysql.RegisterLocalFile(filePath)
|
||||
// err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo")
|
||||
// if err != nil {
|
||||
// ...
|
||||
//
|
||||
func RegisterLocalFile(filePath string) {
|
||||
fileRegisterLock.Lock()
|
||||
// lazy map init
|
||||
if fileRegister == nil {
|
||||
fileRegister = make(map[string]bool)
|
||||
}
|
||||
|
||||
fileRegister[strings.Trim(filePath, `"`)] = true
|
||||
fileRegisterLock.Unlock()
|
||||
}
|
||||
|
||||
// DeregisterLocalFile removes the given filepath from the whitelist.
|
||||
func DeregisterLocalFile(filePath string) {
|
||||
fileRegisterLock.Lock()
|
||||
delete(fileRegister, strings.Trim(filePath, `"`))
|
||||
fileRegisterLock.Unlock()
|
||||
}
|
||||
|
||||
// RegisterReaderHandler registers a handler function which is used
|
||||
// to receive a io.Reader.
|
||||
// The Reader can be used by "LOAD DATA LOCAL INFILE Reader::<name>".
|
||||
// If the handler returns a io.ReadCloser Close() is called when the
|
||||
// request is finished.
|
||||
//
|
||||
// mysql.RegisterReaderHandler("data", func() io.Reader {
|
||||
// var csvReader io.Reader // Some Reader that returns CSV data
|
||||
// ... // Open Reader here
|
||||
// return csvReader
|
||||
// })
|
||||
// err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo")
|
||||
// if err != nil {
|
||||
// ...
|
||||
//
|
||||
func RegisterReaderHandler(name string, handler func() io.Reader) {
|
||||
readerRegisterLock.Lock()
|
||||
// lazy map init
|
||||
if readerRegister == nil {
|
||||
readerRegister = make(map[string]func() io.Reader)
|
||||
}
|
||||
|
||||
readerRegister[name] = handler
|
||||
readerRegisterLock.Unlock()
|
||||
}
|
||||
|
||||
// DeregisterReaderHandler removes the ReaderHandler function with
|
||||
// the given name from the registry.
|
||||
func DeregisterReaderHandler(name string) {
|
||||
readerRegisterLock.Lock()
|
||||
delete(readerRegister, name)
|
||||
readerRegisterLock.Unlock()
|
||||
}
|
||||
|
||||
func deferredClose(err *error, closer io.Closer) {
|
||||
closeErr := closer.Close()
|
||||
if *err == nil {
|
||||
*err = closeErr
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
|
||||
var rdr io.Reader
|
||||
var data []byte
|
||||
packetSize := 16 * 1024 // 16KB is small enough for disk readahead and large enough for TCP
|
||||
if mc.maxWriteSize < packetSize {
|
||||
packetSize = mc.maxWriteSize
|
||||
}
|
||||
|
||||
if idx := strings.Index(name, "Reader::"); idx == 0 || (idx > 0 && name[idx-1] == '/') { // io.Reader
|
||||
// The server might return an an absolute path. See issue #355.
|
||||
name = name[idx+8:]
|
||||
|
||||
readerRegisterLock.RLock()
|
||||
handler, inMap := readerRegister[name]
|
||||
readerRegisterLock.RUnlock()
|
||||
|
||||
if inMap {
|
||||
rdr = handler()
|
||||
if rdr != nil {
|
||||
if cl, ok := rdr.(io.Closer); ok {
|
||||
defer deferredClose(&err, cl)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Reader '%s' is <nil>", name)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Reader '%s' is not registered", name)
|
||||
}
|
||||
} else { // File
|
||||
name = strings.Trim(name, `"`)
|
||||
fileRegisterLock.RLock()
|
||||
fr := fileRegister[name]
|
||||
fileRegisterLock.RUnlock()
|
||||
if mc.cfg.AllowAllFiles || fr {
|
||||
var file *os.File
|
||||
var fi os.FileInfo
|
||||
|
||||
if file, err = os.Open(name); err == nil {
|
||||
defer deferredClose(&err, file)
|
||||
|
||||
// get file size
|
||||
if fi, err = file.Stat(); err == nil {
|
||||
rdr = file
|
||||
if fileSize := int(fi.Size()); fileSize < packetSize {
|
||||
packetSize = fileSize
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("local file '%s' is not registered", name)
|
||||
}
|
||||
}
|
||||
|
||||
// send content packets
|
||||
// if packetSize == 0, the Reader contains no data
|
||||
if err == nil && packetSize > 0 {
|
||||
data := make([]byte, 4+packetSize)
|
||||
var n int
|
||||
for err == nil {
|
||||
n, err = rdr.Read(data[4:])
|
||||
if n > 0 {
|
||||
if ioErr := mc.writePacket(data[:4+n]); ioErr != nil {
|
||||
return ioErr
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
// send empty packet (termination)
|
||||
if data == nil {
|
||||
data = make([]byte, 4)
|
||||
}
|
||||
if ioErr := mc.writePacket(data[:4]); ioErr != nil {
|
||||
return ioErr
|
||||
}
|
||||
|
||||
// read OK packet
|
||||
if err == nil {
|
||||
return mc.readResultOK()
|
||||
}
|
||||
|
||||
mc.readPacket()
|
||||
return err
|
||||
}
|
||||
1301
vendor/github.com/go-sql-driver/mysql/packets.go
generated
vendored
Normal file
1301
vendor/github.com/go-sql-driver/mysql/packets.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
22
vendor/github.com/go-sql-driver/mysql/result.go
generated
vendored
Normal file
22
vendor/github.com/go-sql-driver/mysql/result.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
type mysqlResult struct {
|
||||
affectedRows int64
|
||||
insertId int64
|
||||
}
|
||||
|
||||
func (res *mysqlResult) LastInsertId() (int64, error) {
|
||||
return res.insertId, nil
|
||||
}
|
||||
|
||||
func (res *mysqlResult) RowsAffected() (int64, error) {
|
||||
return res.affectedRows, nil
|
||||
}
|
||||
216
vendor/github.com/go-sql-driver/mysql/rows.go
generated
vendored
Normal file
216
vendor/github.com/go-sql-driver/mysql/rows.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type resultSet struct {
|
||||
columns []mysqlField
|
||||
columnNames []string
|
||||
done bool
|
||||
}
|
||||
|
||||
type mysqlRows struct {
|
||||
mc *mysqlConn
|
||||
rs resultSet
|
||||
finish func()
|
||||
}
|
||||
|
||||
type binaryRows struct {
|
||||
mysqlRows
|
||||
}
|
||||
|
||||
type textRows struct {
|
||||
mysqlRows
|
||||
}
|
||||
|
||||
func (rows *mysqlRows) Columns() []string {
|
||||
if rows.rs.columnNames != nil {
|
||||
return rows.rs.columnNames
|
||||
}
|
||||
|
||||
columns := make([]string, len(rows.rs.columns))
|
||||
if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias {
|
||||
for i := range columns {
|
||||
if tableName := rows.rs.columns[i].tableName; len(tableName) > 0 {
|
||||
columns[i] = tableName + "." + rows.rs.columns[i].name
|
||||
} else {
|
||||
columns[i] = rows.rs.columns[i].name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := range columns {
|
||||
columns[i] = rows.rs.columns[i].name
|
||||
}
|
||||
}
|
||||
|
||||
rows.rs.columnNames = columns
|
||||
return columns
|
||||
}
|
||||
|
||||
func (rows *mysqlRows) ColumnTypeDatabaseTypeName(i int) string {
|
||||
return rows.rs.columns[i].typeDatabaseName()
|
||||
}
|
||||
|
||||
// func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) {
|
||||
// return int64(rows.rs.columns[i].length), true
|
||||
// }
|
||||
|
||||
func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) {
|
||||
return rows.rs.columns[i].flags&flagNotNULL == 0, true
|
||||
}
|
||||
|
||||
func (rows *mysqlRows) ColumnTypePrecisionScale(i int) (int64, int64, bool) {
|
||||
column := rows.rs.columns[i]
|
||||
decimals := int64(column.decimals)
|
||||
|
||||
switch column.fieldType {
|
||||
case fieldTypeDecimal, fieldTypeNewDecimal:
|
||||
if decimals > 0 {
|
||||
return int64(column.length) - 2, decimals, true
|
||||
}
|
||||
return int64(column.length) - 1, decimals, true
|
||||
case fieldTypeTimestamp, fieldTypeDateTime, fieldTypeTime:
|
||||
return decimals, decimals, true
|
||||
case fieldTypeFloat, fieldTypeDouble:
|
||||
if decimals == 0x1f {
|
||||
return math.MaxInt64, math.MaxInt64, true
|
||||
}
|
||||
return math.MaxInt64, decimals, true
|
||||
}
|
||||
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
func (rows *mysqlRows) ColumnTypeScanType(i int) reflect.Type {
|
||||
return rows.rs.columns[i].scanType()
|
||||
}
|
||||
|
||||
func (rows *mysqlRows) Close() (err error) {
|
||||
if f := rows.finish; f != nil {
|
||||
f()
|
||||
rows.finish = nil
|
||||
}
|
||||
|
||||
mc := rows.mc
|
||||
if mc == nil {
|
||||
return nil
|
||||
}
|
||||
if err := mc.error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove unread packets from stream
|
||||
if !rows.rs.done {
|
||||
err = mc.readUntilEOF()
|
||||
}
|
||||
if err == nil {
|
||||
if err = mc.discardResults(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
rows.mc = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (rows *mysqlRows) HasNextResultSet() (b bool) {
|
||||
if rows.mc == nil {
|
||||
return false
|
||||
}
|
||||
return rows.mc.status&statusMoreResultsExists != 0
|
||||
}
|
||||
|
||||
func (rows *mysqlRows) nextResultSet() (int, error) {
|
||||
if rows.mc == nil {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if err := rows.mc.error(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Remove unread packets from stream
|
||||
if !rows.rs.done {
|
||||
if err := rows.mc.readUntilEOF(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
rows.rs.done = true
|
||||
}
|
||||
|
||||
if !rows.HasNextResultSet() {
|
||||
rows.mc = nil
|
||||
return 0, io.EOF
|
||||
}
|
||||
rows.rs = resultSet{}
|
||||
return rows.mc.readResultSetHeaderPacket()
|
||||
}
|
||||
|
||||
func (rows *mysqlRows) nextNotEmptyResultSet() (int, error) {
|
||||
for {
|
||||
resLen, err := rows.nextResultSet()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if resLen > 0 {
|
||||
return resLen, nil
|
||||
}
|
||||
|
||||
rows.rs.done = true
|
||||
}
|
||||
}
|
||||
|
||||
func (rows *binaryRows) NextResultSet() error {
|
||||
resLen, err := rows.nextNotEmptyResultSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows.rs.columns, err = rows.mc.readColumns(resLen)
|
||||
return err
|
||||
}
|
||||
|
||||
func (rows *binaryRows) Next(dest []driver.Value) error {
|
||||
if mc := rows.mc; mc != nil {
|
||||
if err := mc.error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetch next row from stream
|
||||
return rows.readRow(dest)
|
||||
}
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
func (rows *textRows) NextResultSet() (err error) {
|
||||
resLen, err := rows.nextNotEmptyResultSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows.rs.columns, err = rows.mc.readColumns(resLen)
|
||||
return err
|
||||
}
|
||||
|
||||
func (rows *textRows) Next(dest []driver.Value) error {
|
||||
if mc := rows.mc; mc != nil {
|
||||
if err := mc.error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetch next row from stream
|
||||
return rows.readRow(dest)
|
||||
}
|
||||
return io.EOF
|
||||
}
|
||||
211
vendor/github.com/go-sql-driver/mysql/statement.go
generated
vendored
Normal file
211
vendor/github.com/go-sql-driver/mysql/statement.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type mysqlStmt struct {
|
||||
mc *mysqlConn
|
||||
id uint32
|
||||
paramCount int
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Close() error {
|
||||
if stmt.mc == nil || stmt.mc.closed.IsSet() {
|
||||
// driver.Stmt.Close can be called more than once, thus this function
|
||||
// has to be idempotent.
|
||||
// See also Issue #450 and golang/go#16019.
|
||||
//errLog.Print(ErrInvalidConn)
|
||||
return driver.ErrBadConn
|
||||
}
|
||||
|
||||
err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)
|
||||
stmt.mc = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) NumInput() int {
|
||||
return stmt.paramCount
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
|
||||
return converter{}
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||
if stmt.mc.closed.IsSet() {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
// Send command
|
||||
err := stmt.writeExecutePacket(args)
|
||||
if err != nil {
|
||||
return nil, stmt.mc.markBadConn(err)
|
||||
}
|
||||
|
||||
mc := stmt.mc
|
||||
|
||||
mc.affectedRows = 0
|
||||
mc.insertId = 0
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resLen > 0 {
|
||||
// Columns
|
||||
if err = mc.readUntilEOF(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Rows
|
||||
if err := mc.readUntilEOF(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := mc.discardResults(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &mysqlResult{
|
||||
affectedRows: int64(mc.affectedRows),
|
||||
insertId: int64(mc.insertId),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||
return stmt.query(args)
|
||||
}
|
||||
|
||||
func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
|
||||
if stmt.mc.closed.IsSet() {
|
||||
errLog.Print(ErrInvalidConn)
|
||||
return nil, driver.ErrBadConn
|
||||
}
|
||||
// Send command
|
||||
err := stmt.writeExecutePacket(args)
|
||||
if err != nil {
|
||||
return nil, stmt.mc.markBadConn(err)
|
||||
}
|
||||
|
||||
mc := stmt.mc
|
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows := new(binaryRows)
|
||||
|
||||
if resLen > 0 {
|
||||
rows.mc = mc
|
||||
rows.rs.columns, err = mc.readColumns(resLen)
|
||||
} else {
|
||||
rows.rs.done = true
|
||||
|
||||
switch err := rows.NextResultSet(); err {
|
||||
case nil, io.EOF:
|
||||
return rows, nil
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return rows, err
|
||||
}
|
||||
|
||||
type converter struct{}
|
||||
|
||||
// ConvertValue mirrors the reference/default converter in database/sql/driver
|
||||
// with _one_ exception. We support uint64 with their high bit and the default
|
||||
// implementation does not. This function should be kept in sync with
|
||||
// database/sql/driver defaultConverter.ConvertValue() except for that
|
||||
// deliberate difference.
|
||||
func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
|
||||
if driver.IsValue(v) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
if vr, ok := v.(driver.Valuer); ok {
|
||||
sv, err := callValuerValue(vr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !driver.IsValue(sv) {
|
||||
return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
|
||||
}
|
||||
return sv, nil
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Ptr:
|
||||
// indirect pointers
|
||||
if rv.IsNil() {
|
||||
return nil, nil
|
||||
} else {
|
||||
return c.ConvertValue(rv.Elem().Interface())
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return rv.Int(), nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
return int64(rv.Uint()), nil
|
||||
case reflect.Uint64:
|
||||
u64 := rv.Uint()
|
||||
if u64 >= 1<<63 {
|
||||
return strconv.FormatUint(u64, 10), nil
|
||||
}
|
||||
return int64(u64), nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return rv.Float(), nil
|
||||
case reflect.Bool:
|
||||
return rv.Bool(), nil
|
||||
case reflect.Slice:
|
||||
ek := rv.Type().Elem().Kind()
|
||||
if ek == reflect.Uint8 {
|
||||
return rv.Bytes(), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, ek)
|
||||
case reflect.String:
|
||||
return rv.String(), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
|
||||
}
|
||||
|
||||
var valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
|
||||
|
||||
// callValuerValue returns vr.Value(), with one exception:
|
||||
// If vr.Value is an auto-generated method on a pointer type and the
|
||||
// pointer is nil, it would panic at runtime in the panicwrap
|
||||
// method. Treat it like nil instead.
|
||||
//
|
||||
// This is so people can implement driver.Value on value types and
|
||||
// still use nil pointers to those types to mean nil/NULL, just like
|
||||
// string/*string.
|
||||
//
|
||||
// This is an exact copy of the same-named unexported function from the
|
||||
// database/sql package.
|
||||
func callValuerValue(vr driver.Valuer) (v driver.Value, err error) {
|
||||
if rv := reflect.ValueOf(vr); rv.Kind() == reflect.Ptr &&
|
||||
rv.IsNil() &&
|
||||
rv.Type().Elem().Implements(valuerReflectType) {
|
||||
return nil, nil
|
||||
}
|
||||
return vr.Value()
|
||||
}
|
||||
31
vendor/github.com/go-sql-driver/mysql/transaction.go
generated
vendored
Normal file
31
vendor/github.com/go-sql-driver/mysql/transaction.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
type mysqlTx struct {
|
||||
mc *mysqlConn
|
||||
}
|
||||
|
||||
func (tx *mysqlTx) Commit() (err error) {
|
||||
if tx.mc == nil || tx.mc.closed.IsSet() {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
err = tx.mc.exec("COMMIT")
|
||||
tx.mc = nil
|
||||
return
|
||||
}
|
||||
|
||||
func (tx *mysqlTx) Rollback() (err error) {
|
||||
if tx.mc == nil || tx.mc.closed.IsSet() {
|
||||
return ErrInvalidConn
|
||||
}
|
||||
err = tx.mc.exec("ROLLBACK")
|
||||
tx.mc = nil
|
||||
return
|
||||
}
|
||||
710
vendor/github.com/go-sql-driver/mysql/utils.go
generated
vendored
Normal file
710
vendor/github.com/go-sql-driver/mysql/utils.go
generated
vendored
Normal file
@@ -0,0 +1,710 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Registry for custom tls.Configs
|
||||
var (
|
||||
tlsConfigLock sync.RWMutex
|
||||
tlsConfigRegistry map[string]*tls.Config
|
||||
)
|
||||
|
||||
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
|
||||
// Use the key as a value in the DSN where tls=value.
|
||||
//
|
||||
// Note: The provided tls.Config is exclusively owned by the driver after
|
||||
// registering it.
|
||||
//
|
||||
// rootCertPool := x509.NewCertPool()
|
||||
// pem, err := ioutil.ReadFile("/path/ca-cert.pem")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
|
||||
// log.Fatal("Failed to append PEM.")
|
||||
// }
|
||||
// clientCert := make([]tls.Certificate, 0, 1)
|
||||
// certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// clientCert = append(clientCert, certs)
|
||||
// mysql.RegisterTLSConfig("custom", &tls.Config{
|
||||
// RootCAs: rootCertPool,
|
||||
// Certificates: clientCert,
|
||||
// })
|
||||
// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
|
||||
//
|
||||
func RegisterTLSConfig(key string, config *tls.Config) error {
|
||||
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" {
|
||||
return fmt.Errorf("key '%s' is reserved", key)
|
||||
}
|
||||
|
||||
tlsConfigLock.Lock()
|
||||
if tlsConfigRegistry == nil {
|
||||
tlsConfigRegistry = make(map[string]*tls.Config)
|
||||
}
|
||||
|
||||
tlsConfigRegistry[key] = config
|
||||
tlsConfigLock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeregisterTLSConfig removes the tls.Config associated with key.
|
||||
func DeregisterTLSConfig(key string) {
|
||||
tlsConfigLock.Lock()
|
||||
if tlsConfigRegistry != nil {
|
||||
delete(tlsConfigRegistry, key)
|
||||
}
|
||||
tlsConfigLock.Unlock()
|
||||
}
|
||||
|
||||
func getTLSConfigClone(key string) (config *tls.Config) {
|
||||
tlsConfigLock.RLock()
|
||||
if v, ok := tlsConfigRegistry[key]; ok {
|
||||
config = cloneTLSConfig(v)
|
||||
}
|
||||
tlsConfigLock.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the bool value of the input.
|
||||
// The 2nd return value indicates if the input was a valid bool value
|
||||
func readBool(input string) (value bool, valid bool) {
|
||||
switch input {
|
||||
case "1", "true", "TRUE", "True":
|
||||
return true, true
|
||||
case "0", "false", "FALSE", "False":
|
||||
return false, true
|
||||
}
|
||||
|
||||
// Not a valid bool value
|
||||
return
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Time related utils *
|
||||
******************************************************************************/
|
||||
|
||||
// NullTime represents a time.Time that may be NULL.
|
||||
// NullTime implements the Scanner interface so
|
||||
// it can be used as a scan destination:
|
||||
//
|
||||
// var nt NullTime
|
||||
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
|
||||
// ...
|
||||
// if nt.Valid {
|
||||
// // use nt.Time
|
||||
// } else {
|
||||
// // NULL value
|
||||
// }
|
||||
//
|
||||
// This NullTime implementation is not driver-specific
|
||||
type NullTime struct {
|
||||
Time time.Time
|
||||
Valid bool // Valid is true if Time is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
// The value type must be time.Time or string / []byte (formatted time-string),
|
||||
// otherwise Scan fails.
|
||||
func (nt *NullTime) Scan(value interface{}) (err error) {
|
||||
if value == nil {
|
||||
nt.Time, nt.Valid = time.Time{}, false
|
||||
return
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case time.Time:
|
||||
nt.Time, nt.Valid = v, true
|
||||
return
|
||||
case []byte:
|
||||
nt.Time, err = parseDateTime(string(v), time.UTC)
|
||||
nt.Valid = (err == nil)
|
||||
return
|
||||
case string:
|
||||
nt.Time, err = parseDateTime(v, time.UTC)
|
||||
nt.Valid = (err == nil)
|
||||
return
|
||||
}
|
||||
|
||||
nt.Valid = false
|
||||
return fmt.Errorf("Can't convert %T to time.Time", value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (nt NullTime) Value() (driver.Value, error) {
|
||||
if !nt.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return nt.Time, nil
|
||||
}
|
||||
|
||||
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) {
|
||||
base := "0000-00-00 00:00:00.0000000"
|
||||
switch len(str) {
|
||||
case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
|
||||
if str == base[:len(str)] {
|
||||
return
|
||||
}
|
||||
t, err = time.Parse(timeFormat[:len(str)], str)
|
||||
default:
|
||||
err = fmt.Errorf("invalid time string: %s", str)
|
||||
return
|
||||
}
|
||||
|
||||
// Adjust location
|
||||
if err == nil && loc != time.UTC {
|
||||
y, mo, d := t.Date()
|
||||
h, mi, s := t.Clock()
|
||||
t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) {
|
||||
switch num {
|
||||
case 0:
|
||||
return time.Time{}, nil
|
||||
case 4:
|
||||
return time.Date(
|
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
0, 0, 0, 0,
|
||||
loc,
|
||||
), nil
|
||||
case 7:
|
||||
return time.Date(
|
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
int(data[4]), // hour
|
||||
int(data[5]), // minutes
|
||||
int(data[6]), // seconds
|
||||
0,
|
||||
loc,
|
||||
), nil
|
||||
case 11:
|
||||
return time.Date(
|
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
int(data[4]), // hour
|
||||
int(data[5]), // minutes
|
||||
int(data[6]), // seconds
|
||||
int(binary.LittleEndian.Uint32(data[7:11]))*1000, // nanoseconds
|
||||
loc,
|
||||
), nil
|
||||
}
|
||||
return nil, fmt.Errorf("invalid DATETIME packet length %d", num)
|
||||
}
|
||||
|
||||
// zeroDateTime is used in formatBinaryDateTime to avoid an allocation
|
||||
// if the DATE or DATETIME has the zero value.
|
||||
// It must never be changed.
|
||||
// The current behavior depends on database/sql copying the result.
|
||||
var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
|
||||
|
||||
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
||||
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
|
||||
|
||||
func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
|
||||
// length expects the deterministic length of the zero value,
|
||||
// negative time and 100+ hours are automatically added if needed
|
||||
if len(src) == 0 {
|
||||
if justTime {
|
||||
return zeroDateTime[11 : 11+length], nil
|
||||
}
|
||||
return zeroDateTime[:length], nil
|
||||
}
|
||||
var dst []byte // return value
|
||||
var pt, p1, p2, p3 byte // current digit pair
|
||||
var zOffs byte // offset of value in zeroDateTime
|
||||
if justTime {
|
||||
switch length {
|
||||
case
|
||||
8, // time (can be up to 10 when negative and 100+ hours)
|
||||
10, 11, 12, 13, 14, 15: // time with fractional seconds
|
||||
default:
|
||||
return nil, fmt.Errorf("illegal TIME length %d", length)
|
||||
}
|
||||
switch len(src) {
|
||||
case 8, 12:
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
|
||||
}
|
||||
// +2 to enable negative time and 100+ hours
|
||||
dst = make([]byte, 0, length+2)
|
||||
if src[0] == 1 {
|
||||
dst = append(dst, '-')
|
||||
}
|
||||
if src[1] != 0 {
|
||||
hour := uint16(src[1])*24 + uint16(src[5])
|
||||
pt = byte(hour / 100)
|
||||
p1 = byte(hour - 100*uint16(pt))
|
||||
dst = append(dst, digits01[pt])
|
||||
} else {
|
||||
p1 = src[5]
|
||||
}
|
||||
zOffs = 11
|
||||
src = src[6:]
|
||||
} else {
|
||||
switch length {
|
||||
case 10, 19, 21, 22, 23, 24, 25, 26:
|
||||
default:
|
||||
t := "DATE"
|
||||
if length > 10 {
|
||||
t += "TIME"
|
||||
}
|
||||
return nil, fmt.Errorf("illegal %s length %d", t, length)
|
||||
}
|
||||
switch len(src) {
|
||||
case 4, 7, 11:
|
||||
default:
|
||||
t := "DATE"
|
||||
if length > 10 {
|
||||
t += "TIME"
|
||||
}
|
||||
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
|
||||
}
|
||||
dst = make([]byte, 0, length)
|
||||
// start with the date
|
||||
year := binary.LittleEndian.Uint16(src[:2])
|
||||
pt = byte(year / 100)
|
||||
p1 = byte(year - 100*uint16(pt))
|
||||
p2, p3 = src[2], src[3]
|
||||
dst = append(dst,
|
||||
digits10[pt], digits01[pt],
|
||||
digits10[p1], digits01[p1], '-',
|
||||
digits10[p2], digits01[p2], '-',
|
||||
digits10[p3], digits01[p3],
|
||||
)
|
||||
if length == 10 {
|
||||
return dst, nil
|
||||
}
|
||||
if len(src) == 4 {
|
||||
return append(dst, zeroDateTime[10:length]...), nil
|
||||
}
|
||||
dst = append(dst, ' ')
|
||||
p1 = src[4] // hour
|
||||
src = src[5:]
|
||||
}
|
||||
// p1 is 2-digit hour, src is after hour
|
||||
p2, p3 = src[0], src[1]
|
||||
dst = append(dst,
|
||||
digits10[p1], digits01[p1], ':',
|
||||
digits10[p2], digits01[p2], ':',
|
||||
digits10[p3], digits01[p3],
|
||||
)
|
||||
if length <= byte(len(dst)) {
|
||||
return dst, nil
|
||||
}
|
||||
src = src[2:]
|
||||
if len(src) == 0 {
|
||||
return append(dst, zeroDateTime[19:zOffs+length]...), nil
|
||||
}
|
||||
microsecs := binary.LittleEndian.Uint32(src[:4])
|
||||
p1 = byte(microsecs / 10000)
|
||||
microsecs -= 10000 * uint32(p1)
|
||||
p2 = byte(microsecs / 100)
|
||||
microsecs -= 100 * uint32(p2)
|
||||
p3 = byte(microsecs)
|
||||
switch decimals := zOffs + length - 20; decimals {
|
||||
default:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2], digits01[p2],
|
||||
digits10[p3], digits01[p3],
|
||||
), nil
|
||||
case 1:
|
||||
return append(dst, '.',
|
||||
digits10[p1],
|
||||
), nil
|
||||
case 2:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
), nil
|
||||
case 3:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2],
|
||||
), nil
|
||||
case 4:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2], digits01[p2],
|
||||
), nil
|
||||
case 5:
|
||||
return append(dst, '.',
|
||||
digits10[p1], digits01[p1],
|
||||
digits10[p2], digits01[p2],
|
||||
digits10[p3],
|
||||
), nil
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Convert from and to bytes *
|
||||
******************************************************************************/
|
||||
|
||||
func uint64ToBytes(n uint64) []byte {
|
||||
return []byte{
|
||||
byte(n),
|
||||
byte(n >> 8),
|
||||
byte(n >> 16),
|
||||
byte(n >> 24),
|
||||
byte(n >> 32),
|
||||
byte(n >> 40),
|
||||
byte(n >> 48),
|
||||
byte(n >> 56),
|
||||
}
|
||||
}
|
||||
|
||||
func uint64ToString(n uint64) []byte {
|
||||
var a [20]byte
|
||||
i := 20
|
||||
|
||||
// U+0030 = 0
|
||||
// ...
|
||||
// U+0039 = 9
|
||||
|
||||
var q uint64
|
||||
for n >= 10 {
|
||||
i--
|
||||
q = n / 10
|
||||
a[i] = uint8(n-q*10) + 0x30
|
||||
n = q
|
||||
}
|
||||
|
||||
i--
|
||||
a[i] = uint8(n) + 0x30
|
||||
|
||||
return a[i:]
|
||||
}
|
||||
|
||||
// treats string value as unsigned integer representation
|
||||
func stringToInt(b []byte) int {
|
||||
val := 0
|
||||
for i := range b {
|
||||
val *= 10
|
||||
val += int(b[i] - 0x30)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// returns the string read as a bytes slice, wheter the value is NULL,
|
||||
// the number of bytes read and an error, in case the string is longer than
|
||||
// the input slice
|
||||
func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
|
||||
// Get length
|
||||
num, isNull, n := readLengthEncodedInteger(b)
|
||||
if num < 1 {
|
||||
return b[n:n], isNull, n, nil
|
||||
}
|
||||
|
||||
n += int(num)
|
||||
|
||||
// Check data length
|
||||
if len(b) >= n {
|
||||
return b[n-int(num) : n : n], false, n, nil
|
||||
}
|
||||
return nil, false, n, io.EOF
|
||||
}
|
||||
|
||||
// returns the number of bytes skipped and an error, in case the string is
|
||||
// longer than the input slice
|
||||
func skipLengthEncodedString(b []byte) (int, error) {
|
||||
// Get length
|
||||
num, _, n := readLengthEncodedInteger(b)
|
||||
if num < 1 {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
n += int(num)
|
||||
|
||||
// Check data length
|
||||
if len(b) >= n {
|
||||
return n, nil
|
||||
}
|
||||
return n, io.EOF
|
||||
}
|
||||
|
||||
// returns the number read, whether the value is NULL and the number of bytes read
|
||||
func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
|
||||
// See issue #349
|
||||
if len(b) == 0 {
|
||||
return 0, true, 1
|
||||
}
|
||||
|
||||
switch b[0] {
|
||||
// 251: NULL
|
||||
case 0xfb:
|
||||
return 0, true, 1
|
||||
|
||||
// 252: value of following 2
|
||||
case 0xfc:
|
||||
return uint64(b[1]) | uint64(b[2])<<8, false, 3
|
||||
|
||||
// 253: value of following 3
|
||||
case 0xfd:
|
||||
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4
|
||||
|
||||
// 254: value of following 8
|
||||
case 0xfe:
|
||||
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 |
|
||||
uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 |
|
||||
uint64(b[7])<<48 | uint64(b[8])<<56,
|
||||
false, 9
|
||||
}
|
||||
|
||||
// 0-250: value of first byte
|
||||
return uint64(b[0]), false, 1
|
||||
}
|
||||
|
||||
// encodes a uint64 value and appends it to the given bytes slice
|
||||
func appendLengthEncodedInteger(b []byte, n uint64) []byte {
|
||||
switch {
|
||||
case n <= 250:
|
||||
return append(b, byte(n))
|
||||
|
||||
case n <= 0xffff:
|
||||
return append(b, 0xfc, byte(n), byte(n>>8))
|
||||
|
||||
case n <= 0xffffff:
|
||||
return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16))
|
||||
}
|
||||
return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24),
|
||||
byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56))
|
||||
}
|
||||
|
||||
// reserveBuffer checks cap(buf) and expand buffer to len(buf) + appendSize.
|
||||
// If cap(buf) is not enough, reallocate new buffer.
|
||||
func reserveBuffer(buf []byte, appendSize int) []byte {
|
||||
newSize := len(buf) + appendSize
|
||||
if cap(buf) < newSize {
|
||||
// Grow buffer exponentially
|
||||
newBuf := make([]byte, len(buf)*2+appendSize)
|
||||
copy(newBuf, buf)
|
||||
buf = newBuf
|
||||
}
|
||||
return buf[:newSize]
|
||||
}
|
||||
|
||||
// escapeBytesBackslash escapes []byte with backslashes (\)
|
||||
// This escapes the contents of a string (provided as []byte) by adding backslashes before special
|
||||
// characters, and turning others into specific escape sequences, such as
|
||||
// turning newlines into \n and null bytes into \0.
|
||||
// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L823-L932
|
||||
func escapeBytesBackslash(buf, v []byte) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for _, c := range v {
|
||||
switch c {
|
||||
case '\x00':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '0'
|
||||
pos += 2
|
||||
case '\n':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'n'
|
||||
pos += 2
|
||||
case '\r':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'r'
|
||||
pos += 2
|
||||
case '\x1a':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'Z'
|
||||
pos += 2
|
||||
case '\'':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
case '"':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '"'
|
||||
pos += 2
|
||||
case '\\':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\\'
|
||||
pos += 2
|
||||
default:
|
||||
buf[pos] = c
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
||||
|
||||
// escapeStringBackslash is similar to escapeBytesBackslash but for string.
|
||||
func escapeStringBackslash(buf []byte, v string) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for i := 0; i < len(v); i++ {
|
||||
c := v[i]
|
||||
switch c {
|
||||
case '\x00':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '0'
|
||||
pos += 2
|
||||
case '\n':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'n'
|
||||
pos += 2
|
||||
case '\r':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'r'
|
||||
pos += 2
|
||||
case '\x1a':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = 'Z'
|
||||
pos += 2
|
||||
case '\'':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
case '"':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '"'
|
||||
pos += 2
|
||||
case '\\':
|
||||
buf[pos] = '\\'
|
||||
buf[pos+1] = '\\'
|
||||
pos += 2
|
||||
default:
|
||||
buf[pos] = c
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
||||
|
||||
// escapeBytesQuotes escapes apostrophes in []byte by doubling them up.
|
||||
// This escapes the contents of a string by doubling up any apostrophes that
|
||||
// it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
|
||||
// effect on the server.
|
||||
// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L963-L1038
|
||||
func escapeBytesQuotes(buf, v []byte) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for _, c := range v {
|
||||
if c == '\'' {
|
||||
buf[pos] = '\''
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
} else {
|
||||
buf[pos] = c
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
||||
|
||||
// escapeStringQuotes is similar to escapeBytesQuotes but for string.
|
||||
func escapeStringQuotes(buf []byte, v string) []byte {
|
||||
pos := len(buf)
|
||||
buf = reserveBuffer(buf, len(v)*2)
|
||||
|
||||
for i := 0; i < len(v); i++ {
|
||||
c := v[i]
|
||||
if c == '\'' {
|
||||
buf[pos] = '\''
|
||||
buf[pos+1] = '\''
|
||||
pos += 2
|
||||
} else {
|
||||
buf[pos] = c
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
||||
return buf[:pos]
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Sync utils *
|
||||
******************************************************************************/
|
||||
|
||||
// noCopy may be embedded into structs which must not be copied
|
||||
// after the first use.
|
||||
//
|
||||
// See https://github.com/golang/go/issues/8005#issuecomment-190753527
|
||||
// for details.
|
||||
type noCopy struct{}
|
||||
|
||||
// Lock is a no-op used by -copylocks checker from `go vet`.
|
||||
func (*noCopy) Lock() {}
|
||||
|
||||
// atomicBool is a wrapper around uint32 for usage as a boolean value with
|
||||
// atomic access.
|
||||
type atomicBool struct {
|
||||
_noCopy noCopy
|
||||
value uint32
|
||||
}
|
||||
|
||||
// IsSet returns wether the current boolean value is true
|
||||
func (ab *atomicBool) IsSet() bool {
|
||||
return atomic.LoadUint32(&ab.value) > 0
|
||||
}
|
||||
|
||||
// Set sets the value of the bool regardless of the previous value
|
||||
func (ab *atomicBool) Set(value bool) {
|
||||
if value {
|
||||
atomic.StoreUint32(&ab.value, 1)
|
||||
} else {
|
||||
atomic.StoreUint32(&ab.value, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// TrySet sets the value of the bool and returns wether the value changed
|
||||
func (ab *atomicBool) TrySet(value bool) bool {
|
||||
if value {
|
||||
return atomic.SwapUint32(&ab.value, 1) == 0
|
||||
}
|
||||
return atomic.SwapUint32(&ab.value, 0) > 0
|
||||
}
|
||||
|
||||
// atomicError is a wrapper for atomically accessed error values
|
||||
type atomicError struct {
|
||||
_noCopy noCopy
|
||||
value atomic.Value
|
||||
}
|
||||
|
||||
// Set sets the error value regardless of the previous value.
|
||||
// The value must not be nil
|
||||
func (ae *atomicError) Set(value error) {
|
||||
ae.value.Store(value)
|
||||
}
|
||||
|
||||
// Value returns the current error value
|
||||
func (ae *atomicError) Value() error {
|
||||
if v := ae.value.Load(); v != nil {
|
||||
// this will panic if the value doesn't implement the error interface
|
||||
return v.(error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
40
vendor/github.com/go-sql-driver/mysql/utils_go17.go
generated
vendored
Normal file
40
vendor/github.com/go-sql-driver/mysql/utils_go17.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build go1.7
|
||||
// +build !go1.8
|
||||
|
||||
package mysql
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
func cloneTLSConfig(c *tls.Config) *tls.Config {
|
||||
return &tls.Config{
|
||||
Rand: c.Rand,
|
||||
Time: c.Time,
|
||||
Certificates: c.Certificates,
|
||||
NameToCertificate: c.NameToCertificate,
|
||||
GetCertificate: c.GetCertificate,
|
||||
RootCAs: c.RootCAs,
|
||||
NextProtos: c.NextProtos,
|
||||
ServerName: c.ServerName,
|
||||
ClientAuth: c.ClientAuth,
|
||||
ClientCAs: c.ClientCAs,
|
||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||
CipherSuites: c.CipherSuites,
|
||||
PreferServerCipherSuites: c.PreferServerCipherSuites,
|
||||
SessionTicketsDisabled: c.SessionTicketsDisabled,
|
||||
SessionTicketKey: c.SessionTicketKey,
|
||||
ClientSessionCache: c.ClientSessionCache,
|
||||
MinVersion: c.MinVersion,
|
||||
MaxVersion: c.MaxVersion,
|
||||
CurvePreferences: c.CurvePreferences,
|
||||
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
|
||||
Renegotiation: c.Renegotiation,
|
||||
}
|
||||
}
|
||||
50
vendor/github.com/go-sql-driver/mysql/utils_go18.go
generated
vendored
Normal file
50
vendor/github.com/go-sql-driver/mysql/utils_go18.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func cloneTLSConfig(c *tls.Config) *tls.Config {
|
||||
return c.Clone()
|
||||
}
|
||||
|
||||
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
|
||||
dargs := make([]driver.Value, len(named))
|
||||
for n, param := range named {
|
||||
if len(param.Name) > 0 {
|
||||
// TODO: support the use of Named Parameters #561
|
||||
return nil, errors.New("mysql: driver does not support the use of Named Parameters")
|
||||
}
|
||||
dargs[n] = param.Value
|
||||
}
|
||||
return dargs, nil
|
||||
}
|
||||
|
||||
func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
|
||||
switch sql.IsolationLevel(level) {
|
||||
case sql.LevelRepeatableRead:
|
||||
return "REPEATABLE READ", nil
|
||||
case sql.LevelReadCommitted:
|
||||
return "READ COMMITTED", nil
|
||||
case sql.LevelReadUncommitted:
|
||||
return "READ UNCOMMITTED", nil
|
||||
case sql.LevelSerializable:
|
||||
return "SERIALIZABLE", nil
|
||||
default:
|
||||
return "", fmt.Errorf("mysql: unsupported isolation level: %v", level)
|
||||
}
|
||||
}
|
||||
11
vendor/github.com/jinzhu/gorm/.codeclimate.yml
generated
vendored
Normal file
11
vendor/github.com/jinzhu/gorm/.codeclimate.yml
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
engines:
|
||||
gofmt:
|
||||
enabled: true
|
||||
govet:
|
||||
enabled: true
|
||||
golint:
|
||||
enabled: true
|
||||
ratings:
|
||||
paths:
|
||||
- "**.go"
|
||||
2
vendor/github.com/jinzhu/gorm/.gitignore
generated
vendored
Normal file
2
vendor/github.com/jinzhu/gorm/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
documents
|
||||
_book
|
||||
21
vendor/github.com/jinzhu/gorm/License
generated
vendored
Normal file
21
vendor/github.com/jinzhu/gorm/License
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-NOW Jinzhu <wosmvp@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
40
vendor/github.com/jinzhu/gorm/README.md
generated
vendored
Normal file
40
vendor/github.com/jinzhu/gorm/README.md
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
# GORM
|
||||
|
||||
The fantastic ORM library for Golang, aims to be developer friendly.
|
||||
|
||||
[](https://goreportcard.com/report/github.com/jinzhu/gorm)
|
||||
[](https://app.wercker.com/project/byKey/8596cace912c9947dd9c8542ecc8cb8b)
|
||||
[](https://gitter.im/jinzhu/gorm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://opencollective.com/gorm)
|
||||
[](https://opencollective.com/gorm)
|
||||
[](http://opensource.org/licenses/MIT)
|
||||
[](https://godoc.org/github.com/jinzhu/gorm)
|
||||
|
||||
## Overview
|
||||
|
||||
* Full-Featured ORM (almost)
|
||||
* Associations (Has One, Has Many, Belongs To, Many To Many, Polymorphism)
|
||||
* Hooks (Before/After Create/Save/Update/Delete/Find)
|
||||
* Preloading (eager loading)
|
||||
* Transactions
|
||||
* Composite Primary Key
|
||||
* SQL Builder
|
||||
* Auto Migrations
|
||||
* Logger
|
||||
* Extendable, write Plugins based on GORM callbacks
|
||||
* Every feature comes with tests
|
||||
* Developer Friendly
|
||||
|
||||
## Getting Started
|
||||
|
||||
* GORM Guides [http://gorm.io](http://gorm.io)
|
||||
|
||||
## Contributing
|
||||
|
||||
[You can help to deliver a better GORM, check out things you can do](http://gorm.io/contribute.html)
|
||||
|
||||
## License
|
||||
|
||||
© Jinzhu, 2013~time.Now
|
||||
|
||||
Released under the [MIT License](https://github.com/jinzhu/gorm/blob/master/License)
|
||||
375
vendor/github.com/jinzhu/gorm/association.go
generated
vendored
Normal file
375
vendor/github.com/jinzhu/gorm/association.go
generated
vendored
Normal file
@@ -0,0 +1,375 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Association Mode contains some helper methods to handle relationship things easily.
|
||||
type Association struct {
|
||||
Error error
|
||||
scope *Scope
|
||||
column string
|
||||
field *Field
|
||||
}
|
||||
|
||||
// Find find out all related associations
|
||||
func (association *Association) Find(value interface{}) *Association {
|
||||
association.scope.related(value, association.column)
|
||||
return association.setErr(association.scope.db.Error)
|
||||
}
|
||||
|
||||
// Append append new associations for many2many, has_many, replace current association for has_one, belongs_to
|
||||
func (association *Association) Append(values ...interface{}) *Association {
|
||||
if association.Error != nil {
|
||||
return association
|
||||
}
|
||||
|
||||
if relationship := association.field.Relationship; relationship.Kind == "has_one" {
|
||||
return association.Replace(values...)
|
||||
}
|
||||
return association.saveAssociations(values...)
|
||||
}
|
||||
|
||||
// Replace replace current associations with new one
|
||||
func (association *Association) Replace(values ...interface{}) *Association {
|
||||
if association.Error != nil {
|
||||
return association
|
||||
}
|
||||
|
||||
var (
|
||||
relationship = association.field.Relationship
|
||||
scope = association.scope
|
||||
field = association.field.Field
|
||||
newDB = scope.NewDB()
|
||||
)
|
||||
|
||||
// Append new values
|
||||
association.field.Set(reflect.Zero(association.field.Field.Type()))
|
||||
association.saveAssociations(values...)
|
||||
|
||||
// Belongs To
|
||||
if relationship.Kind == "belongs_to" {
|
||||
// Set foreign key to be null when clearing value (length equals 0)
|
||||
if len(values) == 0 {
|
||||
// Set foreign key to be nil
|
||||
var foreignKeyMap = map[string]interface{}{}
|
||||
for _, foreignKey := range relationship.ForeignDBNames {
|
||||
foreignKeyMap[foreignKey] = nil
|
||||
}
|
||||
association.setErr(newDB.Model(scope.Value).UpdateColumn(foreignKeyMap).Error)
|
||||
}
|
||||
} else {
|
||||
// Polymorphic Relations
|
||||
if relationship.PolymorphicDBName != "" {
|
||||
newDB = newDB.Where(fmt.Sprintf("%v = ?", scope.Quote(relationship.PolymorphicDBName)), relationship.PolymorphicValue)
|
||||
}
|
||||
|
||||
// Delete Relations except new created
|
||||
if len(values) > 0 {
|
||||
var associationForeignFieldNames, associationForeignDBNames []string
|
||||
if relationship.Kind == "many_to_many" {
|
||||
// if many to many relations, get association fields name from association foreign keys
|
||||
associationScope := scope.New(reflect.New(field.Type()).Interface())
|
||||
for idx, dbName := range relationship.AssociationForeignFieldNames {
|
||||
if field, ok := associationScope.FieldByName(dbName); ok {
|
||||
associationForeignFieldNames = append(associationForeignFieldNames, field.Name)
|
||||
associationForeignDBNames = append(associationForeignDBNames, relationship.AssociationForeignDBNames[idx])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If has one/many relations, use primary keys
|
||||
for _, field := range scope.New(reflect.New(field.Type()).Interface()).PrimaryFields() {
|
||||
associationForeignFieldNames = append(associationForeignFieldNames, field.Name)
|
||||
associationForeignDBNames = append(associationForeignDBNames, field.DBName)
|
||||
}
|
||||
}
|
||||
|
||||
newPrimaryKeys := scope.getColumnAsArray(associationForeignFieldNames, field.Interface())
|
||||
|
||||
if len(newPrimaryKeys) > 0 {
|
||||
sql := fmt.Sprintf("%v NOT IN (%v)", toQueryCondition(scope, associationForeignDBNames), toQueryMarks(newPrimaryKeys))
|
||||
newDB = newDB.Where(sql, toQueryValues(newPrimaryKeys)...)
|
||||
}
|
||||
}
|
||||
|
||||
if relationship.Kind == "many_to_many" {
|
||||
// if many to many relations, delete related relations from join table
|
||||
var sourceForeignFieldNames []string
|
||||
|
||||
for _, dbName := range relationship.ForeignFieldNames {
|
||||
if field, ok := scope.FieldByName(dbName); ok {
|
||||
sourceForeignFieldNames = append(sourceForeignFieldNames, field.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if sourcePrimaryKeys := scope.getColumnAsArray(sourceForeignFieldNames, scope.Value); len(sourcePrimaryKeys) > 0 {
|
||||
newDB = newDB.Where(fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.ForeignDBNames), toQueryMarks(sourcePrimaryKeys)), toQueryValues(sourcePrimaryKeys)...)
|
||||
|
||||
association.setErr(relationship.JoinTableHandler.Delete(relationship.JoinTableHandler, newDB))
|
||||
}
|
||||
} else if relationship.Kind == "has_one" || relationship.Kind == "has_many" {
|
||||
// has_one or has_many relations, set foreign key to be nil (TODO or delete them?)
|
||||
var foreignKeyMap = map[string]interface{}{}
|
||||
for idx, foreignKey := range relationship.ForeignDBNames {
|
||||
foreignKeyMap[foreignKey] = nil
|
||||
if field, ok := scope.FieldByName(relationship.AssociationForeignFieldNames[idx]); ok {
|
||||
newDB = newDB.Where(fmt.Sprintf("%v = ?", scope.Quote(foreignKey)), field.Field.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
fieldValue := reflect.New(association.field.Field.Type()).Interface()
|
||||
association.setErr(newDB.Model(fieldValue).UpdateColumn(foreignKeyMap).Error)
|
||||
}
|
||||
}
|
||||
return association
|
||||
}
|
||||
|
||||
// Delete remove relationship between source & passed arguments, but won't delete those arguments
|
||||
func (association *Association) Delete(values ...interface{}) *Association {
|
||||
if association.Error != nil {
|
||||
return association
|
||||
}
|
||||
|
||||
var (
|
||||
relationship = association.field.Relationship
|
||||
scope = association.scope
|
||||
field = association.field.Field
|
||||
newDB = scope.NewDB()
|
||||
)
|
||||
|
||||
if len(values) == 0 {
|
||||
return association
|
||||
}
|
||||
|
||||
var deletingResourcePrimaryFieldNames, deletingResourcePrimaryDBNames []string
|
||||
for _, field := range scope.New(reflect.New(field.Type()).Interface()).PrimaryFields() {
|
||||
deletingResourcePrimaryFieldNames = append(deletingResourcePrimaryFieldNames, field.Name)
|
||||
deletingResourcePrimaryDBNames = append(deletingResourcePrimaryDBNames, field.DBName)
|
||||
}
|
||||
|
||||
deletingPrimaryKeys := scope.getColumnAsArray(deletingResourcePrimaryFieldNames, values...)
|
||||
|
||||
if relationship.Kind == "many_to_many" {
|
||||
// source value's foreign keys
|
||||
for idx, foreignKey := range relationship.ForeignDBNames {
|
||||
if field, ok := scope.FieldByName(relationship.ForeignFieldNames[idx]); ok {
|
||||
newDB = newDB.Where(fmt.Sprintf("%v = ?", scope.Quote(foreignKey)), field.Field.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
// get association's foreign fields name
|
||||
var associationScope = scope.New(reflect.New(field.Type()).Interface())
|
||||
var associationForeignFieldNames []string
|
||||
for _, associationDBName := range relationship.AssociationForeignFieldNames {
|
||||
if field, ok := associationScope.FieldByName(associationDBName); ok {
|
||||
associationForeignFieldNames = append(associationForeignFieldNames, field.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// association value's foreign keys
|
||||
deletingPrimaryKeys := scope.getColumnAsArray(associationForeignFieldNames, values...)
|
||||
sql := fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.AssociationForeignDBNames), toQueryMarks(deletingPrimaryKeys))
|
||||
newDB = newDB.Where(sql, toQueryValues(deletingPrimaryKeys)...)
|
||||
|
||||
association.setErr(relationship.JoinTableHandler.Delete(relationship.JoinTableHandler, newDB))
|
||||
} else {
|
||||
var foreignKeyMap = map[string]interface{}{}
|
||||
for _, foreignKey := range relationship.ForeignDBNames {
|
||||
foreignKeyMap[foreignKey] = nil
|
||||
}
|
||||
|
||||
if relationship.Kind == "belongs_to" {
|
||||
// find with deleting relation's foreign keys
|
||||
primaryKeys := scope.getColumnAsArray(relationship.AssociationForeignFieldNames, values...)
|
||||
newDB = newDB.Where(
|
||||
fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.ForeignDBNames), toQueryMarks(primaryKeys)),
|
||||
toQueryValues(primaryKeys)...,
|
||||
)
|
||||
|
||||
// set foreign key to be null if there are some records affected
|
||||
modelValue := reflect.New(scope.GetModelStruct().ModelType).Interface()
|
||||
if results := newDB.Model(modelValue).UpdateColumn(foreignKeyMap); results.Error == nil {
|
||||
if results.RowsAffected > 0 {
|
||||
scope.updatedAttrsWithValues(foreignKeyMap)
|
||||
}
|
||||
} else {
|
||||
association.setErr(results.Error)
|
||||
}
|
||||
} else if relationship.Kind == "has_one" || relationship.Kind == "has_many" {
|
||||
// find all relations
|
||||
primaryKeys := scope.getColumnAsArray(relationship.AssociationForeignFieldNames, scope.Value)
|
||||
newDB = newDB.Where(
|
||||
fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.ForeignDBNames), toQueryMarks(primaryKeys)),
|
||||
toQueryValues(primaryKeys)...,
|
||||
)
|
||||
|
||||
// only include those deleting relations
|
||||
newDB = newDB.Where(
|
||||
fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, deletingResourcePrimaryDBNames), toQueryMarks(deletingPrimaryKeys)),
|
||||
toQueryValues(deletingPrimaryKeys)...,
|
||||
)
|
||||
|
||||
// set matched relation's foreign key to be null
|
||||
fieldValue := reflect.New(association.field.Field.Type()).Interface()
|
||||
association.setErr(newDB.Model(fieldValue).UpdateColumn(foreignKeyMap).Error)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove deleted records from source's field
|
||||
if association.Error == nil {
|
||||
if field.Kind() == reflect.Slice {
|
||||
leftValues := reflect.Zero(field.Type())
|
||||
|
||||
for i := 0; i < field.Len(); i++ {
|
||||
reflectValue := field.Index(i)
|
||||
primaryKey := scope.getColumnAsArray(deletingResourcePrimaryFieldNames, reflectValue.Interface())[0]
|
||||
var isDeleted = false
|
||||
for _, pk := range deletingPrimaryKeys {
|
||||
if equalAsString(primaryKey, pk) {
|
||||
isDeleted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isDeleted {
|
||||
leftValues = reflect.Append(leftValues, reflectValue)
|
||||
}
|
||||
}
|
||||
|
||||
association.field.Set(leftValues)
|
||||
} else if field.Kind() == reflect.Struct {
|
||||
primaryKey := scope.getColumnAsArray(deletingResourcePrimaryFieldNames, field.Interface())[0]
|
||||
for _, pk := range deletingPrimaryKeys {
|
||||
if equalAsString(primaryKey, pk) {
|
||||
association.field.Set(reflect.Zero(field.Type()))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return association
|
||||
}
|
||||
|
||||
// Clear remove relationship between source & current associations, won't delete those associations
|
||||
func (association *Association) Clear() *Association {
|
||||
return association.Replace()
|
||||
}
|
||||
|
||||
// Count return the count of current associations
|
||||
func (association *Association) Count() int {
|
||||
var (
|
||||
count = 0
|
||||
relationship = association.field.Relationship
|
||||
scope = association.scope
|
||||
fieldValue = association.field.Field.Interface()
|
||||
query = scope.DB()
|
||||
)
|
||||
|
||||
if relationship.Kind == "many_to_many" {
|
||||
query = relationship.JoinTableHandler.JoinWith(relationship.JoinTableHandler, query, scope.Value)
|
||||
} else if relationship.Kind == "has_many" || relationship.Kind == "has_one" {
|
||||
primaryKeys := scope.getColumnAsArray(relationship.AssociationForeignFieldNames, scope.Value)
|
||||
query = query.Where(
|
||||
fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.ForeignDBNames), toQueryMarks(primaryKeys)),
|
||||
toQueryValues(primaryKeys)...,
|
||||
)
|
||||
} else if relationship.Kind == "belongs_to" {
|
||||
primaryKeys := scope.getColumnAsArray(relationship.ForeignFieldNames, scope.Value)
|
||||
query = query.Where(
|
||||
fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.AssociationForeignDBNames), toQueryMarks(primaryKeys)),
|
||||
toQueryValues(primaryKeys)...,
|
||||
)
|
||||
}
|
||||
|
||||
if relationship.PolymorphicType != "" {
|
||||
query = query.Where(
|
||||
fmt.Sprintf("%v.%v = ?", scope.New(fieldValue).QuotedTableName(), scope.Quote(relationship.PolymorphicDBName)),
|
||||
relationship.PolymorphicValue,
|
||||
)
|
||||
}
|
||||
|
||||
if err := query.Model(fieldValue).Count(&count).Error; err != nil {
|
||||
association.Error = err
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// saveAssociations save passed values as associations
|
||||
func (association *Association) saveAssociations(values ...interface{}) *Association {
|
||||
var (
|
||||
scope = association.scope
|
||||
field = association.field
|
||||
relationship = field.Relationship
|
||||
)
|
||||
|
||||
saveAssociation := func(reflectValue reflect.Value) {
|
||||
// value has to been pointer
|
||||
if reflectValue.Kind() != reflect.Ptr {
|
||||
reflectPtr := reflect.New(reflectValue.Type())
|
||||
reflectPtr.Elem().Set(reflectValue)
|
||||
reflectValue = reflectPtr
|
||||
}
|
||||
|
||||
// value has to been saved for many2many
|
||||
if relationship.Kind == "many_to_many" {
|
||||
if scope.New(reflectValue.Interface()).PrimaryKeyZero() {
|
||||
association.setErr(scope.NewDB().Save(reflectValue.Interface()).Error)
|
||||
}
|
||||
}
|
||||
|
||||
// Assign Fields
|
||||
var fieldType = field.Field.Type()
|
||||
var setFieldBackToValue, setSliceFieldBackToValue bool
|
||||
if reflectValue.Type().AssignableTo(fieldType) {
|
||||
field.Set(reflectValue)
|
||||
} else if reflectValue.Type().Elem().AssignableTo(fieldType) {
|
||||
// if field's type is struct, then need to set value back to argument after save
|
||||
setFieldBackToValue = true
|
||||
field.Set(reflectValue.Elem())
|
||||
} else if fieldType.Kind() == reflect.Slice {
|
||||
if reflectValue.Type().AssignableTo(fieldType.Elem()) {
|
||||
field.Set(reflect.Append(field.Field, reflectValue))
|
||||
} else if reflectValue.Type().Elem().AssignableTo(fieldType.Elem()) {
|
||||
// if field's type is slice of struct, then need to set value back to argument after save
|
||||
setSliceFieldBackToValue = true
|
||||
field.Set(reflect.Append(field.Field, reflectValue.Elem()))
|
||||
}
|
||||
}
|
||||
|
||||
if relationship.Kind == "many_to_many" {
|
||||
association.setErr(relationship.JoinTableHandler.Add(relationship.JoinTableHandler, scope.NewDB(), scope.Value, reflectValue.Interface()))
|
||||
} else {
|
||||
association.setErr(scope.NewDB().Select(field.Name).Save(scope.Value).Error)
|
||||
|
||||
if setFieldBackToValue {
|
||||
reflectValue.Elem().Set(field.Field)
|
||||
} else if setSliceFieldBackToValue {
|
||||
reflectValue.Elem().Set(field.Field.Index(field.Field.Len() - 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, value := range values {
|
||||
reflectValue := reflect.ValueOf(value)
|
||||
indirectReflectValue := reflect.Indirect(reflectValue)
|
||||
if indirectReflectValue.Kind() == reflect.Struct {
|
||||
saveAssociation(reflectValue)
|
||||
} else if indirectReflectValue.Kind() == reflect.Slice {
|
||||
for i := 0; i < indirectReflectValue.Len(); i++ {
|
||||
saveAssociation(indirectReflectValue.Index(i))
|
||||
}
|
||||
} else {
|
||||
association.setErr(errors.New("invalid value type"))
|
||||
}
|
||||
}
|
||||
return association
|
||||
}
|
||||
|
||||
func (association *Association) setErr(err error) *Association {
|
||||
if err != nil {
|
||||
association.Error = err
|
||||
}
|
||||
return association
|
||||
}
|
||||
242
vendor/github.com/jinzhu/gorm/callback.go
generated
vendored
Normal file
242
vendor/github.com/jinzhu/gorm/callback.go
generated
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
package gorm
|
||||
|
||||
import "log"
|
||||
|
||||
// DefaultCallback default callbacks defined by gorm
|
||||
var DefaultCallback = &Callback{}
|
||||
|
||||
// Callback is a struct that contains all CRUD callbacks
|
||||
// Field `creates` contains callbacks will be call when creating object
|
||||
// Field `updates` contains callbacks will be call when updating object
|
||||
// Field `deletes` contains callbacks will be call when deleting object
|
||||
// Field `queries` contains callbacks will be call when querying object with query methods like Find, First, Related, Association...
|
||||
// Field `rowQueries` contains callbacks will be call when querying object with Row, Rows...
|
||||
// Field `processors` contains all callback processors, will be used to generate above callbacks in order
|
||||
type Callback struct {
|
||||
creates []*func(scope *Scope)
|
||||
updates []*func(scope *Scope)
|
||||
deletes []*func(scope *Scope)
|
||||
queries []*func(scope *Scope)
|
||||
rowQueries []*func(scope *Scope)
|
||||
processors []*CallbackProcessor
|
||||
}
|
||||
|
||||
// CallbackProcessor contains callback informations
|
||||
type CallbackProcessor struct {
|
||||
name string // current callback's name
|
||||
before string // register current callback before a callback
|
||||
after string // register current callback after a callback
|
||||
replace bool // replace callbacks with same name
|
||||
remove bool // delete callbacks with same name
|
||||
kind string // callback type: create, update, delete, query, row_query
|
||||
processor *func(scope *Scope) // callback handler
|
||||
parent *Callback
|
||||
}
|
||||
|
||||
func (c *Callback) clone() *Callback {
|
||||
return &Callback{
|
||||
creates: c.creates,
|
||||
updates: c.updates,
|
||||
deletes: c.deletes,
|
||||
queries: c.queries,
|
||||
rowQueries: c.rowQueries,
|
||||
processors: c.processors,
|
||||
}
|
||||
}
|
||||
|
||||
// Create could be used to register callbacks for creating object
|
||||
// db.Callback().Create().After("gorm:create").Register("plugin:run_after_create", func(*Scope) {
|
||||
// // business logic
|
||||
// ...
|
||||
//
|
||||
// // set error if some thing wrong happened, will rollback the creating
|
||||
// scope.Err(errors.New("error"))
|
||||
// })
|
||||
func (c *Callback) Create() *CallbackProcessor {
|
||||
return &CallbackProcessor{kind: "create", parent: c}
|
||||
}
|
||||
|
||||
// Update could be used to register callbacks for updating object, refer `Create` for usage
|
||||
func (c *Callback) Update() *CallbackProcessor {
|
||||
return &CallbackProcessor{kind: "update", parent: c}
|
||||
}
|
||||
|
||||
// Delete could be used to register callbacks for deleting object, refer `Create` for usage
|
||||
func (c *Callback) Delete() *CallbackProcessor {
|
||||
return &CallbackProcessor{kind: "delete", parent: c}
|
||||
}
|
||||
|
||||
// Query could be used to register callbacks for querying objects with query methods like `Find`, `First`, `Related`, `Association`...
|
||||
// Refer `Create` for usage
|
||||
func (c *Callback) Query() *CallbackProcessor {
|
||||
return &CallbackProcessor{kind: "query", parent: c}
|
||||
}
|
||||
|
||||
// RowQuery could be used to register callbacks for querying objects with `Row`, `Rows`, refer `Create` for usage
|
||||
func (c *Callback) RowQuery() *CallbackProcessor {
|
||||
return &CallbackProcessor{kind: "row_query", parent: c}
|
||||
}
|
||||
|
||||
// After insert a new callback after callback `callbackName`, refer `Callbacks.Create`
|
||||
func (cp *CallbackProcessor) After(callbackName string) *CallbackProcessor {
|
||||
cp.after = callbackName
|
||||
return cp
|
||||
}
|
||||
|
||||
// Before insert a new callback before callback `callbackName`, refer `Callbacks.Create`
|
||||
func (cp *CallbackProcessor) Before(callbackName string) *CallbackProcessor {
|
||||
cp.before = callbackName
|
||||
return cp
|
||||
}
|
||||
|
||||
// Register a new callback, refer `Callbacks.Create`
|
||||
func (cp *CallbackProcessor) Register(callbackName string, callback func(scope *Scope)) {
|
||||
if cp.kind == "row_query" {
|
||||
if cp.before == "" && cp.after == "" && callbackName != "gorm:row_query" {
|
||||
log.Printf("Registing RowQuery callback %v without specify order with Before(), After(), applying Before('gorm:row_query') by default for compatibility...\n", callbackName)
|
||||
cp.before = "gorm:row_query"
|
||||
}
|
||||
}
|
||||
|
||||
cp.name = callbackName
|
||||
cp.processor = &callback
|
||||
cp.parent.processors = append(cp.parent.processors, cp)
|
||||
cp.parent.reorder()
|
||||
}
|
||||
|
||||
// Remove a registered callback
|
||||
// db.Callback().Create().Remove("gorm:update_time_stamp_when_create")
|
||||
func (cp *CallbackProcessor) Remove(callbackName string) {
|
||||
log.Printf("[info] removing callback `%v` from %v\n", callbackName, fileWithLineNum())
|
||||
cp.name = callbackName
|
||||
cp.remove = true
|
||||
cp.parent.processors = append(cp.parent.processors, cp)
|
||||
cp.parent.reorder()
|
||||
}
|
||||
|
||||
// Replace a registered callback with new callback
|
||||
// db.Callback().Create().Replace("gorm:update_time_stamp_when_create", func(*Scope) {
|
||||
// scope.SetColumn("Created", now)
|
||||
// scope.SetColumn("Updated", now)
|
||||
// })
|
||||
func (cp *CallbackProcessor) Replace(callbackName string, callback func(scope *Scope)) {
|
||||
log.Printf("[info] replacing callback `%v` from %v\n", callbackName, fileWithLineNum())
|
||||
cp.name = callbackName
|
||||
cp.processor = &callback
|
||||
cp.replace = true
|
||||
cp.parent.processors = append(cp.parent.processors, cp)
|
||||
cp.parent.reorder()
|
||||
}
|
||||
|
||||
// Get registered callback
|
||||
// db.Callback().Create().Get("gorm:create")
|
||||
func (cp *CallbackProcessor) Get(callbackName string) (callback func(scope *Scope)) {
|
||||
for _, p := range cp.parent.processors {
|
||||
if p.name == callbackName && p.kind == cp.kind && !cp.remove {
|
||||
return *p.processor
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getRIndex get right index from string slice
|
||||
func getRIndex(strs []string, str string) int {
|
||||
for i := len(strs) - 1; i >= 0; i-- {
|
||||
if strs[i] == str {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// sortProcessors sort callback processors based on its before, after, remove, replace
|
||||
func sortProcessors(cps []*CallbackProcessor) []*func(scope *Scope) {
|
||||
var (
|
||||
allNames, sortedNames []string
|
||||
sortCallbackProcessor func(c *CallbackProcessor)
|
||||
)
|
||||
|
||||
for _, cp := range cps {
|
||||
// show warning message the callback name already exists
|
||||
if index := getRIndex(allNames, cp.name); index > -1 && !cp.replace && !cp.remove {
|
||||
log.Printf("[warning] duplicated callback `%v` from %v\n", cp.name, fileWithLineNum())
|
||||
}
|
||||
allNames = append(allNames, cp.name)
|
||||
}
|
||||
|
||||
sortCallbackProcessor = func(c *CallbackProcessor) {
|
||||
if getRIndex(sortedNames, c.name) == -1 { // if not sorted
|
||||
if c.before != "" { // if defined before callback
|
||||
if index := getRIndex(sortedNames, c.before); index != -1 {
|
||||
// if before callback already sorted, append current callback just after it
|
||||
sortedNames = append(sortedNames[:index], append([]string{c.name}, sortedNames[index:]...)...)
|
||||
} else if index := getRIndex(allNames, c.before); index != -1 {
|
||||
// if before callback exists but haven't sorted, append current callback to last
|
||||
sortedNames = append(sortedNames, c.name)
|
||||
sortCallbackProcessor(cps[index])
|
||||
}
|
||||
}
|
||||
|
||||
if c.after != "" { // if defined after callback
|
||||
if index := getRIndex(sortedNames, c.after); index != -1 {
|
||||
// if after callback already sorted, append current callback just before it
|
||||
sortedNames = append(sortedNames[:index+1], append([]string{c.name}, sortedNames[index+1:]...)...)
|
||||
} else if index := getRIndex(allNames, c.after); index != -1 {
|
||||
// if after callback exists but haven't sorted
|
||||
cp := cps[index]
|
||||
// set after callback's before callback to current callback
|
||||
if cp.before == "" {
|
||||
cp.before = c.name
|
||||
}
|
||||
sortCallbackProcessor(cp)
|
||||
}
|
||||
}
|
||||
|
||||
// if current callback haven't been sorted, append it to last
|
||||
if getRIndex(sortedNames, c.name) == -1 {
|
||||
sortedNames = append(sortedNames, c.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, cp := range cps {
|
||||
sortCallbackProcessor(cp)
|
||||
}
|
||||
|
||||
var sortedFuncs []*func(scope *Scope)
|
||||
for _, name := range sortedNames {
|
||||
if index := getRIndex(allNames, name); !cps[index].remove {
|
||||
sortedFuncs = append(sortedFuncs, cps[index].processor)
|
||||
}
|
||||
}
|
||||
|
||||
return sortedFuncs
|
||||
}
|
||||
|
||||
// reorder all registered processors, and reset CRUD callbacks
|
||||
func (c *Callback) reorder() {
|
||||
var creates, updates, deletes, queries, rowQueries []*CallbackProcessor
|
||||
|
||||
for _, processor := range c.processors {
|
||||
if processor.name != "" {
|
||||
switch processor.kind {
|
||||
case "create":
|
||||
creates = append(creates, processor)
|
||||
case "update":
|
||||
updates = append(updates, processor)
|
||||
case "delete":
|
||||
deletes = append(deletes, processor)
|
||||
case "query":
|
||||
queries = append(queries, processor)
|
||||
case "row_query":
|
||||
rowQueries = append(rowQueries, processor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.creates = sortProcessors(creates)
|
||||
c.updates = sortProcessors(updates)
|
||||
c.deletes = sortProcessors(deletes)
|
||||
c.queries = sortProcessors(queries)
|
||||
c.rowQueries = sortProcessors(rowQueries)
|
||||
}
|
||||
164
vendor/github.com/jinzhu/gorm/callback_create.go
generated
vendored
Normal file
164
vendor/github.com/jinzhu/gorm/callback_create.go
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Define callbacks for creating
|
||||
func init() {
|
||||
DefaultCallback.Create().Register("gorm:begin_transaction", beginTransactionCallback)
|
||||
DefaultCallback.Create().Register("gorm:before_create", beforeCreateCallback)
|
||||
DefaultCallback.Create().Register("gorm:save_before_associations", saveBeforeAssociationsCallback)
|
||||
DefaultCallback.Create().Register("gorm:update_time_stamp", updateTimeStampForCreateCallback)
|
||||
DefaultCallback.Create().Register("gorm:create", createCallback)
|
||||
DefaultCallback.Create().Register("gorm:force_reload_after_create", forceReloadAfterCreateCallback)
|
||||
DefaultCallback.Create().Register("gorm:save_after_associations", saveAfterAssociationsCallback)
|
||||
DefaultCallback.Create().Register("gorm:after_create", afterCreateCallback)
|
||||
DefaultCallback.Create().Register("gorm:commit_or_rollback_transaction", commitOrRollbackTransactionCallback)
|
||||
}
|
||||
|
||||
// beforeCreateCallback will invoke `BeforeSave`, `BeforeCreate` method before creating
|
||||
func beforeCreateCallback(scope *Scope) {
|
||||
if !scope.HasError() {
|
||||
scope.CallMethod("BeforeSave")
|
||||
}
|
||||
if !scope.HasError() {
|
||||
scope.CallMethod("BeforeCreate")
|
||||
}
|
||||
}
|
||||
|
||||
// updateTimeStampForCreateCallback will set `CreatedAt`, `UpdatedAt` when creating
|
||||
func updateTimeStampForCreateCallback(scope *Scope) {
|
||||
if !scope.HasError() {
|
||||
now := NowFunc()
|
||||
|
||||
if createdAtField, ok := scope.FieldByName("CreatedAt"); ok {
|
||||
if createdAtField.IsBlank {
|
||||
createdAtField.Set(now)
|
||||
}
|
||||
}
|
||||
|
||||
if updatedAtField, ok := scope.FieldByName("UpdatedAt"); ok {
|
||||
if updatedAtField.IsBlank {
|
||||
updatedAtField.Set(now)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// createCallback the callback used to insert data into database
|
||||
func createCallback(scope *Scope) {
|
||||
if !scope.HasError() {
|
||||
defer scope.trace(NowFunc())
|
||||
|
||||
var (
|
||||
columns, placeholders []string
|
||||
blankColumnsWithDefaultValue []string
|
||||
)
|
||||
|
||||
for _, field := range scope.Fields() {
|
||||
if scope.changeableField(field) {
|
||||
if field.IsNormal {
|
||||
if field.IsBlank && field.HasDefaultValue {
|
||||
blankColumnsWithDefaultValue = append(blankColumnsWithDefaultValue, scope.Quote(field.DBName))
|
||||
scope.InstanceSet("gorm:blank_columns_with_default_value", blankColumnsWithDefaultValue)
|
||||
} else if !field.IsPrimaryKey || !field.IsBlank {
|
||||
columns = append(columns, scope.Quote(field.DBName))
|
||||
placeholders = append(placeholders, scope.AddToVars(field.Field.Interface()))
|
||||
}
|
||||
} else if field.Relationship != nil && field.Relationship.Kind == "belongs_to" {
|
||||
for _, foreignKey := range field.Relationship.ForeignDBNames {
|
||||
if foreignField, ok := scope.FieldByName(foreignKey); ok && !scope.changeableField(foreignField) {
|
||||
columns = append(columns, scope.Quote(foreignField.DBName))
|
||||
placeholders = append(placeholders, scope.AddToVars(foreignField.Field.Interface()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
returningColumn = "*"
|
||||
quotedTableName = scope.QuotedTableName()
|
||||
primaryField = scope.PrimaryField()
|
||||
extraOption string
|
||||
)
|
||||
|
||||
if str, ok := scope.Get("gorm:insert_option"); ok {
|
||||
extraOption = fmt.Sprint(str)
|
||||
}
|
||||
|
||||
if primaryField != nil {
|
||||
returningColumn = scope.Quote(primaryField.DBName)
|
||||
}
|
||||
|
||||
lastInsertIDReturningSuffix := scope.Dialect().LastInsertIDReturningSuffix(quotedTableName, returningColumn)
|
||||
|
||||
if len(columns) == 0 {
|
||||
scope.Raw(fmt.Sprintf(
|
||||
"INSERT INTO %v %v%v%v",
|
||||
quotedTableName,
|
||||
scope.Dialect().DefaultValueStr(),
|
||||
addExtraSpaceIfExist(extraOption),
|
||||
addExtraSpaceIfExist(lastInsertIDReturningSuffix),
|
||||
))
|
||||
} else {
|
||||
scope.Raw(fmt.Sprintf(
|
||||
"INSERT INTO %v (%v) VALUES (%v)%v%v",
|
||||
scope.QuotedTableName(),
|
||||
strings.Join(columns, ","),
|
||||
strings.Join(placeholders, ","),
|
||||
addExtraSpaceIfExist(extraOption),
|
||||
addExtraSpaceIfExist(lastInsertIDReturningSuffix),
|
||||
))
|
||||
}
|
||||
|
||||
// execute create sql
|
||||
if lastInsertIDReturningSuffix == "" || primaryField == nil {
|
||||
if result, err := scope.SQLDB().Exec(scope.SQL, scope.SQLVars...); scope.Err(err) == nil {
|
||||
// set rows affected count
|
||||
scope.db.RowsAffected, _ = result.RowsAffected()
|
||||
|
||||
// set primary value to primary field
|
||||
if primaryField != nil && primaryField.IsBlank {
|
||||
if primaryValue, err := result.LastInsertId(); scope.Err(err) == nil {
|
||||
scope.Err(primaryField.Set(primaryValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if primaryField.Field.CanAddr() {
|
||||
if err := scope.SQLDB().QueryRow(scope.SQL, scope.SQLVars...).Scan(primaryField.Field.Addr().Interface()); scope.Err(err) == nil {
|
||||
primaryField.IsBlank = false
|
||||
scope.db.RowsAffected = 1
|
||||
}
|
||||
} else {
|
||||
scope.Err(ErrUnaddressable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// forceReloadAfterCreateCallback will reload columns that having default value, and set it back to current object
|
||||
func forceReloadAfterCreateCallback(scope *Scope) {
|
||||
if blankColumnsWithDefaultValue, ok := scope.InstanceGet("gorm:blank_columns_with_default_value"); ok {
|
||||
db := scope.DB().New().Table(scope.TableName()).Select(blankColumnsWithDefaultValue.([]string))
|
||||
for _, field := range scope.Fields() {
|
||||
if field.IsPrimaryKey && !field.IsBlank {
|
||||
db = db.Where(fmt.Sprintf("%v = ?", field.DBName), field.Field.Interface())
|
||||
}
|
||||
}
|
||||
db.Scan(scope.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// afterCreateCallback will invoke `AfterCreate`, `AfterSave` method after creating
|
||||
func afterCreateCallback(scope *Scope) {
|
||||
if !scope.HasError() {
|
||||
scope.CallMethod("AfterCreate")
|
||||
}
|
||||
if !scope.HasError() {
|
||||
scope.CallMethod("AfterSave")
|
||||
}
|
||||
}
|
||||
63
vendor/github.com/jinzhu/gorm/callback_delete.go
generated
vendored
Normal file
63
vendor/github.com/jinzhu/gorm/callback_delete.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Define callbacks for deleting
|
||||
func init() {
|
||||
DefaultCallback.Delete().Register("gorm:begin_transaction", beginTransactionCallback)
|
||||
DefaultCallback.Delete().Register("gorm:before_delete", beforeDeleteCallback)
|
||||
DefaultCallback.Delete().Register("gorm:delete", deleteCallback)
|
||||
DefaultCallback.Delete().Register("gorm:after_delete", afterDeleteCallback)
|
||||
DefaultCallback.Delete().Register("gorm:commit_or_rollback_transaction", commitOrRollbackTransactionCallback)
|
||||
}
|
||||
|
||||
// beforeDeleteCallback will invoke `BeforeDelete` method before deleting
|
||||
func beforeDeleteCallback(scope *Scope) {
|
||||
if scope.DB().HasBlockGlobalUpdate() && !scope.hasConditions() {
|
||||
scope.Err(errors.New("Missing WHERE clause while deleting"))
|
||||
return
|
||||
}
|
||||
if !scope.HasError() {
|
||||
scope.CallMethod("BeforeDelete")
|
||||
}
|
||||
}
|
||||
|
||||
// deleteCallback used to delete data from database or set deleted_at to current time (when using with soft delete)
|
||||
func deleteCallback(scope *Scope) {
|
||||
if !scope.HasError() {
|
||||
var extraOption string
|
||||
if str, ok := scope.Get("gorm:delete_option"); ok {
|
||||
extraOption = fmt.Sprint(str)
|
||||
}
|
||||
|
||||
deletedAtField, hasDeletedAtField := scope.FieldByName("DeletedAt")
|
||||
|
||||
if !scope.Search.Unscoped && hasDeletedAtField {
|
||||
scope.Raw(fmt.Sprintf(
|
||||
"UPDATE %v SET %v=%v%v%v",
|
||||
scope.QuotedTableName(),
|
||||
scope.Quote(deletedAtField.DBName),
|
||||
scope.AddToVars(NowFunc()),
|
||||
addExtraSpaceIfExist(scope.CombinedConditionSql()),
|
||||
addExtraSpaceIfExist(extraOption),
|
||||
)).Exec()
|
||||
} else {
|
||||
scope.Raw(fmt.Sprintf(
|
||||
"DELETE FROM %v%v%v",
|
||||
scope.QuotedTableName(),
|
||||
addExtraSpaceIfExist(scope.CombinedConditionSql()),
|
||||
addExtraSpaceIfExist(extraOption),
|
||||
)).Exec()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// afterDeleteCallback will invoke `AfterDelete` method after deleting
|
||||
func afterDeleteCallback(scope *Scope) {
|
||||
if !scope.HasError() {
|
||||
scope.CallMethod("AfterDelete")
|
||||
}
|
||||
}
|
||||
99
vendor/github.com/jinzhu/gorm/callback_query.go
generated
vendored
Normal file
99
vendor/github.com/jinzhu/gorm/callback_query.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Define callbacks for querying
|
||||
func init() {
|
||||
DefaultCallback.Query().Register("gorm:query", queryCallback)
|
||||
DefaultCallback.Query().Register("gorm:preload", preloadCallback)
|
||||
DefaultCallback.Query().Register("gorm:after_query", afterQueryCallback)
|
||||
}
|
||||
|
||||
// queryCallback used to query data from database
|
||||
func queryCallback(scope *Scope) {
|
||||
if _, skip := scope.InstanceGet("gorm:skip_query_callback"); skip {
|
||||
return
|
||||
}
|
||||
|
||||
defer scope.trace(NowFunc())
|
||||
|
||||
var (
|
||||
isSlice, isPtr bool
|
||||
resultType reflect.Type
|
||||
results = scope.IndirectValue()
|
||||
)
|
||||
|
||||
if orderBy, ok := scope.Get("gorm:order_by_primary_key"); ok {
|
||||
if primaryField := scope.PrimaryField(); primaryField != nil {
|
||||
scope.Search.Order(fmt.Sprintf("%v.%v %v", scope.QuotedTableName(), scope.Quote(primaryField.DBName), orderBy))
|
||||
}
|
||||
}
|
||||
|
||||
if value, ok := scope.Get("gorm:query_destination"); ok {
|
||||
results = indirect(reflect.ValueOf(value))
|
||||
}
|
||||
|
||||
if kind := results.Kind(); kind == reflect.Slice {
|
||||
isSlice = true
|
||||
resultType = results.Type().Elem()
|
||||
results.Set(reflect.MakeSlice(results.Type(), 0, 0))
|
||||
|
||||
if resultType.Kind() == reflect.Ptr {
|
||||
isPtr = true
|
||||
resultType = resultType.Elem()
|
||||
}
|
||||
} else if kind != reflect.Struct {
|
||||
scope.Err(errors.New("unsupported destination, should be slice or struct"))
|
||||
return
|
||||
}
|
||||
|
||||
scope.prepareQuerySQL()
|
||||
|
||||
if !scope.HasError() {
|
||||
scope.db.RowsAffected = 0
|
||||
if str, ok := scope.Get("gorm:query_option"); ok {
|
||||
scope.SQL += addExtraSpaceIfExist(fmt.Sprint(str))
|
||||
}
|
||||
|
||||
if rows, err := scope.SQLDB().Query(scope.SQL, scope.SQLVars...); scope.Err(err) == nil {
|
||||
defer rows.Close()
|
||||
|
||||
columns, _ := rows.Columns()
|
||||
for rows.Next() {
|
||||
scope.db.RowsAffected++
|
||||
|
||||
elem := results
|
||||
if isSlice {
|
||||
elem = reflect.New(resultType).Elem()
|
||||
}
|
||||
|
||||
scope.scan(rows, columns, scope.New(elem.Addr().Interface()).Fields())
|
||||
|
||||
if isSlice {
|
||||
if isPtr {
|
||||
results.Set(reflect.Append(results, elem.Addr()))
|
||||
} else {
|
||||
results.Set(reflect.Append(results, elem))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
scope.Err(err)
|
||||
} else if scope.db.RowsAffected == 0 && !isSlice {
|
||||
scope.Err(ErrRecordNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// afterQueryCallback will invoke `AfterFind` method after querying
|
||||
func afterQueryCallback(scope *Scope) {
|
||||
if !scope.HasError() {
|
||||
scope.CallMethod("AfterFind")
|
||||
}
|
||||
}
|
||||
387
vendor/github.com/jinzhu/gorm/callback_query_preload.go
generated
vendored
Normal file
387
vendor/github.com/jinzhu/gorm/callback_query_preload.go
generated
vendored
Normal file
@@ -0,0 +1,387 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// preloadCallback used to preload associations
|
||||
func preloadCallback(scope *Scope) {
|
||||
if _, skip := scope.InstanceGet("gorm:skip_query_callback"); skip {
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := scope.Get("gorm:auto_preload"); ok {
|
||||
autoPreload(scope)
|
||||
}
|
||||
|
||||
if scope.Search.preload == nil || scope.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
preloadedMap = map[string]bool{}
|
||||
fields = scope.Fields()
|
||||
)
|
||||
|
||||
for _, preload := range scope.Search.preload {
|
||||
var (
|
||||
preloadFields = strings.Split(preload.schema, ".")
|
||||
currentScope = scope
|
||||
currentFields = fields
|
||||
)
|
||||
|
||||
for idx, preloadField := range preloadFields {
|
||||
var currentPreloadConditions []interface{}
|
||||
|
||||
if currentScope == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// if not preloaded
|
||||
if preloadKey := strings.Join(preloadFields[:idx+1], "."); !preloadedMap[preloadKey] {
|
||||
|
||||
// assign search conditions to last preload
|
||||
if idx == len(preloadFields)-1 {
|
||||
currentPreloadConditions = preload.conditions
|
||||
}
|
||||
|
||||
for _, field := range currentFields {
|
||||
if field.Name != preloadField || field.Relationship == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch field.Relationship.Kind {
|
||||
case "has_one":
|
||||
currentScope.handleHasOnePreload(field, currentPreloadConditions)
|
||||
case "has_many":
|
||||
currentScope.handleHasManyPreload(field, currentPreloadConditions)
|
||||
case "belongs_to":
|
||||
currentScope.handleBelongsToPreload(field, currentPreloadConditions)
|
||||
case "many_to_many":
|
||||
currentScope.handleManyToManyPreload(field, currentPreloadConditions)
|
||||
default:
|
||||
scope.Err(errors.New("unsupported relation"))
|
||||
}
|
||||
|
||||
preloadedMap[preloadKey] = true
|
||||
break
|
||||
}
|
||||
|
||||
if !preloadedMap[preloadKey] {
|
||||
scope.Err(fmt.Errorf("can't preload field %s for %s", preloadField, currentScope.GetModelStruct().ModelType))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// preload next level
|
||||
if idx < len(preloadFields)-1 {
|
||||
currentScope = currentScope.getColumnAsScope(preloadField)
|
||||
if currentScope != nil {
|
||||
currentFields = currentScope.Fields()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func autoPreload(scope *Scope) {
|
||||
for _, field := range scope.Fields() {
|
||||
if field.Relationship == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if val, ok := field.TagSettings["PRELOAD"]; ok {
|
||||
if preload, err := strconv.ParseBool(val); err != nil {
|
||||
scope.Err(errors.New("invalid preload option"))
|
||||
return
|
||||
} else if !preload {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
scope.Search.Preload(field.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func (scope *Scope) generatePreloadDBWithConditions(conditions []interface{}) (*DB, []interface{}) {
|
||||
var (
|
||||
preloadDB = scope.NewDB()
|
||||
preloadConditions []interface{}
|
||||
)
|
||||
|
||||
for _, condition := range conditions {
|
||||
if scopes, ok := condition.(func(*DB) *DB); ok {
|
||||
preloadDB = scopes(preloadDB)
|
||||
} else {
|
||||
preloadConditions = append(preloadConditions, condition)
|
||||
}
|
||||
}
|
||||
|
||||
return preloadDB, preloadConditions
|
||||
}
|
||||
|
||||
// handleHasOnePreload used to preload has one associations
|
||||
func (scope *Scope) handleHasOnePreload(field *Field, conditions []interface{}) {
|
||||
relation := field.Relationship
|
||||
|
||||
// get relations's primary keys
|
||||
primaryKeys := scope.getColumnAsArray(relation.AssociationForeignFieldNames, scope.Value)
|
||||
if len(primaryKeys) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// preload conditions
|
||||
preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)
|
||||
|
||||
// find relations
|
||||
query := fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.ForeignDBNames), toQueryMarks(primaryKeys))
|
||||
values := toQueryValues(primaryKeys)
|
||||
if relation.PolymorphicType != "" {
|
||||
query += fmt.Sprintf(" AND %v = ?", scope.Quote(relation.PolymorphicDBName))
|
||||
values = append(values, relation.PolymorphicValue)
|
||||
}
|
||||
|
||||
results := makeSlice(field.Struct.Type)
|
||||
scope.Err(preloadDB.Where(query, values...).Find(results, preloadConditions...).Error)
|
||||
|
||||
// assign find results
|
||||
var (
|
||||
resultsValue = indirect(reflect.ValueOf(results))
|
||||
indirectScopeValue = scope.IndirectValue()
|
||||
)
|
||||
|
||||
if indirectScopeValue.Kind() == reflect.Slice {
|
||||
for j := 0; j < indirectScopeValue.Len(); j++ {
|
||||
for i := 0; i < resultsValue.Len(); i++ {
|
||||
result := resultsValue.Index(i)
|
||||
foreignValues := getValueFromFields(result, relation.ForeignFieldNames)
|
||||
if indirectValue := indirect(indirectScopeValue.Index(j)); equalAsString(getValueFromFields(indirectValue, relation.AssociationForeignFieldNames), foreignValues) {
|
||||
indirectValue.FieldByName(field.Name).Set(result)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < resultsValue.Len(); i++ {
|
||||
result := resultsValue.Index(i)
|
||||
scope.Err(field.Set(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleHasManyPreload used to preload has many associations
|
||||
func (scope *Scope) handleHasManyPreload(field *Field, conditions []interface{}) {
|
||||
relation := field.Relationship
|
||||
|
||||
// get relations's primary keys
|
||||
primaryKeys := scope.getColumnAsArray(relation.AssociationForeignFieldNames, scope.Value)
|
||||
if len(primaryKeys) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// preload conditions
|
||||
preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)
|
||||
|
||||
// find relations
|
||||
query := fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.ForeignDBNames), toQueryMarks(primaryKeys))
|
||||
values := toQueryValues(primaryKeys)
|
||||
if relation.PolymorphicType != "" {
|
||||
query += fmt.Sprintf(" AND %v = ?", scope.Quote(relation.PolymorphicDBName))
|
||||
values = append(values, relation.PolymorphicValue)
|
||||
}
|
||||
|
||||
results := makeSlice(field.Struct.Type)
|
||||
scope.Err(preloadDB.Where(query, values...).Find(results, preloadConditions...).Error)
|
||||
|
||||
// assign find results
|
||||
var (
|
||||
resultsValue = indirect(reflect.ValueOf(results))
|
||||
indirectScopeValue = scope.IndirectValue()
|
||||
)
|
||||
|
||||
if indirectScopeValue.Kind() == reflect.Slice {
|
||||
preloadMap := make(map[string][]reflect.Value)
|
||||
for i := 0; i < resultsValue.Len(); i++ {
|
||||
result := resultsValue.Index(i)
|
||||
foreignValues := getValueFromFields(result, relation.ForeignFieldNames)
|
||||
preloadMap[toString(foreignValues)] = append(preloadMap[toString(foreignValues)], result)
|
||||
}
|
||||
|
||||
for j := 0; j < indirectScopeValue.Len(); j++ {
|
||||
object := indirect(indirectScopeValue.Index(j))
|
||||
objectRealValue := getValueFromFields(object, relation.AssociationForeignFieldNames)
|
||||
f := object.FieldByName(field.Name)
|
||||
if results, ok := preloadMap[toString(objectRealValue)]; ok {
|
||||
f.Set(reflect.Append(f, results...))
|
||||
} else {
|
||||
f.Set(reflect.MakeSlice(f.Type(), 0, 0))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
scope.Err(field.Set(resultsValue))
|
||||
}
|
||||
}
|
||||
|
||||
// handleBelongsToPreload used to preload belongs to associations
|
||||
func (scope *Scope) handleBelongsToPreload(field *Field, conditions []interface{}) {
|
||||
relation := field.Relationship
|
||||
|
||||
// preload conditions
|
||||
preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)
|
||||
|
||||
// get relations's primary keys
|
||||
primaryKeys := scope.getColumnAsArray(relation.ForeignFieldNames, scope.Value)
|
||||
if len(primaryKeys) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// find relations
|
||||
results := makeSlice(field.Struct.Type)
|
||||
scope.Err(preloadDB.Where(fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.AssociationForeignDBNames), toQueryMarks(primaryKeys)), toQueryValues(primaryKeys)...).Find(results, preloadConditions...).Error)
|
||||
|
||||
// assign find results
|
||||
var (
|
||||
resultsValue = indirect(reflect.ValueOf(results))
|
||||
indirectScopeValue = scope.IndirectValue()
|
||||
)
|
||||
|
||||
for i := 0; i < resultsValue.Len(); i++ {
|
||||
result := resultsValue.Index(i)
|
||||
if indirectScopeValue.Kind() == reflect.Slice {
|
||||
value := getValueFromFields(result, relation.AssociationForeignFieldNames)
|
||||
for j := 0; j < indirectScopeValue.Len(); j++ {
|
||||
object := indirect(indirectScopeValue.Index(j))
|
||||
if equalAsString(getValueFromFields(object, relation.ForeignFieldNames), value) {
|
||||
object.FieldByName(field.Name).Set(result)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
scope.Err(field.Set(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleManyToManyPreload used to preload many to many associations
|
||||
func (scope *Scope) handleManyToManyPreload(field *Field, conditions []interface{}) {
|
||||
var (
|
||||
relation = field.Relationship
|
||||
joinTableHandler = relation.JoinTableHandler
|
||||
fieldType = field.Struct.Type.Elem()
|
||||
foreignKeyValue interface{}
|
||||
foreignKeyType = reflect.ValueOf(&foreignKeyValue).Type()
|
||||
linkHash = map[string][]reflect.Value{}
|
||||
isPtr bool
|
||||
)
|
||||
|
||||
if fieldType.Kind() == reflect.Ptr {
|
||||
isPtr = true
|
||||
fieldType = fieldType.Elem()
|
||||
}
|
||||
|
||||
var sourceKeys = []string{}
|
||||
for _, key := range joinTableHandler.SourceForeignKeys() {
|
||||
sourceKeys = append(sourceKeys, key.DBName)
|
||||
}
|
||||
|
||||
// preload conditions
|
||||
preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)
|
||||
|
||||
// generate query with join table
|
||||
newScope := scope.New(reflect.New(fieldType).Interface())
|
||||
preloadDB = preloadDB.Table(newScope.TableName()).Model(newScope.Value)
|
||||
|
||||
if len(preloadDB.search.selects) == 0 {
|
||||
preloadDB = preloadDB.Select("*")
|
||||
}
|
||||
|
||||
preloadDB = joinTableHandler.JoinWith(joinTableHandler, preloadDB, scope.Value)
|
||||
|
||||
// preload inline conditions
|
||||
if len(preloadConditions) > 0 {
|
||||
preloadDB = preloadDB.Where(preloadConditions[0], preloadConditions[1:]...)
|
||||
}
|
||||
|
||||
rows, err := preloadDB.Rows()
|
||||
|
||||
if scope.Err(err) != nil {
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
columns, _ := rows.Columns()
|
||||
for rows.Next() {
|
||||
var (
|
||||
elem = reflect.New(fieldType).Elem()
|
||||
fields = scope.New(elem.Addr().Interface()).Fields()
|
||||
)
|
||||
|
||||
// register foreign keys in join tables
|
||||
var joinTableFields []*Field
|
||||
for _, sourceKey := range sourceKeys {
|
||||
joinTableFields = append(joinTableFields, &Field{StructField: &StructField{DBName: sourceKey, IsNormal: true}, Field: reflect.New(foreignKeyType).Elem()})
|
||||
}
|
||||
|
||||
scope.scan(rows, columns, append(fields, joinTableFields...))
|
||||
|
||||
scope.New(elem.Addr().Interface()).
|
||||
InstanceSet("gorm:skip_query_callback", true).
|
||||
callCallbacks(scope.db.parent.callbacks.queries)
|
||||
|
||||
var foreignKeys = make([]interface{}, len(sourceKeys))
|
||||
// generate hashed forkey keys in join table
|
||||
for idx, joinTableField := range joinTableFields {
|
||||
if !joinTableField.Field.IsNil() {
|
||||
foreignKeys[idx] = joinTableField.Field.Elem().Interface()
|
||||
}
|
||||
}
|
||||
hashedSourceKeys := toString(foreignKeys)
|
||||
|
||||
if isPtr {
|
||||
linkHash[hashedSourceKeys] = append(linkHash[hashedSourceKeys], elem.Addr())
|
||||
} else {
|
||||
linkHash[hashedSourceKeys] = append(linkHash[hashedSourceKeys], elem)
|
||||
}
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
scope.Err(err)
|
||||
}
|
||||
|
||||
// assign find results
|
||||
var (
|
||||
indirectScopeValue = scope.IndirectValue()
|
||||
fieldsSourceMap = map[string][]reflect.Value{}
|
||||
foreignFieldNames = []string{}
|
||||
)
|
||||
|
||||
for _, dbName := range relation.ForeignFieldNames {
|
||||
if field, ok := scope.FieldByName(dbName); ok {
|
||||
foreignFieldNames = append(foreignFieldNames, field.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if indirectScopeValue.Kind() == reflect.Slice {
|
||||
for j := 0; j < indirectScopeValue.Len(); j++ {
|
||||
object := indirect(indirectScopeValue.Index(j))
|
||||
key := toString(getValueFromFields(object, foreignFieldNames))
|
||||
fieldsSourceMap[key] = append(fieldsSourceMap[key], object.FieldByName(field.Name))
|
||||
}
|
||||
} else if indirectScopeValue.IsValid() {
|
||||
key := toString(getValueFromFields(indirectScopeValue, foreignFieldNames))
|
||||
fieldsSourceMap[key] = append(fieldsSourceMap[key], indirectScopeValue.FieldByName(field.Name))
|
||||
}
|
||||
for source, link := range linkHash {
|
||||
for i, field := range fieldsSourceMap[source] {
|
||||
//If not 0 this means Value is a pointer and we already added preloaded models to it
|
||||
if fieldsSourceMap[source][i].Len() != 0 {
|
||||
continue
|
||||
}
|
||||
field.Set(reflect.Append(fieldsSourceMap[source][i], link...))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
30
vendor/github.com/jinzhu/gorm/callback_row_query.go
generated
vendored
Normal file
30
vendor/github.com/jinzhu/gorm/callback_row_query.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
package gorm
|
||||
|
||||
import "database/sql"
|
||||
|
||||
// Define callbacks for row query
|
||||
func init() {
|
||||
DefaultCallback.RowQuery().Register("gorm:row_query", rowQueryCallback)
|
||||
}
|
||||
|
||||
type RowQueryResult struct {
|
||||
Row *sql.Row
|
||||
}
|
||||
|
||||
type RowsQueryResult struct {
|
||||
Rows *sql.Rows
|
||||
Error error
|
||||
}
|
||||
|
||||
// queryCallback used to query data from database
|
||||
func rowQueryCallback(scope *Scope) {
|
||||
if result, ok := scope.InstanceGet("row_query_result"); ok {
|
||||
scope.prepareQuerySQL()
|
||||
|
||||
if rowResult, ok := result.(*RowQueryResult); ok {
|
||||
rowResult.Row = scope.SQLDB().QueryRow(scope.SQL, scope.SQLVars...)
|
||||
} else if rowsResult, ok := result.(*RowsQueryResult); ok {
|
||||
rowsResult.Rows, rowsResult.Error = scope.SQLDB().Query(scope.SQL, scope.SQLVars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
170
vendor/github.com/jinzhu/gorm/callback_save.go
generated
vendored
Normal file
170
vendor/github.com/jinzhu/gorm/callback_save.go
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func beginTransactionCallback(scope *Scope) {
|
||||
scope.Begin()
|
||||
}
|
||||
|
||||
func commitOrRollbackTransactionCallback(scope *Scope) {
|
||||
scope.CommitOrRollback()
|
||||
}
|
||||
|
||||
func saveAssociationCheck(scope *Scope, field *Field) (autoUpdate bool, autoCreate bool, saveReference bool, r *Relationship) {
|
||||
checkTruth := func(value interface{}) bool {
|
||||
if v, ok := value.(bool); ok && !v {
|
||||
return false
|
||||
}
|
||||
|
||||
if v, ok := value.(string); ok {
|
||||
v = strings.ToLower(v)
|
||||
if v == "false" || v != "skip" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if scope.changeableField(field) && !field.IsBlank && !field.IsIgnored {
|
||||
if r = field.Relationship; r != nil {
|
||||
autoUpdate, autoCreate, saveReference = true, true, true
|
||||
|
||||
if value, ok := scope.Get("gorm:save_associations"); ok {
|
||||
autoUpdate = checkTruth(value)
|
||||
autoCreate = autoUpdate
|
||||
} else if value, ok := field.TagSettings["SAVE_ASSOCIATIONS"]; ok {
|
||||
autoUpdate = checkTruth(value)
|
||||
autoCreate = autoUpdate
|
||||
}
|
||||
|
||||
if value, ok := scope.Get("gorm:association_autoupdate"); ok {
|
||||
autoUpdate = checkTruth(value)
|
||||
} else if value, ok := field.TagSettings["ASSOCIATION_AUTOUPDATE"]; ok {
|
||||
autoUpdate = checkTruth(value)
|
||||
}
|
||||
|
||||
if value, ok := scope.Get("gorm:association_autocreate"); ok {
|
||||
autoCreate = checkTruth(value)
|
||||
} else if value, ok := field.TagSettings["ASSOCIATION_AUTOCREATE"]; ok {
|
||||
autoCreate = checkTruth(value)
|
||||
}
|
||||
|
||||
if value, ok := scope.Get("gorm:association_save_reference"); ok {
|
||||
saveReference = checkTruth(value)
|
||||
} else if value, ok := field.TagSettings["ASSOCIATION_SAVE_REFERENCE"]; ok {
|
||||
saveReference = checkTruth(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func saveBeforeAssociationsCallback(scope *Scope) {
|
||||
for _, field := range scope.Fields() {
|
||||
autoUpdate, autoCreate, saveReference, relationship := saveAssociationCheck(scope, field)
|
||||
|
||||
if relationship != nil && relationship.Kind == "belongs_to" {
|
||||
fieldValue := field.Field.Addr().Interface()
|
||||
newScope := scope.New(fieldValue)
|
||||
|
||||
if newScope.PrimaryKeyZero() {
|
||||
if autoCreate {
|
||||
scope.Err(scope.NewDB().Save(fieldValue).Error)
|
||||
}
|
||||
} else if autoUpdate {
|
||||
scope.Err(scope.NewDB().Save(fieldValue).Error)
|
||||
}
|
||||
|
||||
if saveReference {
|
||||
if len(relationship.ForeignFieldNames) != 0 {
|
||||
// set value's foreign key
|
||||
for idx, fieldName := range relationship.ForeignFieldNames {
|
||||
associationForeignName := relationship.AssociationForeignDBNames[idx]
|
||||
if foreignField, ok := scope.New(fieldValue).FieldByName(associationForeignName); ok {
|
||||
scope.Err(scope.SetColumn(fieldName, foreignField.Field.Interface()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveAfterAssociationsCallback(scope *Scope) {
|
||||
for _, field := range scope.Fields() {
|
||||
autoUpdate, autoCreate, saveReference, relationship := saveAssociationCheck(scope, field)
|
||||
|
||||
if relationship != nil && (relationship.Kind == "has_one" || relationship.Kind == "has_many" || relationship.Kind == "many_to_many") {
|
||||
value := field.Field
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Slice:
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
newDB := scope.NewDB()
|
||||
elem := value.Index(i).Addr().Interface()
|
||||
newScope := newDB.NewScope(elem)
|
||||
|
||||
if saveReference {
|
||||
if relationship.JoinTableHandler == nil && len(relationship.ForeignFieldNames) != 0 {
|
||||
for idx, fieldName := range relationship.ForeignFieldNames {
|
||||
associationForeignName := relationship.AssociationForeignDBNames[idx]
|
||||
if f, ok := scope.FieldByName(associationForeignName); ok {
|
||||
scope.Err(newScope.SetColumn(fieldName, f.Field.Interface()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if relationship.PolymorphicType != "" {
|
||||
scope.Err(newScope.SetColumn(relationship.PolymorphicType, relationship.PolymorphicValue))
|
||||
}
|
||||
}
|
||||
|
||||
if newScope.PrimaryKeyZero() {
|
||||
if autoCreate {
|
||||
scope.Err(newDB.Save(elem).Error)
|
||||
}
|
||||
} else if autoUpdate {
|
||||
scope.Err(newDB.Save(elem).Error)
|
||||
}
|
||||
|
||||
if !scope.New(newScope.Value).PrimaryKeyZero() && saveReference {
|
||||
if joinTableHandler := relationship.JoinTableHandler; joinTableHandler != nil {
|
||||
scope.Err(joinTableHandler.Add(joinTableHandler, newDB, scope.Value, newScope.Value))
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
elem := value.Addr().Interface()
|
||||
newScope := scope.New(elem)
|
||||
|
||||
if saveReference {
|
||||
if len(relationship.ForeignFieldNames) != 0 {
|
||||
for idx, fieldName := range relationship.ForeignFieldNames {
|
||||
associationForeignName := relationship.AssociationForeignDBNames[idx]
|
||||
if f, ok := scope.FieldByName(associationForeignName); ok {
|
||||
scope.Err(newScope.SetColumn(fieldName, f.Field.Interface()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if relationship.PolymorphicType != "" {
|
||||
scope.Err(newScope.SetColumn(relationship.PolymorphicType, relationship.PolymorphicValue))
|
||||
}
|
||||
}
|
||||
|
||||
if newScope.PrimaryKeyZero() {
|
||||
if autoCreate {
|
||||
scope.Err(scope.NewDB().Save(elem).Error)
|
||||
}
|
||||
} else if autoUpdate {
|
||||
scope.Err(scope.NewDB().Save(elem).Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
119
vendor/github.com/jinzhu/gorm/callback_update.go
generated
vendored
Normal file
119
vendor/github.com/jinzhu/gorm/callback_update.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Define callbacks for updating
|
||||
func init() {
|
||||
DefaultCallback.Update().Register("gorm:assign_updating_attributes", assignUpdatingAttributesCallback)
|
||||
DefaultCallback.Update().Register("gorm:begin_transaction", beginTransactionCallback)
|
||||
DefaultCallback.Update().Register("gorm:before_update", beforeUpdateCallback)
|
||||
DefaultCallback.Update().Register("gorm:save_before_associations", saveBeforeAssociationsCallback)
|
||||
DefaultCallback.Update().Register("gorm:update_time_stamp", updateTimeStampForUpdateCallback)
|
||||
DefaultCallback.Update().Register("gorm:update", updateCallback)
|
||||
DefaultCallback.Update().Register("gorm:save_after_associations", saveAfterAssociationsCallback)
|
||||
DefaultCallback.Update().Register("gorm:after_update", afterUpdateCallback)
|
||||
DefaultCallback.Update().Register("gorm:commit_or_rollback_transaction", commitOrRollbackTransactionCallback)
|
||||
}
|
||||
|
||||
// assignUpdatingAttributesCallback assign updating attributes to model
|
||||
func assignUpdatingAttributesCallback(scope *Scope) {
|
||||
if attrs, ok := scope.InstanceGet("gorm:update_interface"); ok {
|
||||
if updateMaps, hasUpdate := scope.updatedAttrsWithValues(attrs); hasUpdate {
|
||||
scope.InstanceSet("gorm:update_attrs", updateMaps)
|
||||
} else {
|
||||
scope.SkipLeft()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// beforeUpdateCallback will invoke `BeforeSave`, `BeforeUpdate` method before updating
|
||||
func beforeUpdateCallback(scope *Scope) {
|
||||
if scope.DB().HasBlockGlobalUpdate() && !scope.hasConditions() {
|
||||
scope.Err(errors.New("Missing WHERE clause while updating"))
|
||||
return
|
||||
}
|
||||
if _, ok := scope.Get("gorm:update_column"); !ok {
|
||||
if !scope.HasError() {
|
||||
scope.CallMethod("BeforeSave")
|
||||
}
|
||||
if !scope.HasError() {
|
||||
scope.CallMethod("BeforeUpdate")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updateTimeStampForUpdateCallback will set `UpdatedAt` when updating
|
||||
func updateTimeStampForUpdateCallback(scope *Scope) {
|
||||
if _, ok := scope.Get("gorm:update_column"); !ok {
|
||||
scope.SetColumn("UpdatedAt", NowFunc())
|
||||
}
|
||||
}
|
||||
|
||||
// updateCallback the callback used to update data to database
|
||||
func updateCallback(scope *Scope) {
|
||||
if !scope.HasError() {
|
||||
var sqls []string
|
||||
|
||||
if updateAttrs, ok := scope.InstanceGet("gorm:update_attrs"); ok {
|
||||
// Sort the column names so that the generated SQL is the same every time.
|
||||
updateMap := updateAttrs.(map[string]interface{})
|
||||
var columns []string
|
||||
for c := range updateMap {
|
||||
columns = append(columns, c)
|
||||
}
|
||||
sort.Strings(columns)
|
||||
|
||||
for _, column := range columns {
|
||||
value := updateMap[column]
|
||||
sqls = append(sqls, fmt.Sprintf("%v = %v", scope.Quote(column), scope.AddToVars(value)))
|
||||
}
|
||||
} else {
|
||||
for _, field := range scope.Fields() {
|
||||
if scope.changeableField(field) {
|
||||
if !field.IsPrimaryKey && field.IsNormal {
|
||||
sqls = append(sqls, fmt.Sprintf("%v = %v", scope.Quote(field.DBName), scope.AddToVars(field.Field.Interface())))
|
||||
} else if relationship := field.Relationship; relationship != nil && relationship.Kind == "belongs_to" {
|
||||
for _, foreignKey := range relationship.ForeignDBNames {
|
||||
if foreignField, ok := scope.FieldByName(foreignKey); ok && !scope.changeableField(foreignField) {
|
||||
sqls = append(sqls,
|
||||
fmt.Sprintf("%v = %v", scope.Quote(foreignField.DBName), scope.AddToVars(foreignField.Field.Interface())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var extraOption string
|
||||
if str, ok := scope.Get("gorm:update_option"); ok {
|
||||
extraOption = fmt.Sprint(str)
|
||||
}
|
||||
|
||||
if len(sqls) > 0 {
|
||||
scope.Raw(fmt.Sprintf(
|
||||
"UPDATE %v SET %v%v%v",
|
||||
scope.QuotedTableName(),
|
||||
strings.Join(sqls, ", "),
|
||||
addExtraSpaceIfExist(scope.CombinedConditionSql()),
|
||||
addExtraSpaceIfExist(extraOption),
|
||||
)).Exec()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// afterUpdateCallback will invoke `AfterUpdate`, `AfterSave` method after updating
|
||||
func afterUpdateCallback(scope *Scope) {
|
||||
if _, ok := scope.Get("gorm:update_column"); !ok {
|
||||
if !scope.HasError() {
|
||||
scope.CallMethod("AfterUpdate")
|
||||
}
|
||||
if !scope.HasError() {
|
||||
scope.CallMethod("AfterSave")
|
||||
}
|
||||
}
|
||||
}
|
||||
130
vendor/github.com/jinzhu/gorm/dialect.go
generated
vendored
Normal file
130
vendor/github.com/jinzhu/gorm/dialect.go
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Dialect interface contains behaviors that differ across SQL database
|
||||
type Dialect interface {
|
||||
// GetName get dialect's name
|
||||
GetName() string
|
||||
|
||||
// SetDB set db for dialect
|
||||
SetDB(db SQLCommon)
|
||||
|
||||
// BindVar return the placeholder for actual values in SQL statements, in many dbs it is "?", Postgres using $1
|
||||
BindVar(i int) string
|
||||
// Quote quotes field name to avoid SQL parsing exceptions by using a reserved word as a field name
|
||||
Quote(key string) string
|
||||
// DataTypeOf return data's sql type
|
||||
DataTypeOf(field *StructField) string
|
||||
|
||||
// HasIndex check has index or not
|
||||
HasIndex(tableName string, indexName string) bool
|
||||
// HasForeignKey check has foreign key or not
|
||||
HasForeignKey(tableName string, foreignKeyName string) bool
|
||||
// RemoveIndex remove index
|
||||
RemoveIndex(tableName string, indexName string) error
|
||||
// HasTable check has table or not
|
||||
HasTable(tableName string) bool
|
||||
// HasColumn check has column or not
|
||||
HasColumn(tableName string, columnName string) bool
|
||||
// ModifyColumn modify column's type
|
||||
ModifyColumn(tableName string, columnName string, typ string) error
|
||||
|
||||
// LimitAndOffsetSQL return generated SQL with Limit and Offset, as mssql has special case
|
||||
LimitAndOffsetSQL(limit, offset interface{}) string
|
||||
// SelectFromDummyTable return select values, for most dbs, `SELECT values` just works, mysql needs `SELECT value FROM DUAL`
|
||||
SelectFromDummyTable() string
|
||||
// LastInsertIdReturningSuffix most dbs support LastInsertId, but postgres needs to use `RETURNING`
|
||||
LastInsertIDReturningSuffix(tableName, columnName string) string
|
||||
// DefaultValueStr
|
||||
DefaultValueStr() string
|
||||
|
||||
// BuildKeyName returns a valid key name (foreign key, index key) for the given table, field and reference
|
||||
BuildKeyName(kind, tableName string, fields ...string) string
|
||||
|
||||
// CurrentDatabase return current database name
|
||||
CurrentDatabase() string
|
||||
}
|
||||
|
||||
var dialectsMap = map[string]Dialect{}
|
||||
|
||||
func newDialect(name string, db SQLCommon) Dialect {
|
||||
if value, ok := dialectsMap[name]; ok {
|
||||
dialect := reflect.New(reflect.TypeOf(value).Elem()).Interface().(Dialect)
|
||||
dialect.SetDB(db)
|
||||
return dialect
|
||||
}
|
||||
|
||||
fmt.Printf("`%v` is not officially supported, running under compatibility mode.\n", name)
|
||||
commontDialect := &commonDialect{}
|
||||
commontDialect.SetDB(db)
|
||||
return commontDialect
|
||||
}
|
||||
|
||||
// RegisterDialect register new dialect
|
||||
func RegisterDialect(name string, dialect Dialect) {
|
||||
dialectsMap[name] = dialect
|
||||
}
|
||||
|
||||
// ParseFieldStructForDialect get field's sql data type
|
||||
var ParseFieldStructForDialect = func(field *StructField, dialect Dialect) (fieldValue reflect.Value, sqlType string, size int, additionalType string) {
|
||||
// Get redirected field type
|
||||
var (
|
||||
reflectType = field.Struct.Type
|
||||
dataType = field.TagSettings["TYPE"]
|
||||
)
|
||||
|
||||
for reflectType.Kind() == reflect.Ptr {
|
||||
reflectType = reflectType.Elem()
|
||||
}
|
||||
|
||||
// Get redirected field value
|
||||
fieldValue = reflect.Indirect(reflect.New(reflectType))
|
||||
|
||||
if gormDataType, ok := fieldValue.Interface().(interface {
|
||||
GormDataType(Dialect) string
|
||||
}); ok {
|
||||
dataType = gormDataType.GormDataType(dialect)
|
||||
}
|
||||
|
||||
// Get scanner's real value
|
||||
if dataType == "" {
|
||||
var getScannerValue func(reflect.Value)
|
||||
getScannerValue = func(value reflect.Value) {
|
||||
fieldValue = value
|
||||
if _, isScanner := reflect.New(fieldValue.Type()).Interface().(sql.Scanner); isScanner && fieldValue.Kind() == reflect.Struct {
|
||||
getScannerValue(fieldValue.Field(0))
|
||||
}
|
||||
}
|
||||
getScannerValue(fieldValue)
|
||||
}
|
||||
|
||||
// Default Size
|
||||
if num, ok := field.TagSettings["SIZE"]; ok {
|
||||
size, _ = strconv.Atoi(num)
|
||||
} else {
|
||||
size = 255
|
||||
}
|
||||
|
||||
// Default type from tag setting
|
||||
additionalType = field.TagSettings["NOT NULL"] + " " + field.TagSettings["UNIQUE"]
|
||||
if value, ok := field.TagSettings["DEFAULT"]; ok {
|
||||
additionalType = additionalType + " DEFAULT " + value
|
||||
}
|
||||
|
||||
return fieldValue, dataType, size, strings.TrimSpace(additionalType)
|
||||
}
|
||||
|
||||
func currentDatabaseAndTable(dialect Dialect, tableName string) (string, string) {
|
||||
if strings.Contains(tableName, ".") {
|
||||
splitStrings := strings.SplitN(tableName, ".", 2)
|
||||
return splitStrings[0], splitStrings[1]
|
||||
}
|
||||
return dialect.CurrentDatabase(), tableName
|
||||
}
|
||||
176
vendor/github.com/jinzhu/gorm/dialect_common.go
generated
vendored
Normal file
176
vendor/github.com/jinzhu/gorm/dialect_common.go
generated
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DefaultForeignKeyNamer contains the default foreign key name generator method
|
||||
type DefaultForeignKeyNamer struct {
|
||||
}
|
||||
|
||||
type commonDialect struct {
|
||||
db SQLCommon
|
||||
DefaultForeignKeyNamer
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterDialect("common", &commonDialect{})
|
||||
}
|
||||
|
||||
func (commonDialect) GetName() string {
|
||||
return "common"
|
||||
}
|
||||
|
||||
func (s *commonDialect) SetDB(db SQLCommon) {
|
||||
s.db = db
|
||||
}
|
||||
|
||||
func (commonDialect) BindVar(i int) string {
|
||||
return "$$$" // ?
|
||||
}
|
||||
|
||||
func (commonDialect) Quote(key string) string {
|
||||
return fmt.Sprintf(`"%s"`, key)
|
||||
}
|
||||
|
||||
func (s *commonDialect) fieldCanAutoIncrement(field *StructField) bool {
|
||||
if value, ok := field.TagSettings["AUTO_INCREMENT"]; ok {
|
||||
return strings.ToLower(value) != "false"
|
||||
}
|
||||
return field.IsPrimaryKey
|
||||
}
|
||||
|
||||
func (s *commonDialect) DataTypeOf(field *StructField) string {
|
||||
var dataValue, sqlType, size, additionalType = ParseFieldStructForDialect(field, s)
|
||||
|
||||
if sqlType == "" {
|
||||
switch dataValue.Kind() {
|
||||
case reflect.Bool:
|
||||
sqlType = "BOOLEAN"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
|
||||
if s.fieldCanAutoIncrement(field) {
|
||||
sqlType = "INTEGER AUTO_INCREMENT"
|
||||
} else {
|
||||
sqlType = "INTEGER"
|
||||
}
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
if s.fieldCanAutoIncrement(field) {
|
||||
sqlType = "BIGINT AUTO_INCREMENT"
|
||||
} else {
|
||||
sqlType = "BIGINT"
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
sqlType = "FLOAT"
|
||||
case reflect.String:
|
||||
if size > 0 && size < 65532 {
|
||||
sqlType = fmt.Sprintf("VARCHAR(%d)", size)
|
||||
} else {
|
||||
sqlType = "VARCHAR(65532)"
|
||||
}
|
||||
case reflect.Struct:
|
||||
if _, ok := dataValue.Interface().(time.Time); ok {
|
||||
sqlType = "TIMESTAMP"
|
||||
}
|
||||
default:
|
||||
if _, ok := dataValue.Interface().([]byte); ok {
|
||||
if size > 0 && size < 65532 {
|
||||
sqlType = fmt.Sprintf("BINARY(%d)", size)
|
||||
} else {
|
||||
sqlType = "BINARY(65532)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sqlType == "" {
|
||||
panic(fmt.Sprintf("invalid sql type %s (%s) for commonDialect", dataValue.Type().Name(), dataValue.Kind().String()))
|
||||
}
|
||||
|
||||
if strings.TrimSpace(additionalType) == "" {
|
||||
return sqlType
|
||||
}
|
||||
return fmt.Sprintf("%v %v", sqlType, additionalType)
|
||||
}
|
||||
|
||||
func (s commonDialect) HasIndex(tableName string, indexName string) bool {
|
||||
var count int
|
||||
currentDatabase, tableName := currentDatabaseAndTable(&s, tableName)
|
||||
s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.STATISTICS WHERE table_schema = ? AND table_name = ? AND index_name = ?", currentDatabase, tableName, indexName).Scan(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (s commonDialect) RemoveIndex(tableName string, indexName string) error {
|
||||
_, err := s.db.Exec(fmt.Sprintf("DROP INDEX %v", indexName))
|
||||
return err
|
||||
}
|
||||
|
||||
func (s commonDialect) HasForeignKey(tableName string, foreignKeyName string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s commonDialect) HasTable(tableName string) bool {
|
||||
var count int
|
||||
currentDatabase, tableName := currentDatabaseAndTable(&s, tableName)
|
||||
s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = ? AND table_name = ?", currentDatabase, tableName).Scan(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (s commonDialect) HasColumn(tableName string, columnName string) bool {
|
||||
var count int
|
||||
currentDatabase, tableName := currentDatabaseAndTable(&s, tableName)
|
||||
s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = ? AND table_name = ? AND column_name = ?", currentDatabase, tableName, columnName).Scan(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (s commonDialect) ModifyColumn(tableName string, columnName string, typ string) error {
|
||||
_, err := s.db.Exec(fmt.Sprintf("ALTER TABLE %v ALTER COLUMN %v TYPE %v", tableName, columnName, typ))
|
||||
return err
|
||||
}
|
||||
|
||||
func (s commonDialect) CurrentDatabase() (name string) {
|
||||
s.db.QueryRow("SELECT DATABASE()").Scan(&name)
|
||||
return
|
||||
}
|
||||
|
||||
func (commonDialect) LimitAndOffsetSQL(limit, offset interface{}) (sql string) {
|
||||
if limit != nil {
|
||||
if parsedLimit, err := strconv.ParseInt(fmt.Sprint(limit), 0, 0); err == nil && parsedLimit >= 0 {
|
||||
sql += fmt.Sprintf(" LIMIT %d", parsedLimit)
|
||||
}
|
||||
}
|
||||
if offset != nil {
|
||||
if parsedOffset, err := strconv.ParseInt(fmt.Sprint(offset), 0, 0); err == nil && parsedOffset >= 0 {
|
||||
sql += fmt.Sprintf(" OFFSET %d", parsedOffset)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (commonDialect) SelectFromDummyTable() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (commonDialect) LastInsertIDReturningSuffix(tableName, columnName string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (commonDialect) DefaultValueStr() string {
|
||||
return "DEFAULT VALUES"
|
||||
}
|
||||
|
||||
// BuildKeyName returns a valid key name (foreign key, index key) for the given table, field and reference
|
||||
func (DefaultForeignKeyNamer) BuildKeyName(kind, tableName string, fields ...string) string {
|
||||
keyName := fmt.Sprintf("%s_%s_%s", kind, tableName, strings.Join(fields, "_"))
|
||||
keyName = regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(keyName, "_")
|
||||
return keyName
|
||||
}
|
||||
|
||||
// IsByteArrayOrSlice returns true of the reflected value is an array or slice
|
||||
func IsByteArrayOrSlice(value reflect.Value) bool {
|
||||
return (value.Kind() == reflect.Array || value.Kind() == reflect.Slice) && value.Type().Elem() == reflect.TypeOf(uint8(0))
|
||||
}
|
||||
191
vendor/github.com/jinzhu/gorm/dialect_mysql.go
generated
vendored
Normal file
191
vendor/github.com/jinzhu/gorm/dialect_mysql.go
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type mysql struct {
|
||||
commonDialect
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterDialect("mysql", &mysql{})
|
||||
}
|
||||
|
||||
func (mysql) GetName() string {
|
||||
return "mysql"
|
||||
}
|
||||
|
||||
func (mysql) Quote(key string) string {
|
||||
return fmt.Sprintf("`%s`", key)
|
||||
}
|
||||
|
||||
// Get Data Type for MySQL Dialect
|
||||
func (s *mysql) DataTypeOf(field *StructField) string {
|
||||
var dataValue, sqlType, size, additionalType = ParseFieldStructForDialect(field, s)
|
||||
|
||||
// MySQL allows only one auto increment column per table, and it must
|
||||
// be a KEY column.
|
||||
if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok {
|
||||
if _, ok = field.TagSettings["INDEX"]; !ok && !field.IsPrimaryKey {
|
||||
delete(field.TagSettings, "AUTO_INCREMENT")
|
||||
}
|
||||
}
|
||||
|
||||
if sqlType == "" {
|
||||
switch dataValue.Kind() {
|
||||
case reflect.Bool:
|
||||
sqlType = "boolean"
|
||||
case reflect.Int8:
|
||||
if s.fieldCanAutoIncrement(field) {
|
||||
field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
|
||||
sqlType = "tinyint AUTO_INCREMENT"
|
||||
} else {
|
||||
sqlType = "tinyint"
|
||||
}
|
||||
case reflect.Int, reflect.Int16, reflect.Int32:
|
||||
if s.fieldCanAutoIncrement(field) {
|
||||
field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
|
||||
sqlType = "int AUTO_INCREMENT"
|
||||
} else {
|
||||
sqlType = "int"
|
||||
}
|
||||
case reflect.Uint8:
|
||||
if s.fieldCanAutoIncrement(field) {
|
||||
field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
|
||||
sqlType = "tinyint unsigned AUTO_INCREMENT"
|
||||
} else {
|
||||
sqlType = "tinyint unsigned"
|
||||
}
|
||||
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
|
||||
if s.fieldCanAutoIncrement(field) {
|
||||
field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
|
||||
sqlType = "int unsigned AUTO_INCREMENT"
|
||||
} else {
|
||||
sqlType = "int unsigned"
|
||||
}
|
||||
case reflect.Int64:
|
||||
if s.fieldCanAutoIncrement(field) {
|
||||
field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
|
||||
sqlType = "bigint AUTO_INCREMENT"
|
||||
} else {
|
||||
sqlType = "bigint"
|
||||
}
|
||||
case reflect.Uint64:
|
||||
if s.fieldCanAutoIncrement(field) {
|
||||
field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
|
||||
sqlType = "bigint unsigned AUTO_INCREMENT"
|
||||
} else {
|
||||
sqlType = "bigint unsigned"
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
sqlType = "double"
|
||||
case reflect.String:
|
||||
if size > 0 && size < 65532 {
|
||||
sqlType = fmt.Sprintf("varchar(%d)", size)
|
||||
} else {
|
||||
sqlType = "longtext"
|
||||
}
|
||||
case reflect.Struct:
|
||||
if _, ok := dataValue.Interface().(time.Time); ok {
|
||||
precision := ""
|
||||
if p, ok := field.TagSettings["PRECISION"]; ok {
|
||||
precision = fmt.Sprintf("(%s)", p)
|
||||
}
|
||||
|
||||
if _, ok := field.TagSettings["NOT NULL"]; ok {
|
||||
sqlType = fmt.Sprintf("timestamp%v", precision)
|
||||
} else {
|
||||
sqlType = fmt.Sprintf("timestamp%v NULL", precision)
|
||||
}
|
||||
}
|
||||
default:
|
||||
if IsByteArrayOrSlice(dataValue) {
|
||||
if size > 0 && size < 65532 {
|
||||
sqlType = fmt.Sprintf("varbinary(%d)", size)
|
||||
} else {
|
||||
sqlType = "longblob"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sqlType == "" {
|
||||
panic(fmt.Sprintf("invalid sql type %s (%s) for mysql", dataValue.Type().Name(), dataValue.Kind().String()))
|
||||
}
|
||||
|
||||
if strings.TrimSpace(additionalType) == "" {
|
||||
return sqlType
|
||||
}
|
||||
return fmt.Sprintf("%v %v", sqlType, additionalType)
|
||||
}
|
||||
|
||||
func (s mysql) RemoveIndex(tableName string, indexName string) error {
|
||||
_, err := s.db.Exec(fmt.Sprintf("DROP INDEX %v ON %v", indexName, s.Quote(tableName)))
|
||||
return err
|
||||
}
|
||||
|
||||
func (s mysql) ModifyColumn(tableName string, columnName string, typ string) error {
|
||||
_, err := s.db.Exec(fmt.Sprintf("ALTER TABLE %v MODIFY COLUMN %v %v", tableName, columnName, typ))
|
||||
return err
|
||||
}
|
||||
|
||||
func (s mysql) LimitAndOffsetSQL(limit, offset interface{}) (sql string) {
|
||||
if limit != nil {
|
||||
if parsedLimit, err := strconv.ParseInt(fmt.Sprint(limit), 0, 0); err == nil && parsedLimit >= 0 {
|
||||
sql += fmt.Sprintf(" LIMIT %d", parsedLimit)
|
||||
|
||||
if offset != nil {
|
||||
if parsedOffset, err := strconv.ParseInt(fmt.Sprint(offset), 0, 0); err == nil && parsedOffset >= 0 {
|
||||
sql += fmt.Sprintf(" OFFSET %d", parsedOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s mysql) HasForeignKey(tableName string, foreignKeyName string) bool {
|
||||
var count int
|
||||
currentDatabase, tableName := currentDatabaseAndTable(&s, tableName)
|
||||
s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=? AND TABLE_NAME=? AND CONSTRAINT_NAME=? AND CONSTRAINT_TYPE='FOREIGN KEY'", currentDatabase, tableName, foreignKeyName).Scan(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (s mysql) CurrentDatabase() (name string) {
|
||||
s.db.QueryRow("SELECT DATABASE()").Scan(&name)
|
||||
return
|
||||
}
|
||||
|
||||
func (mysql) SelectFromDummyTable() string {
|
||||
return "FROM DUAL"
|
||||
}
|
||||
|
||||
func (s mysql) BuildKeyName(kind, tableName string, fields ...string) string {
|
||||
keyName := s.commonDialect.BuildKeyName(kind, tableName, fields...)
|
||||
if utf8.RuneCountInString(keyName) <= 64 {
|
||||
return keyName
|
||||
}
|
||||
h := sha1.New()
|
||||
h.Write([]byte(keyName))
|
||||
bs := h.Sum(nil)
|
||||
|
||||
// sha1 is 40 characters, keep first 24 characters of destination
|
||||
destRunes := []rune(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(fields[0], "_"))
|
||||
if len(destRunes) > 24 {
|
||||
destRunes = destRunes[:24]
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%x", string(destRunes), bs)
|
||||
}
|
||||
|
||||
func (mysql) DefaultValueStr() string {
|
||||
return "VALUES()"
|
||||
}
|
||||
143
vendor/github.com/jinzhu/gorm/dialect_postgres.go
generated
vendored
Normal file
143
vendor/github.com/jinzhu/gorm/dialect_postgres.go
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type postgres struct {
|
||||
commonDialect
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterDialect("postgres", &postgres{})
|
||||
RegisterDialect("cloudsqlpostgres", &postgres{})
|
||||
}
|
||||
|
||||
func (postgres) GetName() string {
|
||||
return "postgres"
|
||||
}
|
||||
|
||||
func (postgres) BindVar(i int) string {
|
||||
return fmt.Sprintf("$%v", i)
|
||||
}
|
||||
|
||||
func (s *postgres) DataTypeOf(field *StructField) string {
|
||||
var dataValue, sqlType, size, additionalType = ParseFieldStructForDialect(field, s)
|
||||
|
||||
if sqlType == "" {
|
||||
switch dataValue.Kind() {
|
||||
case reflect.Bool:
|
||||
sqlType = "boolean"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uintptr:
|
||||
if s.fieldCanAutoIncrement(field) {
|
||||
field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
|
||||
sqlType = "serial"
|
||||
} else {
|
||||
sqlType = "integer"
|
||||
}
|
||||
case reflect.Int64, reflect.Uint32, reflect.Uint64:
|
||||
if s.fieldCanAutoIncrement(field) {
|
||||
field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
|
||||
sqlType = "bigserial"
|
||||
} else {
|
||||
sqlType = "bigint"
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
sqlType = "numeric"
|
||||
case reflect.String:
|
||||
if _, ok := field.TagSettings["SIZE"]; !ok {
|
||||
size = 0 // if SIZE haven't been set, use `text` as the default type, as there are no performance different
|
||||
}
|
||||
|
||||
if size > 0 && size < 65532 {
|
||||
sqlType = fmt.Sprintf("varchar(%d)", size)
|
||||
} else {
|
||||
sqlType = "text"
|
||||
}
|
||||
case reflect.Struct:
|
||||
if _, ok := dataValue.Interface().(time.Time); ok {
|
||||
sqlType = "timestamp with time zone"
|
||||
}
|
||||
case reflect.Map:
|
||||
if dataValue.Type().Name() == "Hstore" {
|
||||
sqlType = "hstore"
|
||||
}
|
||||
default:
|
||||
if IsByteArrayOrSlice(dataValue) {
|
||||
sqlType = "bytea"
|
||||
|
||||
if isUUID(dataValue) {
|
||||
sqlType = "uuid"
|
||||
}
|
||||
|
||||
if isJSON(dataValue) {
|
||||
sqlType = "jsonb"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sqlType == "" {
|
||||
panic(fmt.Sprintf("invalid sql type %s (%s) for postgres", dataValue.Type().Name(), dataValue.Kind().String()))
|
||||
}
|
||||
|
||||
if strings.TrimSpace(additionalType) == "" {
|
||||
return sqlType
|
||||
}
|
||||
return fmt.Sprintf("%v %v", sqlType, additionalType)
|
||||
}
|
||||
|
||||
func (s postgres) HasIndex(tableName string, indexName string) bool {
|
||||
var count int
|
||||
s.db.QueryRow("SELECT count(*) FROM pg_indexes WHERE tablename = $1 AND indexname = $2 AND schemaname = CURRENT_SCHEMA()", tableName, indexName).Scan(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (s postgres) HasForeignKey(tableName string, foreignKeyName string) bool {
|
||||
var count int
|
||||
s.db.QueryRow("SELECT count(con.conname) FROM pg_constraint con WHERE $1::regclass::oid = con.conrelid AND con.conname = $2 AND con.contype='f'", tableName, foreignKeyName).Scan(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (s postgres) HasTable(tableName string) bool {
|
||||
var count int
|
||||
s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.tables WHERE table_name = $1 AND table_type = 'BASE TABLE' AND table_schema = CURRENT_SCHEMA()", tableName).Scan(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (s postgres) HasColumn(tableName string, columnName string) bool {
|
||||
var count int
|
||||
s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.columns WHERE table_name = $1 AND column_name = $2 AND table_schema = CURRENT_SCHEMA()", tableName, columnName).Scan(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (s postgres) CurrentDatabase() (name string) {
|
||||
s.db.QueryRow("SELECT CURRENT_DATABASE()").Scan(&name)
|
||||
return
|
||||
}
|
||||
|
||||
func (s postgres) LastInsertIDReturningSuffix(tableName, key string) string {
|
||||
return fmt.Sprintf("RETURNING %v.%v", tableName, key)
|
||||
}
|
||||
|
||||
func (postgres) SupportLastInsertID() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func isUUID(value reflect.Value) bool {
|
||||
if value.Kind() != reflect.Array || value.Type().Len() != 16 {
|
||||
return false
|
||||
}
|
||||
typename := value.Type().Name()
|
||||
lower := strings.ToLower(typename)
|
||||
return "uuid" == lower || "guid" == lower
|
||||
}
|
||||
|
||||
func isJSON(value reflect.Value) bool {
|
||||
_, ok := value.Interface().(json.RawMessage)
|
||||
return ok
|
||||
}
|
||||
107
vendor/github.com/jinzhu/gorm/dialect_sqlite3.go
generated
vendored
Normal file
107
vendor/github.com/jinzhu/gorm/dialect_sqlite3.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type sqlite3 struct {
|
||||
commonDialect
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterDialect("sqlite3", &sqlite3{})
|
||||
}
|
||||
|
||||
func (sqlite3) GetName() string {
|
||||
return "sqlite3"
|
||||
}
|
||||
|
||||
// Get Data Type for Sqlite Dialect
|
||||
func (s *sqlite3) DataTypeOf(field *StructField) string {
|
||||
var dataValue, sqlType, size, additionalType = ParseFieldStructForDialect(field, s)
|
||||
|
||||
if sqlType == "" {
|
||||
switch dataValue.Kind() {
|
||||
case reflect.Bool:
|
||||
sqlType = "bool"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
|
||||
if s.fieldCanAutoIncrement(field) {
|
||||
field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
|
||||
sqlType = "integer primary key autoincrement"
|
||||
} else {
|
||||
sqlType = "integer"
|
||||
}
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
if s.fieldCanAutoIncrement(field) {
|
||||
field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT"
|
||||
sqlType = "integer primary key autoincrement"
|
||||
} else {
|
||||
sqlType = "bigint"
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
sqlType = "real"
|
||||
case reflect.String:
|
||||
if size > 0 && size < 65532 {
|
||||
sqlType = fmt.Sprintf("varchar(%d)", size)
|
||||
} else {
|
||||
sqlType = "text"
|
||||
}
|
||||
case reflect.Struct:
|
||||
if _, ok := dataValue.Interface().(time.Time); ok {
|
||||
sqlType = "datetime"
|
||||
}
|
||||
default:
|
||||
if IsByteArrayOrSlice(dataValue) {
|
||||
sqlType = "blob"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sqlType == "" {
|
||||
panic(fmt.Sprintf("invalid sql type %s (%s) for sqlite3", dataValue.Type().Name(), dataValue.Kind().String()))
|
||||
}
|
||||
|
||||
if strings.TrimSpace(additionalType) == "" {
|
||||
return sqlType
|
||||
}
|
||||
return fmt.Sprintf("%v %v", sqlType, additionalType)
|
||||
}
|
||||
|
||||
func (s sqlite3) HasIndex(tableName string, indexName string) bool {
|
||||
var count int
|
||||
s.db.QueryRow(fmt.Sprintf("SELECT count(*) FROM sqlite_master WHERE tbl_name = ? AND sql LIKE '%%INDEX %v ON%%'", indexName), tableName).Scan(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (s sqlite3) HasTable(tableName string) bool {
|
||||
var count int
|
||||
s.db.QueryRow("SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?", tableName).Scan(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (s sqlite3) HasColumn(tableName string, columnName string) bool {
|
||||
var count int
|
||||
s.db.QueryRow(fmt.Sprintf("SELECT count(*) FROM sqlite_master WHERE tbl_name = ? AND (sql LIKE '%%\"%v\" %%' OR sql LIKE '%%%v %%');\n", columnName, columnName), tableName).Scan(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (s sqlite3) CurrentDatabase() (name string) {
|
||||
var (
|
||||
ifaces = make([]interface{}, 3)
|
||||
pointers = make([]*string, 3)
|
||||
i int
|
||||
)
|
||||
for i = 0; i < 3; i++ {
|
||||
ifaces[i] = &pointers[i]
|
||||
}
|
||||
if err := s.db.QueryRow("PRAGMA database_list").Scan(ifaces...); err != nil {
|
||||
return
|
||||
}
|
||||
if pointers[1] != nil {
|
||||
name = *pointers[1]
|
||||
}
|
||||
return
|
||||
}
|
||||
30
vendor/github.com/jinzhu/gorm/docker-compose.yml
generated
vendored
Normal file
30
vendor/github.com/jinzhu/gorm/docker-compose.yml
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: 'mysql:latest'
|
||||
ports:
|
||||
- 9910:3306
|
||||
environment:
|
||||
- MYSQL_DATABASE=gorm
|
||||
- MYSQL_USER=gorm
|
||||
- MYSQL_PASSWORD=gorm
|
||||
- MYSQL_RANDOM_ROOT_PASSWORD="yes"
|
||||
postgres:
|
||||
image: 'postgres:latest'
|
||||
ports:
|
||||
- 9920:5432
|
||||
environment:
|
||||
- POSTGRES_USER=gorm
|
||||
- POSTGRES_DB=gorm
|
||||
- POSTGRES_PASSWORD=gorm
|
||||
mssql:
|
||||
image: 'mcmoe/mssqldocker:latest'
|
||||
ports:
|
||||
- 9930:1433
|
||||
environment:
|
||||
- ACCEPT_EULA=Y
|
||||
- SA_PASSWORD=LoremIpsum86
|
||||
- MSSQL_DB=gorm
|
||||
- MSSQL_USER=gorm
|
||||
- MSSQL_PASSWORD=LoremIpsum86
|
||||
72
vendor/github.com/jinzhu/gorm/errors.go
generated
vendored
Normal file
72
vendor/github.com/jinzhu/gorm/errors.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrRecordNotFound record not found error, happens when haven't find any matched data when looking up with a struct
|
||||
ErrRecordNotFound = errors.New("record not found")
|
||||
// ErrInvalidSQL invalid SQL error, happens when you passed invalid SQL
|
||||
ErrInvalidSQL = errors.New("invalid SQL")
|
||||
// ErrInvalidTransaction invalid transaction when you are trying to `Commit` or `Rollback`
|
||||
ErrInvalidTransaction = errors.New("no valid transaction")
|
||||
// ErrCantStartTransaction can't start transaction when you are trying to start one with `Begin`
|
||||
ErrCantStartTransaction = errors.New("can't start transaction")
|
||||
// ErrUnaddressable unaddressable value
|
||||
ErrUnaddressable = errors.New("using unaddressable value")
|
||||
)
|
||||
|
||||
// Errors contains all happened errors
|
||||
type Errors []error
|
||||
|
||||
// IsRecordNotFoundError returns current error has record not found error or not
|
||||
func IsRecordNotFoundError(err error) bool {
|
||||
if errs, ok := err.(Errors); ok {
|
||||
for _, err := range errs {
|
||||
if err == ErrRecordNotFound {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return err == ErrRecordNotFound
|
||||
}
|
||||
|
||||
// GetErrors gets all happened errors
|
||||
func (errs Errors) GetErrors() []error {
|
||||
return errs
|
||||
}
|
||||
|
||||
// Add adds an error
|
||||
func (errs Errors) Add(newErrors ...error) Errors {
|
||||
for _, err := range newErrors {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if errors, ok := err.(Errors); ok {
|
||||
errs = errs.Add(errors...)
|
||||
} else {
|
||||
ok = true
|
||||
for _, e := range errs {
|
||||
if err == e {
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// Error format happened errors
|
||||
func (errs Errors) Error() string {
|
||||
var errors = []string{}
|
||||
for _, e := range errs {
|
||||
errors = append(errors, e.Error())
|
||||
}
|
||||
return strings.Join(errors, "; ")
|
||||
}
|
||||
58
vendor/github.com/jinzhu/gorm/field.go
generated
vendored
Normal file
58
vendor/github.com/jinzhu/gorm/field.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Field model field definition
|
||||
type Field struct {
|
||||
*StructField
|
||||
IsBlank bool
|
||||
Field reflect.Value
|
||||
}
|
||||
|
||||
// Set set a value to the field
|
||||
func (field *Field) Set(value interface{}) (err error) {
|
||||
if !field.Field.IsValid() {
|
||||
return errors.New("field value not valid")
|
||||
}
|
||||
|
||||
if !field.Field.CanAddr() {
|
||||
return ErrUnaddressable
|
||||
}
|
||||
|
||||
reflectValue, ok := value.(reflect.Value)
|
||||
if !ok {
|
||||
reflectValue = reflect.ValueOf(value)
|
||||
}
|
||||
|
||||
fieldValue := field.Field
|
||||
if reflectValue.IsValid() {
|
||||
if reflectValue.Type().ConvertibleTo(fieldValue.Type()) {
|
||||
fieldValue.Set(reflectValue.Convert(fieldValue.Type()))
|
||||
} else {
|
||||
if fieldValue.Kind() == reflect.Ptr {
|
||||
if fieldValue.IsNil() {
|
||||
fieldValue.Set(reflect.New(field.Struct.Type.Elem()))
|
||||
}
|
||||
fieldValue = fieldValue.Elem()
|
||||
}
|
||||
|
||||
if reflectValue.Type().ConvertibleTo(fieldValue.Type()) {
|
||||
fieldValue.Set(reflectValue.Convert(fieldValue.Type()))
|
||||
} else if scanner, ok := fieldValue.Addr().Interface().(sql.Scanner); ok {
|
||||
err = scanner.Scan(reflectValue.Interface())
|
||||
} else {
|
||||
err = fmt.Errorf("could not convert argument of field %s from %s to %s", field.Name, reflectValue.Type(), fieldValue.Type())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
field.Field.Set(reflect.Zero(field.Field.Type()))
|
||||
}
|
||||
|
||||
field.IsBlank = isBlank(field.Field)
|
||||
return err
|
||||
}
|
||||
20
vendor/github.com/jinzhu/gorm/interface.go
generated
vendored
Normal file
20
vendor/github.com/jinzhu/gorm/interface.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
package gorm
|
||||
|
||||
import "database/sql"
|
||||
|
||||
// SQLCommon is the minimal database connection functionality gorm requires. Implemented by *sql.DB.
|
||||
type SQLCommon interface {
|
||||
Exec(query string, args ...interface{}) (sql.Result, error)
|
||||
Prepare(query string) (*sql.Stmt, error)
|
||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||
QueryRow(query string, args ...interface{}) *sql.Row
|
||||
}
|
||||
|
||||
type sqlDb interface {
|
||||
Begin() (*sql.Tx, error)
|
||||
}
|
||||
|
||||
type sqlTx interface {
|
||||
Commit() error
|
||||
Rollback() error
|
||||
}
|
||||
211
vendor/github.com/jinzhu/gorm/join_table_handler.go
generated
vendored
Normal file
211
vendor/github.com/jinzhu/gorm/join_table_handler.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JoinTableHandlerInterface is an interface for how to handle many2many relations
|
||||
type JoinTableHandlerInterface interface {
|
||||
// initialize join table handler
|
||||
Setup(relationship *Relationship, tableName string, source reflect.Type, destination reflect.Type)
|
||||
// Table return join table's table name
|
||||
Table(db *DB) string
|
||||
// Add create relationship in join table for source and destination
|
||||
Add(handler JoinTableHandlerInterface, db *DB, source interface{}, destination interface{}) error
|
||||
// Delete delete relationship in join table for sources
|
||||
Delete(handler JoinTableHandlerInterface, db *DB, sources ...interface{}) error
|
||||
// JoinWith query with `Join` conditions
|
||||
JoinWith(handler JoinTableHandlerInterface, db *DB, source interface{}) *DB
|
||||
// SourceForeignKeys return source foreign keys
|
||||
SourceForeignKeys() []JoinTableForeignKey
|
||||
// DestinationForeignKeys return destination foreign keys
|
||||
DestinationForeignKeys() []JoinTableForeignKey
|
||||
}
|
||||
|
||||
// JoinTableForeignKey join table foreign key struct
|
||||
type JoinTableForeignKey struct {
|
||||
DBName string
|
||||
AssociationDBName string
|
||||
}
|
||||
|
||||
// JoinTableSource is a struct that contains model type and foreign keys
|
||||
type JoinTableSource struct {
|
||||
ModelType reflect.Type
|
||||
ForeignKeys []JoinTableForeignKey
|
||||
}
|
||||
|
||||
// JoinTableHandler default join table handler
|
||||
type JoinTableHandler struct {
|
||||
TableName string `sql:"-"`
|
||||
Source JoinTableSource `sql:"-"`
|
||||
Destination JoinTableSource `sql:"-"`
|
||||
}
|
||||
|
||||
// SourceForeignKeys return source foreign keys
|
||||
func (s *JoinTableHandler) SourceForeignKeys() []JoinTableForeignKey {
|
||||
return s.Source.ForeignKeys
|
||||
}
|
||||
|
||||
// DestinationForeignKeys return destination foreign keys
|
||||
func (s *JoinTableHandler) DestinationForeignKeys() []JoinTableForeignKey {
|
||||
return s.Destination.ForeignKeys
|
||||
}
|
||||
|
||||
// Setup initialize a default join table handler
|
||||
func (s *JoinTableHandler) Setup(relationship *Relationship, tableName string, source reflect.Type, destination reflect.Type) {
|
||||
s.TableName = tableName
|
||||
|
||||
s.Source = JoinTableSource{ModelType: source}
|
||||
s.Source.ForeignKeys = []JoinTableForeignKey{}
|
||||
for idx, dbName := range relationship.ForeignFieldNames {
|
||||
s.Source.ForeignKeys = append(s.Source.ForeignKeys, JoinTableForeignKey{
|
||||
DBName: relationship.ForeignDBNames[idx],
|
||||
AssociationDBName: dbName,
|
||||
})
|
||||
}
|
||||
|
||||
s.Destination = JoinTableSource{ModelType: destination}
|
||||
s.Destination.ForeignKeys = []JoinTableForeignKey{}
|
||||
for idx, dbName := range relationship.AssociationForeignFieldNames {
|
||||
s.Destination.ForeignKeys = append(s.Destination.ForeignKeys, JoinTableForeignKey{
|
||||
DBName: relationship.AssociationForeignDBNames[idx],
|
||||
AssociationDBName: dbName,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Table return join table's table name
|
||||
func (s JoinTableHandler) Table(db *DB) string {
|
||||
return DefaultTableNameHandler(db, s.TableName)
|
||||
}
|
||||
|
||||
func (s JoinTableHandler) updateConditionMap(conditionMap map[string]interface{}, db *DB, joinTableSources []JoinTableSource, sources ...interface{}) {
|
||||
for _, source := range sources {
|
||||
scope := db.NewScope(source)
|
||||
modelType := scope.GetModelStruct().ModelType
|
||||
|
||||
for _, joinTableSource := range joinTableSources {
|
||||
if joinTableSource.ModelType == modelType {
|
||||
for _, foreignKey := range joinTableSource.ForeignKeys {
|
||||
if field, ok := scope.FieldByName(foreignKey.AssociationDBName); ok {
|
||||
conditionMap[foreignKey.DBName] = field.Field.Interface()
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add create relationship in join table for source and destination
|
||||
func (s JoinTableHandler) Add(handler JoinTableHandlerInterface, db *DB, source interface{}, destination interface{}) error {
|
||||
var (
|
||||
scope = db.NewScope("")
|
||||
conditionMap = map[string]interface{}{}
|
||||
)
|
||||
|
||||
// Update condition map for source
|
||||
s.updateConditionMap(conditionMap, db, []JoinTableSource{s.Source}, source)
|
||||
|
||||
// Update condition map for destination
|
||||
s.updateConditionMap(conditionMap, db, []JoinTableSource{s.Destination}, destination)
|
||||
|
||||
var assignColumns, binVars, conditions []string
|
||||
var values []interface{}
|
||||
for key, value := range conditionMap {
|
||||
assignColumns = append(assignColumns, scope.Quote(key))
|
||||
binVars = append(binVars, `?`)
|
||||
conditions = append(conditions, fmt.Sprintf("%v = ?", scope.Quote(key)))
|
||||
values = append(values, value)
|
||||
}
|
||||
|
||||
for _, value := range values {
|
||||
values = append(values, value)
|
||||
}
|
||||
|
||||
quotedTable := scope.Quote(handler.Table(db))
|
||||
sql := fmt.Sprintf(
|
||||
"INSERT INTO %v (%v) SELECT %v %v WHERE NOT EXISTS (SELECT * FROM %v WHERE %v)",
|
||||
quotedTable,
|
||||
strings.Join(assignColumns, ","),
|
||||
strings.Join(binVars, ","),
|
||||
scope.Dialect().SelectFromDummyTable(),
|
||||
quotedTable,
|
||||
strings.Join(conditions, " AND "),
|
||||
)
|
||||
|
||||
return db.Exec(sql, values...).Error
|
||||
}
|
||||
|
||||
// Delete delete relationship in join table for sources
|
||||
func (s JoinTableHandler) Delete(handler JoinTableHandlerInterface, db *DB, sources ...interface{}) error {
|
||||
var (
|
||||
scope = db.NewScope(nil)
|
||||
conditions []string
|
||||
values []interface{}
|
||||
conditionMap = map[string]interface{}{}
|
||||
)
|
||||
|
||||
s.updateConditionMap(conditionMap, db, []JoinTableSource{s.Source, s.Destination}, sources...)
|
||||
|
||||
for key, value := range conditionMap {
|
||||
conditions = append(conditions, fmt.Sprintf("%v = ?", scope.Quote(key)))
|
||||
values = append(values, value)
|
||||
}
|
||||
|
||||
return db.Table(handler.Table(db)).Where(strings.Join(conditions, " AND "), values...).Delete("").Error
|
||||
}
|
||||
|
||||
// JoinWith query with `Join` conditions
|
||||
func (s JoinTableHandler) JoinWith(handler JoinTableHandlerInterface, db *DB, source interface{}) *DB {
|
||||
var (
|
||||
scope = db.NewScope(source)
|
||||
tableName = handler.Table(db)
|
||||
quotedTableName = scope.Quote(tableName)
|
||||
joinConditions []string
|
||||
values []interface{}
|
||||
)
|
||||
|
||||
if s.Source.ModelType == scope.GetModelStruct().ModelType {
|
||||
destinationTableName := db.NewScope(reflect.New(s.Destination.ModelType).Interface()).QuotedTableName()
|
||||
for _, foreignKey := range s.Destination.ForeignKeys {
|
||||
joinConditions = append(joinConditions, fmt.Sprintf("%v.%v = %v.%v", quotedTableName, scope.Quote(foreignKey.DBName), destinationTableName, scope.Quote(foreignKey.AssociationDBName)))
|
||||
}
|
||||
|
||||
var foreignDBNames []string
|
||||
var foreignFieldNames []string
|
||||
|
||||
for _, foreignKey := range s.Source.ForeignKeys {
|
||||
foreignDBNames = append(foreignDBNames, foreignKey.DBName)
|
||||
if field, ok := scope.FieldByName(foreignKey.AssociationDBName); ok {
|
||||
foreignFieldNames = append(foreignFieldNames, field.Name)
|
||||
}
|
||||
}
|
||||
|
||||
foreignFieldValues := scope.getColumnAsArray(foreignFieldNames, scope.Value)
|
||||
|
||||
var condString string
|
||||
if len(foreignFieldValues) > 0 {
|
||||
var quotedForeignDBNames []string
|
||||
for _, dbName := range foreignDBNames {
|
||||
quotedForeignDBNames = append(quotedForeignDBNames, tableName+"."+dbName)
|
||||
}
|
||||
|
||||
condString = fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, quotedForeignDBNames), toQueryMarks(foreignFieldValues))
|
||||
|
||||
keys := scope.getColumnAsArray(foreignFieldNames, scope.Value)
|
||||
values = append(values, toQueryValues(keys))
|
||||
} else {
|
||||
condString = fmt.Sprintf("1 <> 1")
|
||||
}
|
||||
|
||||
return db.Joins(fmt.Sprintf("INNER JOIN %v ON %v", quotedTableName, strings.Join(joinConditions, " AND "))).
|
||||
Where(condString, toQueryValues(foreignFieldValues)...)
|
||||
}
|
||||
|
||||
db.Error = errors.New("wrong source type for join table handler")
|
||||
return db
|
||||
}
|
||||
119
vendor/github.com/jinzhu/gorm/logger.go
generated
vendored
Normal file
119
vendor/github.com/jinzhu/gorm/logger.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultLogger = Logger{log.New(os.Stdout, "\r\n", 0)}
|
||||
sqlRegexp = regexp.MustCompile(`\?`)
|
||||
numericPlaceHolderRegexp = regexp.MustCompile(`\$\d+`)
|
||||
)
|
||||
|
||||
func isPrintable(s string) bool {
|
||||
for _, r := range s {
|
||||
if !unicode.IsPrint(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var LogFormatter = func(values ...interface{}) (messages []interface{}) {
|
||||
if len(values) > 1 {
|
||||
var (
|
||||
sql string
|
||||
formattedValues []string
|
||||
level = values[0]
|
||||
currentTime = "\n\033[33m[" + NowFunc().Format("2006-01-02 15:04:05") + "]\033[0m"
|
||||
source = fmt.Sprintf("\033[35m(%v)\033[0m", values[1])
|
||||
)
|
||||
|
||||
messages = []interface{}{source, currentTime}
|
||||
|
||||
if level == "sql" {
|
||||
// duration
|
||||
messages = append(messages, fmt.Sprintf(" \033[36;1m[%.2fms]\033[0m ", float64(values[2].(time.Duration).Nanoseconds()/1e4)/100.0))
|
||||
// sql
|
||||
|
||||
for _, value := range values[4].([]interface{}) {
|
||||
indirectValue := reflect.Indirect(reflect.ValueOf(value))
|
||||
if indirectValue.IsValid() {
|
||||
value = indirectValue.Interface()
|
||||
if t, ok := value.(time.Time); ok {
|
||||
formattedValues = append(formattedValues, fmt.Sprintf("'%v'", t.Format("2006-01-02 15:04:05")))
|
||||
} else if b, ok := value.([]byte); ok {
|
||||
if str := string(b); isPrintable(str) {
|
||||
formattedValues = append(formattedValues, fmt.Sprintf("'%v'", str))
|
||||
} else {
|
||||
formattedValues = append(formattedValues, "'<binary>'")
|
||||
}
|
||||
} else if r, ok := value.(driver.Valuer); ok {
|
||||
if value, err := r.Value(); err == nil && value != nil {
|
||||
formattedValues = append(formattedValues, fmt.Sprintf("'%v'", value))
|
||||
} else {
|
||||
formattedValues = append(formattedValues, "NULL")
|
||||
}
|
||||
} else {
|
||||
formattedValues = append(formattedValues, fmt.Sprintf("'%v'", value))
|
||||
}
|
||||
} else {
|
||||
formattedValues = append(formattedValues, "NULL")
|
||||
}
|
||||
}
|
||||
|
||||
// differentiate between $n placeholders or else treat like ?
|
||||
if numericPlaceHolderRegexp.MatchString(values[3].(string)) {
|
||||
sql = values[3].(string)
|
||||
for index, value := range formattedValues {
|
||||
placeholder := fmt.Sprintf(`\$%d([^\d]|$)`, index+1)
|
||||
sql = regexp.MustCompile(placeholder).ReplaceAllString(sql, value+"$1")
|
||||
}
|
||||
} else {
|
||||
formattedValuesLength := len(formattedValues)
|
||||
for index, value := range sqlRegexp.Split(values[3].(string), -1) {
|
||||
sql += value
|
||||
if index < formattedValuesLength {
|
||||
sql += formattedValues[index]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messages = append(messages, sql)
|
||||
messages = append(messages, fmt.Sprintf(" \n\033[36;31m[%v]\033[0m ", strconv.FormatInt(values[5].(int64), 10)+" rows affected or returned "))
|
||||
} else {
|
||||
messages = append(messages, "\033[31;1m")
|
||||
messages = append(messages, values[2:]...)
|
||||
messages = append(messages, "\033[0m")
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type logger interface {
|
||||
Print(v ...interface{})
|
||||
}
|
||||
|
||||
// LogWriter log writer interface
|
||||
type LogWriter interface {
|
||||
Println(v ...interface{})
|
||||
}
|
||||
|
||||
// Logger default logger
|
||||
type Logger struct {
|
||||
LogWriter
|
||||
}
|
||||
|
||||
// Print format & print log
|
||||
func (logger Logger) Print(values ...interface{}) {
|
||||
logger.Println(LogFormatter(values...)...)
|
||||
}
|
||||
777
vendor/github.com/jinzhu/gorm/main.go
generated
vendored
Normal file
777
vendor/github.com/jinzhu/gorm/main.go
generated
vendored
Normal file
@@ -0,0 +1,777 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DB contains information for current db connection
|
||||
type DB struct {
|
||||
Value interface{}
|
||||
Error error
|
||||
RowsAffected int64
|
||||
|
||||
// single db
|
||||
db SQLCommon
|
||||
blockGlobalUpdate bool
|
||||
logMode int
|
||||
logger logger
|
||||
search *search
|
||||
values map[string]interface{}
|
||||
|
||||
// global db
|
||||
parent *DB
|
||||
callbacks *Callback
|
||||
dialect Dialect
|
||||
singularTable bool
|
||||
}
|
||||
|
||||
// Open initialize a new db connection, need to import driver first, e.g:
|
||||
//
|
||||
// import _ "github.com/go-sql-driver/mysql"
|
||||
// func main() {
|
||||
// db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
|
||||
// }
|
||||
// GORM has wrapped some drivers, for easier to remember driver's import path, so you could import the mysql driver with
|
||||
// import _ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
// // import _ "github.com/jinzhu/gorm/dialects/postgres"
|
||||
// // import _ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
// // import _ "github.com/jinzhu/gorm/dialects/mssql"
|
||||
func Open(dialect string, args ...interface{}) (db *DB, err error) {
|
||||
if len(args) == 0 {
|
||||
err = errors.New("invalid database source")
|
||||
return nil, err
|
||||
}
|
||||
var source string
|
||||
var dbSQL SQLCommon
|
||||
|
||||
switch value := args[0].(type) {
|
||||
case string:
|
||||
var driver = dialect
|
||||
if len(args) == 1 {
|
||||
source = value
|
||||
} else if len(args) >= 2 {
|
||||
driver = value
|
||||
source = args[1].(string)
|
||||
}
|
||||
dbSQL, err = sql.Open(driver, source)
|
||||
case SQLCommon:
|
||||
dbSQL = value
|
||||
}
|
||||
|
||||
db = &DB{
|
||||
db: dbSQL,
|
||||
logger: defaultLogger,
|
||||
values: map[string]interface{}{},
|
||||
callbacks: DefaultCallback,
|
||||
dialect: newDialect(dialect, dbSQL),
|
||||
}
|
||||
db.parent = db
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Send a ping to make sure the database connection is alive.
|
||||
if d, ok := dbSQL.(*sql.DB); ok {
|
||||
if err = d.Ping(); err != nil {
|
||||
d.Close()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// New clone a new db connection without search conditions
|
||||
func (s *DB) New() *DB {
|
||||
clone := s.clone()
|
||||
clone.search = nil
|
||||
clone.Value = nil
|
||||
return clone
|
||||
}
|
||||
|
||||
type closer interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Close close current db connection. If database connection is not an io.Closer, returns an error.
|
||||
func (s *DB) Close() error {
|
||||
if db, ok := s.parent.db.(closer); ok {
|
||||
return db.Close()
|
||||
}
|
||||
return errors.New("can't close current db")
|
||||
}
|
||||
|
||||
// DB get `*sql.DB` from current connection
|
||||
// If the underlying database connection is not a *sql.DB, returns nil
|
||||
func (s *DB) DB() *sql.DB {
|
||||
db, _ := s.db.(*sql.DB)
|
||||
return db
|
||||
}
|
||||
|
||||
// CommonDB return the underlying `*sql.DB` or `*sql.Tx` instance, mainly intended to allow coexistence with legacy non-GORM code.
|
||||
func (s *DB) CommonDB() SQLCommon {
|
||||
return s.db
|
||||
}
|
||||
|
||||
// Dialect get dialect
|
||||
func (s *DB) Dialect() Dialect {
|
||||
return s.parent.dialect
|
||||
}
|
||||
|
||||
// Callback return `Callbacks` container, you could add/change/delete callbacks with it
|
||||
// db.Callback().Create().Register("update_created_at", updateCreated)
|
||||
// Refer https://jinzhu.github.io/gorm/development.html#callbacks
|
||||
func (s *DB) Callback() *Callback {
|
||||
s.parent.callbacks = s.parent.callbacks.clone()
|
||||
return s.parent.callbacks
|
||||
}
|
||||
|
||||
// SetLogger replace default logger
|
||||
func (s *DB) SetLogger(log logger) {
|
||||
s.logger = log
|
||||
}
|
||||
|
||||
// LogMode set log mode, `true` for detailed logs, `false` for no log, default, will only print error logs
|
||||
func (s *DB) LogMode(enable bool) *DB {
|
||||
if enable {
|
||||
s.logMode = 2
|
||||
} else {
|
||||
s.logMode = 1
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// BlockGlobalUpdate if true, generates an error on update/delete without where clause.
|
||||
// This is to prevent eventual error with empty objects updates/deletions
|
||||
func (s *DB) BlockGlobalUpdate(enable bool) *DB {
|
||||
s.blockGlobalUpdate = enable
|
||||
return s
|
||||
}
|
||||
|
||||
// HasBlockGlobalUpdate return state of block
|
||||
func (s *DB) HasBlockGlobalUpdate() bool {
|
||||
return s.blockGlobalUpdate
|
||||
}
|
||||
|
||||
// SingularTable use singular table by default
|
||||
func (s *DB) SingularTable(enable bool) {
|
||||
modelStructsMap = newModelStructsMap()
|
||||
s.parent.singularTable = enable
|
||||
}
|
||||
|
||||
// NewScope create a scope for current operation
|
||||
func (s *DB) NewScope(value interface{}) *Scope {
|
||||
dbClone := s.clone()
|
||||
dbClone.Value = value
|
||||
return &Scope{db: dbClone, Search: dbClone.search.clone(), Value: value}
|
||||
}
|
||||
|
||||
// QueryExpr returns the query as expr object
|
||||
func (s *DB) QueryExpr() *expr {
|
||||
scope := s.NewScope(s.Value)
|
||||
scope.InstanceSet("skip_bindvar", true)
|
||||
scope.prepareQuerySQL()
|
||||
|
||||
return Expr(scope.SQL, scope.SQLVars...)
|
||||
}
|
||||
|
||||
// SubQuery returns the query as sub query
|
||||
func (s *DB) SubQuery() *expr {
|
||||
scope := s.NewScope(s.Value)
|
||||
scope.InstanceSet("skip_bindvar", true)
|
||||
scope.prepareQuerySQL()
|
||||
|
||||
return Expr(fmt.Sprintf("(%v)", scope.SQL), scope.SQLVars...)
|
||||
}
|
||||
|
||||
// Where return a new relation, filter records with given conditions, accepts `map`, `struct` or `string` as conditions, refer http://jinzhu.github.io/gorm/crud.html#query
|
||||
func (s *DB) Where(query interface{}, args ...interface{}) *DB {
|
||||
return s.clone().search.Where(query, args...).db
|
||||
}
|
||||
|
||||
// Or filter records that match before conditions or this one, similar to `Where`
|
||||
func (s *DB) Or(query interface{}, args ...interface{}) *DB {
|
||||
return s.clone().search.Or(query, args...).db
|
||||
}
|
||||
|
||||
// Not filter records that don't match current conditions, similar to `Where`
|
||||
func (s *DB) Not(query interface{}, args ...interface{}) *DB {
|
||||
return s.clone().search.Not(query, args...).db
|
||||
}
|
||||
|
||||
// Limit specify the number of records to be retrieved
|
||||
func (s *DB) Limit(limit interface{}) *DB {
|
||||
return s.clone().search.Limit(limit).db
|
||||
}
|
||||
|
||||
// Offset specify the number of records to skip before starting to return the records
|
||||
func (s *DB) Offset(offset interface{}) *DB {
|
||||
return s.clone().search.Offset(offset).db
|
||||
}
|
||||
|
||||
// Order specify order when retrieve records from database, set reorder to `true` to overwrite defined conditions
|
||||
// db.Order("name DESC")
|
||||
// db.Order("name DESC", true) // reorder
|
||||
// db.Order(gorm.Expr("name = ? DESC", "first")) // sql expression
|
||||
func (s *DB) Order(value interface{}, reorder ...bool) *DB {
|
||||
return s.clone().search.Order(value, reorder...).db
|
||||
}
|
||||
|
||||
// Select specify fields that you want to retrieve from database when querying, by default, will select all fields;
|
||||
// When creating/updating, specify fields that you want to save to database
|
||||
func (s *DB) Select(query interface{}, args ...interface{}) *DB {
|
||||
return s.clone().search.Select(query, args...).db
|
||||
}
|
||||
|
||||
// Omit specify fields that you want to ignore when saving to database for creating, updating
|
||||
func (s *DB) Omit(columns ...string) *DB {
|
||||
return s.clone().search.Omit(columns...).db
|
||||
}
|
||||
|
||||
// Group specify the group method on the find
|
||||
func (s *DB) Group(query string) *DB {
|
||||
return s.clone().search.Group(query).db
|
||||
}
|
||||
|
||||
// Having specify HAVING conditions for GROUP BY
|
||||
func (s *DB) Having(query interface{}, values ...interface{}) *DB {
|
||||
return s.clone().search.Having(query, values...).db
|
||||
}
|
||||
|
||||
// Joins specify Joins conditions
|
||||
// db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Find(&user)
|
||||
func (s *DB) Joins(query string, args ...interface{}) *DB {
|
||||
return s.clone().search.Joins(query, args...).db
|
||||
}
|
||||
|
||||
// Scopes pass current database connection to arguments `func(*DB) *DB`, which could be used to add conditions dynamically
|
||||
// func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
|
||||
// return db.Where("amount > ?", 1000)
|
||||
// }
|
||||
//
|
||||
// func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB {
|
||||
// return func (db *gorm.DB) *gorm.DB {
|
||||
// return db.Scopes(AmountGreaterThan1000).Where("status in (?)", status)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
|
||||
// Refer https://jinzhu.github.io/gorm/crud.html#scopes
|
||||
func (s *DB) Scopes(funcs ...func(*DB) *DB) *DB {
|
||||
for _, f := range funcs {
|
||||
s = f(s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Unscoped return all record including deleted record, refer Soft Delete https://jinzhu.github.io/gorm/crud.html#soft-delete
|
||||
func (s *DB) Unscoped() *DB {
|
||||
return s.clone().search.unscoped().db
|
||||
}
|
||||
|
||||
// Attrs initialize struct with argument if record not found with `FirstOrInit` https://jinzhu.github.io/gorm/crud.html#firstorinit or `FirstOrCreate` https://jinzhu.github.io/gorm/crud.html#firstorcreate
|
||||
func (s *DB) Attrs(attrs ...interface{}) *DB {
|
||||
return s.clone().search.Attrs(attrs...).db
|
||||
}
|
||||
|
||||
// Assign assign result with argument regardless it is found or not with `FirstOrInit` https://jinzhu.github.io/gorm/crud.html#firstorinit or `FirstOrCreate` https://jinzhu.github.io/gorm/crud.html#firstorcreate
|
||||
func (s *DB) Assign(attrs ...interface{}) *DB {
|
||||
return s.clone().search.Assign(attrs...).db
|
||||
}
|
||||
|
||||
// First find first record that match given conditions, order by primary key
|
||||
func (s *DB) First(out interface{}, where ...interface{}) *DB {
|
||||
newScope := s.NewScope(out)
|
||||
newScope.Search.Limit(1)
|
||||
return newScope.Set("gorm:order_by_primary_key", "ASC").
|
||||
inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db
|
||||
}
|
||||
|
||||
// Take return a record that match given conditions, the order will depend on the database implementation
|
||||
func (s *DB) Take(out interface{}, where ...interface{}) *DB {
|
||||
newScope := s.NewScope(out)
|
||||
newScope.Search.Limit(1)
|
||||
return newScope.inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db
|
||||
}
|
||||
|
||||
// Last find last record that match given conditions, order by primary key
|
||||
func (s *DB) Last(out interface{}, where ...interface{}) *DB {
|
||||
newScope := s.NewScope(out)
|
||||
newScope.Search.Limit(1)
|
||||
return newScope.Set("gorm:order_by_primary_key", "DESC").
|
||||
inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db
|
||||
}
|
||||
|
||||
// Find find records that match given conditions
|
||||
func (s *DB) Find(out interface{}, where ...interface{}) *DB {
|
||||
return s.NewScope(out).inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db
|
||||
}
|
||||
|
||||
// Scan scan value to a struct
|
||||
func (s *DB) Scan(dest interface{}) *DB {
|
||||
return s.NewScope(s.Value).Set("gorm:query_destination", dest).callCallbacks(s.parent.callbacks.queries).db
|
||||
}
|
||||
|
||||
// Row return `*sql.Row` with given conditions
|
||||
func (s *DB) Row() *sql.Row {
|
||||
return s.NewScope(s.Value).row()
|
||||
}
|
||||
|
||||
// Rows return `*sql.Rows` with given conditions
|
||||
func (s *DB) Rows() (*sql.Rows, error) {
|
||||
return s.NewScope(s.Value).rows()
|
||||
}
|
||||
|
||||
// ScanRows scan `*sql.Rows` to give struct
|
||||
func (s *DB) ScanRows(rows *sql.Rows, result interface{}) error {
|
||||
var (
|
||||
scope = s.NewScope(result)
|
||||
clone = scope.db
|
||||
columns, err = rows.Columns()
|
||||
)
|
||||
|
||||
if clone.AddError(err) == nil {
|
||||
scope.scan(rows, columns, scope.Fields())
|
||||
}
|
||||
|
||||
return clone.Error
|
||||
}
|
||||
|
||||
// Pluck used to query single column from a model as a map
|
||||
// var ages []int64
|
||||
// db.Find(&users).Pluck("age", &ages)
|
||||
func (s *DB) Pluck(column string, value interface{}) *DB {
|
||||
return s.NewScope(s.Value).pluck(column, value).db
|
||||
}
|
||||
|
||||
// Count get how many records for a model
|
||||
func (s *DB) Count(value interface{}) *DB {
|
||||
return s.NewScope(s.Value).count(value).db
|
||||
}
|
||||
|
||||
// Related get related associations
|
||||
func (s *DB) Related(value interface{}, foreignKeys ...string) *DB {
|
||||
return s.NewScope(s.Value).related(value, foreignKeys...).db
|
||||
}
|
||||
|
||||
// FirstOrInit find first matched record or initialize a new one with given conditions (only works with struct, map conditions)
|
||||
// https://jinzhu.github.io/gorm/crud.html#firstorinit
|
||||
func (s *DB) FirstOrInit(out interface{}, where ...interface{}) *DB {
|
||||
c := s.clone()
|
||||
if result := c.First(out, where...); result.Error != nil {
|
||||
if !result.RecordNotFound() {
|
||||
return result
|
||||
}
|
||||
c.NewScope(out).inlineCondition(where...).initialize()
|
||||
} else {
|
||||
c.NewScope(out).updatedAttrsWithValues(c.search.assignAttrs)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// FirstOrCreate find first matched record or create a new one with given conditions (only works with struct, map conditions)
|
||||
// https://jinzhu.github.io/gorm/crud.html#firstorcreate
|
||||
func (s *DB) FirstOrCreate(out interface{}, where ...interface{}) *DB {
|
||||
c := s.clone()
|
||||
if result := s.First(out, where...); result.Error != nil {
|
||||
if !result.RecordNotFound() {
|
||||
return result
|
||||
}
|
||||
return c.NewScope(out).inlineCondition(where...).initialize().callCallbacks(c.parent.callbacks.creates).db
|
||||
} else if len(c.search.assignAttrs) > 0 {
|
||||
return c.NewScope(out).InstanceSet("gorm:update_interface", c.search.assignAttrs).callCallbacks(c.parent.callbacks.updates).db
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Update update attributes with callbacks, refer: https://jinzhu.github.io/gorm/crud.html#update
|
||||
func (s *DB) Update(attrs ...interface{}) *DB {
|
||||
return s.Updates(toSearchableMap(attrs...), true)
|
||||
}
|
||||
|
||||
// Updates update attributes with callbacks, refer: https://jinzhu.github.io/gorm/crud.html#update
|
||||
func (s *DB) Updates(values interface{}, ignoreProtectedAttrs ...bool) *DB {
|
||||
return s.NewScope(s.Value).
|
||||
Set("gorm:ignore_protected_attrs", len(ignoreProtectedAttrs) > 0).
|
||||
InstanceSet("gorm:update_interface", values).
|
||||
callCallbacks(s.parent.callbacks.updates).db
|
||||
}
|
||||
|
||||
// UpdateColumn update attributes without callbacks, refer: https://jinzhu.github.io/gorm/crud.html#update
|
||||
func (s *DB) UpdateColumn(attrs ...interface{}) *DB {
|
||||
return s.UpdateColumns(toSearchableMap(attrs...))
|
||||
}
|
||||
|
||||
// UpdateColumns update attributes without callbacks, refer: https://jinzhu.github.io/gorm/crud.html#update
|
||||
func (s *DB) UpdateColumns(values interface{}) *DB {
|
||||
return s.NewScope(s.Value).
|
||||
Set("gorm:update_column", true).
|
||||
Set("gorm:save_associations", false).
|
||||
InstanceSet("gorm:update_interface", values).
|
||||
callCallbacks(s.parent.callbacks.updates).db
|
||||
}
|
||||
|
||||
// Save update value in database, if the value doesn't have primary key, will insert it
|
||||
func (s *DB) Save(value interface{}) *DB {
|
||||
scope := s.NewScope(value)
|
||||
if !scope.PrimaryKeyZero() {
|
||||
newDB := scope.callCallbacks(s.parent.callbacks.updates).db
|
||||
if newDB.Error == nil && newDB.RowsAffected == 0 {
|
||||
return s.New().FirstOrCreate(value)
|
||||
}
|
||||
return newDB
|
||||
}
|
||||
return scope.callCallbacks(s.parent.callbacks.creates).db
|
||||
}
|
||||
|
||||
// Create insert the value into database
|
||||
func (s *DB) Create(value interface{}) *DB {
|
||||
scope := s.NewScope(value)
|
||||
return scope.callCallbacks(s.parent.callbacks.creates).db
|
||||
}
|
||||
|
||||
// Delete delete value match given conditions, if the value has primary key, then will including the primary key as condition
|
||||
func (s *DB) Delete(value interface{}, where ...interface{}) *DB {
|
||||
return s.NewScope(value).inlineCondition(where...).callCallbacks(s.parent.callbacks.deletes).db
|
||||
}
|
||||
|
||||
// Raw use raw sql as conditions, won't run it unless invoked by other methods
|
||||
// db.Raw("SELECT name, age FROM users WHERE name = ?", 3).Scan(&result)
|
||||
func (s *DB) Raw(sql string, values ...interface{}) *DB {
|
||||
return s.clone().search.Raw(true).Where(sql, values...).db
|
||||
}
|
||||
|
||||
// Exec execute raw sql
|
||||
func (s *DB) Exec(sql string, values ...interface{}) *DB {
|
||||
scope := s.NewScope(nil)
|
||||
generatedSQL := scope.buildCondition(map[string]interface{}{"query": sql, "args": values}, true)
|
||||
generatedSQL = strings.TrimSuffix(strings.TrimPrefix(generatedSQL, "("), ")")
|
||||
scope.Raw(generatedSQL)
|
||||
return scope.Exec().db
|
||||
}
|
||||
|
||||
// Model specify the model you would like to run db operations
|
||||
// // update all users's name to `hello`
|
||||
// db.Model(&User{}).Update("name", "hello")
|
||||
// // if user's primary key is non-blank, will use it as condition, then will only update the user's name to `hello`
|
||||
// db.Model(&user).Update("name", "hello")
|
||||
func (s *DB) Model(value interface{}) *DB {
|
||||
c := s.clone()
|
||||
c.Value = value
|
||||
return c
|
||||
}
|
||||
|
||||
// Table specify the table you would like to run db operations
|
||||
func (s *DB) Table(name string) *DB {
|
||||
clone := s.clone()
|
||||
clone.search.Table(name)
|
||||
clone.Value = nil
|
||||
return clone
|
||||
}
|
||||
|
||||
// Debug start debug mode
|
||||
func (s *DB) Debug() *DB {
|
||||
return s.clone().LogMode(true)
|
||||
}
|
||||
|
||||
// Begin begin a transaction
|
||||
func (s *DB) Begin() *DB {
|
||||
c := s.clone()
|
||||
if db, ok := c.db.(sqlDb); ok && db != nil {
|
||||
tx, err := db.Begin()
|
||||
c.db = interface{}(tx).(SQLCommon)
|
||||
c.AddError(err)
|
||||
} else {
|
||||
c.AddError(ErrCantStartTransaction)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Commit commit a transaction
|
||||
func (s *DB) Commit() *DB {
|
||||
if db, ok := s.db.(sqlTx); ok && db != nil {
|
||||
s.AddError(db.Commit())
|
||||
} else {
|
||||
s.AddError(ErrInvalidTransaction)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Rollback rollback a transaction
|
||||
func (s *DB) Rollback() *DB {
|
||||
if db, ok := s.db.(sqlTx); ok && db != nil {
|
||||
s.AddError(db.Rollback())
|
||||
} else {
|
||||
s.AddError(ErrInvalidTransaction)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NewRecord check if value's primary key is blank
|
||||
func (s *DB) NewRecord(value interface{}) bool {
|
||||
return s.NewScope(value).PrimaryKeyZero()
|
||||
}
|
||||
|
||||
// RecordNotFound check if returning ErrRecordNotFound error
|
||||
func (s *DB) RecordNotFound() bool {
|
||||
for _, err := range s.GetErrors() {
|
||||
if err == ErrRecordNotFound {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CreateTable create table for models
|
||||
func (s *DB) CreateTable(models ...interface{}) *DB {
|
||||
db := s.Unscoped()
|
||||
for _, model := range models {
|
||||
db = db.NewScope(model).createTable().db
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// DropTable drop table for models
|
||||
func (s *DB) DropTable(values ...interface{}) *DB {
|
||||
db := s.clone()
|
||||
for _, value := range values {
|
||||
if tableName, ok := value.(string); ok {
|
||||
db = db.Table(tableName)
|
||||
}
|
||||
|
||||
db = db.NewScope(value).dropTable().db
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// DropTableIfExists drop table if it is exist
|
||||
func (s *DB) DropTableIfExists(values ...interface{}) *DB {
|
||||
db := s.clone()
|
||||
for _, value := range values {
|
||||
if s.HasTable(value) {
|
||||
db.AddError(s.DropTable(value).Error)
|
||||
}
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// HasTable check has table or not
|
||||
func (s *DB) HasTable(value interface{}) bool {
|
||||
var (
|
||||
scope = s.NewScope(value)
|
||||
tableName string
|
||||
)
|
||||
|
||||
if name, ok := value.(string); ok {
|
||||
tableName = name
|
||||
} else {
|
||||
tableName = scope.TableName()
|
||||
}
|
||||
|
||||
has := scope.Dialect().HasTable(tableName)
|
||||
s.AddError(scope.db.Error)
|
||||
return has
|
||||
}
|
||||
|
||||
// AutoMigrate run auto migration for given models, will only add missing fields, won't delete/change current data
|
||||
func (s *DB) AutoMigrate(values ...interface{}) *DB {
|
||||
db := s.Unscoped()
|
||||
for _, value := range values {
|
||||
db = db.NewScope(value).autoMigrate().db
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// ModifyColumn modify column to type
|
||||
func (s *DB) ModifyColumn(column string, typ string) *DB {
|
||||
scope := s.NewScope(s.Value)
|
||||
scope.modifyColumn(column, typ)
|
||||
return scope.db
|
||||
}
|
||||
|
||||
// DropColumn drop a column
|
||||
func (s *DB) DropColumn(column string) *DB {
|
||||
scope := s.NewScope(s.Value)
|
||||
scope.dropColumn(column)
|
||||
return scope.db
|
||||
}
|
||||
|
||||
// AddIndex add index for columns with given name
|
||||
func (s *DB) AddIndex(indexName string, columns ...string) *DB {
|
||||
scope := s.Unscoped().NewScope(s.Value)
|
||||
scope.addIndex(false, indexName, columns...)
|
||||
return scope.db
|
||||
}
|
||||
|
||||
// AddUniqueIndex add unique index for columns with given name
|
||||
func (s *DB) AddUniqueIndex(indexName string, columns ...string) *DB {
|
||||
scope := s.Unscoped().NewScope(s.Value)
|
||||
scope.addIndex(true, indexName, columns...)
|
||||
return scope.db
|
||||
}
|
||||
|
||||
// RemoveIndex remove index with name
|
||||
func (s *DB) RemoveIndex(indexName string) *DB {
|
||||
scope := s.NewScope(s.Value)
|
||||
scope.removeIndex(indexName)
|
||||
return scope.db
|
||||
}
|
||||
|
||||
// AddForeignKey Add foreign key to the given scope, e.g:
|
||||
// db.Model(&User{}).AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")
|
||||
func (s *DB) AddForeignKey(field string, dest string, onDelete string, onUpdate string) *DB {
|
||||
scope := s.NewScope(s.Value)
|
||||
scope.addForeignKey(field, dest, onDelete, onUpdate)
|
||||
return scope.db
|
||||
}
|
||||
|
||||
// RemoveForeignKey Remove foreign key from the given scope, e.g:
|
||||
// db.Model(&User{}).RemoveForeignKey("city_id", "cities(id)")
|
||||
func (s *DB) RemoveForeignKey(field string, dest string) *DB {
|
||||
scope := s.clone().NewScope(s.Value)
|
||||
scope.removeForeignKey(field, dest)
|
||||
return scope.db
|
||||
}
|
||||
|
||||
// Association start `Association Mode` to handler relations things easir in that mode, refer: https://jinzhu.github.io/gorm/associations.html#association-mode
|
||||
func (s *DB) Association(column string) *Association {
|
||||
var err error
|
||||
var scope = s.Set("gorm:association:source", s.Value).NewScope(s.Value)
|
||||
|
||||
if primaryField := scope.PrimaryField(); primaryField.IsBlank {
|
||||
err = errors.New("primary key can't be nil")
|
||||
} else {
|
||||
if field, ok := scope.FieldByName(column); ok {
|
||||
if field.Relationship == nil || len(field.Relationship.ForeignFieldNames) == 0 {
|
||||
err = fmt.Errorf("invalid association %v for %v", column, scope.IndirectValue().Type())
|
||||
} else {
|
||||
return &Association{scope: scope, column: column, field: field}
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("%v doesn't have column %v", scope.IndirectValue().Type(), column)
|
||||
}
|
||||
}
|
||||
|
||||
return &Association{Error: err}
|
||||
}
|
||||
|
||||
// Preload preload associations with given conditions
|
||||
// db.Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
|
||||
func (s *DB) Preload(column string, conditions ...interface{}) *DB {
|
||||
return s.clone().search.Preload(column, conditions...).db
|
||||
}
|
||||
|
||||
// Set set setting by name, which could be used in callbacks, will clone a new db, and update its setting
|
||||
func (s *DB) Set(name string, value interface{}) *DB {
|
||||
return s.clone().InstantSet(name, value)
|
||||
}
|
||||
|
||||
// InstantSet instant set setting, will affect current db
|
||||
func (s *DB) InstantSet(name string, value interface{}) *DB {
|
||||
s.values[name] = value
|
||||
return s
|
||||
}
|
||||
|
||||
// Get get setting by name
|
||||
func (s *DB) Get(name string) (value interface{}, ok bool) {
|
||||
value, ok = s.values[name]
|
||||
return
|
||||
}
|
||||
|
||||
// SetJoinTableHandler set a model's join table handler for a relation
|
||||
func (s *DB) SetJoinTableHandler(source interface{}, column string, handler JoinTableHandlerInterface) {
|
||||
scope := s.NewScope(source)
|
||||
for _, field := range scope.GetModelStruct().StructFields {
|
||||
if field.Name == column || field.DBName == column {
|
||||
if many2many := field.TagSettings["MANY2MANY"]; many2many != "" {
|
||||
source := (&Scope{Value: source}).GetModelStruct().ModelType
|
||||
destination := (&Scope{Value: reflect.New(field.Struct.Type).Interface()}).GetModelStruct().ModelType
|
||||
handler.Setup(field.Relationship, many2many, source, destination)
|
||||
field.Relationship.JoinTableHandler = handler
|
||||
if table := handler.Table(s); scope.Dialect().HasTable(table) {
|
||||
s.Table(table).AutoMigrate(handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddError add error to the db
|
||||
func (s *DB) AddError(err error) error {
|
||||
if err != nil {
|
||||
if err != ErrRecordNotFound {
|
||||
if s.logMode == 0 {
|
||||
go s.print(fileWithLineNum(), err)
|
||||
} else {
|
||||
s.log(err)
|
||||
}
|
||||
|
||||
errors := Errors(s.GetErrors())
|
||||
errors = errors.Add(err)
|
||||
if len(errors) > 1 {
|
||||
err = errors
|
||||
}
|
||||
}
|
||||
|
||||
s.Error = err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// GetErrors get happened errors from the db
|
||||
func (s *DB) GetErrors() []error {
|
||||
if errs, ok := s.Error.(Errors); ok {
|
||||
return errs
|
||||
} else if s.Error != nil {
|
||||
return []error{s.Error}
|
||||
}
|
||||
return []error{}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Private Methods For DB
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (s *DB) clone() *DB {
|
||||
db := &DB{
|
||||
db: s.db,
|
||||
parent: s.parent,
|
||||
logger: s.logger,
|
||||
logMode: s.logMode,
|
||||
values: map[string]interface{}{},
|
||||
Value: s.Value,
|
||||
Error: s.Error,
|
||||
blockGlobalUpdate: s.blockGlobalUpdate,
|
||||
}
|
||||
|
||||
for key, value := range s.values {
|
||||
db.values[key] = value
|
||||
}
|
||||
|
||||
if s.search == nil {
|
||||
db.search = &search{limit: -1, offset: -1}
|
||||
} else {
|
||||
db.search = s.search.clone()
|
||||
}
|
||||
|
||||
db.search.db = db
|
||||
return db
|
||||
}
|
||||
|
||||
func (s *DB) print(v ...interface{}) {
|
||||
s.logger.Print(v...)
|
||||
}
|
||||
|
||||
func (s *DB) log(v ...interface{}) {
|
||||
if s != nil && s.logMode == 2 {
|
||||
s.print(append([]interface{}{"log", fileWithLineNum()}, v...)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DB) slog(sql string, t time.Time, vars ...interface{}) {
|
||||
if s.logMode == 2 {
|
||||
s.print("sql", fileWithLineNum(), NowFunc().Sub(t), sql, vars, s.RowsAffected)
|
||||
}
|
||||
}
|
||||
14
vendor/github.com/jinzhu/gorm/model.go
generated
vendored
Normal file
14
vendor/github.com/jinzhu/gorm/model.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
package gorm
|
||||
|
||||
import "time"
|
||||
|
||||
// Model base model definition, including fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`, which could be embedded in your models
|
||||
// type User struct {
|
||||
// gorm.Model
|
||||
// }
|
||||
type Model struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time `sql:"index"`
|
||||
}
|
||||
629
vendor/github.com/jinzhu/gorm/model_struct.go
generated
vendored
Normal file
629
vendor/github.com/jinzhu/gorm/model_struct.go
generated
vendored
Normal file
@@ -0,0 +1,629 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"go/ast"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/inflection"
|
||||
)
|
||||
|
||||
// DefaultTableNameHandler default table name handler
|
||||
var DefaultTableNameHandler = func(db *DB, defaultTableName string) string {
|
||||
return defaultTableName
|
||||
}
|
||||
|
||||
type safeModelStructsMap struct {
|
||||
m map[reflect.Type]*ModelStruct
|
||||
l *sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *safeModelStructsMap) Set(key reflect.Type, value *ModelStruct) {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
s.m[key] = value
|
||||
}
|
||||
|
||||
func (s *safeModelStructsMap) Get(key reflect.Type) *ModelStruct {
|
||||
s.l.RLock()
|
||||
defer s.l.RUnlock()
|
||||
return s.m[key]
|
||||
}
|
||||
|
||||
func newModelStructsMap() *safeModelStructsMap {
|
||||
return &safeModelStructsMap{l: new(sync.RWMutex), m: make(map[reflect.Type]*ModelStruct)}
|
||||
}
|
||||
|
||||
var modelStructsMap = newModelStructsMap()
|
||||
|
||||
// ModelStruct model definition
|
||||
type ModelStruct struct {
|
||||
PrimaryFields []*StructField
|
||||
StructFields []*StructField
|
||||
ModelType reflect.Type
|
||||
defaultTableName string
|
||||
}
|
||||
|
||||
// TableName get model's table name
|
||||
func (s *ModelStruct) TableName(db *DB) string {
|
||||
if s.defaultTableName == "" && db != nil && s.ModelType != nil {
|
||||
// Set default table name
|
||||
if tabler, ok := reflect.New(s.ModelType).Interface().(tabler); ok {
|
||||
s.defaultTableName = tabler.TableName()
|
||||
} else {
|
||||
tableName := ToDBName(s.ModelType.Name())
|
||||
if db == nil || !db.parent.singularTable {
|
||||
tableName = inflection.Plural(tableName)
|
||||
}
|
||||
s.defaultTableName = tableName
|
||||
}
|
||||
}
|
||||
|
||||
return DefaultTableNameHandler(db, s.defaultTableName)
|
||||
}
|
||||
|
||||
// StructField model field's struct definition
|
||||
type StructField struct {
|
||||
DBName string
|
||||
Name string
|
||||
Names []string
|
||||
IsPrimaryKey bool
|
||||
IsNormal bool
|
||||
IsIgnored bool
|
||||
IsScanner bool
|
||||
HasDefaultValue bool
|
||||
Tag reflect.StructTag
|
||||
TagSettings map[string]string
|
||||
Struct reflect.StructField
|
||||
IsForeignKey bool
|
||||
Relationship *Relationship
|
||||
}
|
||||
|
||||
func (structField *StructField) clone() *StructField {
|
||||
clone := &StructField{
|
||||
DBName: structField.DBName,
|
||||
Name: structField.Name,
|
||||
Names: structField.Names,
|
||||
IsPrimaryKey: structField.IsPrimaryKey,
|
||||
IsNormal: structField.IsNormal,
|
||||
IsIgnored: structField.IsIgnored,
|
||||
IsScanner: structField.IsScanner,
|
||||
HasDefaultValue: structField.HasDefaultValue,
|
||||
Tag: structField.Tag,
|
||||
TagSettings: map[string]string{},
|
||||
Struct: structField.Struct,
|
||||
IsForeignKey: structField.IsForeignKey,
|
||||
}
|
||||
|
||||
if structField.Relationship != nil {
|
||||
relationship := *structField.Relationship
|
||||
clone.Relationship = &relationship
|
||||
}
|
||||
|
||||
for key, value := range structField.TagSettings {
|
||||
clone.TagSettings[key] = value
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
// Relationship described the relationship between models
|
||||
type Relationship struct {
|
||||
Kind string
|
||||
PolymorphicType string
|
||||
PolymorphicDBName string
|
||||
PolymorphicValue string
|
||||
ForeignFieldNames []string
|
||||
ForeignDBNames []string
|
||||
AssociationForeignFieldNames []string
|
||||
AssociationForeignDBNames []string
|
||||
JoinTableHandler JoinTableHandlerInterface
|
||||
}
|
||||
|
||||
func getForeignField(column string, fields []*StructField) *StructField {
|
||||
for _, field := range fields {
|
||||
if field.Name == column || field.DBName == column || field.DBName == ToDBName(column) {
|
||||
return field
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetModelStruct get value's model struct, relationships based on struct and tag definition
|
||||
func (scope *Scope) GetModelStruct() *ModelStruct {
|
||||
var modelStruct ModelStruct
|
||||
// Scope value can't be nil
|
||||
if scope.Value == nil {
|
||||
return &modelStruct
|
||||
}
|
||||
|
||||
reflectType := reflect.ValueOf(scope.Value).Type()
|
||||
for reflectType.Kind() == reflect.Slice || reflectType.Kind() == reflect.Ptr {
|
||||
reflectType = reflectType.Elem()
|
||||
}
|
||||
|
||||
// Scope value need to be a struct
|
||||
if reflectType.Kind() != reflect.Struct {
|
||||
return &modelStruct
|
||||
}
|
||||
|
||||
// Get Cached model struct
|
||||
if value := modelStructsMap.Get(reflectType); value != nil {
|
||||
return value
|
||||
}
|
||||
|
||||
modelStruct.ModelType = reflectType
|
||||
|
||||
// Get all fields
|
||||
for i := 0; i < reflectType.NumField(); i++ {
|
||||
if fieldStruct := reflectType.Field(i); ast.IsExported(fieldStruct.Name) {
|
||||
field := &StructField{
|
||||
Struct: fieldStruct,
|
||||
Name: fieldStruct.Name,
|
||||
Names: []string{fieldStruct.Name},
|
||||
Tag: fieldStruct.Tag,
|
||||
TagSettings: parseTagSetting(fieldStruct.Tag),
|
||||
}
|
||||
|
||||
// is ignored field
|
||||
if _, ok := field.TagSettings["-"]; ok {
|
||||
field.IsIgnored = true
|
||||
} else {
|
||||
if _, ok := field.TagSettings["PRIMARY_KEY"]; ok {
|
||||
field.IsPrimaryKey = true
|
||||
modelStruct.PrimaryFields = append(modelStruct.PrimaryFields, field)
|
||||
}
|
||||
|
||||
if _, ok := field.TagSettings["DEFAULT"]; ok {
|
||||
field.HasDefaultValue = true
|
||||
}
|
||||
|
||||
if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok && !field.IsPrimaryKey {
|
||||
field.HasDefaultValue = true
|
||||
}
|
||||
|
||||
indirectType := fieldStruct.Type
|
||||
for indirectType.Kind() == reflect.Ptr {
|
||||
indirectType = indirectType.Elem()
|
||||
}
|
||||
|
||||
fieldValue := reflect.New(indirectType).Interface()
|
||||
if _, isScanner := fieldValue.(sql.Scanner); isScanner {
|
||||
// is scanner
|
||||
field.IsScanner, field.IsNormal = true, true
|
||||
if indirectType.Kind() == reflect.Struct {
|
||||
for i := 0; i < indirectType.NumField(); i++ {
|
||||
for key, value := range parseTagSetting(indirectType.Field(i).Tag) {
|
||||
if _, ok := field.TagSettings[key]; !ok {
|
||||
field.TagSettings[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if _, isTime := fieldValue.(*time.Time); isTime {
|
||||
// is time
|
||||
field.IsNormal = true
|
||||
} else if _, ok := field.TagSettings["EMBEDDED"]; ok || fieldStruct.Anonymous {
|
||||
// is embedded struct
|
||||
for _, subField := range scope.New(fieldValue).GetModelStruct().StructFields {
|
||||
subField = subField.clone()
|
||||
subField.Names = append([]string{fieldStruct.Name}, subField.Names...)
|
||||
if prefix, ok := field.TagSettings["EMBEDDED_PREFIX"]; ok {
|
||||
subField.DBName = prefix + subField.DBName
|
||||
}
|
||||
|
||||
if subField.IsPrimaryKey {
|
||||
if _, ok := subField.TagSettings["PRIMARY_KEY"]; ok {
|
||||
modelStruct.PrimaryFields = append(modelStruct.PrimaryFields, subField)
|
||||
} else {
|
||||
subField.IsPrimaryKey = false
|
||||
}
|
||||
}
|
||||
|
||||
if subField.Relationship != nil && subField.Relationship.JoinTableHandler != nil {
|
||||
if joinTableHandler, ok := subField.Relationship.JoinTableHandler.(*JoinTableHandler); ok {
|
||||
newJoinTableHandler := &JoinTableHandler{}
|
||||
newJoinTableHandler.Setup(subField.Relationship, joinTableHandler.TableName, reflectType, joinTableHandler.Destination.ModelType)
|
||||
subField.Relationship.JoinTableHandler = newJoinTableHandler
|
||||
}
|
||||
}
|
||||
|
||||
modelStruct.StructFields = append(modelStruct.StructFields, subField)
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
// build relationships
|
||||
switch indirectType.Kind() {
|
||||
case reflect.Slice:
|
||||
defer func(field *StructField) {
|
||||
var (
|
||||
relationship = &Relationship{}
|
||||
toScope = scope.New(reflect.New(field.Struct.Type).Interface())
|
||||
foreignKeys []string
|
||||
associationForeignKeys []string
|
||||
elemType = field.Struct.Type
|
||||
)
|
||||
|
||||
if foreignKey := field.TagSettings["FOREIGNKEY"]; foreignKey != "" {
|
||||
foreignKeys = strings.Split(foreignKey, ",")
|
||||
}
|
||||
|
||||
if foreignKey := field.TagSettings["ASSOCIATION_FOREIGNKEY"]; foreignKey != "" {
|
||||
associationForeignKeys = strings.Split(foreignKey, ",")
|
||||
} else if foreignKey := field.TagSettings["ASSOCIATIONFOREIGNKEY"]; foreignKey != "" {
|
||||
associationForeignKeys = strings.Split(foreignKey, ",")
|
||||
}
|
||||
|
||||
for elemType.Kind() == reflect.Slice || elemType.Kind() == reflect.Ptr {
|
||||
elemType = elemType.Elem()
|
||||
}
|
||||
|
||||
if elemType.Kind() == reflect.Struct {
|
||||
if many2many := field.TagSettings["MANY2MANY"]; many2many != "" {
|
||||
relationship.Kind = "many_to_many"
|
||||
|
||||
{ // Foreign Keys for Source
|
||||
joinTableDBNames := []string{}
|
||||
|
||||
if foreignKey := field.TagSettings["JOINTABLE_FOREIGNKEY"]; foreignKey != "" {
|
||||
joinTableDBNames = strings.Split(foreignKey, ",")
|
||||
}
|
||||
|
||||
// if no foreign keys defined with tag
|
||||
if len(foreignKeys) == 0 {
|
||||
for _, field := range modelStruct.PrimaryFields {
|
||||
foreignKeys = append(foreignKeys, field.DBName)
|
||||
}
|
||||
}
|
||||
|
||||
for idx, foreignKey := range foreignKeys {
|
||||
if foreignField := getForeignField(foreignKey, modelStruct.StructFields); foreignField != nil {
|
||||
// source foreign keys (db names)
|
||||
relationship.ForeignFieldNames = append(relationship.ForeignFieldNames, foreignField.DBName)
|
||||
|
||||
// setup join table foreign keys for source
|
||||
if len(joinTableDBNames) > idx {
|
||||
// if defined join table's foreign key
|
||||
relationship.ForeignDBNames = append(relationship.ForeignDBNames, joinTableDBNames[idx])
|
||||
} else {
|
||||
defaultJointableForeignKey := ToDBName(reflectType.Name()) + "_" + foreignField.DBName
|
||||
relationship.ForeignDBNames = append(relationship.ForeignDBNames, defaultJointableForeignKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // Foreign Keys for Association (Destination)
|
||||
associationJoinTableDBNames := []string{}
|
||||
|
||||
if foreignKey := field.TagSettings["ASSOCIATION_JOINTABLE_FOREIGNKEY"]; foreignKey != "" {
|
||||
associationJoinTableDBNames = strings.Split(foreignKey, ",")
|
||||
}
|
||||
|
||||
// if no association foreign keys defined with tag
|
||||
if len(associationForeignKeys) == 0 {
|
||||
for _, field := range toScope.PrimaryFields() {
|
||||
associationForeignKeys = append(associationForeignKeys, field.DBName)
|
||||
}
|
||||
}
|
||||
|
||||
for idx, name := range associationForeignKeys {
|
||||
if field, ok := toScope.FieldByName(name); ok {
|
||||
// association foreign keys (db names)
|
||||
relationship.AssociationForeignFieldNames = append(relationship.AssociationForeignFieldNames, field.DBName)
|
||||
|
||||
// setup join table foreign keys for association
|
||||
if len(associationJoinTableDBNames) > idx {
|
||||
relationship.AssociationForeignDBNames = append(relationship.AssociationForeignDBNames, associationJoinTableDBNames[idx])
|
||||
} else {
|
||||
// join table foreign keys for association
|
||||
joinTableDBName := ToDBName(elemType.Name()) + "_" + field.DBName
|
||||
relationship.AssociationForeignDBNames = append(relationship.AssociationForeignDBNames, joinTableDBName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
joinTableHandler := JoinTableHandler{}
|
||||
joinTableHandler.Setup(relationship, many2many, reflectType, elemType)
|
||||
relationship.JoinTableHandler = &joinTableHandler
|
||||
field.Relationship = relationship
|
||||
} else {
|
||||
// User has many comments, associationType is User, comment use UserID as foreign key
|
||||
var associationType = reflectType.Name()
|
||||
var toFields = toScope.GetStructFields()
|
||||
relationship.Kind = "has_many"
|
||||
|
||||
if polymorphic := field.TagSettings["POLYMORPHIC"]; polymorphic != "" {
|
||||
// Dog has many toys, tag polymorphic is Owner, then associationType is Owner
|
||||
// Toy use OwnerID, OwnerType ('dogs') as foreign key
|
||||
if polymorphicType := getForeignField(polymorphic+"Type", toFields); polymorphicType != nil {
|
||||
associationType = polymorphic
|
||||
relationship.PolymorphicType = polymorphicType.Name
|
||||
relationship.PolymorphicDBName = polymorphicType.DBName
|
||||
// if Dog has multiple set of toys set name of the set (instead of default 'dogs')
|
||||
if value, ok := field.TagSettings["POLYMORPHIC_VALUE"]; ok {
|
||||
relationship.PolymorphicValue = value
|
||||
} else {
|
||||
relationship.PolymorphicValue = scope.TableName()
|
||||
}
|
||||
polymorphicType.IsForeignKey = true
|
||||
}
|
||||
}
|
||||
|
||||
// if no foreign keys defined with tag
|
||||
if len(foreignKeys) == 0 {
|
||||
// if no association foreign keys defined with tag
|
||||
if len(associationForeignKeys) == 0 {
|
||||
for _, field := range modelStruct.PrimaryFields {
|
||||
foreignKeys = append(foreignKeys, associationType+field.Name)
|
||||
associationForeignKeys = append(associationForeignKeys, field.Name)
|
||||
}
|
||||
} else {
|
||||
// generate foreign keys from defined association foreign keys
|
||||
for _, scopeFieldName := range associationForeignKeys {
|
||||
if foreignField := getForeignField(scopeFieldName, modelStruct.StructFields); foreignField != nil {
|
||||
foreignKeys = append(foreignKeys, associationType+foreignField.Name)
|
||||
associationForeignKeys = append(associationForeignKeys, foreignField.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// generate association foreign keys from foreign keys
|
||||
if len(associationForeignKeys) == 0 {
|
||||
for _, foreignKey := range foreignKeys {
|
||||
if strings.HasPrefix(foreignKey, associationType) {
|
||||
associationForeignKey := strings.TrimPrefix(foreignKey, associationType)
|
||||
if foreignField := getForeignField(associationForeignKey, modelStruct.StructFields); foreignField != nil {
|
||||
associationForeignKeys = append(associationForeignKeys, associationForeignKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(associationForeignKeys) == 0 && len(foreignKeys) == 1 {
|
||||
associationForeignKeys = []string{scope.PrimaryKey()}
|
||||
}
|
||||
} else if len(foreignKeys) != len(associationForeignKeys) {
|
||||
scope.Err(errors.New("invalid foreign keys, should have same length"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for idx, foreignKey := range foreignKeys {
|
||||
if foreignField := getForeignField(foreignKey, toFields); foreignField != nil {
|
||||
if associationField := getForeignField(associationForeignKeys[idx], modelStruct.StructFields); associationField != nil {
|
||||
// source foreign keys
|
||||
foreignField.IsForeignKey = true
|
||||
relationship.AssociationForeignFieldNames = append(relationship.AssociationForeignFieldNames, associationField.Name)
|
||||
relationship.AssociationForeignDBNames = append(relationship.AssociationForeignDBNames, associationField.DBName)
|
||||
|
||||
// association foreign keys
|
||||
relationship.ForeignFieldNames = append(relationship.ForeignFieldNames, foreignField.Name)
|
||||
relationship.ForeignDBNames = append(relationship.ForeignDBNames, foreignField.DBName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(relationship.ForeignFieldNames) != 0 {
|
||||
field.Relationship = relationship
|
||||
}
|
||||
}
|
||||
} else {
|
||||
field.IsNormal = true
|
||||
}
|
||||
}(field)
|
||||
case reflect.Struct:
|
||||
defer func(field *StructField) {
|
||||
var (
|
||||
// user has one profile, associationType is User, profile use UserID as foreign key
|
||||
// user belongs to profile, associationType is Profile, user use ProfileID as foreign key
|
||||
associationType = reflectType.Name()
|
||||
relationship = &Relationship{}
|
||||
toScope = scope.New(reflect.New(field.Struct.Type).Interface())
|
||||
toFields = toScope.GetStructFields()
|
||||
tagForeignKeys []string
|
||||
tagAssociationForeignKeys []string
|
||||
)
|
||||
|
||||
if foreignKey := field.TagSettings["FOREIGNKEY"]; foreignKey != "" {
|
||||
tagForeignKeys = strings.Split(foreignKey, ",")
|
||||
}
|
||||
|
||||
if foreignKey := field.TagSettings["ASSOCIATION_FOREIGNKEY"]; foreignKey != "" {
|
||||
tagAssociationForeignKeys = strings.Split(foreignKey, ",")
|
||||
} else if foreignKey := field.TagSettings["ASSOCIATIONFOREIGNKEY"]; foreignKey != "" {
|
||||
tagAssociationForeignKeys = strings.Split(foreignKey, ",")
|
||||
}
|
||||
|
||||
if polymorphic := field.TagSettings["POLYMORPHIC"]; polymorphic != "" {
|
||||
// Cat has one toy, tag polymorphic is Owner, then associationType is Owner
|
||||
// Toy use OwnerID, OwnerType ('cats') as foreign key
|
||||
if polymorphicType := getForeignField(polymorphic+"Type", toFields); polymorphicType != nil {
|
||||
associationType = polymorphic
|
||||
relationship.PolymorphicType = polymorphicType.Name
|
||||
relationship.PolymorphicDBName = polymorphicType.DBName
|
||||
// if Cat has several different types of toys set name for each (instead of default 'cats')
|
||||
if value, ok := field.TagSettings["POLYMORPHIC_VALUE"]; ok {
|
||||
relationship.PolymorphicValue = value
|
||||
} else {
|
||||
relationship.PolymorphicValue = scope.TableName()
|
||||
}
|
||||
polymorphicType.IsForeignKey = true
|
||||
}
|
||||
}
|
||||
|
||||
// Has One
|
||||
{
|
||||
var foreignKeys = tagForeignKeys
|
||||
var associationForeignKeys = tagAssociationForeignKeys
|
||||
// if no foreign keys defined with tag
|
||||
if len(foreignKeys) == 0 {
|
||||
// if no association foreign keys defined with tag
|
||||
if len(associationForeignKeys) == 0 {
|
||||
for _, primaryField := range modelStruct.PrimaryFields {
|
||||
foreignKeys = append(foreignKeys, associationType+primaryField.Name)
|
||||
associationForeignKeys = append(associationForeignKeys, primaryField.Name)
|
||||
}
|
||||
} else {
|
||||
// generate foreign keys form association foreign keys
|
||||
for _, associationForeignKey := range tagAssociationForeignKeys {
|
||||
if foreignField := getForeignField(associationForeignKey, modelStruct.StructFields); foreignField != nil {
|
||||
foreignKeys = append(foreignKeys, associationType+foreignField.Name)
|
||||
associationForeignKeys = append(associationForeignKeys, foreignField.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// generate association foreign keys from foreign keys
|
||||
if len(associationForeignKeys) == 0 {
|
||||
for _, foreignKey := range foreignKeys {
|
||||
if strings.HasPrefix(foreignKey, associationType) {
|
||||
associationForeignKey := strings.TrimPrefix(foreignKey, associationType)
|
||||
if foreignField := getForeignField(associationForeignKey, modelStruct.StructFields); foreignField != nil {
|
||||
associationForeignKeys = append(associationForeignKeys, associationForeignKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(associationForeignKeys) == 0 && len(foreignKeys) == 1 {
|
||||
associationForeignKeys = []string{scope.PrimaryKey()}
|
||||
}
|
||||
} else if len(foreignKeys) != len(associationForeignKeys) {
|
||||
scope.Err(errors.New("invalid foreign keys, should have same length"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for idx, foreignKey := range foreignKeys {
|
||||
if foreignField := getForeignField(foreignKey, toFields); foreignField != nil {
|
||||
if scopeField := getForeignField(associationForeignKeys[idx], modelStruct.StructFields); scopeField != nil {
|
||||
foreignField.IsForeignKey = true
|
||||
// source foreign keys
|
||||
relationship.AssociationForeignFieldNames = append(relationship.AssociationForeignFieldNames, scopeField.Name)
|
||||
relationship.AssociationForeignDBNames = append(relationship.AssociationForeignDBNames, scopeField.DBName)
|
||||
|
||||
// association foreign keys
|
||||
relationship.ForeignFieldNames = append(relationship.ForeignFieldNames, foreignField.Name)
|
||||
relationship.ForeignDBNames = append(relationship.ForeignDBNames, foreignField.DBName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(relationship.ForeignFieldNames) != 0 {
|
||||
relationship.Kind = "has_one"
|
||||
field.Relationship = relationship
|
||||
} else {
|
||||
var foreignKeys = tagForeignKeys
|
||||
var associationForeignKeys = tagAssociationForeignKeys
|
||||
|
||||
if len(foreignKeys) == 0 {
|
||||
// generate foreign keys & association foreign keys
|
||||
if len(associationForeignKeys) == 0 {
|
||||
for _, primaryField := range toScope.PrimaryFields() {
|
||||
foreignKeys = append(foreignKeys, field.Name+primaryField.Name)
|
||||
associationForeignKeys = append(associationForeignKeys, primaryField.Name)
|
||||
}
|
||||
} else {
|
||||
// generate foreign keys with association foreign keys
|
||||
for _, associationForeignKey := range associationForeignKeys {
|
||||
if foreignField := getForeignField(associationForeignKey, toFields); foreignField != nil {
|
||||
foreignKeys = append(foreignKeys, field.Name+foreignField.Name)
|
||||
associationForeignKeys = append(associationForeignKeys, foreignField.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// generate foreign keys & association foreign keys
|
||||
if len(associationForeignKeys) == 0 {
|
||||
for _, foreignKey := range foreignKeys {
|
||||
if strings.HasPrefix(foreignKey, field.Name) {
|
||||
associationForeignKey := strings.TrimPrefix(foreignKey, field.Name)
|
||||
if foreignField := getForeignField(associationForeignKey, toFields); foreignField != nil {
|
||||
associationForeignKeys = append(associationForeignKeys, associationForeignKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(associationForeignKeys) == 0 && len(foreignKeys) == 1 {
|
||||
associationForeignKeys = []string{toScope.PrimaryKey()}
|
||||
}
|
||||
} else if len(foreignKeys) != len(associationForeignKeys) {
|
||||
scope.Err(errors.New("invalid foreign keys, should have same length"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for idx, foreignKey := range foreignKeys {
|
||||
if foreignField := getForeignField(foreignKey, modelStruct.StructFields); foreignField != nil {
|
||||
if associationField := getForeignField(associationForeignKeys[idx], toFields); associationField != nil {
|
||||
foreignField.IsForeignKey = true
|
||||
|
||||
// association foreign keys
|
||||
relationship.AssociationForeignFieldNames = append(relationship.AssociationForeignFieldNames, associationField.Name)
|
||||
relationship.AssociationForeignDBNames = append(relationship.AssociationForeignDBNames, associationField.DBName)
|
||||
|
||||
// source foreign keys
|
||||
relationship.ForeignFieldNames = append(relationship.ForeignFieldNames, foreignField.Name)
|
||||
relationship.ForeignDBNames = append(relationship.ForeignDBNames, foreignField.DBName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(relationship.ForeignFieldNames) != 0 {
|
||||
relationship.Kind = "belongs_to"
|
||||
field.Relationship = relationship
|
||||
}
|
||||
}
|
||||
}(field)
|
||||
default:
|
||||
field.IsNormal = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Even it is ignored, also possible to decode db value into the field
|
||||
if value, ok := field.TagSettings["COLUMN"]; ok {
|
||||
field.DBName = value
|
||||
} else {
|
||||
field.DBName = ToDBName(fieldStruct.Name)
|
||||
}
|
||||
|
||||
modelStruct.StructFields = append(modelStruct.StructFields, field)
|
||||
}
|
||||
}
|
||||
|
||||
if len(modelStruct.PrimaryFields) == 0 {
|
||||
if field := getForeignField("id", modelStruct.StructFields); field != nil {
|
||||
field.IsPrimaryKey = true
|
||||
modelStruct.PrimaryFields = append(modelStruct.PrimaryFields, field)
|
||||
}
|
||||
}
|
||||
|
||||
modelStructsMap.Set(reflectType, &modelStruct)
|
||||
|
||||
return &modelStruct
|
||||
}
|
||||
|
||||
// GetStructFields get model's field structs
|
||||
func (scope *Scope) GetStructFields() (fields []*StructField) {
|
||||
return scope.GetModelStruct().StructFields
|
||||
}
|
||||
|
||||
func parseTagSetting(tags reflect.StructTag) map[string]string {
|
||||
setting := map[string]string{}
|
||||
for _, str := range []string{tags.Get("sql"), tags.Get("gorm")} {
|
||||
tags := strings.Split(str, ";")
|
||||
for _, value := range tags {
|
||||
v := strings.Split(value, ":")
|
||||
k := strings.TrimSpace(strings.ToUpper(v[0]))
|
||||
if len(v) >= 2 {
|
||||
setting[k] = strings.Join(v[1:], ":")
|
||||
} else {
|
||||
setting[k] = k
|
||||
}
|
||||
}
|
||||
}
|
||||
return setting
|
||||
}
|
||||
1381
vendor/github.com/jinzhu/gorm/scope.go
generated
vendored
Normal file
1381
vendor/github.com/jinzhu/gorm/scope.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
153
vendor/github.com/jinzhu/gorm/search.go
generated
vendored
Normal file
153
vendor/github.com/jinzhu/gorm/search.go
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type search struct {
|
||||
db *DB
|
||||
whereConditions []map[string]interface{}
|
||||
orConditions []map[string]interface{}
|
||||
notConditions []map[string]interface{}
|
||||
havingConditions []map[string]interface{}
|
||||
joinConditions []map[string]interface{}
|
||||
initAttrs []interface{}
|
||||
assignAttrs []interface{}
|
||||
selects map[string]interface{}
|
||||
omits []string
|
||||
orders []interface{}
|
||||
preload []searchPreload
|
||||
offset interface{}
|
||||
limit interface{}
|
||||
group string
|
||||
tableName string
|
||||
raw bool
|
||||
Unscoped bool
|
||||
ignoreOrderQuery bool
|
||||
}
|
||||
|
||||
type searchPreload struct {
|
||||
schema string
|
||||
conditions []interface{}
|
||||
}
|
||||
|
||||
func (s *search) clone() *search {
|
||||
clone := *s
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (s *search) Where(query interface{}, values ...interface{}) *search {
|
||||
s.whereConditions = append(s.whereConditions, map[string]interface{}{"query": query, "args": values})
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Not(query interface{}, values ...interface{}) *search {
|
||||
s.notConditions = append(s.notConditions, map[string]interface{}{"query": query, "args": values})
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Or(query interface{}, values ...interface{}) *search {
|
||||
s.orConditions = append(s.orConditions, map[string]interface{}{"query": query, "args": values})
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Attrs(attrs ...interface{}) *search {
|
||||
s.initAttrs = append(s.initAttrs, toSearchableMap(attrs...))
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Assign(attrs ...interface{}) *search {
|
||||
s.assignAttrs = append(s.assignAttrs, toSearchableMap(attrs...))
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Order(value interface{}, reorder ...bool) *search {
|
||||
if len(reorder) > 0 && reorder[0] {
|
||||
s.orders = []interface{}{}
|
||||
}
|
||||
|
||||
if value != nil && value != "" {
|
||||
s.orders = append(s.orders, value)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Select(query interface{}, args ...interface{}) *search {
|
||||
s.selects = map[string]interface{}{"query": query, "args": args}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Omit(columns ...string) *search {
|
||||
s.omits = columns
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Limit(limit interface{}) *search {
|
||||
s.limit = limit
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Offset(offset interface{}) *search {
|
||||
s.offset = offset
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Group(query string) *search {
|
||||
s.group = s.getInterfaceAsSQL(query)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Having(query interface{}, values ...interface{}) *search {
|
||||
if val, ok := query.(*expr); ok {
|
||||
s.havingConditions = append(s.havingConditions, map[string]interface{}{"query": val.expr, "args": val.args})
|
||||
} else {
|
||||
s.havingConditions = append(s.havingConditions, map[string]interface{}{"query": query, "args": values})
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Joins(query string, values ...interface{}) *search {
|
||||
s.joinConditions = append(s.joinConditions, map[string]interface{}{"query": query, "args": values})
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Preload(schema string, values ...interface{}) *search {
|
||||
var preloads []searchPreload
|
||||
for _, preload := range s.preload {
|
||||
if preload.schema != schema {
|
||||
preloads = append(preloads, preload)
|
||||
}
|
||||
}
|
||||
preloads = append(preloads, searchPreload{schema, values})
|
||||
s.preload = preloads
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Raw(b bool) *search {
|
||||
s.raw = b
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) unscoped() *search {
|
||||
s.Unscoped = true
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) Table(name string) *search {
|
||||
s.tableName = name
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *search) getInterfaceAsSQL(value interface{}) (str string) {
|
||||
switch value.(type) {
|
||||
case string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
str = fmt.Sprintf("%v", value)
|
||||
default:
|
||||
s.db.AddError(ErrInvalidSQL)
|
||||
}
|
||||
|
||||
if str == "-1" {
|
||||
return ""
|
||||
}
|
||||
return
|
||||
}
|
||||
5
vendor/github.com/jinzhu/gorm/test_all.sh
generated
vendored
Executable file
5
vendor/github.com/jinzhu/gorm/test_all.sh
generated
vendored
Executable file
@@ -0,0 +1,5 @@
|
||||
dialects=("postgres" "mysql" "mssql" "sqlite")
|
||||
|
||||
for dialect in "${dialects[@]}" ; do
|
||||
DEBUG=false GORM_DIALECT=${dialect} go test
|
||||
done
|
||||
285
vendor/github.com/jinzhu/gorm/utils.go
generated
vendored
Normal file
285
vendor/github.com/jinzhu/gorm/utils.go
generated
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
package gorm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NowFunc returns current time, this function is exported in order to be able
|
||||
// to give the flexibility to the developer to customize it according to their
|
||||
// needs, e.g:
|
||||
// gorm.NowFunc = func() time.Time {
|
||||
// return time.Now().UTC()
|
||||
// }
|
||||
var NowFunc = func() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// Copied from golint
|
||||
var commonInitialisms = []string{"API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "LHS", "QPS", "RAM", "RHS", "RPC", "SLA", "SMTP", "SSH", "TLS", "TTL", "UID", "UI", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XSRF", "XSS"}
|
||||
var commonInitialismsReplacer *strings.Replacer
|
||||
|
||||
var goSrcRegexp = regexp.MustCompile(`jinzhu/gorm/.*.go`)
|
||||
var goTestRegexp = regexp.MustCompile(`jinzhu/gorm/.*test.go`)
|
||||
|
||||
func init() {
|
||||
var commonInitialismsForReplacer []string
|
||||
for _, initialism := range commonInitialisms {
|
||||
commonInitialismsForReplacer = append(commonInitialismsForReplacer, initialism, strings.Title(strings.ToLower(initialism)))
|
||||
}
|
||||
commonInitialismsReplacer = strings.NewReplacer(commonInitialismsForReplacer...)
|
||||
}
|
||||
|
||||
type safeMap struct {
|
||||
m map[string]string
|
||||
l *sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *safeMap) Set(key string, value string) {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
s.m[key] = value
|
||||
}
|
||||
|
||||
func (s *safeMap) Get(key string) string {
|
||||
s.l.RLock()
|
||||
defer s.l.RUnlock()
|
||||
return s.m[key]
|
||||
}
|
||||
|
||||
func newSafeMap() *safeMap {
|
||||
return &safeMap{l: new(sync.RWMutex), m: make(map[string]string)}
|
||||
}
|
||||
|
||||
var smap = newSafeMap()
|
||||
|
||||
type strCase bool
|
||||
|
||||
const (
|
||||
lower strCase = false
|
||||
upper strCase = true
|
||||
)
|
||||
|
||||
// ToDBName convert string to db name
|
||||
func ToDBName(name string) string {
|
||||
if v := smap.Get(name); v != "" {
|
||||
return v
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
var (
|
||||
value = commonInitialismsReplacer.Replace(name)
|
||||
buf = bytes.NewBufferString("")
|
||||
lastCase, currCase, nextCase strCase
|
||||
)
|
||||
|
||||
for i, v := range value[:len(value)-1] {
|
||||
nextCase = strCase(value[i+1] >= 'A' && value[i+1] <= 'Z')
|
||||
if i > 0 {
|
||||
if currCase == upper {
|
||||
if lastCase == upper && nextCase == upper {
|
||||
buf.WriteRune(v)
|
||||
} else {
|
||||
if value[i-1] != '_' && value[i+1] != '_' {
|
||||
buf.WriteRune('_')
|
||||
}
|
||||
buf.WriteRune(v)
|
||||
}
|
||||
} else {
|
||||
buf.WriteRune(v)
|
||||
if i == len(value)-2 && nextCase == upper {
|
||||
buf.WriteRune('_')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currCase = upper
|
||||
buf.WriteRune(v)
|
||||
}
|
||||
lastCase = currCase
|
||||
currCase = nextCase
|
||||
}
|
||||
|
||||
buf.WriteByte(value[len(value)-1])
|
||||
|
||||
s := strings.ToLower(buf.String())
|
||||
smap.Set(name, s)
|
||||
return s
|
||||
}
|
||||
|
||||
// SQL expression
|
||||
type expr struct {
|
||||
expr string
|
||||
args []interface{}
|
||||
}
|
||||
|
||||
// Expr generate raw SQL expression, for example:
|
||||
// DB.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
|
||||
func Expr(expression string, args ...interface{}) *expr {
|
||||
return &expr{expr: expression, args: args}
|
||||
}
|
||||
|
||||
func indirect(reflectValue reflect.Value) reflect.Value {
|
||||
for reflectValue.Kind() == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
}
|
||||
return reflectValue
|
||||
}
|
||||
|
||||
func toQueryMarks(primaryValues [][]interface{}) string {
|
||||
var results []string
|
||||
|
||||
for _, primaryValue := range primaryValues {
|
||||
var marks []string
|
||||
for range primaryValue {
|
||||
marks = append(marks, "?")
|
||||
}
|
||||
|
||||
if len(marks) > 1 {
|
||||
results = append(results, fmt.Sprintf("(%v)", strings.Join(marks, ",")))
|
||||
} else {
|
||||
results = append(results, strings.Join(marks, ""))
|
||||
}
|
||||
}
|
||||
return strings.Join(results, ",")
|
||||
}
|
||||
|
||||
func toQueryCondition(scope *Scope, columns []string) string {
|
||||
var newColumns []string
|
||||
for _, column := range columns {
|
||||
newColumns = append(newColumns, scope.Quote(column))
|
||||
}
|
||||
|
||||
if len(columns) > 1 {
|
||||
return fmt.Sprintf("(%v)", strings.Join(newColumns, ","))
|
||||
}
|
||||
return strings.Join(newColumns, ",")
|
||||
}
|
||||
|
||||
func toQueryValues(values [][]interface{}) (results []interface{}) {
|
||||
for _, value := range values {
|
||||
for _, v := range value {
|
||||
results = append(results, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func fileWithLineNum() string {
|
||||
for i := 2; i < 15; i++ {
|
||||
_, file, line, ok := runtime.Caller(i)
|
||||
if ok && (!goSrcRegexp.MatchString(file) || goTestRegexp.MatchString(file)) {
|
||||
return fmt.Sprintf("%v:%v", file, line)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func isBlank(value reflect.Value) bool {
|
||||
switch value.Kind() {
|
||||
case reflect.String:
|
||||
return value.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !value.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return value.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return value.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return value.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return value.IsNil()
|
||||
}
|
||||
|
||||
return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface())
|
||||
}
|
||||
|
||||
func toSearchableMap(attrs ...interface{}) (result interface{}) {
|
||||
if len(attrs) > 1 {
|
||||
if str, ok := attrs[0].(string); ok {
|
||||
result = map[string]interface{}{str: attrs[1]}
|
||||
}
|
||||
} else if len(attrs) == 1 {
|
||||
if attr, ok := attrs[0].(map[string]interface{}); ok {
|
||||
result = attr
|
||||
}
|
||||
|
||||
if attr, ok := attrs[0].(interface{}); ok {
|
||||
result = attr
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func equalAsString(a interface{}, b interface{}) bool {
|
||||
return toString(a) == toString(b)
|
||||
}
|
||||
|
||||
func toString(str interface{}) string {
|
||||
if values, ok := str.([]interface{}); ok {
|
||||
var results []string
|
||||
for _, value := range values {
|
||||
results = append(results, toString(value))
|
||||
}
|
||||
return strings.Join(results, "_")
|
||||
} else if bytes, ok := str.([]byte); ok {
|
||||
return string(bytes)
|
||||
} else if reflectValue := reflect.Indirect(reflect.ValueOf(str)); reflectValue.IsValid() {
|
||||
return fmt.Sprintf("%v", reflectValue.Interface())
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func makeSlice(elemType reflect.Type) interface{} {
|
||||
if elemType.Kind() == reflect.Slice {
|
||||
elemType = elemType.Elem()
|
||||
}
|
||||
sliceType := reflect.SliceOf(elemType)
|
||||
slice := reflect.New(sliceType)
|
||||
slice.Elem().Set(reflect.MakeSlice(sliceType, 0, 0))
|
||||
return slice.Interface()
|
||||
}
|
||||
|
||||
func strInSlice(a string, list []string) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getValueFromFields return given fields's value
|
||||
func getValueFromFields(value reflect.Value, fieldNames []string) (results []interface{}) {
|
||||
// If value is a nil pointer, Indirect returns a zero Value!
|
||||
// Therefor we need to check for a zero value,
|
||||
// as FieldByName could panic
|
||||
if indirectValue := reflect.Indirect(value); indirectValue.IsValid() {
|
||||
for _, fieldName := range fieldNames {
|
||||
if fieldValue := indirectValue.FieldByName(fieldName); fieldValue.IsValid() {
|
||||
result := fieldValue.Interface()
|
||||
if r, ok := result.(driver.Valuer); ok {
|
||||
result, _ = r.Value()
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func addExtraSpaceIfExist(str string) string {
|
||||
if str != "" {
|
||||
return " " + str
|
||||
}
|
||||
return ""
|
||||
}
|
||||
148
vendor/github.com/jinzhu/gorm/wercker.yml
generated
vendored
Normal file
148
vendor/github.com/jinzhu/gorm/wercker.yml
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
# use the default golang container from Docker Hub
|
||||
box: golang
|
||||
|
||||
services:
|
||||
- name: mariadb
|
||||
id: mariadb:latest
|
||||
env:
|
||||
MYSQL_DATABASE: gorm
|
||||
MYSQL_USER: gorm
|
||||
MYSQL_PASSWORD: gorm
|
||||
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
|
||||
- name: mysql57
|
||||
id: mysql:5.7
|
||||
env:
|
||||
MYSQL_DATABASE: gorm
|
||||
MYSQL_USER: gorm
|
||||
MYSQL_PASSWORD: gorm
|
||||
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
|
||||
- name: mysql56
|
||||
id: mysql:5.6
|
||||
env:
|
||||
MYSQL_DATABASE: gorm
|
||||
MYSQL_USER: gorm
|
||||
MYSQL_PASSWORD: gorm
|
||||
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
|
||||
- name: mysql55
|
||||
id: mysql:5.5
|
||||
env:
|
||||
MYSQL_DATABASE: gorm
|
||||
MYSQL_USER: gorm
|
||||
MYSQL_PASSWORD: gorm
|
||||
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
|
||||
- name: postgres
|
||||
id: postgres:latest
|
||||
env:
|
||||
POSTGRES_USER: gorm
|
||||
POSTGRES_PASSWORD: gorm
|
||||
POSTGRES_DB: gorm
|
||||
- name: postgres96
|
||||
id: postgres:9.6
|
||||
env:
|
||||
POSTGRES_USER: gorm
|
||||
POSTGRES_PASSWORD: gorm
|
||||
POSTGRES_DB: gorm
|
||||
- name: postgres95
|
||||
id: postgres:9.5
|
||||
env:
|
||||
POSTGRES_USER: gorm
|
||||
POSTGRES_PASSWORD: gorm
|
||||
POSTGRES_DB: gorm
|
||||
- name: postgres94
|
||||
id: postgres:9.4
|
||||
env:
|
||||
POSTGRES_USER: gorm
|
||||
POSTGRES_PASSWORD: gorm
|
||||
POSTGRES_DB: gorm
|
||||
- name: postgres93
|
||||
id: postgres:9.3
|
||||
env:
|
||||
POSTGRES_USER: gorm
|
||||
POSTGRES_PASSWORD: gorm
|
||||
POSTGRES_DB: gorm
|
||||
- name: mssql
|
||||
id: mcmoe/mssqldocker:latest
|
||||
env:
|
||||
ACCEPT_EULA: Y
|
||||
SA_PASSWORD: LoremIpsum86
|
||||
MSSQL_DB: gorm
|
||||
MSSQL_USER: gorm
|
||||
MSSQL_PASSWORD: LoremIpsum86
|
||||
|
||||
# The steps that will be executed in the build pipeline
|
||||
build:
|
||||
# The steps that will be executed on build
|
||||
steps:
|
||||
# Sets the go workspace and places you package
|
||||
# at the right place in the workspace tree
|
||||
- setup-go-workspace
|
||||
|
||||
# Gets the dependencies
|
||||
- script:
|
||||
name: go get
|
||||
code: |
|
||||
cd $WERCKER_SOURCE_DIR
|
||||
go version
|
||||
go get -t ./...
|
||||
|
||||
# Build the project
|
||||
- script:
|
||||
name: go build
|
||||
code: |
|
||||
go build ./...
|
||||
|
||||
# Test the project
|
||||
- script:
|
||||
name: test sqlite
|
||||
code: |
|
||||
go test ./...
|
||||
|
||||
- script:
|
||||
name: test mariadb
|
||||
code: |
|
||||
GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mariadb:3306)/gorm?charset=utf8&parseTime=True" go test ./...
|
||||
|
||||
- script:
|
||||
name: test mysql5.7
|
||||
code: |
|
||||
GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql57:3306)/gorm?charset=utf8&parseTime=True" go test ./...
|
||||
|
||||
- script:
|
||||
name: test mysql5.6
|
||||
code: |
|
||||
GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql56:3306)/gorm?charset=utf8&parseTime=True" go test ./...
|
||||
|
||||
- script:
|
||||
name: test mysql5.5
|
||||
code: |
|
||||
GORM_DIALECT=mysql GORM_DSN="gorm:gorm@tcp(mysql55:3306)/gorm?charset=utf8&parseTime=True" go test ./...
|
||||
|
||||
- script:
|
||||
name: test postgres
|
||||
code: |
|
||||
GORM_DIALECT=postgres GORM_DSN="host=postgres user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./...
|
||||
|
||||
- script:
|
||||
name: test postgres96
|
||||
code: |
|
||||
GORM_DIALECT=postgres GORM_DSN="host=postgres96 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./...
|
||||
|
||||
- script:
|
||||
name: test postgres95
|
||||
code: |
|
||||
GORM_DIALECT=postgres GORM_DSN="host=postgres95 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./...
|
||||
|
||||
- script:
|
||||
name: test postgres94
|
||||
code: |
|
||||
GORM_DIALECT=postgres GORM_DSN="host=postgres94 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./...
|
||||
|
||||
- script:
|
||||
name: test postgres93
|
||||
code: |
|
||||
GORM_DIALECT=postgres GORM_DSN="host=postgres93 user=gorm password=gorm DB.name=gorm port=5432 sslmode=disable" go test ./...
|
||||
|
||||
- script:
|
||||
name: test mssql
|
||||
code: |
|
||||
GORM_DIALECT=mssql GORM_DSN="sqlserver://gorm:LoremIpsum86@mssql:1433?database=gorm" go test ./...
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user