1
0
mirror of https://github.com/docker/docs.git synced 2026-11-06 12:54:24 +03:00

Merge pull request #130 from docker/signer_persistence

Signer persistence
This commit is contained in:
Nathan McCauley
2015-07-27 09:30:06 -07:00
171 changed files with 175051 additions and 9191 deletions

33
Godeps/Godeps.json generated
View File

@@ -38,12 +38,12 @@
"Rev": "fed58bd2d3c096055c0e69c2fb86c9a4965d1b8b"
},
{
"ImportPath": "github.com/docker/distribution/registry/auth",
"ImportPath": "github.com/docker/distribution/registry/api/errcode",
"Comment": "v2.0.0-353-gfed58bd",
"Rev": "fed58bd2d3c096055c0e69c2fb86c9a4965d1b8b"
},
{
"ImportPath": "github.com/docker/distribution/registry/api/errcode",
"ImportPath": "github.com/docker/distribution/registry/auth",
"Comment": "v2.0.0-353-gfed58bd",
"Rev": "fed58bd2d3c096055c0e69c2fb86c9a4965d1b8b"
},
@@ -61,9 +61,13 @@
"ImportPath": "github.com/docker/libtrust",
"Rev": "fa567046d9b14f6aa788882a950d69651d230b21"
},
{
"ImportPath": "github.com/dvsekhvalnov/jose2go",
"Rev": "5307afb3bb6169b0f68cdf519a5964c843344441"
},
{
"ImportPath": "github.com/endophage/gotuf",
"Rev": "374908abc8af7e953a2813c5c2b3944ab625ca68"
"Rev": "5f508200b262dc8f56362523f621417853192812"
},
{
"ImportPath": "github.com/go-sql-driver/mysql",
@@ -86,6 +90,10 @@
"ImportPath": "github.com/inconshreveable/mousetrap",
"Rev": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
},
{
"ImportPath": "github.com/jinzhu/gorm",
"Rev": "82d726bbfd8cefbe2dcdc7f7f0484551c0d40433"
},
{
"ImportPath": "github.com/kr/pretty",
"Comment": "go.weekly.2011-12-22-18-gbc9499c",
@@ -95,11 +103,21 @@
"ImportPath": "github.com/kr/text",
"Rev": "6807e777504f54ad073ecef66747de158294b639"
},
{
"ImportPath": "github.com/lib/pq/hstore",
"Comment": "go1.0-cutoff-58-g0dad96c",
"Rev": "0dad96c0b94f8dee039aa40467f767467392a0af"
},
{
"ImportPath": "github.com/magiconair/properties",
"Comment": "v1.5.3",
"Rev": "624009598839a9432bd97bb75552389422357723"
},
{
"ImportPath": "github.com/mattn/go-sqlite3",
"Comment": "v1.0.0",
"Rev": "b4142c444a8941d0d92b0b7103a24df9cd815e42"
},
{
"ImportPath": "github.com/miekg/pkcs11",
"Rev": "88c9f842544e629ec046105d7fb50d5daafae737"
@@ -128,20 +146,11 @@
"ImportPath": "github.com/spf13/viper",
"Rev": "be5ff3e4840cf692388bde7a057595a474ef379e"
},
{
"ImportPath": "github.com/stretchr/objx",
"Rev": "cbeaeb16a013161a98496fad62933b1d21786672"
},
{
"ImportPath": "github.com/stretchr/testify/assert",
"Comment": "v1.0-17-g089c718",
"Rev": "089c7181b8c728499929ff09b62d3fdd8df8adff"
},
{
"ImportPath": "github.com/stretchr/testify/mock",
"Comment": "v1.0-17-g089c718",
"Rev": "089c7181b8c728499929ff09b62d3fdd8df8adff"
},
{
"ImportPath": "github.com/tent/canonical-json-go",
"Rev": "96e4ba3a7613a1216cbd1badca4efe382adea337"

View File

@@ -20,3 +20,4 @@ _cgo_export.*
_testmain.go
*.exe
*.test

View File

@@ -1,8 +1,6 @@
objx - by Mat Ryer and Tyler Bunnell
The MIT License (MIT)
Copyright (c) 2014 Stretchr, Inc.
Copyright (c) 2014
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -0,0 +1,741 @@
# Golang (GO) Javascript Object Signing and Encryption (JOSE) and JSON Web Token (JWT) implementation
Pure Golang (GO) library for generating, decoding and encrypting [JSON Web Tokens](http://tools.ietf.org/html/draft-jones-json-web-token-10). Zero dependency, relies only
on standard library.
Supports full suite of signing, encryption and compression algorithms defined by [JSON Web Algorithms](https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-31) as of July 4, 2014 version.
Extensively unit tested and cross tested (100+ tests) for compatibility with [jose.4.j](https://bitbucket.org/b_c/jose4j/wiki/Home), [Nimbus-JOSE-JWT](https://bitbucket.org/nimbusds/nimbus-jose-jwt/wiki/Home), [json-jwt](https://github.com/nov/json-jwt) and
[jose-jwt](https://github.com/dvsekhvalnov/jose-jwt) libraries.
## Status
Used in production. GA ready. Current version is 1.1
## Important
v1.2 breaks `jose.Decode` interface by returning 3 values instead of 2.
###Migration to v1.2
Pre v1.2 decoding:
```Go
payload,err := jose.Decode(token,sharedKey)
```
Should be updated to v1.2:
```Go
payload, headers, err := jose.Decode(token,sharedKey)
```
## Supported JWA algorithms
**Signing**
- HMAC signatures with HS256, HS384 and HS512.
- RSASSA-PKCS1-V1_5 signatures with RS256, RS384 and RS512.
- RSASSA-PSS signatures (probabilistic signature scheme with appendix) with PS256, PS384 and PS512.
- ECDSA signatures with ES256, ES384 and ES512.
- NONE (unprotected) plain text algorithm without integrity protection
**Encryption**
- RSAES OAEP (using SHA-1 and MGF1 with SHA-1) encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM
- RSAES OAEP 256 (using SHA-256 and MGF1 with SHA-256) encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM
- RSAES-PKCS1-V1_5 encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM
- A128KW, A192KW, A256KW encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM
- A128GCMKW, A192GCMKW, A256GCMKW encryption with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM
- ECDH-ES with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM
- ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM
- PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW with A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM, A256GCM
- Direct symmetric key encryption with pre-shared key A128CBC-HS256, A192CBC-HS384, A256CBC-HS512, A128GCM, A192GCM and A256GCM
**Compression**
- DEFLATE compression
## Installation
### Grab package from github
`go get github.com/dvsekhvalnov/jose2go` or `go get -u github.com/dvsekhvalnov/jose2go` to update to latest version
### Import package
```Go
import (
"github.com/dvsekhvalnov/jose2go"
)
```
## Usage
#### Creating Plaintext (unprotected) Tokens
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
payload := `{"hello": "world"}`
token,err := jose.Sign(payload,jose.NONE, nil)
if(err==nil) {
//go use token
fmt.Printf("\nPlaintext = %v\n",token)
}
}
```
### Creating signed tokens
#### HS-256, HS-384 and HS-512
Signing with HS256, HS384, HS512 expecting `[]byte` array key of corresponding length:
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
payload := `{"hello": "world"}`
key := []byte{97,48,97,50,97,98,100,56,45,54,49,54,50,45,52,49,99,51,45,56,51,100,54,45,49,99,102,53,53,57,98,52,54,97,102,99}
token,err := jose.Sign(payload,jose.HS256,key)
if(err==nil) {
//go use token
fmt.Printf("\nHS256 = %v\n",token)
}
}
```
#### RS-256, RS-384 and RS-512, PS-256, PS-384 and PS-512
Signing with RS256, RS384, RS512, PS256, PS384, PS512 expecting `*rsa.PrivateKey` private key of corresponding length. **jose2go** [provides convinient utils](#dealing-with-keys) to construct `*rsa.PrivateKey` instance from PEM encoded PKCS1 or PKCS8 data: `Rsa.ReadPrivate([]byte)` under `jose2go/keys/rsa` package.
```Go
package main
import (
"fmt"
"io/ioutil"
"github.com/dvsekhvalnov/jose2go/keys/rsa"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
payload := `{"hello": "world"}`
keyBytes,err := ioutil.ReadFile("private.key")
if(err!=nil) {
panic("invalid key file")
}
privateKey,e:=Rsa.ReadPrivate(keyBytes)
if(e!=nil) {
panic("invalid key format")
}
token,err := jose.Sign(payload,jose.RS256, privateKey)
if(err==nil) {
//go use token
fmt.Printf("\nRS256 = %v\n",token)
}
}
```
#### ES-256, ES-384 and ES-512
ES256, ES384, ES512 ECDSA signatures expecting `*ecdsa.PrivateKey` private elliptic curve key of corresponding length. **jose2go** [provides convinient utils](#dealing-with-keys) to construct `*ecdsa.PrivateKey` instance from PEM encoded PKCS1 or PKCS8 data: `ecc.ReadPrivate([]byte)` or directly from `X,Y,D` parameters: `ecc.NewPrivate(x,y,d []byte)` under `jose2go/keys/ecc` package.
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go/keys/ecc"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
payload := `{"hello":"world"}`
privateKey:=ecc.NewPrivate([]byte{4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9},
[]byte{131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53},
[]byte{ 42, 148, 231, 48, 225, 196, 166, 201, 23, 190, 229, 199, 20, 39, 226, 70, 209, 148, 29, 70, 125, 14, 174, 66, 9, 198, 80, 251, 95, 107, 98, 206 })
token,err := jose.Sign(payload, jose.ES256, privateKey)
if(err==nil) {
//go use token
fmt.Printf("\ntoken = %v\n",token)
}
}
```
### Creating encrypted tokens
#### RSA-OAEP-256, RSA-OAEP and RSA1\_5 key management algorithm
RSA-OAEP-256, RSA-OAEP and RSA1_5 key management expecting `*rsa.PublicKey` public key of corresponding length.
```Go
package main
import (
"fmt"
"io/ioutil"
"github.com/dvsekhvalnov/jose2go/keys/rsa"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
payload := `{"hello": "world"}`
keyBytes,err := ioutil.ReadFile("public.key")
if(err!=nil) {
panic("invalid key file")
}
publicKey,e:=Rsa.ReadPublic(keyBytes)
if(e!=nil) {
panic("invalid key format")
}
//OR:
//token,err := jose.Encrypt(payload, jose.RSA1_5, jose.A256GCM, publicKey)
token,err := jose.Encrypt(payload, jose.RSA_OAEP, jose.A256GCM, publicKey)
if(err==nil) {
//go use token
fmt.Printf("\ntoken = %v\n",token)
}
}
```
#### AES Key Wrap key management family of algorithms
AES128KW, AES192KW and AES256KW key management requires `[]byte` array key of corresponding length
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
payload := `{"hello": "world"}`
sharedKey :=[]byte{194,164,235,6,138,248,171,239,24,216,11,22,137,199,215,133}
token,err := jose.Encrypt(payload,jose.A128KW,jose.A128GCM,sharedKey)
if(err==nil) {
//go use token
fmt.Printf("\nA128KW A128GCM = %v\n",token)
}
}
```
#### AES GCM Key Wrap key management family of algorithms
AES128GCMKW, AES192GCMKW and AES256GCMKW key management requires `[]byte` array key of corresponding length
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
payload := `{"hello": "world"}`
sharedKey :=[]byte{194,164,235,6,138,248,171,239,24,216,11,22,137,199,215,133}
token,err := jose.Encrypt(payload,jose.A128GCMKW,jose.A128GCM,sharedKey)
if(err==nil) {
//go use token
fmt.Printf("\nA128GCMKW A128GCM = %v\n",token)
}
}
```
#### ECDH-ES and ECDH-ES with AES Key Wrap key management family of algorithms
ECDH-ES and ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW key management requires `*ecdsa.PublicKey` elliptic curve key of corresponding length. **jose2go** [provides convinient utils](#dealing-with-keys) to construct `*ecdsa.PublicKey` instance from PEM encoded PKCS1 X509 certificate or PKIX data: `ecc.ReadPublic([]byte)` or directly from `X,Y` parameters: `ecc.NewPublic(x,y []byte)`under `jose2go/keys/ecc` package:
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go/keys/ecc"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
payload := `{"hello":"world"}`
publicKey:=ecc.NewPublic([]byte{4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9},
[]byte{131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53})
token,err := jose.Encrypt(payload, jose.ECDH_ES, jose.A128CBC_HS256, publicKey)
if(err==nil) {
//go use token
fmt.Printf("\ntoken = %v\n",token)
}
}
```
#### PBES2 using HMAC SHA with AES Key Wrap key management family of algorithms
PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW key management requires `string` passphrase from which actual key will be derived
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
payload := `{"hello": "world"}`
passphrase := `top secret`
token,err := jose.Encrypt(payload,jose.PBES2_HS256_A128KW,jose.A256GCM,passphrase)
if(err==nil) {
//go use token
fmt.Printf("\nPBES2_HS256_A128KW A256GCM = %v\n",token)
}
}
```
#### DIR direct pre-shared symmetric key management
Direct key management with pre-shared symmetric keys expecting `[]byte` array key of corresponding length:
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
payload := `{"hello": "world"}`
sharedKey :=[]byte{194,164,235,6,138,248,171,239,24,216,11,22,137,199,215,133}
token,err := jose.Encrypt(payload,jose.DIR,jose.A128GCM,sharedKey)
if(err==nil) {
//go use token
fmt.Printf("\nDIR A128GCM = %v\n",token)
}
}
```
### Creating compressed & encrypted tokens
#### DEFLATE compression
**jose2go** supports optional DEFLATE compression of payload before encrypting, can be used with all supported encryption and key management algorithms:
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
payload := `{"hello": "world"}`
sharedKey :=[]byte{194,164,235,6,138,248,171,239,24,216,11,22,137,199,215,133}
token,err := jose.Compress(payload,jose.DIR,jose.A128GCM,jose.DEF, sharedKey)
if(err==nil) {
//go use token
fmt.Printf("\nDIR A128GCM DEFLATED= %v\n",token)
}
}
```
### Verifying, Decoding and Decompressing tokens
Decoding json web tokens is fully symmetric to creating signed or encrypted tokens (with respect to public/private cryptography), decompressing deflated payloads is handled automatically:
As of v1.2 decode method defined as `jose.Decode() payload string, headers map[string]interface{}, err error` and returns both payload as unprocessed string and headers as map.
**HS256, HS384, HS512** signatures, **A128KW, A192KW, A256KW**,**A128GCMKW, A192GCMKW, A256GCMKW** and **DIR** key management algorithm expecting `[]byte` array key:
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
token := "eyJhbGciOiJIUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.chIoYWrQMA8XL5nFz6oLDJyvgHk2KA4BrFGrKymjC8E"
sharedKey :=[]byte{97,48,97,50,97,98,100,56,45,54,49,54,50,45,52,49,99,51,45,56,51,100,54,45,49,99,102,53,53,57,98,52,54,97,102,99}
payload, headers, err := jose.Decode(token,sharedKey)
if(err==nil) {
//go use token
fmt.Printf("\npayload = %v\n",payload)
//and/or use headers
fmt.Printf("\nheaders = %v\n",headers)
}
}
```
**RS256, RS384, RS512**,**PS256, PS384, PS512** signatures expecting `*rsa.PublicKey` public key of corresponding length. **jose2go** [provides convinient utils](#dealing-with-keys) to construct `*rsa.PublicKey` instance from PEM encoded PKCS1 X509 certificate or PKIX data: `Rsa.ReadPublic([]byte)` under `jose2go/keys/rsa` package:
```Go
package main
import (
"fmt"
"io/ioutil"
"github.com/dvsekhvalnov/jose2go/keys/rsa"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
token := "eyJhbGciOiJSUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.NL_dfVpZkhNn4bZpCyMq5TmnXbT4yiyecuB6Kax_lV8Yq2dG8wLfea-T4UKnrjLOwxlbwLwuKzffWcnWv3LVAWfeBxhGTa0c4_0TX_wzLnsgLuU6s9M2GBkAIuSMHY6UTFumJlEeRBeiqZNrlqvmAzQ9ppJHfWWkW4stcgLCLMAZbTqvRSppC1SMxnvPXnZSWn_Fk_q3oGKWw6Nf0-j-aOhK0S0Lcr0PV69ZE4xBYM9PUS1MpMe2zF5J3Tqlc1VBcJ94fjDj1F7y8twmMT3H1PI9RozO-21R0SiXZ_a93fxhE_l_dj5drgOek7jUN9uBDjkXUwJPAyp9YPehrjyLdw"
keyBytes, err := ioutil.ReadFile("public.key")
if(err!=nil) {
panic("invalid key file")
}
publicKey, e:=Rsa.ReadPublic(keyBytes)
if(e!=nil) {
panic("invalid key format")
}
payload, headers, err := jose.Decode(token, publicKey)
if(err==nil) {
//go use token
fmt.Printf("\npayload = %v\n",payload)
//and/or use headers
fmt.Printf("\nheaders = %v\n",headers)
}
}
```
**RSA-OAEP-256**, **RSA-OAEP** and **RSA1_5** key management algorithms expecting `*rsa.PrivateKey` private key of corresponding length:
```Go
package main
import (
"fmt"
"io/ioutil"
"github.com/dvsekhvalnov/jose2go/keys/rsa"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
token := "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMjU2R0NNIn0.ixD3WVOkvaxeLKi0kyVqTzM6W2EW25SHHYCAr9473Xq528xSK0AVux6kUtv7QMkQKgkMvO8X4VdvonyGkDZTK2jgYUiI06dz7I1sjWJIbyNVrANbBsmBiwikwB-9DLEaKuM85Lwu6gnzbOF6B9R0428ckxmITCPDrzMaXwYZHh46FiSg9djChUTex0pHGhNDiEIgaINpsmqsOFX1L2Y7KM2ZR7wtpR3kidMV3JlxHdKheiPKnDx_eNcdoE-eogPbRGFdkhEE8Dyass1ZSxt4fP27NwsIer5pc0b922_3XWdi1r1TL_fLvGktHLvt6HK6IruXFHpU4x5Z2gTXWxEIog.zzTNmovBowdX2_hi.QSPSgXn0w25ugvzmu2TnhePn.0I3B9BE064HFNP2E0I7M9g"
keyBytes, err := ioutil.ReadFile("private.key")
if(err!=nil) {
panic("invalid key file")
}
privateKey, e:=Rsa.ReadPrivate(keyBytes)
if(e!=nil) {
panic("invalid key format")
}
payload, headers, err := jose.Decode(token, privateKey)
if(err==nil) {
//go use payload
fmt.Printf("\npayload = %v\n",payload)
//and/or use headers
fmt.Printf("\nheaders = %v\n",headers)
}
}
```
**PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW** key management algorithms expects `string` passpharase as a key
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
token := `eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJjIjo4MTkyLCJwMnMiOiJlZWpFZTF0YmJVbU5XV2s2In0.J2HTgltxH3p7A2zDgQWpZPgA2CHTSnDmMhlZWeSOMoZ0YvhphCeg-w.FzYG5AOptknu7jsG.L8jAxfxZhDNIqb0T96YWoznQ.yNeOfQWUbm8KuDGZ_5lL_g`
passphrase := `top secret`
payload, headers, err := jose.Decode(token,passphrase)
if(err==nil) {
//go use token
fmt.Printf("\npayload = %v\n",payload)
//and/or use headers
fmt.Printf("\nheaders = %v\n",headers)
}
}
```
**ES256, ES284, ES512** signatures expecting `*ecdsa.PublicKey` public elliptic curve key of corresponding length. **jose2go** [provides convinient utils](#dealing-with-keys) to construct `*ecdsa.PublicKey` instance from PEM encoded PKCS1 X509 certificate or PKIX data: `ecc.ReadPublic([]byte)` or directly from `X,Y` parameters: `ecc.NewPublic(x,y []byte)`under `jose2go/keys/ecc` package:
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go/keys/ecc"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
token := "eyJhbGciOiJFUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.EVnmDMlz-oi05AQzts-R3aqWvaBlwVZddWkmaaHyMx5Phb2NSLgyI0kccpgjjAyo1S5KCB3LIMPfmxCX_obMKA"
publicKey:=ecc.NewPublic([]byte{4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9},
[]byte{131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53})
payload, headers, err := jose.Decode(token, publicKey)
if(err==nil) {
//go use token
fmt.Printf("\npayload = %v\n",payload)
//and/or use headers
fmt.Printf("\nheaders = %v\n",headers)
}
}
```
**ECDH-ES** and **ECDH-ES+A128KW**, **ECDH-ES+A192KW**, **ECDH-ES+A256KW** key management expecting `*ecdsa.PrivateKey` private elliptic curve key of corresponding length. **jose2go** [provides convinient utils](#dealing-with-keys) to construct `*ecdsa.PrivateKey` instance from PEM encoded PKCS1 or PKCS8 data: `ecc.ReadPrivate([]byte)` or directly from `X,Y,D` parameters: `ecc.NewPrivate(x,y,d []byte)` under `jose2go/keys/ecc` package:
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go/keys/ecc"
"github.com/dvsekhvalnov/jose2go"
)
func main() {
token := "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTEyOENCQy1IUzI1NiIsImVwayI6eyJrdHkiOiJFQyIsIngiOiItVk1LTG5NeW9IVHRGUlpGNnFXNndkRm5BN21KQkdiNzk4V3FVMFV3QVhZIiwieSI6ImhQQWNReTgzVS01Qjl1U21xbnNXcFZzbHVoZGJSZE1nbnZ0cGdmNVhXTjgiLCJjcnYiOiJQLTI1NiJ9fQ..UA3N2j-TbYKKD361AxlXUA.XxFur_nY1GauVp5W_KO2DEHfof5s7kUwvOgghiNNNmnB4Vxj5j8VRS8vMOb51nYy2wqmBb2gBf1IHDcKZdACkCOMqMIcpBvhyqbuKiZPLHiilwSgVV6ubIV88X0vK0C8ZPe5lEyRudbgFjdlTnf8TmsvuAsdtPn9dXwDjUR23bD2ocp8UGAV0lKqKzpAw528vTfD0gwMG8gt_op8yZAxqqLLljMuZdTnjofAfsW2Rq3Z6GyLUlxR51DAUlQKi6UpsKMJoXTrm1Jw8sXBHpsRqA.UHCYOtnqk4SfhAknCnymaQ"
privateKey:=ecc.NewPrivate([]byte{4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9},
[]byte{131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53},
[]byte{ 42, 148, 231, 48, 225, 196, 166, 201, 23, 190, 229, 199, 20, 39, 226, 70, 209, 148, 29, 70, 125, 14, 174, 66, 9, 198, 80, 251, 95, 107, 98, 206 })
payload, headers, err := jose.Decode(token, privateKey)
if(err==nil) {
//go use token
fmt.Printf("\npayload = %v\n",payload)
//and/or use headers
fmt.Printf("\nheaders = %v\n",headers)
}
}
```
### Dealing with keys
**jose2go** provides several helper methods to simplify loading & importing of elliptic and rsa keys. Import `jose2go/keys/rsa` or `jose2go/keys/ecc` respectively:
#### RSA keys
1. `Rsa.ReadPrivate(raw []byte) (key *rsa.PrivateKey,err error)` attempts to parse RSA private key from PKCS1 or PKCS8 format (`BEGIN RSA PRIVATE KEY` and `BEGIN PRIVATE KEY` headers)
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go/keys/rsa"
"io/ioutil"
)
func main() {
keyBytes, _ := ioutil.ReadFile("private.key")
privateKey, err:=Rsa.ReadPrivate(keyBytes)
if(err!=nil) {
panic("invalid key format")
}
fmt.Printf("privateKey = %v\n",privateKey)
}
```
2. `Rsa.ReadPublic(raw []byte) (key *rsa.PublicKey,err error)` attempts to parse RSA public key from PKIX key format or PKCS1 X509 certificate (`BEGIN PUBLIC KEY` and `BEGIN CERTIFICATE` headers)
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go/keys/rsa"
"io/ioutil"
)
func main() {
keyBytes, _ := ioutil.ReadFile("public.cer")
publicKey, err:=Rsa.ReadPublic(keyBytes)
if(err!=nil) {
panic("invalid key format")
}
fmt.Printf("publicKey = %v\n",publicKey)
}
```
#### ECC keys
1. `ecc.ReadPrivate(raw []byte) (key *ecdsa.PrivateKey,err error)` attemps to parse elliptic curve private key from PKCS1 or PKCS8 format (`BEGIN EC PRIVATE KEY` and `BEGIN PRIVATE KEY` headers)
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go/keys/ecc"
"io/ioutil"
)
func main() {
keyBytes, _ := ioutil.ReadFile("ec-private.pem")
ecPrivKey, err:=ecc.ReadPrivate(keyBytes)
if(err!=nil) {
panic("invalid key format")
}
fmt.Printf("ecPrivKey = %v\n",ecPrivKey)
}
```
2. `ecc.ReadPublic(raw []byte) (key *ecdsa.PublicKey,err error)` attemps to parse elliptic curve public key from PKCS1 X509 or PKIX format (`BEGIN PUBLIC KEY` and `BEGIN CERTIFICATE` headers)
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go/keys/ecc"
"io/ioutil"
)
func main() {
keyBytes, _ := ioutil.ReadFile("ec-public.key")
ecPubKey, err:=ecc.ReadPublic(keyBytes)
if(err!=nil) {
panic("invalid key format")
}
fmt.Printf("ecPubKey = %v\n",ecPubKey)
}
```
3. `ecc.NewPublic(x,y []byte) (*ecdsa.PublicKey)` constructs elliptic public key from (X,Y) represented as bytes. Supported are NIST curves P-256,P-384 and P-521. Curve detected automatically by input length.
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go/keys/ecc"
)
func main() {
ecPubKey:=ecc.NewPublic([]byte{4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9},
[]byte{131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53})
fmt.Printf("ecPubKey = %v\n",ecPubKey)
}
```
4. `ecc.NewPrivate(x,y,d []byte) (*ecdsa.PrivateKey)` constructs elliptic private key from (X,Y) and D represented as bytes. Supported are NIST curves P-256,P-384 and P-521. Curve detected automatically by input length.
```Go
package main
import (
"fmt"
"github.com/dvsekhvalnov/jose2go/keys/ecc"
)
func main() {
ecPrivKey:=ecc.NewPrivate([]byte{4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9},
[]byte{131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53},
[]byte{ 42, 148, 231, 48, 225, 196, 166, 201, 23, 190, 229, 199, 20, 39, 226, 70, 209, 148, 29, 70, 125, 14, 174, 66, 9, 198, 80, 251, 95, 107, 98, 206 })
fmt.Printf("ecPrivKey = %v\n",ecPrivKey)
}
```
### More examples
Checkout `jose_test.go` for more examples.
##Changelog
### 1.2
- interface to access token headers after decoding
### 1.1
- security and bug fixes
### 1.0
- initial stable version with full suite JOSE spec support

View File

@@ -0,0 +1,68 @@
// Package aes contains provides AES Key Wrap and ECB mode implementations
package aes
import (
"crypto/cipher"
)
type ecb struct {
b cipher.Block
}
type ecbEncrypter ecb
type ecbDecrypter ecb
// NewECBEncrypter creates BlockMode for AES encryption in ECB mode
func NewECBEncrypter(b cipher.Block) cipher.BlockMode {
return &ecbEncrypter {b:b}
}
// NewECBDecrypter creates BlockMode for AES decryption in ECB mode
func NewECBDecrypter(b cipher.Block) cipher.BlockMode {
return &ecbDecrypter {b:b}
}
func (x *ecbEncrypter) BlockSize() int { return x.b.BlockSize() }
func (x *ecbDecrypter) BlockSize() int { return x.b.BlockSize() }
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
bs := x.BlockSize()
if len(src) % bs != 0 {
panic("ecbDecrypter.CryptBlocks(): input not full blocks")
}
if len(dst) < len(src) {
panic("ecbDecrypter.CryptBlocks(): output smaller than input")
}
if len(src) == 0 {
return
}
for len(src) > 0 {
x.b.Decrypt(dst, src)
src = src[bs:]
}
}
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
bs := x.BlockSize()
if len(src) % bs != 0 {
panic("ecbEncrypter.CryptBlocks(): input not full blocks")
}
if len(dst) < len(src) {
panic("ecbEncrypter.CryptBlocks(): output smaller than input")
}
if len(src) == 0 {
return
}
for len(src) > 0 {
x.b.Encrypt(dst, src)
src = src[bs:]
}
}

View File

@@ -0,0 +1,38 @@
package aes
import (
// "fmt"
"crypto/aes"
// "crypto/cipher"
. "gopkg.in/check.v1"
)
func (s *TestSuite) TestNewECBEncryptor(c *C) {
//given
plaintext := []byte{0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255}
kek := []byte{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}
block, _ := aes.NewCipher(kek)
test := make([]byte,len(plaintext))
//when
NewECBEncrypter(block).CryptBlocks(test,plaintext)
//then
c.Assert(test, DeepEquals, []byte{105,196,224,216,106,123,4,48,216,205,183,128,112,180,197,90})
}
func (s *TestSuite) TestNewECBDecryptor(c *C) {
//given
ciphertext := []byte{105,196,224,216,106,123,4,48,216,205,183,128,112,180,197,90}
kek := []byte{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}
block, _ := aes.NewCipher(kek)
test := make([]byte,len(ciphertext))
//when
NewECBDecrypter(block).CryptBlocks(test,ciphertext)
//then
c.Assert(test, DeepEquals, []byte{0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255})
}

View File

@@ -0,0 +1,113 @@
package aes
import (
"github.com/dvsekhvalnov/jose2go/arrays"
"crypto/cipher"
"crypto/aes"
"crypto/hmac"
"errors"
)
var defaultIV=[]byte { 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6 }
// KeyWrap encrypts provided key (CEK) with KEK key using AES Key Wrap (rfc 3394) algorithm
func KeyWrap(cek,kek []byte) ([]byte,error) {
// 1) Initialize variables
a := defaultIV // Set A = IV, an initial value
r := arrays.Slice(cek, 8) // For i = 1 to n
// R[0][i] = P[i]
n := uint64(len(r))
// 2) Calculate intermediate values.
var j,i,t uint64
for j = 0; j < 6; j++ { // For j = 0 to 5
for i = 0; i < n; i++ { // For i=1 to n
t = n * j + i + 1;
b,e := aesEnc(kek, arrays.Concat(a, r[i])) // B=AES(K, A | R[i])
if e!=nil { return nil, e }
a = b[:len(b)/2] // A=MSB(64,B) ^ t where t = (n*j)+i
r[i] = b[len(b)/2:] // R[i] = LSB(64, B)
a = arrays.Xor(a, arrays.UInt64ToBytes(t))
}
}
// 3) Output the results
c := make([][]byte, n+1, n+1)
c[0] = a; // Set C[0] = A
for i = 1; i <= n; i++ { // For i = 1 to n
c[i] = r[i - 1] // C[i] = R[i]
}
return arrays.Unwrap(c),nil
}
// KeyUnwrap decrypts previously encrypted key (CEK) with KEK key using AES Key Wrap (rfc 3394) algorithm
func KeyUnwrap(encryptedCek, kek []byte) ([]byte,error) {
// 1) Initialize variables
c := arrays.Slice(encryptedCek, 8);
a := c[0]; // Set A = C[0]
r := make([][]byte,len(c) - 1);
for i := 1; i < len(c); i++ { // For i = 1 to n
r[i - 1] = c[i]; // R[i] = C[i]
}
n := uint64(len(r))
// 2) Calculate intermediate values
var t,j uint64
for j = 6; j > 0; j-- { // For j = 5 to 0
for i := n; i > 0; i-- { // For i = n to 1
t = n * (j-1) + i;
a = arrays.Xor(a, arrays.UInt64ToBytes(t))
b,e := aesDec(kek, arrays.Concat(a, r[i-1])) // B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i
if e!=nil { return nil,e }
a = b[:len(b)/2] // A = MSB(64, B)
r[i-1] = b[len(b)/2:] // R[i] = LSB(64, B)
}
}
// 3) Output the results
if (!hmac.Equal(defaultIV, a)) { // If A is an appropriate initial value
return nil, errors.New("aes.KeyUnwrap(): integrity check failed.")
}
// For i = 1 to n
return arrays.Unwrap(r),nil // P[i] = R[i]
}
func aesEnc(kek, plainText []byte) (cipherText []byte, err error) {
var block cipher.Block
if block, err = aes.NewCipher(kek);err!=nil {
return nil,err
}
cipherText = make([]byte, len(plainText))
NewECBEncrypter(block).CryptBlocks(cipherText,plainText)
return cipherText,nil
}
func aesDec(kek, cipherText []byte) (plainText []byte,err error) {
var block cipher.Block
if block, err = aes.NewCipher(kek);err!=nil {
return nil,err
}
plainText = make([]byte, len(cipherText))
NewECBDecrypter(block).CryptBlocks(plainText,cipherText)
return plainText,nil
}

View File

@@ -0,0 +1,240 @@
package aes
import (
"testing"
// "fmt"
. "gopkg.in/check.v1"
// "github.com/dvsekhvalnov/jose2go/arrays"
)
func Test(t *testing.T) { TestingT(t) }
type TestSuite struct{}
var _ = Suite(&TestSuite{})
func (s *TestSuite) TestWrap_128Key_128Kek(c *C) {
//given (Section 4.1)
//000102030405060708090A0B0C0D0E0F
kek := []byte{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}
//00112233445566778899AABBCCDDEEFF
key := []byte{0,17,34,51,68,85,102,119,136,153,170,187,204,221,238,255}
//1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5
expected := []byte{ 31, 166, 139, 10, 129, 18, 180, 71, 174, 243, 75, 216, 251, 90, 123, 130, 157, 62, 134,35, 113, 210, 207, 229}
//when
test,_ := KeyWrap(key, kek);
//then
c.Assert(test, DeepEquals, expected)
}
func (s *TestSuite) Test_Unwrap128Key_128Kek(c *C) {
//given (Section 4.1)
//000102030405060708090A0B0C0D0E0F
kek := []byte{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }
//00112233445566778899AABBCCDDEEFF
expected := []byte{ 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255 }
//1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5
key := []byte{ 31, 166, 139, 10, 129, 18, 180, 71, 174, 243, 75, 216, 251, 90, 123, 130, 157, 62, 134, 35, 113, 210, 207, 229 }
//when
test,_ := KeyUnwrap(key, kek)
//then
c.Assert(test, DeepEquals, expected)
}
func (s *TestSuite) Test_Wrap_128Key_192Kek(c *C) {
//given (Section 4.2)
//000102030405060708090A0B0C0D0E0F1011121314151617
kek := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23}
//00112233445566778899AABBCCDDEEFF
key := []byte{0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255}
//96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D
expected := []byte{150, 119, 139, 37, 174, 108, 164, 53, 249, 43, 91, 151, 192, 80, 174, 210, 70, 138, 184, 161, 122, 216, 78, 93}
//when
test,_ := KeyWrap(key, kek);
//then
c.Assert(test, DeepEquals, expected)
}
func (s *TestSuite) Test_Unwrap128Key_192Kek(c *C) {
//given (Section 4.2)
//000102030405060708090A0B0C0D0E0F1011121314151617
kek := []byte{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 }
//00112233445566778899AABBCCDDEEFF
expected := []byte{ 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255 }
//96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D
key := []byte{ 150, 119, 139, 37, 174, 108, 164, 53, 249, 43, 91, 151, 192, 80, 174, 210, 70, 138, 184, 161, 122, 216, 78, 93 }
//when
test,_ := KeyUnwrap(key, kek)
//then
c.Assert(test, DeepEquals, expected)
}
func (s *TestSuite) Test_Wrap_128Key_256Kek(c *C) {
//given (Section 4.3)
//000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
kek := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
//00112233445566778899AABBCCDDEEFF
key := []byte{0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255}
//64E8C3F9CE0F5BA263E9777905818A2A93C8191E7D6E8AE7
expected := []byte{100, 232, 195, 249, 206, 15, 91, 162, 99, 233, 119, 121, 5, 129, 138, 42, 147, 200, 25, 30, 125, 110, 138, 231}
//when
test,_ := KeyWrap(key, kek);
//then
c.Assert(test, DeepEquals, expected)
}
func (s *TestSuite) Test_Unwrap128Key_256Kek(c *C) {
//given (Section 4.3)
//000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
kek := []byte{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }
//00112233445566778899AABBCCDDEEFF
expected := []byte{ 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255 }
//64E8C3F9CE0F5BA263E9777905818A2A93C8191E7D6E8AE7
key := []byte{ 100, 232, 195, 249, 206, 15, 91, 162, 99, 233, 119, 121, 5, 129, 138, 42, 147, 200, 25, 30, 125, 110, 138, 231 }
//when
test,_ := KeyUnwrap(key, kek)
//then
c.Assert(test, DeepEquals, expected)
}
func (s *TestSuite) Test_Wrap_192Key_192Kek(c *C) {
//given (Section 4.4)
//000102030405060708090A0B0C0D0E0F1011121314151617
kek := []byte{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 }
//00112233445566778899AABBCCDDEEFF0001020304050607
key := []byte{ 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255, 0, 1, 2, 3, 4, 5, 6, 7 }
//031D33264E15D33268F24EC260743EDCE1C6C7DDEE725A936BA814915C6762D2
expected := []byte{ 3, 29, 51, 38, 78, 21, 211, 50, 104, 242, 78, 194, 96, 116, 62, 220, 225, 198, 199, 221, 238, 114, 90, 147, 107, 168, 20, 145, 92, 103, 98, 210 }
//when
test,_ := KeyWrap(key, kek);
//then
c.Assert(test, DeepEquals, expected)
}
func (s *TestSuite) Test_Unwrap192Key_192Kek(c *C) {
//given (Section 4.4)
//000102030405060708090A0B0C0D0E0F1011121314151617
kek := []byte{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 }
//00112233445566778899AABBCCDDEEFF0001020304050607
expected := []byte{ 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255, 0, 1, 2, 3, 4, 5, 6, 7 }
//031D33264E15D33268F24EC260743EDCE1C6C7DDEE725A936BA814915C6762D2
key := []byte{ 3, 29, 51, 38, 78, 21, 211, 50, 104, 242, 78, 194, 96, 116, 62, 220, 225, 198, 199, 221, 238, 114, 90, 147, 107, 168, 20, 145, 92, 103, 98, 210 }
//when
test,_ := KeyUnwrap(key, kek)
//then
c.Assert(test, DeepEquals, expected)
}
func (s *TestSuite) Test_Wrap_192Key_256Kek(c *C) {
//given (Section 4.5)
//000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
kek := []byte{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }
//00112233445566778899AABBCCDDEEFF0001020304050607
key := []byte{ 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255, 0, 1, 2, 3, 4, 5, 6, 7 }
//A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1
expected := []byte{ 168, 249, 188, 22, 18, 198, 139, 63, 246, 230, 244, 251, 227, 14, 113, 228, 118, 156, 139, 128, 163, 44, 184, 149, 140, 213, 209, 125, 107, 37, 77, 161 }
//when
test,_ := KeyWrap(key, kek);
//then
c.Assert(test, DeepEquals, expected)
}
func (s *TestSuite) Test_Unwrap192Key_256Kek(c *C) {
//given (Section 4.5)
//000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
kek := []byte{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }
//00112233445566778899AABBCCDDEEFF0001020304050607
expected := []byte{ 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255, 0, 1, 2, 3, 4, 5, 6, 7 }
//A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1
key := []byte{ 168, 249, 188, 22, 18, 198, 139, 63, 246, 230, 244, 251, 227, 14, 113, 228, 118, 156, 139, 128, 163, 44, 184, 149, 140, 213, 209, 125, 107, 37, 77, 161 }
//when
test,_ := KeyUnwrap(key, kek)
//then
c.Assert(test, DeepEquals, expected)
}
func (s *TestSuite) Test_Wrap_256Key_256Kek(c *C) {
//given (Section 4.6)
//000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
kek := []byte{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }
//00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F
key := []byte{ 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }
//28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21
expected := []byte{ 40, 201, 244, 4, 196, 184, 16, 244, 203, 204, 179, 92, 251, 135, 248, 38, 63, 87, 134, 226, 216, 14, 211, 38, 203, 199, 240, 231, 26, 153, 244, 59, 251, 152, 139, 155, 122, 2, 221, 33 }
//when
test,_ := KeyWrap(key, kek);
//then
c.Assert(test, DeepEquals, expected)
}
func (s *TestSuite) Test_Unwrap256Key_256Kek(c *C) {
//given (Section 4.6)
//000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
kek := []byte{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }
//00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F
expected := []byte{ 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }
//28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21
key := []byte{ 40, 201, 244, 4, 196, 184, 16, 244, 203, 204, 179, 92, 251, 135, 248, 38, 63, 87, 134, 226, 216, 14, 211, 38, 203, 199, 240, 231, 26, 153, 244, 59, 251, 152, 139, 155, 122, 2, 221, 33 }
//when
test,_ := KeyUnwrap(key, kek)
//then
c.Assert(test, DeepEquals, expected)
}

View File

@@ -0,0 +1,108 @@
package jose
import (
"errors"
"github.com/dvsekhvalnov/jose2go/arrays"
"github.com/dvsekhvalnov/jose2go/padding"
"crypto/hmac"
"crypto/cipher"
"crypto/aes"
"fmt"
)
// AES CBC with HMAC authenticated encryption algorithm implementation
type AesCbcHmac struct{
keySizeBits int
}
func init() {
RegisterJwe(&AesCbcHmac{keySizeBits:256})
RegisterJwe(&AesCbcHmac{keySizeBits:384})
RegisterJwe(&AesCbcHmac{keySizeBits:512})
}
func (alg *AesCbcHmac) Name() string {
switch alg.keySizeBits {
case 256: return A128CBC_HS256
case 384: return A192CBC_HS384
default: return A256CBC_HS512
}
}
func (alg *AesCbcHmac) KeySizeBits() int {
return alg.keySizeBits
}
func (alg *AesCbcHmac) Encrypt(aad, plainText, cek []byte) (iv, cipherText, authTag []byte, err error) {
cekSizeBits := len(cek)<<3
if cekSizeBits != alg.keySizeBits {
return nil,nil,nil, errors.New(fmt.Sprintf("AesCbcHmac.Encrypt(): expected key of size %v bits, but was given %v bits.",alg.keySizeBits, cekSizeBits))
}
hmacKey := cek[0:len(cek)/2]
aesKey := cek[len(cek)/2:]
if iv,err = arrays.Random(16);err!=nil {
return nil,nil,nil,err
}
var block cipher.Block
if block, err = aes.NewCipher(aesKey);err!=nil {
return nil,nil,nil,err
}
padded := padding.AddPkcs7(plainText,16)
cipherText = make([]byte,len(padded),cap(padded))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(cipherText,padded)
authTag = alg.computeAuthTag(aad, iv, cipherText, hmacKey)
return iv,cipherText,authTag,nil
}
func (alg *AesCbcHmac) Decrypt(aad, cek, iv, cipherText, authTag []byte) (plainText []byte, err error) {
cekSizeBits := len(cek)<<3
if cekSizeBits != alg.keySizeBits {
return nil, errors.New(fmt.Sprintf("AesCbcHmac.Decrypt(): expected key of size %v bits, but was given %v bits.",alg.keySizeBits, cekSizeBits))
}
hmacKey := cek[0:len(cek)/2]
aesKey := cek[len(cek)/2:]
// Check MAC
expectedAuthTag := alg.computeAuthTag(aad, iv, cipherText, hmacKey);
if !hmac.Equal(expectedAuthTag, authTag) {
return nil,errors.New("AesCbcHmac.Decrypt(): Authentication tag do not match.")
}
var block cipher.Block
if block, err = aes.NewCipher(aesKey);err==nil {
mode := cipher.NewCBCDecrypter(block, iv)
var padded []byte=make([]byte, len(cipherText), cap(cipherText))
mode.CryptBlocks(padded, cipherText)
return padding.RemovePkcs7(padded,16),nil
}
return nil,err
}
func (alg *AesCbcHmac) computeAuthTag(aad []byte, iv []byte, cipherText []byte, hmacKey []byte) (signature []byte) {
al := arrays.UInt64ToBytes(uint64(len(aad) << 3));
hmacInput := arrays.Concat(aad, iv, cipherText, al)
hmac :=calculateHmac(alg.keySizeBits, hmacInput, hmacKey);
return hmac[0:len(hmac)/2];
}

View File

@@ -0,0 +1,94 @@
package jose
import (
"fmt"
"errors"
"crypto/aes"
"crypto/cipher"
"github.com/dvsekhvalnov/jose2go/arrays"
)
// AES GCM authenticated encryption algorithm implementation
type AesGcm struct{
keySizeBits int
}
func init() {
RegisterJwe(&AesGcm{keySizeBits:128})
RegisterJwe(&AesGcm{keySizeBits:192})
RegisterJwe(&AesGcm{keySizeBits:256})
}
func (alg *AesGcm) Name() string {
switch alg.keySizeBits {
case 128: return A128GCM
case 192: return A192GCM
default: return A256GCM
}
}
func (alg *AesGcm) KeySizeBits() int {
return alg.keySizeBits
}
func (alg *AesGcm) Encrypt(aad, plainText, cek []byte) (iv, cipherText, authTag []byte, err error) {
cekSizeBits := len(cek)<<3
if cekSizeBits != alg.keySizeBits {
return nil,nil,nil, errors.New(fmt.Sprintf("AesGcm.Encrypt(): expected key of size %v bits, but was given %v bits.",alg.keySizeBits, cekSizeBits))
}
if iv,err = arrays.Random(12);err!=nil {
return nil,nil,nil,err
}
var block cipher.Block
if block, err = aes.NewCipher(cek);err!=nil {
return nil,nil,nil,err
}
var aesgcm cipher.AEAD
if aesgcm,err = cipher.NewGCM(block);err!=nil {
return nil,nil,nil,err
}
cipherWithTag := aesgcm.Seal(nil, iv, plainText, aad)
cipherText=cipherWithTag[:len(cipherWithTag)-aesgcm.Overhead()]
authTag=cipherWithTag[len(cipherWithTag)-aesgcm.Overhead():]
return iv, cipherText, authTag, nil
}
func (alg *AesGcm) Decrypt(aad, cek, iv, cipherText, authTag []byte) (plainText []byte, err error) {
cekSizeBits := len(cek)<<3
if cekSizeBits != alg.keySizeBits {
return nil, errors.New(fmt.Sprintf("AesGcm.Decrypt(): expected key of size %v bits, but was given %v bits.",alg.keySizeBits, cekSizeBits))
}
var block cipher.Block
if block, err = aes.NewCipher(cek);err!=nil {
return nil,err
}
var aesgcm cipher.AEAD
if aesgcm,err = cipher.NewGCM(block);err!=nil {
return nil,err
}
cipherWithTag:=append(cipherText,authTag...)
if plainText,err = aesgcm.Open(nil, iv, cipherWithTag, aad);err!=nil {
return nil,err
}
return plainText,nil
}

View File

@@ -0,0 +1,128 @@
package jose
import (
"errors"
"fmt"
"github.com/dvsekhvalnov/jose2go/base64url"
"github.com/dvsekhvalnov/jose2go/arrays"
"crypto/aes"
"crypto/cipher"
)
func init() {
RegisterJwa(&AesGcmKW{ keySizeBits: 128})
RegisterJwa(&AesGcmKW{ keySizeBits: 192})
RegisterJwa(&AesGcmKW{ keySizeBits: 256})
}
// AES GCM Key Wrap key management algorithm implementation
type AesGcmKW struct {
keySizeBits int
}
func (alg *AesGcmKW) Name() string {
switch alg.keySizeBits {
case 128: return A128GCMKW
case 192: return A192GCMKW
default: return A256GCMKW
}
}
func (alg *AesGcmKW) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) {
if kek,ok:=key.([]byte); ok {
kekSizeBits := len(kek) << 3
if kekSizeBits != alg.keySizeBits {
return nil,nil, errors.New(fmt.Sprintf("AesGcmKW.WrapNewKey(): expected key of size %v bits, but was given %v bits.",alg.keySizeBits, kekSizeBits))
}
if cek,err = arrays.Random(cekSizeBits>>3);err!=nil {
return nil,nil,err
}
var iv []byte
if iv,err = arrays.Random(12);err!=nil {
return nil,nil,err
}
var block cipher.Block
if block, err = aes.NewCipher(kek);err!=nil {
return nil,nil,err
}
var aesgcm cipher.AEAD
if aesgcm,err = cipher.NewGCM(block);err!=nil {
return nil,nil,err
}
cipherWithTag := aesgcm.Seal(nil, iv, cek, nil)
cipherText := cipherWithTag[:len(cipherWithTag)-aesgcm.Overhead()]
authTag := cipherWithTag[len(cipherWithTag)-aesgcm.Overhead():]
header["iv"]=base64url.Encode(iv)
header["tag"]=base64url.Encode(authTag)
return cek,cipherText,nil
}
return nil,nil,errors.New("AesGcmKW.WrapNewKey(): expected key to be '[]byte' array")
}
func (alg *AesGcmKW) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) {
if kek,ok:=key.([]byte); ok {
kekSizeBits := len(kek) << 3
if kekSizeBits != alg.keySizeBits {
return nil,errors.New(fmt.Sprintf("AesGcmKW.Unwrap(): expected key of size %v bits, but was given %v bits.", alg.keySizeBits, kekSizeBits))
}
var iv,tag string
if iv,ok = header["iv"].(string);!ok {
return nil,errors.New("AesGcmKW.Unwrap(): expected 'iv' param in JWT header, but was not found.")
}
if tag,ok = header["tag"].(string);!ok {
return nil,errors.New("AesGcmKW.Unwrap(): expected 'tag' param in JWT header, but was not found.")
}
var ivBytes,tagBytes []byte
if ivBytes,err = base64url.Decode(iv);err!=nil {
return nil,err
}
if tagBytes,err = base64url.Decode(tag);err!=nil {
return nil,err
}
var block cipher.Block
if block, err = aes.NewCipher(kek);err!=nil {
return nil,err
}
var aesgcm cipher.AEAD
if aesgcm,err = cipher.NewGCM(block);err!=nil {
return nil,err
}
cipherAndTag:=append(encryptedCek,tagBytes...)
if cek,err = aesgcm.Open(nil, ivBytes,cipherAndTag , nil);err!=nil {
fmt.Printf("err = %v\n",err)
return nil,err
}
return cek,nil
}
return nil,errors.New("AesGcmKW.Unwrap(): expected key to be '[]byte' array")
}

View File

@@ -0,0 +1,64 @@
package jose
import (
"errors"
"fmt"
"github.com/dvsekhvalnov/jose2go/aes"
"github.com/dvsekhvalnov/jose2go/arrays"
)
func init() {
RegisterJwa(&AesKW{ keySizeBits: 128})
RegisterJwa(&AesKW{ keySizeBits: 192})
RegisterJwa(&AesKW{ keySizeBits: 256})
}
// AES Key Wrap key management algorithm implementation
type AesKW struct {
keySizeBits int
}
func (alg *AesKW) Name() string {
switch alg.keySizeBits {
case 128: return A128KW
case 192: return A192KW
default: return A256KW
}
}
func (alg *AesKW) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) {
if kek,ok:=key.([]byte); ok {
kekSizeBits := len(kek) << 3
if kekSizeBits != alg.keySizeBits {
return nil,nil, errors.New(fmt.Sprintf("AesKW.WrapNewKey(): expected key of size %v bits, but was given %v bits.",alg.keySizeBits, kekSizeBits))
}
if cek,err = arrays.Random(cekSizeBits>>3);err==nil {
encryptedCek,err=aes.KeyWrap(cek,kek)
return
}
return nil,nil,err
}
return nil,nil,errors.New("AesKW.WrapNewKey(): expected key to be '[]byte' array")
}
func (alg *AesKW) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) {
if kek,ok:=key.([]byte); ok {
kekSizeBits := len(kek) << 3
if kekSizeBits != alg.keySizeBits {
return nil,errors.New(fmt.Sprintf("AesKW.Unwrap(): expected key of size %v bits, but was given %v bits.", alg.keySizeBits, kekSizeBits))
}
return aes.KeyUnwrap(encryptedCek, kek)
}
return nil,errors.New("AesKW.Unwrap(): expected key to be '[]byte' array")
}

View File

@@ -0,0 +1,116 @@
// Package arrays provides various byte array utilities
package arrays
import (
"encoding/binary"
"bytes"
"github.com/dvsekhvalnov/jose2go/base64url"
"crypto/rand"
"fmt"
)
// Xor is doing byte by byte exclusive or of 2 byte arrays
func Xor(left,right []byte) []byte {
result:=make([]byte,len(left))
for i:=0;i<len(left);i++ {
result[i]=left[i] ^ right[i]
}
return result
}
// Slice is splitting input byte array into slice of subarrays. Each of count length.
func Slice(arr []byte, count int) [][]byte {
sliceCount := len(arr) / count;
result:=make([][]byte, sliceCount)
for i:=0; i<sliceCount; i++ {
start:=i*count
end:=i*count+count
result[i]=arr[start:end]
}
return result
}
// Random generates byte array with random data of byteCount length
func Random(byteCount int) ([]byte,error) {
data := make([]byte,byteCount)
if _, err := rand.Read(data);err!=nil {
return nil,err
}
return data,nil
}
// Concat combine several arrays into single one, resulting slice = A1 | A2 | A3 | ... | An
func Concat(arrays ...[]byte) []byte {
var result []byte=arrays[0]
for _,arr := range(arrays[1:]) {
result=append(result,arr...)
}
return result
}
// Unwrap same thing as Contact, just different interface, combines several array into single one
func Unwrap(arrays [][]byte) []byte {
var result []byte=arrays[0]
for _,arr := range(arrays[1:]) {
result=append(result,arr...)
}
return result
}
// UInt64ToBytes unwrap uint64 value to byte array of length 8 using big endian
func UInt64ToBytes(value uint64) []byte {
result := make([]byte, 8)
binary.BigEndian.PutUint64(result, value)
return result
}
// UInt32ToBytes unwrap uint32 value to byte array of length 4 using big endian
func UInt32ToBytes(value uint32) []byte {
result := make([]byte, 4)
binary.BigEndian.PutUint32(result, value)
return result
}
// Dump produces printable debug representation of byte array as string
func Dump(arr []byte) string {
var buf bytes.Buffer
buf.WriteString("(")
buf.WriteString(fmt.Sprintf("%v",len(arr)))
buf.WriteString(" bytes)[")
for idx,b := range(arr) {
buf.WriteString(fmt.Sprintf("%v",b))
if idx!=len(arr)-1 {
buf.WriteString(", ")
}
}
buf.WriteString("], Hex: [")
for idx,b := range(arr) {
buf.WriteString(fmt.Sprintf("%X",b))
if idx!=len(arr)-1 {
buf.WriteString(" ")
}
}
buf.WriteString("], Base64Url:")
buf.WriteString(base64url.Encode(arr))
return buf.String()
}

View File

@@ -0,0 +1,80 @@
package arrays
import (
"testing"
// "fmt"
. "gopkg.in/check.v1"
)
func Test(t *testing.T) { TestingT(t) }
type TestSuite struct{}
var _ = Suite(&TestSuite{})
func (s *TestSuite) TestXor(c *C) {
//given
data:=[]byte { 0xFF, 0x00, 0xF0, 0x0F, 0x55, 0xAA, 0xBB, 0xCC}
//when
test_1:=Xor(data,[]byte{0x00, 0xFF, 0x0F, 0xF0, 0xAA, 0x55, 0x44, 0x33})
test_2:=Xor(data,[]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
//then
c.Assert(test_1, DeepEquals, []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
c.Assert(test_2, DeepEquals, []byte{0x00, 0xFF, 0x0F, 0xF0, 0xAA, 0x55, 0x44, 0x33})
}
func (s *TestSuite) TestSlice(c *C) {
//given
data:=[]byte{ 0, 1, 2, 3, 4, 5, 6, 7, 8 }
//when
test:=Slice(data,3)
//then
c.Assert(len(test), Equals, 3)
c.Assert(test[0], DeepEquals, []byte{0,1,2})
c.Assert(test[1], DeepEquals, []byte{3,4,5})
c.Assert(test[2], DeepEquals, []byte{6,7,8})
}
func (s *TestSuite) TestConcat(c *C) {
//given
a:=[]byte{1,2,3}
b:=[]byte{4,5}
d:=[]byte{6}
e:=[]byte{}
f:=[]byte{7,8,9,10}
//when
test:=Concat(a,b,d,e,f)
//then
c.Assert(test, DeepEquals, []byte{1,2,3,4,5,6,7,8,9,10})
}
func (s *TestSuite) TestUnwrap(c *C) {
//given
a:=[]byte{1,2,3}
b:=[]byte{4,5}
d:=[]byte{6}
e:=[]byte{}
f:=[]byte{7,8,9,10}
//when
test:=Unwrap([][]byte{a,b,d,e,f})
//then
c.Assert(test, DeepEquals, []byte{1,2,3,4,5,6,7,8,9,10})
}
func (s *TestSuite) TestUInt32ToBytes(c *C) {
//then
c.Assert(UInt32ToBytes(0xFF), DeepEquals, []byte{ 0x00, 0x00, 0x00, 0xFF })
c.Assert(UInt32ToBytes(0xFFFFFFFE), DeepEquals, []byte{ 0xff, 0xff, 0xff, 0xfe })
}
func (s *TestSuite) TestUInt64ToBytes(c *C) {
//then
c.Assert(UInt64ToBytes(0xFF), DeepEquals, []byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF })
c.Assert(UInt64ToBytes(0xFFFFFFFFFFFFFFFE), DeepEquals, []byte{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe })
}

View File

@@ -0,0 +1,31 @@
// package base64url provides base64url encoding/decoding support
package base64url
import (
"strings"
"encoding/base64"
)
// Decode decodes base64url string to byte array
func Decode(data string) ([]byte,error) {
data = strings.Replace(data, "-", "+", -1) // 62nd char of encoding
data = strings.Replace(data, "_", "/", -1) // 63rd char of encoding
switch(len(data) % 4) { // Pad with trailing '='s
case 0: // no padding
case 2: data+="==" // 2 pad chars
case 3: data+="=" // 1 pad char
}
return base64.StdEncoding.DecodeString(data)
}
// Encode encodes given byte array to base64url string
func Encode(data []byte) string {
result := base64.StdEncoding.EncodeToString(data)
result = strings.Replace(result, "+", "-", -1) // 62nd char of encoding
result = strings.Replace(result, "/", "_", -1) // 63rd char of encoding
result = strings.Replace(result, "=", "", -1) // Remove any trailing '='s
return result
}

View File

@@ -0,0 +1,40 @@
package base64url
import (
"testing"
"fmt"
. "gopkg.in/check.v1"
)
func Test(t *testing.T) { TestingT(t) }
type TestSuite struct{}
var _ = Suite(&TestSuite{})
func (s *TestSuite) TestEncode(c *C) {
//given
in:=[]byte{72,101,108,108,111,32,66,97,115,101,54,52,85,114,108,32,101,110,99,111,100,105,110,103,33}
//when
test:=Encode(in)
//then
c.Assert(test, Equals, "SGVsbG8gQmFzZTY0VXJsIGVuY29kaW5nIQ")
}
func (s *TestSuite) TestDecode(c *C) {
//when
test,err := Decode("SGVsbG8gQmFzZTY0VXJsIGVuY29kaW5nIQ")
//then
c.Assert(err, IsNil)
c.Assert(test, DeepEquals, []byte{72,101,108,108,111,32,66,97,115,101,54,52,85,114,108,32,101,110,99,111,100,105,110,103,33})
}
func (s *TestSuite) TestDecodeIllegalBase64String(c *C) {
//when
_,err := Decode("SGVsbG8gQmFzZTY0VXJsIGVuY29kaW5nQ")
//then
c.Assert(err,NotNil)
fmt.Printf("err = %v\n",err)
}

View File

@@ -0,0 +1,34 @@
// package compact provides function to work with json compact serialization format
package compact
import (
"strings"
"github.com/dvsekhvalnov/jose2go/base64url"
)
// Parse splitting & decoding compact serialized json web token, returns slice of byte arrays, each representing part of token
func Parse(token string) [][]byte {
parts:=strings.Split(token,".")
result:=make([][]byte,len(parts))
var e error
for i,part:=range parts {
if result[i],e=base64url.Decode(part);e!=nil {
panic(e)
}
}
return result
}
// Serialize converts given parts into compact serialization format
func Serialize(parts ...[]byte) string {
result:=make([]string,len(parts))
for i,part:=range parts {
result[i]=base64url.Encode(part)
}
return strings.Join(result,".")
}

View File

@@ -0,0 +1,90 @@
package compact
import (
"testing"
. "gopkg.in/check.v1"
)
func Test(t *testing.T) { TestingT(t) }
type TestSuite struct{}
var _ = Suite(&TestSuite{})
func (s *TestSuite) TestParseThreeParts(c *C) {
//when
test:=Parse("eyJhbGciOiJIUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.chIoYWrQMA8XL5nFz6oLDJyvgHk2KA4BrFGrKymjC8E")
//then
c.Assert(test, HasLen, 3)
c.Assert(test[0], DeepEquals, []byte{123,34,97,108,103,34,58,34,72,83,50,53,54,34,44,34,99,116,121,34,58,34,116,101,120,116,92,47,112,108,97,105,110,34,125})
c.Assert(test[1], DeepEquals, []byte{123,34,104,101,108,108,111,34,58,32,34,119,111,114,108,100,34,125})
c.Assert(test[2], DeepEquals, []byte{114,18,40,97,106,208,48,15,23,47,153,197,207,170,11,12,156,175,128,121,54,40,14,1,172,81,171,43,41,163,11,193})
}
func (s *TestSuite) TestParseEmptyTrailingPart(c *C) {
//when
test:=Parse("eyJhbGciOiJub25lIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.")
//then
c.Assert(test, HasLen, 3)
c.Assert(test[0], DeepEquals, []byte{123,34,97,108,103,34,58,34,110,111,110,101,34,125})
c.Assert(test[1], DeepEquals, []byte{123,34,104,101,108,108,111,34,58,32,34,119,111,114,108,100,34,125})
c.Assert(test[2], DeepEquals, []byte{})
}
func (s *TestSuite) TestParseFiveParts(c *C) {
//when
test:=Parse("eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4R0NNIn0.FojyyzygtFOyNBjzqTRfr9HVHPrvtqbVUt9sXSuU59ZhLlzk7FrirryFnFGtj8YC9lx-IX156Ro9rBaJTCU_dfERd05DhPMffT40rdcDiLxfCLOY0E2PfsMyGQPhI6YtNBtf_sQjXWEBC59zH_VoswFAUstkvXY9eVVecoM-W9HFlIxwUXMVpEPtS96xZX5LMksDgJ9sYDTNa6EQOA0hfzw07fD_FFJShcueqJuoJjILYbad-AHbpnLTV4oTbFTYjskRxpEYQr9plFZsT4_xKiCU89slT9EFhmuaiUI_-NGdX-kNDyQZj2Vtid4LSOVv5kGxyygThuQb6wjr1AGe1g.O92pf8iqwlBIQmXA.YdGjkN7lzeKYIv743XlPRYTd3x4VA0xwa5WVoGf1hiHlhQuXGEg4Jv3elk4JoFJzgVuMMQMex8fpFFL3t5I4H9bH18pbrEo7wLXvGOsP971cuOOaXPxhX6qClkwx5qkWhcTbO_2AuJxzIaU9qBwtwWaxJm9axofAPYgYbdaMZkU4F5sFdaFY8IOe94wUA1Ocn_gxC_DYp9IEAyZut0j5RImmthPgiRO_0pK9OvusE_Xg3iGfdxu70x0KpoItuNwlEf0LUA.uP5jOGMxtDUiT6E3ubucBw")
//then
c.Assert(test, HasLen, 5)
c.Assert(test[0], DeepEquals, []byte{123,34,97,108,103,34,58,34,82,83,65,49,95,53,34,44,34,101,110,99,34,58,34,65,49,50,56,71,67,77,34,125})
c.Assert(test[1], DeepEquals, []byte{22,136,242,203,60,160,180,83,178,52,24,243,169,52,95,175,209,213,28,250,239,182,166,213,82,223,108,93,43,148,231,214,97,46,92,228,236,90,226,174,188,133,156,81,173,143,198,2,246,92,126,33,125,121,233,26,61,172,22,137,76,37,63,117,241,17,119,78,67,132,243,31,125,62,52,173,215,3,136,188,95,8,179,152,208,77,143,126,195,50,25,3,225,35,166,45,52,27,95,254,196,35,93,97,1,11,159,115,31,245,104,179,1,64,82,203,100,189,118,61,121,85,94,114,131,62,91,209,197,148,140,112,81,115,21,164,67,237,75,222,177,101,126,75,50,75,3,128,159,108,96,52,205,107,161,16,56,13,33,127,60,52,237,240,255,20,82,82,133,203,158,168,155,168,38,50,11,97,182,157,248,1,219,166,114,211,87,138,19,108,84,216,142,201,17,198,145,24,66,191,105,148,86,108,79,143,241,42,32,148,243,219,37,79,209,5,134,107,154,137,66,63,248,209,157,95,233,13,15,36,25,143,101,109,137,222,11,72,229,111,230,65,177,203,40,19,134,228,27,235,8,235,212,1,158,214})
c.Assert(test[2], DeepEquals, []byte{59,221,169,127,200,170,194,80,72,66,101,192})
c.Assert(test[3], DeepEquals, []byte{97,209,163,144,222,229,205,226,152,34,254,248,221,121,79,69,132,221,223,30,21,3,76,112,107,149,149,160,103,245,134,33,229,133,11,151,24,72,56,38,253,222,150,78,9,160,82,115,129,91,140,49,3,30,199,199,233,20,82,247,183,146,56,31,214,199,215,202,91,172,74,59,192,181,239,24,235,15,247,189,92,184,227,154,92,252,97,95,170,130,150,76,49,230,169,22,133,196,219,59,253,128,184,156,115,33,165,61,168,28,45,193,102,177,38,111,90,198,135,192,61,136,24,109,214,140,102,69,56,23,155,5,117,161,88,240,131,158,247,140,20,3,83,156,159,248,49,11,240,216,167,210,4,3,38,110,183,72,249,68,137,166,182,19,224,137,19,191,210,146,189,58,251,172,19,245,224,222,33,159,119,27,187,211,29,10,166,130,45,184,220,37,17,253,11,80})
c.Assert(test[4], DeepEquals, []byte{184,254,99,56,99,49,180,53,34,79,161,55,185,187,156,7})
}
func (s *TestSuite) TestParseEmptyMiddlePart(c *C) {
//when
test:=Parse("eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4R0NNIn0..yVi-LdQQngN0C5WS.1McwSmhZzAtmmLp9y-OdnJwaJFo1nj_4ashmzl2LhubGf0Jl1OTEVJzsHZb7bkup7cGTkuxh6Vfv10ljHsjWf_URXoxP3stQqQeViVcuPV0y2Q_WHYzTNGZpmHGe-hM6gjDhyZyvu3yeXGFSvfPQmp9pWVOgDjI4RC0MQ83rzzn-rRdnZkznWjbmOPxwPrR72Qng0BISsEwbkPn4oO8-vlHkVmPpuDTaYzCT2ZR5K9JnIU8d8QdxEAGb7-s8GEJ1yqtd_w._umbK59DAKA3O89h15VoKQ")
//then
c.Assert(test, HasLen, 5)
c.Assert(test[0], DeepEquals, []byte{123,34,97,108,103,34,58,34,100,105,114,34,44,34,101,110,99,34,58,34,65,49,50,56,71,67,77,34,125})
c.Assert(test[1], DeepEquals, []byte{})
c.Assert(test[2], DeepEquals, []byte{201,88,190,45,212,16,158,3,116,11,149,146})
c.Assert(test[3], DeepEquals, []byte{212,199,48,74,104,89,204,11,102,152,186,125,203,227,157,156,156,26,36,90,53,158,63,248,106,200,102,206,93,139,134,230,198,127,66,101,212,228,196,84,156,236,29,150,251,110,75,169,237,193,147,146,236,97,233,87,239,215,73,99,30,200,214,127,245,17,94,140,79,222,203,80,169,7,149,137,87,46,61,93,50,217,15,214,29,140,211,52,102,105,152,113,158,250,19,58,130,48,225,201,156,175,187,124,158,92,97,82,189,243,208,154,159,105,89,83,160,14,50,56,68,45,12,67,205,235,207,57,254,173,23,103,102,76,231,90,54,230,56,252,112,62,180,123,217,9,224,208,18,18,176,76,27,144,249,248,160,239,62,190,81,228,86,99,233,184,52,218,99,48,147,217,148,121,43,210,103,33,79,29,241,7,113,16,1,155,239,235,60,24,66,117,202,171,93,255})
c.Assert(test[4], DeepEquals, []byte{254,233,155,43,159,67,0,160,55,59,207,97,215,149,104,41})
}
func (s *TestSuite) TestSerialize(c *C) {
//when
test:=Serialize([]byte{123,34,97,108,103,34,58,34,72,83,50,53,54,34,44,34,99,116,121,34,58,34,116,101,120,116,92,47,112,108,97,105,110,34,125},
[]byte{123,34,104,101,108,108,111,34,58,32,34,119,111,114,108,100,34,125},
[]byte{114,18,40,97,106,208,48,15,23,47,153,197,207,170,11,12,156,175,128,121,54,40,14,1,172,81,171,43,41,163,11,193})
//then
c.Assert(test, Equals, "eyJhbGciOiJIUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.chIoYWrQMA8XL5nFz6oLDJyvgHk2KA4BrFGrKymjC8E")
}
func (s *TestSuite) TestSerializeEmptyTrailingPart(c *C) {
//when
test:=Serialize([]byte{123,34,97,108,103,34,58,34,72,83,50,53,54,34,44,34,99,116,121,34,58,34,116,101,120,116,92,47,112,108,97,105,110,34,125},
[]byte{123,34,104,101,108,108,111,34,58,32,34,119,111,114,108,100,34,125},
[]byte{})
//then
c.Assert(test, Equals, "eyJhbGciOiJIUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.")
}
func (s *TestSuite) TestSerializeEmptyMiddlePart(c *C) {
//when
test:=Serialize([]byte{123,34,97,108,103,34,58,34,72,83,50,53,54,34,44,34,99,116,121,34,58,34,116,101,120,116,92,47,112,108,97,105,110,34,125},
[]byte{},
[]byte{114,18,40,97,106,208,48,15,23,47,153,197,207,170,11,12,156,175,128,121,54,40,14,1,172,81,171,43,41,163,11,193})
//then
c.Assert(test, Equals, "eyJhbGciOiJIUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0..chIoYWrQMA8XL5nFz6oLDJyvgHk2KA4BrFGrKymjC8E")
}

View File

@@ -0,0 +1,39 @@
package jose
import (
"bytes"
"compress/flate"
"io/ioutil"
)
func init() {
RegisterJwc(new(Deflate))
}
// Deflate compression algorithm implementation
type Deflate struct {}
func (alg *Deflate) Name() string {
return DEF
}
func (alg *Deflate) Compress(plainText []byte) []byte {
var buf bytes.Buffer
deflate,_ := flate.NewWriter(&buf, 8) //level=DEFLATED
deflate.Write(plainText)
deflate.Close()
return buf.Bytes()
}
func (alg *Deflate) Decompress(compressedText []byte) []byte {
enflated,_ := ioutil.ReadAll(
flate.NewReader(
bytes.NewReader(compressedText)))
return enflated
}

View File

@@ -0,0 +1,39 @@
package jose
import (
"errors"
)
func init() {
RegisterJwa(new(Direct))
}
// Direct (pre-shared) key management algorithm implementation
type Direct struct{
}
func (alg *Direct) Name() string {
return DIR
}
func (alg *Direct) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) {
if cek,ok:=key.([]byte); ok {
return cek,[]byte{},nil
}
return nil,nil,errors.New("Direct.WrapNewKey(): expected key to be '[]byte' array")
}
func (alg *Direct) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) {
if(len(encryptedCek)!=0) {
return nil, errors.New("Direct.Unwrap(): expected empty encrypted CEK")
}
if cek,ok:=key.([]byte); ok {
return cek,nil
}
return nil,errors.New("Direct.Unwrap(): expected key to be '[]byte' array")
}

View File

@@ -0,0 +1,151 @@
package jose
import (
"errors"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"fmt"
"math/big"
"github.com/dvsekhvalnov/jose2go/base64url"
"github.com/dvsekhvalnov/jose2go/arrays"
"github.com/dvsekhvalnov/jose2go/keys/ecc"
"github.com/dvsekhvalnov/jose2go/kdf"
"github.com/dvsekhvalnov/jose2go/padding"
)
func init() {
RegisterJwa(&Ecdh{directAgreement:true})
}
// Elliptic curve Diffie–Hellman key management (key agreement) algorithm implementation
type Ecdh struct{
directAgreement bool
}
func (alg *Ecdh) Name() string {
return ECDH_ES
}
func (alg *Ecdh) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) {
if pubKey,ok := key.(*ecdsa.PublicKey);ok {
if _,ok := header[alg.idHeader()].(string);!ok {
return nil, nil, errors.New(fmt.Sprintf("Ecdh.WrapNewKey(): expected '%v' param in JWT header, but was not found.",alg.idHeader()))
}
var d []byte
var x, y *big.Int
if d, x, y, err=elliptic.GenerateKey(pubKey.Curve,rand.Reader); err!=nil {
return nil,nil,err
}
ephemeral:=ecc.NewPrivate(x.Bytes(),y.Bytes(),d)
xBytes:=padding.Align(x.Bytes(), pubKey.Curve.Params().BitSize)
yBytes:=padding.Align(y.Bytes(), pubKey.Curve.Params().BitSize)
epk:= map[string]string {
"kty": "EC",
"x": base64url.Encode(xBytes),
"y": base64url.Encode(yBytes),
"crv": name(pubKey.Curve),
}
header["epk"]=epk
return alg.deriveKey(pubKey,ephemeral,cekSizeBits,header),nil,nil
}
return nil,nil,errors.New("Ecdh.WrapNewKey(): expected key to be '*ecdsa.PublicKey'")
}
func (alg *Ecdh) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) {
if privKey,ok := key.(*ecdsa.PrivateKey);ok {
var epk map[string]interface{}
if epk,ok = header["epk"].(map[string]interface{});!ok {
return nil,errors.New("Ecdh.Unwrap(): expected 'epk' param in JWT header, but was not found.")
}
if _,ok := header[alg.idHeader()].(string);!ok {
return nil,errors.New(fmt.Sprintf("Ecdh.Unwrap(): expected '%v' param in JWT header, but was not found.",alg.idHeader()))
}
var x,y,crv string
var xBytes, yBytes []byte
if x,ok=epk["x"].(string);!ok {
return nil,errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'x' was not found.")
}
if y,ok=epk["y"].(string);!ok {
return nil,errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'y' was not found.")
}
if crv,ok=epk["crv"].(string);!ok {
return nil,errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'crv' was not found.")
}
if crv!="P-256" && crv!="P-384" && crv!="P-521" {
return nil,errors.New(fmt.Sprintf("Ecdh.Unwrap(): unknown or unsupported curve %v",crv))
}
if xBytes,err=base64url.Decode(x);err!=nil {
return nil,err
}
if yBytes,err=base64url.Decode(y);err!=nil {
return nil,err
}
pubKey := ecc.NewPublic(xBytes,yBytes)
return alg.deriveKey(pubKey,privKey,cekSizeBits,header),nil
}
return nil,errors.New("Ecdh.Unwrap(): expected key to be '*ecdsa.PrivateKey'")
}
func (alg *Ecdh) deriveKey(pubKey *ecdsa.PublicKey, privKey *ecdsa.PrivateKey, keySizeBits int, header map[string]interface{}) []byte {
var enc,apv,apu []byte
var err error
enc=[]byte(header[alg.idHeader()].(string))
if a,ok:=header["apv"].(string);!ok {
if apv,err=base64url.Decode(a);err!=nil {
apv = nil
}
}
if a,ok:=header["apu"].(string);!ok {
if apu,err=base64url.Decode(a);err!=nil {
apu = nil
}
}
z, _ := pubKey.Curve.ScalarMult(pubKey.X, pubKey.Y, privKey.D.Bytes())
zBytes := padding.Align(z.Bytes(), privKey.Curve.Params().BitSize)
return kdf.DeriveConcatKDF(keySizeBits,zBytes, prependDatalen(enc), prependDatalen(apu), prependDatalen(apv),arrays.UInt32ToBytes(uint32(keySizeBits)),nil,sha256.New())
}
func(alg *Ecdh) idHeader() string {
if alg.directAgreement { return "enc" }
return "alg"
}
func name(curve elliptic.Curve) string {
return fmt.Sprintf("P-%v",curve.Params().BitSize)
}
func prependDatalen(bytes []byte) []byte {
return arrays.Concat(arrays.UInt32ToBytes(uint32(len(bytes))),bytes)
}

View File

@@ -0,0 +1,42 @@
package jose
func init() {
RegisterJwa(&EcdhAesKW{ keySizeBits: 128, aesKW: &AesKW{ keySizeBits: 128}, ecdh: &Ecdh{directAgreement:false}})
RegisterJwa(&EcdhAesKW{ keySizeBits: 192, aesKW: &AesKW{ keySizeBits: 192}, ecdh: &Ecdh{directAgreement:false}})
RegisterJwa(&EcdhAesKW{ keySizeBits: 256, aesKW: &AesKW{ keySizeBits: 256}, ecdh: &Ecdh{directAgreement:false}})
}
// Elliptic curve Diffie–Hellman with AES Key Wrap key management algorithm implementation
type EcdhAesKW struct{
keySizeBits int
aesKW JwaAlgorithm
ecdh JwaAlgorithm
}
func (alg *EcdhAesKW) Name() string {
switch alg.keySizeBits {
case 128: return ECDH_ES_A128KW
case 192: return ECDH_ES_A192KW
default: return ECDH_ES_A256KW
}
}
func (alg *EcdhAesKW) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) {
var kek []byte
if kek,_,err=alg.ecdh.WrapNewKey(alg.keySizeBits, key, header);err!=nil {
return nil,nil,err
}
return alg.aesKW.WrapNewKey(cekSizeBits,kek,header)
}
func (alg *EcdhAesKW) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) {
var kek []byte
if kek,err=alg.ecdh.Unwrap(nil, key, alg.keySizeBits, header);err!=nil {
return nil,err
}
return alg.aesKW.Unwrap(encryptedCek,kek,cekSizeBits,header)
}

View File

@@ -0,0 +1,76 @@
package jose
import (
"crypto/rand"
"math/big"
"crypto/ecdsa"
"errors"
"github.com/dvsekhvalnov/jose2go/arrays"
"github.com/dvsekhvalnov/jose2go/padding"
"fmt"
)
func init() {
RegisterJws(&EcdsaUsingSha{keySizeBits: 256, hashSizeBits: 256})
RegisterJws(&EcdsaUsingSha{keySizeBits: 384, hashSizeBits: 384})
RegisterJws(&EcdsaUsingSha{keySizeBits: 521, hashSizeBits: 512})
}
// ECDSA signing algorithm implementation
type EcdsaUsingSha struct{
keySizeBits int
hashSizeBits int
}
func (alg *EcdsaUsingSha) Name() string {
switch alg.keySizeBits {
case 256: return ES256
case 384: return ES384
default: return ES512
}
}
func (alg *EcdsaUsingSha) Verify(securedInput, signature []byte, key interface{}) error {
if pubKey,ok:=key.(*ecdsa.PublicKey);ok {
if sizeBits:=pubKey.Curve.Params().BitSize;sizeBits!=alg.keySizeBits {
return errors.New(fmt.Sprintf("EcdsaUsingSha.Verify(): expected key of size %v bits, but was given %v bits.",alg.keySizeBits,sizeBits))
}
r:=new(big.Int).SetBytes(signature[:len(signature)/2])
s:=new(big.Int).SetBytes(signature[len(signature)/2:])
if ok:=ecdsa.Verify(pubKey, sha(alg.hashSizeBits, securedInput), r,s); ok {
return nil
}
return errors.New("EcdsaUsingSha.Verify(): Signature is not valid.")
}
return errors.New("EcdsaUsingSha.Verify(): expects key to be '*ecdsa.PublicKey'")
}
func (alg *EcdsaUsingSha) Sign(securedInput []byte, key interface{}) (signature []byte, err error) {
if privKey,ok := key.(*ecdsa.PrivateKey);ok {
if sizeBits:=privKey.Curve.Params().BitSize;sizeBits!=alg.keySizeBits {
return nil,errors.New(fmt.Sprintf("EcdsaUsingSha.Sign(): expected key of size %v bits, but was given %v bits.",alg.keySizeBits,sizeBits))
}
var r,s *big.Int
if r,s,err = ecdsa.Sign(rand.Reader, privKey, sha(alg.hashSizeBits, securedInput));err==nil {
rBytes:=padding.Align(r.Bytes(), alg.keySizeBits)
sBytes:=padding.Align(s.Bytes(), alg.keySizeBits)
return arrays.Concat(rBytes,sBytes),nil
}
return nil, err
}
return nil,errors.New("EcdsaUsingSha.Sign(): expects key to be '*ecdsa.PrivateKey'")
}

View File

@@ -0,0 +1,13 @@
package jose
import (
"crypto/hmac"
"hash"
)
func calculateHmac(keySizeBits int, securedInput []byte, key []byte) []byte {
hasher := hmac.New(func() hash.Hash { return hashAlg(keySizeBits)}, key)
hasher.Write(securedInput)
return hasher.Sum(nil)
}

View File

@@ -0,0 +1,46 @@
package jose
import (
"crypto/hmac"
"errors"
)
func init() {
RegisterJws(&HmacUsingSha{keySizeBits: 256})
RegisterJws(&HmacUsingSha{keySizeBits: 384})
RegisterJws(&HmacUsingSha{keySizeBits: 512})
}
// HMAC with SHA signing algorithm implementation
type HmacUsingSha struct{
keySizeBits int
}
func (alg *HmacUsingSha) Name() string {
switch alg.keySizeBits {
case 256: return HS256
case 384: return HS384
default: return HS512
}
}
func (alg *HmacUsingSha) Verify(securedInput, signature []byte, key interface{}) error {
actualSig,_ := alg.Sign(securedInput, key)
if !hmac.Equal(signature, actualSig) {
return errors.New("HmacUsingSha.Verify(): Signature is invalid")
}
return nil
}
func (alg *HmacUsingSha) Sign(securedInput []byte, key interface{}) (signature []byte, err error) {
//TODO: assert min key size
if pubKey,ok:=key.([]byte); ok {
return calculateHmac(alg.keySizeBits, securedInput, pubKey),nil
}
return nil,errors.New("HmacUsingSha.Sign(): expects key to be '[]byte' array")
}

View File

@@ -0,0 +1,317 @@
//Package jose provides high level functions for producing (signing, encrypting and
// compressing) or consuming (decoding) Json Web Tokens using Java Object Signing and Encryption spec
package jose
import (
"encoding/json"
"errors"
"fmt"
"github.com/dvsekhvalnov/jose2go/compact"
)
const (
NONE = "none" //plaintext (unprotected) without signature / encryption
HS256 = "HS256" //HMAC using SHA-256 hash
HS384 = "HS384" //HMAC using SHA-384 hash
HS512 = "HS512" //HMAC using SHA-512 hash
RS256 = "RS256" //RSASSA-PKCS-v1_5 using SHA-256 hash
RS384 = "RS384" //RSASSA-PKCS-v1_5 using SHA-384 hash
RS512 = "RS512" //RSASSA-PKCS-v1_5 using SHA-512 hash
PS256 = "PS256" //RSASSA-PSS using SHA-256 hash
PS384 = "PS384" //RSASSA-PSS using SHA-384 hash
PS512 = "PS512" //RSASSA-PSS using SHA-512 hash
ES256 = "ES256" //ECDSA using P-256 curve and SHA-256 hash
ES384 = "ES384" //ECDSA using P-384 curve and SHA-384 hash
ES512 = "ES512" //ECDSA using P-521 curve and SHA-512 hash
A128CBC_HS256 = "A128CBC-HS256" //AES in CBC mode with PKCS #5 (NIST.800-38A) padding with HMAC using 256 bit key
A192CBC_HS384 = "A192CBC-HS384" //AES in CBC mode with PKCS #5 (NIST.800-38A) padding with HMAC using 384 bit key
A256CBC_HS512 = "A256CBC-HS512" //AES in CBC mode with PKCS #5 (NIST.800-38A) padding with HMAC using 512 bit key
A128GCM = "A128GCM" //AES in GCM mode with 128 bit key
A192GCM = "A192GCM" //AES in GCM mode with 192 bit key
A256GCM = "A256GCM" //AES in GCM mode with 256 bit key
DIR = "dir" //Direct use of pre-shared symmetric key
RSA1_5 = "RSA1_5" //RSAES with PKCS #1 v1.5 padding, RFC 3447
RSA_OAEP = "RSA-OAEP" //RSAES using Optimal Assymetric Encryption Padding, RFC 3447
RSA_OAEP_256 = "RSA-OAEP-256" //RSAES using Optimal Assymetric Encryption Padding with SHA-256, RFC 3447
A128KW = "A128KW" //AES Key Wrap Algorithm using 128 bit keys, RFC 3394
A192KW = "A192KW" //AES Key Wrap Algorithm using 192 bit keys, RFC 3394
A256KW = "A256KW" //AES Key Wrap Algorithm using 256 bit keys, RFC 3394
A128GCMKW = "A128GCMKW" //AES GCM Key Wrap Algorithm using 128 bit keys
A192GCMKW = "A192GCMKW" //AES GCM Key Wrap Algorithm using 192 bit keys
A256GCMKW = "A256GCMKW" //AES GCM Key Wrap Algorithm using 256 bit keys
PBES2_HS256_A128KW = "PBES2-HS256+A128KW" //Password Based Encryption using PBES2 schemes with HMAC-SHA and AES Key Wrap using 128 bit key
PBES2_HS384_A192KW = "PBES2-HS384+A192KW" //Password Based Encryption using PBES2 schemes with HMAC-SHA and AES Key Wrap using 192 bit key
PBES2_HS512_A256KW = "PBES2-HS512+A256KW" //Password Based Encryption using PBES2 schemes with HMAC-SHA and AES Key Wrap using 256 bit key
ECDH_ES = "ECDH-ES" //Elliptic Curve Diffie Hellman key agreement
ECDH_ES_A128KW = "ECDH-ES+A128KW" //Elliptic Curve Diffie Hellman key agreement with AES Key Wrap using 128 bit key
ECDH_ES_A192KW = "ECDH-ES+A192KW" //Elliptic Curve Diffie Hellman key agreement with AES Key Wrap using 192 bit key
ECDH_ES_A256KW = "ECDH-ES+A256KW" //Elliptic Curve Diffie Hellman key agreement with AES Key Wrap using 256 bit key
DEF = "DEF" //DEFLATE compression, RFC 1951
)
var jwsHashers = map[string]JwsAlgorithm{}
var jweEncryptors = map[string]JweEncryption{}
var jwaAlgorithms = map[string]JwaAlgorithm{}
var jwcCompressors = map[string]JwcAlgorithm{}
// RegisterJwe register new encryption algorithm
func RegisterJwe(alg JweEncryption) {
jweEncryptors[alg.Name()] = alg
}
// RegisterJwa register new key management algorithm
func RegisterJwa(alg JwaAlgorithm) {
jwaAlgorithms[alg.Name()] = alg
}
// RegisterJws register new signing algorithm
func RegisterJws(alg JwsAlgorithm) {
jwsHashers[alg.Name()] = alg
}
// RegisterJwc register new compression algorithm
func RegisterJwc(alg JwcAlgorithm) {
jwcCompressors[alg.Name()] = alg
}
// JweEncryption is a contract for implementing encryption algorithm
type JweEncryption interface {
Encrypt(aad, plainText, cek []byte) (iv, cipherText, authTag []byte, err error)
Decrypt(aad, cek, iv, cipherText, authTag []byte) (plainText []byte, err error)
KeySizeBits() int
Name() string
}
// JwaAlgorithm is a contract for implementing key management algorithm
type JwaAlgorithm interface {
WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error)
Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error)
Name() string
}
// JwsAlgorithm is a contract for implementing signing algorithm
type JwsAlgorithm interface {
Verify(securedInput, signature []byte, key interface{}) error
Sign(securedInput []byte, key interface{}) (signature []byte, err error)
Name() string
}
// JwcAlgorithm is a contract for implementing compression algorithm
type JwcAlgorithm interface {
Compress(plainText []byte) []byte
Decompress(compressedText []byte) []byte
Name() string
}
// Sign produces signed JWT token given arbitrary payload, signature algorithm to use (see constants for list of supported algs) and signing key.
// Signing key is of different type for different signing alg, see specific
// signing alg implementation documentation.
//
// It returns 3 parts signed JWT token as string and not nil error if something went wrong.
func Sign(payload string, signingAlg string, key interface{}) (token string, err error) {
if signer, ok := jwsHashers[signingAlg]; ok {
jwtHeader := map[string]interface{}{
"typ": "JWT",
"alg": signingAlg,
}
paloadBytes := []byte(payload)
var header []byte
var signature []byte
if header, err = json.Marshal(jwtHeader); err == nil {
securedInput := []byte(compact.Serialize(header, paloadBytes))
if signature, err = signer.Sign(securedInput, key); err == nil {
return compact.Serialize(header, paloadBytes, signature), nil
}
}
return "", err
}
return "", errors.New(fmt.Sprintf("jwt.Sign(): unknown algorithm: '%v'", signingAlg))
}
// Encrypt produces encrypted JWT token given arbitrary payload, key management and encryption algorithms to use (see constants for list of supported algs) and management key.
// Management key is of different type for different key management alg, see specific
// key management alg implementation documentation.
//
// It returns 5 parts encrypted JWT token as string and not nil error if something went wrong.
func Encrypt(payload string, alg string, enc string, key interface{}) (token string, err error) {
jwtHeader := map[string]interface{}{
"enc": enc,
"alg": alg,
}
return encrypt([]byte(payload), jwtHeader, key)
}
// Compress produces encrypted & comressed JWT token given arbitrary payload, key management , encryption and compression algorithms to use (see constants for list of supported algs) and management key.
// Management key is of different type for different key management alg, see specific
// key management alg implementation documentation.
//
// It returns 5 parts encrypted & compressed JWT token as string and not nil error if something went wrong.
func Compress(payload string, alg string, enc string, zip string, key interface{}) (token string, err error) {
if zipAlg, ok := jwcCompressors[zip]; ok {
compressed := zipAlg.Compress([]byte(payload))
jwtHeader := map[string]interface{}{
"enc": enc,
"alg": alg,
"zip": zip,
}
return encrypt(compressed, jwtHeader, key)
}
return "", errors.New(fmt.Sprintf("jwt.Compress(): Unknown compression method '%v'", zip))
}
// Decode verifies, decrypts and decompresses given JWT token using management key.
// Management key is of different type for different key management or signing algorithms, see specific alg implementation documentation.
//
// Returns decoded payload as a string and not nil error if something went wrong.
func Decode(token string, key interface{}) (string, map[string]interface{}, error) {
parts := compact.Parse(token)
if len(parts) == 3 {
return verify(parts, key)
}
if len(parts) == 5 {
return decrypt(parts, key)
}
return "", nil, errors.New(fmt.Sprintf("jwt.Decode() expects token of 3 or 5 parts, but was given: %v parts", len(parts)))
}
func encrypt(payload []byte, jwtHeader map[string]interface{}, key interface{}) (token string, err error) {
var ok bool
var keyMgmtAlg JwaAlgorithm
var encAlg JweEncryption
alg := jwtHeader["alg"].(string)
enc := jwtHeader["enc"].(string)
if keyMgmtAlg, ok = jwaAlgorithms[alg]; !ok {
return "", errors.New(fmt.Sprintf("jwt.encrypt(): Unknown key management algorithm '%v'", alg))
}
if encAlg, ok = jweEncryptors[enc]; !ok {
return "", errors.New(fmt.Sprintf("jwt.encrypt(): Unknown encryption algorithm '%v'", enc))
}
var cek, encryptedCek, header, iv, cipherText, authTag []byte
if cek, encryptedCek, err = keyMgmtAlg.WrapNewKey(encAlg.KeySizeBits(), key, jwtHeader); err != nil {
return "", err
}
if header, err = json.Marshal(jwtHeader); err != nil {
return "", err
}
if iv, cipherText, authTag, err = encAlg.Encrypt([]byte(compact.Serialize(header)), payload, cek); err != nil {
return "", err
}
return compact.Serialize(header, encryptedCek, iv, cipherText, authTag), nil
}
func verify(parts [][]byte, key interface{}) (plainText string, headers map[string]interface{}, err error) {
header, payload, signature := parts[0], parts[1], parts[2]
secured := []byte(compact.Serialize(header, payload))
var jwtHeader map[string]interface{}
if err = json.Unmarshal(header, &jwtHeader); err != nil {
return "", nil, err
}
alg := jwtHeader["alg"].(string)
if verifier, ok := jwsHashers[alg]; ok {
key = retrieveActualKey(jwtHeader, string(payload), key)
if err = verifier.Verify(secured, signature, key); err == nil {
return string(payload), jwtHeader, nil
}
return "", nil, err
}
return "", nil, errors.New(fmt.Sprintf("jwt.Decode(): Unknown algorithm: '%v'", alg))
}
func decrypt(parts [][]byte, key interface{}) (plainText string, headers map[string]interface{}, err error) {
header, encryptedCek, iv, cipherText, authTag := parts[0], parts[1], parts[2], parts[3], parts[4]
var jwtHeader map[string]interface{}
if e := json.Unmarshal(header, &jwtHeader); e != nil {
return "", nil, e
}
alg := jwtHeader["alg"].(string)
enc := jwtHeader["enc"].(string)
aad := []byte(compact.Serialize(header))
var keyMgmtAlg JwaAlgorithm
var encAlg JweEncryption
var zipAlg JwcAlgorithm
var cek, plainBytes []byte
var ok bool
if keyMgmtAlg, ok = jwaAlgorithms[alg]; ok {
if encAlg, ok = jweEncryptors[enc]; ok {
key = retrieveActualKey(jwtHeader, string(cipherText), key)
if cek, err = keyMgmtAlg.Unwrap(encryptedCek, key, encAlg.KeySizeBits(), jwtHeader); err == nil {
if plainBytes, err = encAlg.Decrypt(aad, cek, iv, cipherText, authTag); err == nil {
if zip, compressed := jwtHeader["zip"].(string); compressed {
if zipAlg, ok = jwcCompressors[zip]; !ok {
return "", nil, errors.New(fmt.Sprintf("jwt.decrypt(): Unknown compression algorithm '%v'", zip))
}
plainBytes = zipAlg.Decompress(plainBytes)
}
return string(plainBytes), jwtHeader, nil
}
return "", nil, err
}
return "", nil, err
}
return "", nil, errors.New(fmt.Sprintf("jwt.decrypt(): Unknown encryption algorithm '%v'", enc))
}
return "", nil, errors.New(fmt.Sprintf("jwt.decrypt(): Unknown key management algorithm '%v'", alg))
}
func retrieveActualKey(headers map[string]interface{}, payload string, key interface{}) interface{} {
if keyCallback, ok := key.(func(headers map[string]interface{}, payload string) interface{}); ok {
return keyCallback(headers, payload)
}
return key
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
package kdf
import (
"hash"
"math"
"github.com/dvsekhvalnov/jose2go/arrays"
)
// DeriveConcatKDF implements NIST SP 800-56A Concatenation Key Derivation Function. Derives
// key material of keydatalen bits size given Z (sharedSecret), OtherInfo (AlgorithmID |
// PartyUInfo | PartyVInfo | SuppPubInfo | SuppPrivInfo) and hash function
func DeriveConcatKDF(keydatalen int, sharedSecret, algId, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo []byte, h hash.Hash) []byte {
otherInfo := arrays.Concat(algId, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo)
keyLenBytes := keydatalen >> 3
reps := int(math.Ceil(float64(keyLenBytes) / float64(h.Size())))
if reps > 4294967295 {
panic("kdf.DeriveConcatKDF: too much iterations (more than 2^32-1).")
}
dk:=make([]byte, 0, keyLenBytes)
for counter := 1;counter <= reps;counter++ {
h.Reset()
counterBytes:=arrays.UInt32ToBytes(uint32(counter))
h.Write(counterBytes)
h.Write(sharedSecret)
h.Write(otherInfo)
dk = h.Sum(dk)
}
return dk[:keyLenBytes]
}

View File

@@ -0,0 +1,57 @@
package kdf
import (
. "gopkg.in/check.v1"
"crypto/sha256"
"github.com/dvsekhvalnov/jose2go/arrays"
"github.com/dvsekhvalnov/jose2go/base64url"
)
var z=[]byte{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31}
var z2=[]byte{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47}
var algId=prependDatalen([]byte("alg"))
var partyUInfo=datalenFormat("pui")
var partyVInfo=datalenFormat("pvi")
func (s *TestSuite) Test256Secret_256Key(c *C) {
//when
test:=DeriveConcatKDF(256,z,algId,partyUInfo,partyVInfo,arrays.UInt32ToBytes(256),nil,sha256.New())
//then
c.Assert(test, DeepEquals, []byte{190, 69, 15, 62, 38, 64, 30, 141, 208, 163, 55, 202, 18, 71, 176, 174, 114, 221, 249, 255, 207, 131, 190, 77, 12, 115, 220, 144, 102, 149, 78, 28})
}
func (s *TestSuite) Test256Secret_384Key(c *C) {
//when
test:=DeriveConcatKDF(384,z,algId,partyUInfo,partyVInfo,arrays.UInt32ToBytes(384),nil,sha256.New())
//then
c.Assert(test, DeepEquals, []byte{187, 16, 110, 253, 151, 199, 57, 235, 219, 8, 73, 191, 208, 108, 63, 241, 235, 137, 178, 149, 3, 199, 216, 99, 105, 217, 45, 114, 109, 213, 83, 198, 52, 101, 99, 146, 193, 31, 172, 157, 19, 140, 25, 202, 153, 92, 252, 192})
}
func (s *TestSuite) Test256Secret_521Key(c *C) {
//when
test:=DeriveConcatKDF(521,z,algId,partyUInfo,partyVInfo,arrays.UInt32ToBytes(521),nil,sha256.New())
//then
c.Assert(test, DeepEquals, []byte{245, 40, 159, 13, 25, 70, 170, 74, 210, 240, 242, 224, 37, 215, 23, 201, 126, 90, 108, 103, 205, 180, 48, 193, 131, 40, 86, 183, 97, 144, 1, 150, 154, 186, 196, 127, 6, 19, 17, 230, 75, 144, 229, 195, 61, 240, 20, 173, 167, 159, 50, 103, 133, 177, 241, 145, 134, 84, 50, 246, 157, 252, 51, 24, 35})
}
func (s *TestSuite) Test384Secret_256Key(c *C) {
//when
test:=DeriveConcatKDF(256,z2,algId,partyUInfo,partyVInfo,arrays.UInt32ToBytes(256),nil,sha256.New())
//then
c.Assert(test, DeepEquals, []byte{27, 55, 33, 99, 20, 191, 202, 69, 84, 176, 250, 108, 99, 7, 91, 49, 200, 47, 219, 142, 190, 216, 197, 154, 235, 17, 76, 12, 165, 75, 201, 108})
}
//test utils
func datalenFormat(str string) []byte {
bytes,_:=base64url.Decode(str)
return prependDatalen(bytes)
}
func prependDatalen(bytes []byte) []byte {
return arrays.Concat(arrays.UInt32ToBytes(uint32(len(bytes))),bytes)
}

View File

@@ -0,0 +1,62 @@
// package kdf contains implementations of various key derivation functions
package kdf
import (
"crypto/hmac"
"fmt"
"github.com/dvsekhvalnov/jose2go/arrays"
"hash"
"math"
)
// DerivePBKDF2 implements Password Based Key Derivation Function 2, RFC 2898. Derives key of keyBitLength size, given password, salt, iteration count and hash function
func DerivePBKDF2(password, salt []byte, iterationCount, keyBitLength int, h hash.Hash) []byte {
prf := hmac.New(func() hash.Hash { return h }, password)
hLen := prf.Size()
dkLen := keyBitLength >> 3 //size of derived key in bytes
l := int(math.Ceil(float64(dkLen) / float64(hLen))) // l = CEIL (dkLen / hLen)
r := dkLen - (l-1)*hLen
// 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and stop.
if dkLen > 4294967295 {
panic(fmt.Sprintf("kdf.DerivePBKDF2: expects derived key size to be not more that (2^32-1) bits, but was requested %v bits.", keyBitLength))
}
dk := make([]byte, 0, dkLen)
for i := 0; i < l; i++ {
t := f(salt, iterationCount, i+1, prf) // T_l = F (P, S, c, l)
if i == (l - 1) {
t = t[:r]
} // truncate last block to r bits
dk = append(dk, t...) // DK = T_1 || T_2 || ... || T_l<0..r-1>
}
return dk
}
func f(salt []byte, iterationCount, blockIndex int, prf hash.Hash) []byte {
prf.Reset()
prf.Write(salt)
prf.Write(arrays.UInt32ToBytes(uint32(blockIndex)))
u := prf.Sum(nil) // U_1 = PRF (P, S || INT (i))
result := u
for i := 2; i <= iterationCount; i++ {
prf.Reset()
prf.Write(u)
u = prf.Sum(nil) // U_c = PRF (P, U_{c-1}) .
result = arrays.Xor(result, u) // U_1 \xor U_2 \xor ... \xor U_c
}
return result
}

View File

@@ -0,0 +1,112 @@
package kdf
import (
"testing"
. "gopkg.in/check.v1"
"crypto/sha256"
"crypto/sha512"
)
func Test(t *testing.T) { TestingT(t) }
type TestSuite struct{}
var _ = Suite(&TestSuite{})
var password=[]byte("password")
var salt=[]byte("salt")
func (s *TestSuite) TestDerivePbkdf2Sha256Count1(c *C) {
//when
test:=DerivePBKDF2(password, salt, 1, 256, sha256.New())
//then
c.Assert(test, DeepEquals, []byte{ 18, 15, 182, 207, 252, 248, 179, 44, 67, 231, 34, 82, 86, 196, 248, 55, 168, 101, 72, 201, 44, 204, 53, 72, 8, 5, 152, 124, 183, 11, 225, 123 })
}
func (s *TestSuite) TestDerivePbkdf2Sha256Count2(c *C) {
//when
test:=DerivePBKDF2(password, salt, 2, 256, sha256.New())
//then
c.Assert(test, DeepEquals, []byte{ 174, 77, 12, 149, 175, 107, 70, 211, 45, 10, 223, 249, 40, 240, 109, 208, 42, 48, 63, 142, 243, 194, 81, 223, 214, 226, 216, 90, 149, 71, 76, 67 })
}
func (s *TestSuite) TestDerivePbkdf2Sha256Count4096(c *C) {
//when
test:=DerivePBKDF2(password, salt, 4096, 256, sha256.New())
//then
c.Assert(test, DeepEquals, []byte{ 197, 228, 120, 213, 146, 136, 200, 65, 170, 83, 13, 182, 132, 92, 76, 141, 150, 40, 147, 160, 1, 206, 78, 17, 164, 150, 56, 115, 170, 152, 19, 74 })
}
func (s *TestSuite) TestDerivePbkdf2Sha256Count4096Len320(c *C) {
//when
test:=DerivePBKDF2([]byte("passwordPASSWORDpassword"), []byte("saltSALTsaltSALTsaltSALTsaltSALTsalt"), 4096, 320, sha256.New())
//then
c.Assert(test, DeepEquals, []byte{ 52, 140, 137, 219, 203, 211, 43, 47, 50, 216, 20, 184, 17, 110, 132, 207, 43, 23, 52, 126, 188, 24, 0, 24, 28, 78, 42, 31, 184, 221, 83, 225, 198, 53, 81, 140, 125, 172, 71, 233 })
}
func (s *TestSuite) TestDerivePbkdf2Sha384Count1Len384(c *C) {
//when
test:=DerivePBKDF2(password,salt, 1, 384, sha512.New384())
//then
c.Assert(test, DeepEquals, []byte{ 192, 225, 79, 6, 228, 158, 50, 215, 63, 159, 82, 221, 241, 208, 197, 199, 25, 22, 9, 35, 54, 49, 218, 221, 118, 165, 103, 219, 66, 183, 134, 118, 179, 143, 200, 0, 204, 83, 221, 182, 66, 245, 199, 68, 66, 230, 43, 228 })
}
func (s *TestSuite) TestDerivePbkdf2Sha384Count2Len384(c *C) {
//when
test:=DerivePBKDF2(password,salt, 2, 384, sha512.New384())
//then
c.Assert(test, DeepEquals, []byte{ 84, 247, 117, 198, 215, 144, 242, 25, 48, 69, 145, 98, 252, 83, 93, 191, 4, 169, 57, 24, 81, 39, 1, 106, 4, 23, 106, 7, 48, 198, 241, 244, 251, 72, 131, 42, 209, 38, 27, 170, 221, 44, 237, 213, 8, 20, 177, 200 })
}
func (s *TestSuite) TestDerivePbkdf2Sha384Count4096Len384(c *C) {
//when
test:=DerivePBKDF2(password,salt, 4096, 384, sha512.New384())
//then
c.Assert(test, DeepEquals, []byte{ 85, 151, 38, 190, 56, 219, 18, 91, 200, 94, 215, 137, 95, 110, 60, 245, 116, 199, 160, 28, 8, 12, 52, 71, 219, 30, 138, 118, 118, 77, 235, 60, 48, 123, 148, 133, 63, 190, 66, 79, 100, 136, 197, 244, 241, 40, 150, 38 })
}
func (s *TestSuite) TestDerivePbkdf2Sha384Count4096Len768(c *C) {
//when
test:=DerivePBKDF2([]byte("passwordPASSWORDpassword"), []byte("saltSALTsaltSALTsaltSALTsaltSALTsalt"), 4096, 768, sha512.New384())
//then
c.Assert(test, DeepEquals, []byte{ 129, 145, 67, 173, 102, 223, 154, 85, 37, 89, 185, 225, 49, 197, 42, 230, 197, 193, 176, 238, 209, 143, 77, 40, 59, 140, 92, 158, 174, 185, 43, 57, 44, 20, 124, 194, 210, 134, 157, 88, 255, 226, 247, 218, 19, 209, 95, 141, 146, 87, 33, 240, 237, 26, 250, 250, 36, 72, 13, 85, 207, 96, 96, 177, 127, 17, 42, 61, 231, 76, 174, 37, 253, 243, 86, 158, 36, 127, 41, 228, 219, 184, 68, 33, 132, 120, 34, 234, 153, 189, 32, 40, 60, 58, 37, 166 })
}
func (s *TestSuite) TestDerivePbkdf2Sha512Count1Len512(c *C) {
//when
test:=DerivePBKDF2(password, salt, 1, 512, sha512.New())
//then
c.Assert(test, DeepEquals, []byte{ 134, 127, 112, 207, 26, 222, 2, 207, 243, 117, 37, 153, 163, 165, 61, 196, 175, 52, 199, 166, 105, 129, 90, 229, 213, 19, 85, 78, 28, 140, 242, 82, 192, 45, 71, 10, 40, 90, 5, 1, 186, 217, 153, 191, 233, 67, 192, 143, 5, 2, 53, 215, 214, 139, 29, 165, 94, 99, 247, 59, 96, 165, 127, 206 })
}
func (s *TestSuite) TestDerivePbkdf2Sha512Count2Len512(c *C) {
//when
test:=DerivePBKDF2(password, salt, 2, 512, sha512.New())
//then
c.Assert(test, DeepEquals, []byte{ 225, 217, 193, 106, 166, 129, 112, 138, 69, 245, 199, 196, 226, 21, 206, 182, 110, 1, 26, 46, 159, 0, 64, 113, 63, 24, 174, 253, 184, 102, 213, 60, 247, 108, 171, 40, 104, 163, 155, 159, 120, 64, 237, 206, 79, 239, 90, 130, 190, 103, 51, 92, 119, 166, 6, 142, 4, 17, 39, 84, 242, 124, 207, 78 })
}
func (s *TestSuite) TestDerivePbkdf2Sha512Count4096Len512(c *C) {
//when
test:=DerivePBKDF2(password, salt, 4096, 512, sha512.New())
//then
c.Assert(test, DeepEquals, []byte{ 209, 151, 177, 179, 61, 176, 20, 62, 1, 139, 18, 243, 209, 209, 71, 158, 108, 222, 189, 204, 151, 197, 192, 248, 127, 105, 2, 224, 114, 244, 87, 181, 20, 63, 48, 96, 38, 65, 179, 213, 92, 211, 53, 152, 140, 179, 107, 132, 55, 96, 96, 236, 213, 50, 224, 57, 183, 66, 162, 57, 67, 74, 242, 213 })
}
func (s *TestSuite) TestDerivePbkdf2Sha256Count4096Len1024(c *C) {
//when
test:=DerivePBKDF2([]byte("passwordPASSWORDpassword"), []byte("saltSALTsaltSALTsaltSALTsaltSALTsalt"), 4096, 1024, sha512.New())
//then
c.Assert(test, DeepEquals, []byte{ 140, 5, 17, 244, 198, 229, 151, 198, 172, 99, 21, 216, 240, 54, 46, 34, 95, 60, 80, 20, 149, 186, 35, 184, 104, 192, 5, 23, 77, 196, 238, 113, 17, 91, 89, 249, 230, 12, 217, 83, 47, 163, 62, 15, 117, 174, 254, 48, 34, 92, 88, 58, 24, 108, 216, 43, 212, 218, 234, 151, 36, 163, 211, 184, 4, 247, 91, 221, 65, 73, 79, 163, 36, 202, 178, 75, 204, 104, 15, 179, 185, 106, 48, 207, 93, 33, 250, 195, 194, 135, 89, 19, 145, 159, 51, 153, 177, 217, 206, 126, 181, 76, 149, 186, 73, 17, 133, 150, 207, 116, 101, 113, 155, 190, 2, 196, 236, 171, 27, 21, 65, 41, 140, 50, 29, 19, 198, 246 })
}

View File

@@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICJjCCAc6gAwIBAgIJAOCtH/xv+cfpMAkGByqGSM49BAEwRTELMAkGA1UEBhMC
QVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdp
dHMgUHR5IEx0ZDAeFw0xNDA4MTIxMTU5MTVaFw0xODA1MDgxMTU5MTVaMEUxCzAJ
BgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l
dCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASqGi1p
Eudyh+Nw//zeAGIYSQG/H/q/x6Xz2TIg3GN4hbD5BS1f4/vEitpui+TpmtJyggzo
x5D55N9kjq0X/hyyo4GnMIGkMB0GA1UdDgQWBBQvGSEecx9JdDECRIorVpeWy7oA
ujB1BgNVHSMEbjBsgBQvGSEecx9JdDECRIorVpeWy7oAuqFJpEcwRTELMAkGA1UE
BhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdp
ZGdpdHMgUHR5IEx0ZIIJAOCtH/xv+cfpMAwGA1UdEwQFMAMBAf8wCQYHKoZIzj0E
AQNHADBEAiAUQheZrGjbsy6PfpWGZEhTFzqvBVXtbqtz+6aTkOCrCQIgLfvw9C+0
SDn/abV4NtgYOM0OLkoNRTCIzzguHxhhaJ4=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIA/32XgQpS/tKRhw8jSdr8ivBmMyD/vbG5KT3s3XPArcoAoGCCqGSM49
AwEHoUQDQgAEqhotaRLncofjcP/83gBiGEkBvx/6v8el89kyINxjeIWw+QUtX+P7
xIrabovk6ZrScoIM6MeQ+eTfZI6tF/4csg==
-----END EC PRIVATE KEY-----

View File

@@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgD/fZeBClL+0pGHDy
NJ2vyK8GYzIP+9sbkpPezdc8CtyhRANCAASqGi1pEudyh+Nw//zeAGIYSQG/H/q/
x6Xz2TIg3GN4hbD5BS1f4/vEitpui+TpmtJyggzox5D55N9kjq0X/hyy
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqhotaRLncofjcP/83gBiGEkBvx/6
v8el89kyINxjeIWw+QUtX+P7xIrabovk6ZrScoIM6MeQ+eTfZI6tF/4csg==
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,88 @@
//package ecc provides helpers for creating elliptic curve leys
package ecc
import (
"math/big"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"encoding/pem"
"errors"
)
// ReadPublic loads ecdsa.PublicKey from given PKCS1 X509 or PKIX blobs
func ReadPublic(raw []byte) (key *ecdsa.PublicKey,err error) {
var encoded *pem.Block
if encoded, _ = pem.Decode(raw); encoded == nil {
return nil, errors.New("Ecc.ReadPublic(): Key must be PEM encoded PKCS1 X509 certificate or PKIX EC public key")
}
var parsedKey interface{}
var cert *x509.Certificate
if parsedKey, err = x509.ParsePKIXPublicKey(encoded.Bytes); err != nil {
if cert,err = x509.ParseCertificate(encoded.Bytes);err!=nil {
return nil, err
}
parsedKey=cert.PublicKey
}
var ok bool
if key, ok = parsedKey.(*ecdsa.PublicKey); !ok {
return nil, errors.New("Ecc.ReadPublic(): Key is not a valid *ecdsa.PublicKey")
}
return key, nil
}
// ReadPrivate loads ecdsa.PrivateKey from given PKCS1 or PKCS8 blobs
func ReadPrivate(raw []byte) (key *ecdsa.PrivateKey,err error) {
var encoded *pem.Block
if encoded, _ = pem.Decode(raw); encoded == nil {
return nil, errors.New("Ecc.ReadPrivate(): Key must be PEM encoded PKCS1 or PKCS8 EC private key")
}
var parsedKey interface{}
if parsedKey,err=x509.ParseECPrivateKey(encoded.Bytes);err!=nil {
if parsedKey, err = x509.ParsePKCS8PrivateKey(encoded.Bytes);err!=nil {
return nil,err
}
}
var ok bool
if key,ok=parsedKey.(*ecdsa.PrivateKey);!ok {
return nil, errors.New("Ecc.ReadPrivate(): Key is not valid *ecdsa.PrivateKey")
}
return key,nil
}
// NewPublic constructs ecdsa.PublicKey from given (X,Y)
func NewPublic(x,y []byte) (*ecdsa.PublicKey) {
return &ecdsa.PublicKey{ Curve: curve(len(x)),
X:new(big.Int).SetBytes(x),
Y:new(big.Int).SetBytes(y) }
}
// NewPrivate constructs ecdsa.PrivateKey from given (X,Y) and D
func NewPrivate(x,y,d []byte) (*ecdsa.PrivateKey) {
return &ecdsa.PrivateKey {D:new(big.Int).SetBytes(d),
PublicKey: ecdsa.PublicKey{ Curve:curve(len(x)),
X:new(big.Int).SetBytes(x),
Y:new(big.Int).SetBytes(y)}}
}
func curve(size int) (elliptic.Curve) {
switch size {
case 32: return elliptic.P256()
case 48: return elliptic.P384()
case 65,66: return elliptic.P521() //adjust for P-521 curve, which can be 65 or 66 bytes
default: return nil //unsupported curve
}
}

View File

@@ -0,0 +1,113 @@
package ecc
import (
"testing"
. "gopkg.in/check.v1"
"io/ioutil"
"math/big"
"crypto/elliptic"
)
func Test(t *testing.T) { TestingT(t) }
type TestSuite struct{}
var _ = Suite(&TestSuite{})
func (s *TestSuite) TestNewPublic(c *C) {
//given
x:=[]byte {4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9}
y:=[]byte {131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53}
//when
test:=NewPublic(x,y)
//then
c.Assert(test.X, DeepEquals, bigInt("2010878128539620107131539503221291822343443356718189500659356750794038206985"))
c.Assert(test.Y, DeepEquals, bigInt("59457993017710823357637488495120101390437944162821778556218889662829000218677"))
c.Assert(test.Curve, Equals, elliptic.P256())
}
func (s *TestSuite) TestNewPrivate(c *C) {
//given
x:=[]byte {4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9}
y:=[]byte {131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53}
d:=[]byte{ 42, 148, 231, 48, 225, 196, 166, 201, 23, 190, 229, 199, 20, 39, 226, 70, 209, 148, 29, 70, 125, 14, 174, 66, 9, 198, 80, 251, 95, 107, 98, 206 }
//when
test:=NewPrivate(x,y,d)
//then
c.Assert(test.X, DeepEquals, bigInt("2010878128539620107131539503221291822343443356718189500659356750794038206985"))
c.Assert(test.Y, DeepEquals, bigInt("59457993017710823357637488495120101390437944162821778556218889662829000218677"))
c.Assert(test.D, DeepEquals, bigInt("19260228627344101198652694952536756709538941185117188878548538012226554651342"))
c.Assert(test.Curve, Equals, elliptic.P256())
}
func (s *TestSuite) TestReadPublicPKIX(c *C) {
//given
keyBytes, _ := ioutil.ReadFile("./ec_public.key")
//when
test,e := ReadPublic(keyBytes)
//then
c.Assert(e, IsNil)
c.Assert(test.X, DeepEquals, bigInt("76939435694210362824363841832595476784225842365248086547769733757874741672069"))
c.Assert(test.Y, DeepEquals, bigInt("80047042001812490693675653292813886154388201612539715595028491948003157744818"))
c.Assert(test.Curve, Equals, elliptic.P256())
}
func (s *TestSuite) TestReadPublicPKCS1(c *C) {
//given
keyBytes, _ := ioutil.ReadFile("./ec_cert.pem")
//when
test,e := ReadPublic(keyBytes)
//then
c.Assert(e, IsNil)
c.Assert(test.X, DeepEquals, bigInt("76939435694210362824363841832595476784225842365248086547769733757874741672069"))
c.Assert(test.Y, DeepEquals, bigInt("80047042001812490693675653292813886154388201612539715595028491948003157744818"))
c.Assert(test.Curve, Equals, elliptic.P256())
}
func (s *TestSuite) TestReadPrivatePKCS1(c *C) {
//given
keyBytes, _ := ioutil.ReadFile("./ec_private.key")
//when
test,e := ReadPrivate(keyBytes)
//then
c.Assert(e, IsNil)
c.Assert(test.X, DeepEquals, bigInt("76939435694210362824363841832595476784225842365248086547769733757874741672069"))
c.Assert(test.Y, DeepEquals, bigInt("80047042001812490693675653292813886154388201612539715595028491948003157744818"))
c.Assert(test.D, DeepEquals, bigInt("7222604869653061109880849859470152714201198955914263913554931724612175399644"))
c.Assert(test.Curve, Equals, elliptic.P256())
}
func (s *TestSuite) TestReadPrivatePKCS8(c *C) {
//given
keyBytes, _ := ioutil.ReadFile("./ec_private.pem")
//when
test,e := ReadPrivate(keyBytes)
//then
c.Assert(e, IsNil)
c.Assert(test.X, DeepEquals, bigInt("76939435694210362824363841832595476784225842365248086547769733757874741672069"))
c.Assert(test.Y, DeepEquals, bigInt("80047042001812490693675653292813886154388201612539715595028491948003157744818"))
c.Assert(test.D, DeepEquals, bigInt("7222604869653061109880849859470152714201198955914263913554931724612175399644"))
c.Assert(test.Curve, Equals, elliptic.P256())
}
//utils
func bigInt(value string) *big.Int {
i:=new (big.Int)
i.SetString(value,10)
return i
}

View File

@@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALx97GSHCGkevvUS
sXMscNd+08MjO8BbkrzzlDuokJzVvQQprSEFYCO1ojp1UheAImeQvMe1wAWrGNfb
Fw34jQCSkv8liWLh5aHqHPrU8DTgKsL+XjHGaMwsg8y68pEmZrpyV/N49yXKlh3C
1PLnFJrTmZq0PHLqOXINNvMWFv7jAgMBAAECgYEAsYc0RzY7AK7ZkX7KrLw1h3FH
R2n+09wrp1UOzuWjVmOkw6/xBMHIW7mtkrt+1u1y+fIDK2GN+oi8PEl4PEtVmI8L
jaExLu5fsp/Z+BbHfcs4L5So9pdGZn5Dhfh606LWRZ0qqSjdtXitpNMrjx736+Jt
J6/kHlCdmYDyThtljbECQQDoDDAznyi6Yl2T+taoi2VcCP7wFAIYf3Mu6nqiEBhc
p1lVOuWjyR+mBU8+o6hDs40oVAOdpCdqtDJ3ppWABKKZAkEAz/LIq8Uwq8ephNwn
WOSuhkjUz+O01v74GHyS6tc7WGckFR7JS1cughXlRRq7hD1z1dhTYq0W2g4Yrujf
GFTW2wJBAIwtQLkOfqYJYgpQz3fFrZdpf8g77gAqjcRbtXVNT8o49gg8qhjFGK9M
KdDnQHCVeMJR7lU+oukcrhgFs+4/3pECQBcvX5ZfPwT4Fvt8PFrZ7GeGeUvQfJo4
BVtdkFfktXYu0cQVEaZ3yvSwEkb5Kw0ceOzP2MQ4vkKDrdbamf0xgF8CQFiz2P8h
Vq/Q3fFKCWamZ1olx08zo4x4y2kYKO275GSZabhiVoulVhUtRgi9BcPfW9kakqps
wEe4//EeSbl38Bk=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQC8fexkhwhpHr71ErFzLHDXftPDIzvAW5K885Q7qJCc1b0EKa0h
BWAjtaI6dVIXgCJnkLzHtcAFqxjX2xcN+I0AkpL/JYli4eWh6hz61PA04CrC/l4x
xmjMLIPMuvKRJma6clfzePclypYdwtTy5xSa05matDxy6jlyDTbzFhb+4wIDAQAB
AoGBALGHNEc2OwCu2ZF+yqy8NYdxR0dp/tPcK6dVDs7lo1ZjpMOv8QTByFu5rZK7
ftbtcvnyAythjfqIvDxJeDxLVZiPC42hMS7uX7Kf2fgWx33LOC+UqPaXRmZ+Q4X4
etOi1kWdKqko3bV4raTTK48e9+vibSev5B5QnZmA8k4bZY2xAkEA6AwwM58oumJd
k/rWqItlXAj+8BQCGH9zLup6ohAYXKdZVTrlo8kfpgVPPqOoQ7ONKFQDnaQnarQy
d6aVgASimQJBAM/yyKvFMKvHqYTcJ1jkroZI1M/jtNb++Bh8kurXO1hnJBUeyUtX
LoIV5UUau4Q9c9XYU2KtFtoOGK7o3xhU1tsCQQCMLUC5Dn6mCWIKUM93xa2XaX/I
O+4AKo3EW7V1TU/KOPYIPKoYxRivTCnQ50BwlXjCUe5VPqLpHK4YBbPuP96RAkAX
L1+WXz8E+Bb7fDxa2exnhnlL0HyaOAVbXZBX5LV2LtHEFRGmd8r0sBJG+SsNHHjs
z9jEOL5Cg63W2pn9MYBfAkBYs9j/IVav0N3xSglmpmdaJcdPM6OMeMtpGCjtu+Rk
mWm4YlaLpVYVLUYIvQXD31vZGpKqbMBHuP/xHkm5d/AZ
-----END RSA PRIVATE KEY-----

View File

@@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8fexkhwhpHr71ErFzLHDXftPD
IzvAW5K885Q7qJCc1b0EKa0hBWAjtaI6dVIXgCJnkLzHtcAFqxjX2xcN+I0AkpL/
JYli4eWh6hz61PA04CrC/l4xxmjMLIPMuvKRJma6clfzePclypYdwtTy5xSa05ma
tDxy6jlyDTbzFhb+4wIDAQAB
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBjTCB9wIEchk3tjANBgkqhkiG9w0BAQUFADAOMQwwCgYDVQQDEwNqd3QwHhcN
MTQwMTA4MTM0NDUxWhcNMTUwMjA3MjAwMDAwWjAOMQwwCgYDVQQDEwNqd3QwgZ8w
DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALx97GSHCGkevvUSsXMscNd+08MjO8Bb
krzzlDuokJzVvQQprSEFYCO1ojp1UheAImeQvMe1wAWrGNfbFw34jQCSkv8liWLh
5aHqHPrU8DTgKsL+XjHGaMwsg8y68pEmZrpyV/N49yXKlh3C1PLnFJrTmZq0PHLq
OXINNvMWFv7jAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAJcAJ4zK0ZZLUHRdxhQdj
e0Xv7fPYosE7gV0apbPJXnuhU1XH4XKFQQNYWyxjwfFSjghAyyZqlfcWgl0STOXx
JnFfEbSQL1EB5xKj9e1taMd+84mFXkVNvhN3wphe3EbPr9M99BRLic/MSGAqnJRS
OPIdhqg16dZmPJ4kCG8lLmc=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,62 @@
//package Rsa provides helpers for creating rsa leys
package Rsa
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
)
// ReadPrivate loads rsa.PrivateKey from PKCS1 or PKCS8 blobs
func ReadPrivate(raw []byte) (key *rsa.PrivateKey,err error) {
var encoded *pem.Block
if encoded, _ = pem.Decode(raw); encoded == nil {
return nil, errors.New("Rsa.NewPrivate(): Key must be PEM encoded PKCS1 or PKCS8 private key")
}
var parsedKey interface{}
if parsedKey,err=x509.ParsePKCS1PrivateKey(encoded.Bytes);err!=nil {
if parsedKey, err = x509.ParsePKCS8PrivateKey(encoded.Bytes);err!=nil {
return nil,err
}
}
var ok bool
if key,ok=parsedKey.(*rsa.PrivateKey);!ok {
return nil, errors.New("Rsa.NewPrivate(): Key is not valid *rsa.PrivateKey")
}
return key,nil
}
// ReadPublic loads rsa.PublicKey from PKIX or PKCS1 X509 blobs
func ReadPublic(raw []byte) (key *rsa.PublicKey,err error) {
var encoded *pem.Block
if encoded, _ = pem.Decode(raw); encoded == nil {
return nil, errors.New("Rsa.NewPublic(): Key must be PEM encoded PKCS1 X509 certificate or PKIX public key")
}
var parsedKey interface{}
var cert *x509.Certificate
if parsedKey, err = x509.ParsePKIXPublicKey(encoded.Bytes); err != nil {
if cert,err = x509.ParseCertificate(encoded.Bytes);err!=nil {
return nil, err
}
parsedKey=cert.PublicKey
}
var ok bool
if key, ok = parsedKey.(*rsa.PublicKey); !ok {
return nil, errors.New("Rsa.NewPublic(): Key is not a valid RSA public key")
}
return key, nil
}

View File

@@ -0,0 +1,85 @@
package Rsa
import (
"io/ioutil"
"math/big"
"testing"
. "gopkg.in/check.v1"
)
func Test(t *testing.T) { TestingT(t) }
type TestSuite struct{}
var _ = Suite(&TestSuite{})
func (s *TestSuite) TestNewPrivateRsaKeyPKCS1(c *C) {
//given
keyBytes, _ := ioutil.ReadFile("priv.pem")
//when
test,e := ReadPrivate(keyBytes)
//then
c.Assert(e, IsNil)
c.Assert(test.D, DeepEquals, bigInt("124664500442337916425629948081980373708538939606058968438393904444884872144817323585651521088406960965803456218656807473171367526874204593887158860695954252405916280866383288176587587469870736553178754458686625418367566925639753001190058223163696966510495553421774149906056482201169970508069587759758018186673"))
c.Assert(test.Primes, HasLen, 2)
c.Assert(test.Primes[0], DeepEquals, bigInt("12153319500662601635346432957857245055929638609873673015885135318360261418965835817520733410832489021447922460160007366037402643276624674886502627435389593"))
c.Assert(test.Primes[1], DeepEquals, bigInt("10891140096045830766355607974974880425030121636560867106153739255802668290122259699296919947637286459736524694263050343032461619592457541146877906031793883"))
c.Assert(test.E, Equals, 65537)
c.Assert(test.N, DeepEquals, bigInt("132363505313722155184876628715249052276006747427497555521215412160460427148722445294983292629743653314149228192824806664548107146354876411009845771623017501212568714555984634301944063168503260813720165931647377932654326833738173931173570212088015547040743996672392373035728084429156775233489529905624779259619"))
}
func (s *TestSuite) TestNewPrivateRsaKeyPKCS8(c *C) {
//given
keyBytes, _ := ioutil.ReadFile("priv.pem")
//when
test,e := ReadPrivate(keyBytes)
//then
c.Assert(e, IsNil)
c.Assert(test.D, DeepEquals, bigInt("124664500442337916425629948081980373708538939606058968438393904444884872144817323585651521088406960965803456218656807473171367526874204593887158860695954252405916280866383288176587587469870736553178754458686625418367566925639753001190058223163696966510495553421774149906056482201169970508069587759758018186673"))
c.Assert(test.Primes, HasLen, 2)
c.Assert(test.Primes[0], DeepEquals, bigInt("12153319500662601635346432957857245055929638609873673015885135318360261418965835817520733410832489021447922460160007366037402643276624674886502627435389593"))
c.Assert(test.Primes[1], DeepEquals, bigInt("10891140096045830766355607974974880425030121636560867106153739255802668290122259699296919947637286459736524694263050343032461619592457541146877906031793883"))
c.Assert(test.E, Equals, 65537)
c.Assert(test.N, DeepEquals, bigInt("132363505313722155184876628715249052276006747427497555521215412160460427148722445294983292629743653314149228192824806664548107146354876411009845771623017501212568714555984634301944063168503260813720165931647377932654326833738173931173570212088015547040743996672392373035728084429156775233489529905624779259619"))
}
func (s *TestSuite) TestNewPublicRsaKeyPKCS1(c *C) {
//given
keyBytes, _ := ioutil.ReadFile("pub.pem")
//when
test,e := ReadPublic(keyBytes)
//then
c.Assert(e, IsNil)
c.Assert(test.E, Equals, 65537)
c.Assert(test.N, DeepEquals, bigInt("132363505313722155184876628715249052276006747427497555521215412160460427148722445294983292629743653314149228192824806664548107146354876411009845771623017501212568714555984634301944063168503260813720165931647377932654326833738173931173570212088015547040743996672392373035728084429156775233489529905624779259619"))
}
func (s *TestSuite) TestNewPublicRsaKeyPKIX(c *C) {
//given
keyBytes, _ := ioutil.ReadFile("pub.key")
//when
test,e := ReadPublic(keyBytes)
//then
c.Assert(e, IsNil)
c.Assert(test.E, Equals, 65537)
c.Assert(test.N, DeepEquals, bigInt("132363505313722155184876628715249052276006747427497555521215412160460427148722445294983292629743653314149228192824806664548107146354876411009845771623017501212568714555984634301944063168503260813720165931647377932654326833738173931173570212088015547040743996672392373035728084429156775233489529905624779259619"))
}
//utils
func bigInt(value string) *big.Int {
i:=new (big.Int)
i.SetString(value,10)
return i
}

View File

@@ -0,0 +1,23 @@
// package padding provides various padding algorithms
package padding
import (
"bytes"
)
// Align left pads given byte array with zeros till it have at least bitSize length.
func Align(data []byte, bitSize int) []byte {
actual:=len(data)
required:=bitSize >> 3
if (bitSize % 8) > 0 {
required++ //extra byte if needed
}
if (actual >= required) {
return data
}
return append(bytes.Repeat([]byte{0}, required-actual), data...)
}

View File

@@ -0,0 +1,52 @@
package padding
import (
. "gopkg.in/check.v1"
)
func (s *TestSuite) TestAlignOneByte(c *C) {
//given
data:=[]byte{1,2,3}
//when
test:=Align(data,27)
//then
c.Assert(test, DeepEquals, []byte{0,1,2,3})
}
func (s *TestSuite) TestAlignMultiByte(c *C) {
//given
data:=[]byte{1,2,3}
//when
test:=Align(data,40)
//then
c.Assert(test, DeepEquals, []byte{0,0,1,2,3})
}
func (s *TestSuite) TestAlignMultiBytePartial(c *C) {
//given
data:=[]byte{1,2,3}
//when
test:=Align(data,43)
//then
c.Assert(test, DeepEquals, []byte{0,0,0,1,2,3})
}
func (s *TestSuite) TestAlignedArray(c *C) {
//given
data:=[]byte{1,2,3}
//when
test:=Align(data,24)
//then
c.Assert(test, DeepEquals, []byte{1,2,3})
}

View File

@@ -0,0 +1,38 @@
package padding
import (
"bytes"
)
// AddPkcs7 pads given byte array using pkcs7 padding schema till it has blockSize length in bytes
func AddPkcs7(data []byte, blockSize int) []byte {
var paddingCount int
if paddingCount = blockSize - (len(data) % blockSize);paddingCount == 0 {
paddingCount=blockSize
}
return append(data, bytes.Repeat([]byte{byte(paddingCount)}, paddingCount)...)
}
// RemovePkcs7 removes pkcs7 padding from previously padded byte array
func RemovePkcs7(padded []byte, blockSize int) []byte {
dataLen:=len(padded)
paddingCount:=int(padded[dataLen-1])
if(paddingCount > blockSize || paddingCount <= 0) {
return padded //data is not padded (or not padded correctly), return as is
}
padding := padded[dataLen-paddingCount : dataLen-1]
for _, b := range padding {
if int(b) != paddingCount {
return padded //data is not padded (or not padded correcly), return as is
}
}
return padded[:len(padded)-paddingCount] //return data - padding
}

View File

@@ -0,0 +1,109 @@
package padding
import (
"testing"
. "gopkg.in/check.v1"
)
func Test(t *testing.T) { TestingT(t) }
type TestSuite struct{}
var _ = Suite(&TestSuite{})
func (s *TestSuite) TestRemovePkcs7NoPadding(c *C) {
//given
padded:=[]byte{1,2,3,4,5,6,7,8}
//when
test:=RemovePkcs7(padded,8)
//then
c.Assert(test, DeepEquals, []byte{1,2,3,4,5,6,7,8})
}
func (s *TestSuite) TestRemovePkcs7(c *C) {
//given
padded:=[]byte{1,2,3,4,5,3,3,3}
//when
test:=RemovePkcs7(padded,8)
//then
c.Assert(test, DeepEquals, []byte{1,2,3,4,5})
}
func (s *TestSuite) TestRemovePkcs7OneBytePadding(c *C) {
//given
padded:=[]byte{1,2,3,4,5,1}
//when
test:=RemovePkcs7(padded,6)
//then
c.Assert(test, DeepEquals, []byte{1,2,3,4,5})
}
func (s *TestSuite) TestRemovePkcs7TrailingZeroByte(c *C) {
//given
padded:=[]byte{1,2,3,4,5,0}
//when
test:=RemovePkcs7(padded,6)
//then
c.Assert(test, DeepEquals, []byte{1,2,3,4,5,0})
}
func (s *TestSuite) TestRemovePkcs7ExtraBlockPadding(c *C) {
//given
padded:=[]byte{1,2,3,4,5,5,5,5,5,5}
//when
test:=RemovePkcs7(padded,5)
//then
c.Assert(test, DeepEquals, []byte{1,2,3,4,5})
}
func (s *TestSuite) TestRemovePkcs7TrailingByteGreaterBlockSize(c *C) {
//given
padded:=[]byte{1,2,3,4,5,10}
//when
test:=RemovePkcs7(padded,6)
//then
c.Assert(test, DeepEquals, []byte{1,2,3,4,5,10})
}
func (s *TestSuite) TestAddPkcs7(c *C) {
//given
in:=[]byte{1,2,3,4,5}
//when
test := AddPkcs7(in,8)
//then
c.Assert(test, DeepEquals, []byte{1,2,3,4,5,3,3,3})
}
func (s *TestSuite) TestAddPkcs7OneBytePadding(c *C) {
//given
in:=[]byte{1,2,3,4,5}
//when
test := AddPkcs7(in,6)
//then
c.Assert(test, DeepEquals, []byte{1,2,3,4,5,1})
}
func (s *TestSuite) TestAddPkcs7ExtraBlockPadding(c *C) {
//given
in:=[]byte{1,2,3,4,5,6,7,8}
//when
test := AddPkcs7(in,8)
//then
c.Assert(test, DeepEquals, []byte{1,2,3,4,5,6,7,8,8,8,8,8,8,8,8,8})
}

View File

@@ -0,0 +1,96 @@
package jose
import (
"errors"
"github.com/dvsekhvalnov/jose2go/arrays"
"github.com/dvsekhvalnov/jose2go/base64url"
"github.com/dvsekhvalnov/jose2go/kdf"
"crypto/sha256"
"crypto/sha512"
"hash"
)
func init() {
RegisterJwa(&Pbse2HmacAesKW{keySizeBits: 128,aesKW: &AesKW{ keySizeBits: 128}})
RegisterJwa(&Pbse2HmacAesKW{keySizeBits: 192,aesKW: &AesKW{ keySizeBits: 192}})
RegisterJwa(&Pbse2HmacAesKW{keySizeBits: 256,aesKW: &AesKW{ keySizeBits: 256}})
}
// PBSE2 with HMAC key management algorithm implementation
type Pbse2HmacAesKW struct{
keySizeBits int
aesKW JwaAlgorithm
}
func (alg *Pbse2HmacAesKW) Name() string {
switch alg.keySizeBits {
case 128: return PBES2_HS256_A128KW
case 192: return PBES2_HS384_A192KW
default: return PBES2_HS512_A256KW
}
}
func (alg *Pbse2HmacAesKW) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) {
if passphrase,ok:=key.(string); ok {
algId := []byte(header["alg"].(string))
iterationCount := 8192;
var saltInput []byte
if saltInput,err = arrays.Random(12);err!=nil {
return nil,nil,err
}
header["p2c"] = iterationCount;
header["p2s"] = base64url.Encode(saltInput)
salt := arrays.Concat(algId, []byte{0}, saltInput);
kek := kdf.DerivePBKDF2([]byte(passphrase), salt, iterationCount, alg.keySizeBits, alg.prf())
return alg.aesKW.WrapNewKey(cekSizeBits, kek, header)
}
return nil,nil,errors.New("Pbse2HmacAesKW.WrapNewKey(): expected key to be 'string' array")
}
func (alg *Pbse2HmacAesKW) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) {
if passphrase,ok:=key.(string); ok {
var p2s string
var p2c float64
if p2c,ok = header["p2c"].(float64);!ok {
return nil,errors.New("Pbse2HmacAesKW.Unwrap(): expected 'p2c' param in JWT header, but was not found.")
}
if p2s,ok = header["p2s"].(string);!ok {
return nil,errors.New("Pbse2HmacAesKW.Unwrap(): expected 'p2s' param in JWT header, but was not found.")
}
var saltInput []byte
algId := []byte(header["alg"].(string))
if saltInput,err = base64url.Decode(p2s);err!=nil {
return nil,err
}
salt := arrays.Concat(algId,[]byte{0},saltInput)
kek := kdf.DerivePBKDF2([]byte(passphrase), salt, int(p2c), alg.keySizeBits, alg.prf())
return alg.aesKW.Unwrap(encryptedCek, kek, cekSizeBits, header)
}
return nil,errors.New("Pbse2HmacAesKW.Unwrap(): expected key to be 'string' array")
}
func (alg *Pbse2HmacAesKW) prf() hash.Hash {
switch alg.keySizeBits {
case 128: return sha256.New()
case 192: return sha512.New384()
default: return sha512.New()
}
}

View File

@@ -0,0 +1,38 @@
package jose
import (
"errors"
)
// Plaintext (no signing) signing algorithm implementation
type Plaintext struct{}
func init() {
RegisterJws(new(Plaintext))
}
func (alg *Plaintext) Name() string {
return NONE
}
func (alg *Plaintext) Verify(securedInput []byte, signature []byte, key interface{}) error {
if key != nil {
return errors.New("Plaintext.Verify() expects key to be nil")
}
if len(signature) != 0 {
return errors.New("Plaintext.Verify() expects signature to be empty.")
}
return nil
}
func (alg *Plaintext) Sign(securedInput []byte, key interface{}) (signature []byte, err error) {
if key != nil {
return nil, errors.New("Plaintext.Verify() expects key to be nil")
}
return []byte{}, nil
}

View File

@@ -0,0 +1,57 @@
package jose
import (
"errors"
"crypto/rsa"
"crypto/rand"
"hash"
"crypto/sha1"
"crypto/sha256"
"github.com/dvsekhvalnov/jose2go/arrays"
)
// RS-AES using OAEP key management algorithm implementation
func init() {
RegisterJwa(&RsaOaep {shaSizeBits:1})
RegisterJwa(&RsaOaep {shaSizeBits:256})
}
type RsaOaep struct{
shaSizeBits int
// func shaF() hash.Hash
}
func (alg *RsaOaep) Name() string {
switch alg.shaSizeBits {
case 1: return RSA_OAEP
default: return RSA_OAEP_256
}
}
func (alg *RsaOaep) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) {
if pubKey,ok:=key.(*rsa.PublicKey);ok {
if cek,err = arrays.Random(cekSizeBits>>3);err==nil {
encryptedCek,err=rsa.EncryptOAEP(alg.sha(),rand.Reader,pubKey,cek,nil)
return
}
return nil,nil,err
}
return nil,nil,errors.New("RsaOaep.WrapNewKey(): expected key to be '*rsa.PublicKey'")
}
func (alg *RsaOaep) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) {
if privKey,ok:=key.(*rsa.PrivateKey);ok {
return rsa.DecryptOAEP(alg.sha(), rand.Reader, privKey, encryptedCek, nil)
}
return nil,errors.New("RsaOaep.Unwrap(): expected key to be '*rsa.PrivateKey'")
}
func (alg *RsaOaep) sha() hash.Hash {
switch alg.shaSizeBits {
case 1: return sha1.New()
default: return sha256.New()
}
}

View File

@@ -0,0 +1,41 @@
package jose
import (
"errors"
"crypto/rsa"
"crypto/rand"
"github.com/dvsekhvalnov/jose2go/arrays"
)
func init() {
RegisterJwa(new(RsaPkcs1v15))
}
// RS-AES using PKCS #1 v1.5 padding key management algorithm implementation
type RsaPkcs1v15 struct{
}
func (alg *RsaPkcs1v15) Name() string {
return RSA1_5
}
func (alg *RsaPkcs1v15) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) {
if pubKey,ok:=key.(*rsa.PublicKey);ok {
if cek,err = arrays.Random(cekSizeBits>>3);err==nil {
encryptedCek,err=rsa.EncryptPKCS1v15(rand.Reader,pubKey,cek)
return
}
return nil,nil,err
}
return nil,nil,errors.New("RsaPkcs1v15.WrapNewKey(): expected key to be '*rsa.PublicKey'")
}
func (alg *RsaPkcs1v15) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) {
if privKey,ok:=key.(*rsa.PrivateKey);ok {
return rsa.DecryptPKCS1v15(rand.Reader,privKey,encryptedCek)
}
return nil,errors.New("RsaPkcs1v15.Unwrap(): expected key to be '*rsa.PrivateKey'")
}

View File

@@ -0,0 +1,50 @@
package jose
import (
"crypto/rand"
"crypto/rsa"
"errors"
)
func init() {
RegisterJws(&RsaUsingSha{keySizeBits: 256})
RegisterJws(&RsaUsingSha{keySizeBits: 384})
RegisterJws(&RsaUsingSha{keySizeBits: 512})
}
// RSA using SHA signature algorithm implementation
type RsaUsingSha struct{
keySizeBits int
}
func (alg *RsaUsingSha) Name() string {
switch alg.keySizeBits {
case 256: return RS256
case 384: return RS384
default: return RS512
}
}
func (alg *RsaUsingSha) Verify(securedInput, signature []byte, key interface{}) error {
if pubKey,ok:=key.(*rsa.PublicKey);ok {
return rsa.VerifyPKCS1v15(pubKey, hashFunc(alg.keySizeBits), sha(alg.keySizeBits, securedInput), signature)
}
return errors.New("RsaUsingSha.Verify(): expects key to be '*rsa.PublicKey'")
}
func (alg *RsaUsingSha) Sign(securedInput []byte, key interface{}) (signature []byte, err error) {
if privKey,ok:=key.(*rsa.PrivateKey);ok {
return rsa.SignPKCS1v15(rand.Reader, privKey, hashFunc(alg.keySizeBits), sha(alg.keySizeBits, securedInput))
}
return nil,errors.New("RsaUsingSha.Sign(): expects key to be '*rsa.PrivateKey'")
}
func sha(keySizeBits int, input []byte) (hash []byte) {
hasher := hashAlg(keySizeBits)
hasher.Write(input)
return hasher.Sum(nil)
}

View File

@@ -0,0 +1,43 @@
package jose
import (
"crypto/rand"
"crypto/rsa"
"errors"
)
func init() {
RegisterJws(&RsaPssUsingSha{keySizeBits: 256, saltSizeBytes: 32})
RegisterJws(&RsaPssUsingSha{keySizeBits: 384, saltSizeBytes: 48})
RegisterJws(&RsaPssUsingSha{keySizeBits: 512, saltSizeBytes: 64})
}
// RSA with PSS using SHA signing algorithm implementation
type RsaPssUsingSha struct{
keySizeBits int
saltSizeBytes int
}
func (alg *RsaPssUsingSha) Name() string {
switch alg.keySizeBits {
case 256: return PS256
case 384: return PS384
default: return PS512
}
}
func (alg *RsaPssUsingSha) Verify(securedInput, signature []byte, key interface{}) error {
if pubKey,ok:=key.(*rsa.PublicKey);ok {
return rsa.VerifyPSS(pubKey, hashFunc(alg.keySizeBits), sha(alg.keySizeBits, securedInput), signature, &rsa.PSSOptions{SaltLength:alg.saltSizeBytes})
}
return errors.New("RsaPssUsingSha.Verify(): expects key to be '*rsa.PublicKey'")
}
func (alg *RsaPssUsingSha) Sign(securedInput []byte, key interface{}) (signature []byte, err error) {
if privKey,ok:=key.(*rsa.PrivateKey);ok {
return rsa.SignPSS(rand.Reader, privKey, hashFunc(alg.keySizeBits), sha(alg.keySizeBits, securedInput), &rsa.PSSOptions{SaltLength:alg.saltSizeBytes})
}
return nil,errors.New("RsaPssUsingSha.Sign(): expects key to be '*rsa.PrivateKey'")
}

View File

@@ -0,0 +1,24 @@
package jose
import (
"hash"
"crypto"
"crypto/sha256"
"crypto/sha512"
)
func hashFunc(keySizeBits int) crypto.Hash {
switch keySizeBits {
case 256: return crypto.SHA256
case 384: return crypto.SHA384
default: return crypto.SHA512
}
}
func hashAlg(keySizeBits int) hash.Hash {
switch keySizeBits {
case 256: return sha256.New()
case 384: return sha512.New384()
default: return sha512.New()
}
}

View File

@@ -5,3 +5,9 @@ type ErrMetaNotFound struct{}
func (err ErrMetaNotFound) Error() string {
return "no trust data available"
}
type ErrKeyNotAvailable struct{}
func (err ErrKeyNotAvailable) Error() string {
return "could not retrieve timestamp public key"
}

View File

@@ -204,6 +204,9 @@ func (s HTTPStore) GetKey(role string) ([]byte, error) {
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, ErrKeyNotAvailable{}
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {

21
Godeps/_workspace/src/github.com/jinzhu/gorm/License generated vendored Normal file
View 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.

1224
Godeps/_workspace/src/github.com/jinzhu/gorm/README.md generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,185 @@
package gorm
import (
"errors"
"fmt"
"reflect"
)
type Association struct {
Scope *Scope
PrimaryKey interface{}
Column string
Error error
Field *Field
}
func (association *Association) setErr(err error) *Association {
if err != nil {
association.Error = err
}
return association
}
func (association *Association) Find(value interface{}) *Association {
association.Scope.related(value, association.Column)
return association.setErr(association.Scope.db.Error)
}
func (association *Association) Append(values ...interface{}) *Association {
scope := association.Scope
field := association.Field
for _, value := range values {
reflectvalue := reflect.Indirect(reflect.ValueOf(value))
if reflectvalue.Kind() == reflect.Struct {
field.Set(reflect.Append(field.Field, reflectvalue))
} else if reflectvalue.Kind() == reflect.Slice {
field.Set(reflect.AppendSlice(field.Field, reflectvalue))
} else {
association.setErr(errors.New("invalid association type"))
}
}
scope.Search.Select(association.Column)
scope.callCallbacks(scope.db.parent.callback.updates)
return association.setErr(scope.db.Error)
}
func (association *Association) getPrimaryKeys(values ...interface{}) []interface{} {
primaryKeys := []interface{}{}
scope := association.Scope
for _, value := range values {
reflectValue := reflect.Indirect(reflect.ValueOf(value))
if reflectValue.Kind() == reflect.Slice {
for i := 0; i < reflectValue.Len(); i++ {
if primaryField := scope.New(reflectValue.Index(i).Interface()).PrimaryField(); !primaryField.IsBlank {
primaryKeys = append(primaryKeys, primaryField.Field.Interface())
}
}
} else if reflectValue.Kind() == reflect.Struct {
if primaryField := scope.New(value).PrimaryField(); !primaryField.IsBlank {
primaryKeys = append(primaryKeys, primaryField.Field.Interface())
}
}
}
return primaryKeys
}
func (association *Association) Delete(values ...interface{}) *Association {
primaryKeys := association.getPrimaryKeys(values...)
if len(primaryKeys) == 0 {
association.setErr(errors.New("no primary key found"))
} else {
scope := association.Scope
relationship := association.Field.Relationship
// many to many
if relationship.Kind == "many_to_many" {
sql := fmt.Sprintf("%v = ? AND %v IN (?)", scope.Quote(relationship.ForeignDBName), scope.Quote(relationship.AssociationForeignDBName))
query := scope.NewDB().Where(sql, association.PrimaryKey, primaryKeys)
if err := relationship.JoinTableHandler.Delete(relationship.JoinTableHandler, query, relationship); err == nil {
leftValues := reflect.Zero(association.Field.Field.Type())
for i := 0; i < association.Field.Field.Len(); i++ {
value := association.Field.Field.Index(i)
if primaryField := association.Scope.New(value.Interface()).PrimaryField(); primaryField != nil {
var included = false
for _, primaryKey := range primaryKeys {
if equalAsString(primaryKey, primaryField.Field.Interface()) {
included = true
}
}
if !included {
leftValues = reflect.Append(leftValues, value)
}
}
}
association.Field.Set(leftValues)
}
} else {
association.setErr(errors.New("delete only support many to many"))
}
}
return association
}
func (association *Association) Replace(values ...interface{}) *Association {
relationship := association.Field.Relationship
scope := association.Scope
if relationship.Kind == "many_to_many" {
field := association.Field.Field
oldPrimaryKeys := association.getPrimaryKeys(field.Interface())
association.Field.Set(reflect.Zero(association.Field.Field.Type()))
association.Append(values...)
newPrimaryKeys := association.getPrimaryKeys(field.Interface())
var addedPrimaryKeys = []interface{}{}
for _, newKey := range newPrimaryKeys {
hasEqual := false
for _, oldKey := range oldPrimaryKeys {
if reflect.DeepEqual(newKey, oldKey) {
hasEqual = true
break
}
}
if !hasEqual {
addedPrimaryKeys = append(addedPrimaryKeys, newKey)
}
}
for _, primaryKey := range association.getPrimaryKeys(values...) {
addedPrimaryKeys = append(addedPrimaryKeys, primaryKey)
}
if len(addedPrimaryKeys) > 0 {
sql := fmt.Sprintf("%v = ? AND %v NOT IN (?)", scope.Quote(relationship.ForeignDBName), scope.Quote(relationship.AssociationForeignDBName))
query := scope.NewDB().Where(sql, association.PrimaryKey, addedPrimaryKeys)
association.setErr(relationship.JoinTableHandler.Delete(relationship.JoinTableHandler, query, relationship))
}
} else {
association.setErr(errors.New("replace only support many to many"))
}
return association
}
func (association *Association) Clear() *Association {
relationship := association.Field.Relationship
scope := association.Scope
if relationship.Kind == "many_to_many" {
sql := fmt.Sprintf("%v = ?", scope.Quote(relationship.ForeignDBName))
query := scope.NewDB().Where(sql, association.PrimaryKey)
if err := relationship.JoinTableHandler.Delete(relationship.JoinTableHandler, query, relationship); err == nil {
association.Field.Set(reflect.Zero(association.Field.Field.Type()))
} else {
association.setErr(err)
}
} else {
association.setErr(errors.New("clear only support many to many"))
}
return association
}
func (association *Association) Count() int {
count := -1
relationship := association.Field.Relationship
scope := association.Scope
newScope := scope.New(association.Field.Field.Interface())
if relationship.Kind == "many_to_many" {
relationship.JoinTableHandler.JoinWith(relationship.JoinTableHandler, scope.NewDB(), association.Scope.Value).Table(newScope.TableName()).Count(&count)
} else if relationship.Kind == "has_many" || relationship.Kind == "has_one" {
whereSql := fmt.Sprintf("%v.%v = ?", newScope.QuotedTableName(), newScope.Quote(relationship.ForeignDBName))
countScope := scope.DB().Table(newScope.TableName()).Where(whereSql, association.PrimaryKey)
if relationship.PolymorphicType != "" {
countScope = countScope.Where(fmt.Sprintf("%v.%v = ?", newScope.QuotedTableName(), newScope.Quote(relationship.PolymorphicDBName)), scope.TableName())
}
countScope.Count(&count)
} else if relationship.Kind == "belongs_to" {
if v, ok := scope.FieldByName(association.Column); ok {
whereSql := fmt.Sprintf("%v.%v = ?", newScope.QuotedTableName(), newScope.Quote(relationship.ForeignDBName))
scope.DB().Table(newScope.TableName()).Where(whereSql, v).Count(&count)
}
}
return count
}

View File

@@ -0,0 +1,262 @@
package gorm_test
import (
"fmt"
"testing"
)
func TestHasOneAndHasManyAssociation(t *testing.T) {
DB.DropTable(Category{})
DB.DropTable(Post{})
DB.DropTable(Comment{})
DB.CreateTable(Category{})
DB.CreateTable(Post{})
DB.CreateTable(Comment{})
post := Post{
Title: "post 1",
Body: "body 1",
Comments: []*Comment{{Content: "Comment 1"}, {Content: "Comment 2"}},
Category: Category{Name: "Category 1"},
MainCategory: Category{Name: "Main Category 1"},
}
if err := DB.Save(&post).Error; err != nil {
t.Errorf("Got errors when save post")
}
if DB.First(&Category{}, "name = ?", "Category 1").Error != nil {
t.Errorf("Category should be saved")
}
var p Post
DB.First(&p, post.Id)
if post.CategoryId.Int64 == 0 || p.CategoryId.Int64 == 0 || post.MainCategoryId == 0 || p.MainCategoryId == 0 {
t.Errorf("Category Id should exist")
}
if DB.First(&Comment{}, "content = ?", "Comment 1").Error != nil {
t.Errorf("Comment 1 should be saved")
}
if post.Comments[0].PostId == 0 {
t.Errorf("Comment Should have post id")
}
var comment Comment
if DB.First(&comment, "content = ?", "Comment 2").Error != nil {
t.Errorf("Comment 2 should be saved")
}
if comment.PostId == 0 {
t.Errorf("Comment 2 Should have post id")
}
comment3 := Comment{Content: "Comment 3", Post: Post{Title: "Title 3", Body: "Body 3"}}
DB.Save(&comment3)
}
func TestRelated(t *testing.T) {
user := User{
Name: "jinzhu",
BillingAddress: Address{Address1: "Billing Address - Address 1"},
ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
Emails: []Email{{Email: "jinzhu@example.com"}, {Email: "jinzhu-2@example@example.com"}},
CreditCard: CreditCard{Number: "1234567890"},
Company: Company{Name: "company1"},
}
DB.Save(&user)
if user.CreditCard.ID == 0 {
t.Errorf("After user save, credit card should have id")
}
if user.BillingAddress.ID == 0 {
t.Errorf("After user save, billing address should have id")
}
if user.Emails[0].Id == 0 {
t.Errorf("After user save, billing address should have id")
}
var emails []Email
DB.Model(&user).Related(&emails)
if len(emails) != 2 {
t.Errorf("Should have two emails")
}
var emails2 []Email
DB.Model(&user).Where("email = ?", "jinzhu@example.com").Related(&emails2)
if len(emails2) != 1 {
t.Errorf("Should have two emails")
}
var user1 User
DB.Model(&user).Related(&user1.Emails)
if len(user1.Emails) != 2 {
t.Errorf("Should have only one email match related condition")
}
var address1 Address
DB.Model(&user).Related(&address1, "BillingAddressId")
if address1.Address1 != "Billing Address - Address 1" {
t.Errorf("Should get billing address from user correctly")
}
user1 = User{}
DB.Model(&address1).Related(&user1, "BillingAddressId")
if DB.NewRecord(user1) {
t.Errorf("Should get user from address correctly")
}
var user2 User
DB.Model(&emails[0]).Related(&user2)
if user2.Id != user.Id || user2.Name != user.Name {
t.Errorf("Should get user from email correctly")
}
var creditcard CreditCard
var user3 User
DB.First(&creditcard, "number = ?", "1234567890")
DB.Model(&creditcard).Related(&user3)
if user3.Id != user.Id || user3.Name != user.Name {
t.Errorf("Should get user from credit card correctly")
}
if !DB.Model(&CreditCard{}).Related(&User{}).RecordNotFound() {
t.Errorf("RecordNotFound for Related")
}
var company Company
if DB.Model(&user).Related(&company, "Company").RecordNotFound() || company.Name != "company1" {
t.Errorf("RecordNotFound for Related")
}
}
func TestManyToMany(t *testing.T) {
DB.Raw("delete from languages")
var languages = []Language{{Name: "ZH"}, {Name: "EN"}}
user := User{Name: "Many2Many", Languages: languages}
DB.Save(&user)
// Query
var newLanguages []Language
DB.Model(&user).Related(&newLanguages, "Languages")
if len(newLanguages) != len([]string{"ZH", "EN"}) {
t.Errorf("Query many to many relations")
}
DB.Model(&user).Association("Languages").Find(&newLanguages)
if len(newLanguages) != len([]string{"ZH", "EN"}) {
t.Errorf("Should be able to find many to many relations")
}
if DB.Model(&user).Association("Languages").Count() != len([]string{"ZH", "EN"}) {
t.Errorf("Count should return correct result")
}
// Append
DB.Model(&user).Association("Languages").Append(&Language{Name: "DE"})
if DB.Where("name = ?", "DE").First(&Language{}).RecordNotFound() {
t.Errorf("New record should be saved when append")
}
languageA := Language{Name: "AA"}
DB.Save(&languageA)
DB.Model(&User{Id: user.Id}).Association("Languages").Append(languageA)
languageC := Language{Name: "CC"}
DB.Save(&languageC)
DB.Model(&user).Association("Languages").Append(&[]Language{{Name: "BB"}, languageC})
DB.Model(&User{Id: user.Id}).Association("Languages").Append(&[]Language{{Name: "DD"}, {Name: "EE"}})
totalLanguages := []string{"ZH", "EN", "DE", "AA", "BB", "CC", "DD", "EE"}
if DB.Model(&user).Association("Languages").Count() != len(totalLanguages) {
t.Errorf("All appended languages should be saved")
}
// Delete
user.Languages = []Language{}
DB.Model(&user).Association("Languages").Find(&user.Languages)
var language Language
DB.Where("name = ?", "EE").First(&language)
DB.Model(&user).Association("Languages").Delete(language, &language)
if DB.Model(&user).Association("Languages").Count() != len(totalLanguages)-1 || len(user.Languages) != len(totalLanguages)-1 {
t.Errorf("Relations should be deleted with Delete")
}
if DB.Where("name = ?", "EE").First(&Language{}).RecordNotFound() {
t.Errorf("Language EE should not be deleted")
}
DB.Where("name IN (?)", []string{"CC", "DD"}).Find(&languages)
user2 := User{Name: "Many2Many_User2", Languages: languages}
DB.Save(&user2)
DB.Model(&user).Association("Languages").Delete(languages, &languages)
if DB.Model(&user).Association("Languages").Count() != len(totalLanguages)-3 || len(user.Languages) != len(totalLanguages)-3 {
t.Errorf("Relations should be deleted with Delete")
}
if DB.Model(&user2).Association("Languages").Count() == 0 {
t.Errorf("Other user's relations should not be deleted")
}
// Replace
var languageB Language
DB.Where("name = ?", "BB").First(&languageB)
DB.Model(&user).Association("Languages").Replace(languageB)
if len(user.Languages) != 1 || DB.Model(&user).Association("Languages").Count() != 1 {
t.Errorf("Relations should be replaced")
}
DB.Model(&user).Association("Languages").Replace(&[]Language{{Name: "FF"}, {Name: "JJ"}})
if len(user.Languages) != 2 || DB.Model(&user).Association("Languages").Count() != len([]string{"FF", "JJ"}) {
t.Errorf("Relations should be replaced")
}
// Clear
DB.Model(&user).Association("Languages").Clear()
if len(user.Languages) != 0 || DB.Model(&user).Association("Languages").Count() != 0 {
t.Errorf("Relations should be cleared")
}
}
func TestForeignKey(t *testing.T) {
for _, structField := range DB.NewScope(&User{}).GetStructFields() {
for _, foreignKey := range []string{"BillingAddressID", "ShippingAddressId", "CompanyID"} {
if structField.Name == foreignKey && !structField.IsForeignKey {
t.Errorf(fmt.Sprintf("%v should be foreign key", foreignKey))
}
}
}
for _, structField := range DB.NewScope(&Email{}).GetStructFields() {
for _, foreignKey := range []string{"UserId"} {
if structField.Name == foreignKey && !structField.IsForeignKey {
t.Errorf(fmt.Sprintf("%v should be foreign key", foreignKey))
}
}
}
for _, structField := range DB.NewScope(&Post{}).GetStructFields() {
for _, foreignKey := range []string{"CategoryId", "MainCategoryId"} {
if structField.Name == foreignKey && !structField.IsForeignKey {
t.Errorf(fmt.Sprintf("%v should be foreign key", foreignKey))
}
}
}
for _, structField := range DB.NewScope(&Comment{}).GetStructFields() {
for _, foreignKey := range []string{"PostId"} {
if structField.Name == foreignKey && !structField.IsForeignKey {
t.Errorf(fmt.Sprintf("%v should be foreign key", foreignKey))
}
}
}
}

View File

@@ -0,0 +1,200 @@
package gorm
import (
"fmt"
)
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
}
type callbackProcessor struct {
name string
before string
after string
replace bool
remove bool
typ string
processor *func(scope *Scope)
callback *callback
}
func (c *callback) addProcessor(typ string) *callbackProcessor {
cp := &callbackProcessor{typ: typ, callback: c}
c.processors = append(c.processors, cp)
return cp
}
func (c *callback) clone() *callback {
return &callback{
creates: c.creates,
updates: c.updates,
deletes: c.deletes,
queries: c.queries,
processors: c.processors,
}
}
func (c *callback) Create() *callbackProcessor {
return c.addProcessor("create")
}
func (c *callback) Update() *callbackProcessor {
return c.addProcessor("update")
}
func (c *callback) Delete() *callbackProcessor {
return c.addProcessor("delete")
}
func (c *callback) Query() *callbackProcessor {
return c.addProcessor("query")
}
func (c *callback) RowQuery() *callbackProcessor {
return c.addProcessor("row_query")
}
func (cp *callbackProcessor) Before(name string) *callbackProcessor {
cp.before = name
return cp
}
func (cp *callbackProcessor) After(name string) *callbackProcessor {
cp.after = name
return cp
}
func (cp *callbackProcessor) Register(name string, fc func(scope *Scope)) {
cp.name = name
cp.processor = &fc
cp.callback.sort()
}
func (cp *callbackProcessor) Remove(name string) {
fmt.Printf("[info] removing callback `%v` from %v\n", name, fileWithLineNum())
cp.name = name
cp.remove = true
cp.callback.sort()
}
func (cp *callbackProcessor) Replace(name string, fc func(scope *Scope)) {
fmt.Printf("[info] replacing callback `%v` from %v\n", name, fileWithLineNum())
cp.name = name
cp.processor = &fc
cp.replace = true
cp.callback.sort()
}
func getRIndex(strs []string, str string) int {
for i := len(strs) - 1; i >= 0; i-- {
if strs[i] == str {
return i
}
}
return -1
}
func sortProcessors(cps []*callbackProcessor) []*func(scope *Scope) {
var sortCallbackProcessor func(c *callbackProcessor)
var names, sortedNames = []string{}, []string{}
for _, cp := range cps {
if index := getRIndex(names, cp.name); index > -1 {
if !cp.replace && !cp.remove {
fmt.Printf("[warning] duplicated callback `%v` from %v\n", cp.name, fileWithLineNum())
}
}
names = append(names, cp.name)
}
sortCallbackProcessor = func(c *callbackProcessor) {
if getRIndex(sortedNames, c.name) > -1 {
return
}
if len(c.before) > 0 {
if index := getRIndex(sortedNames, c.before); index > -1 {
sortedNames = append(sortedNames[:index], append([]string{c.name}, sortedNames[index:]...)...)
} else if index := getRIndex(names, c.before); index > -1 {
sortedNames = append(sortedNames, c.name)
sortCallbackProcessor(cps[index])
} else {
sortedNames = append(sortedNames, c.name)
}
}
if len(c.after) > 0 {
if index := getRIndex(sortedNames, c.after); index > -1 {
sortedNames = append(sortedNames[:index+1], append([]string{c.name}, sortedNames[index+1:]...)...)
} else if index := getRIndex(names, c.after); index > -1 {
cp := cps[index]
if len(cp.before) == 0 {
cp.before = c.name
}
sortCallbackProcessor(cp)
} else {
sortedNames = append(sortedNames, c.name)
}
}
if getRIndex(sortedNames, c.name) == -1 {
sortedNames = append(sortedNames, c.name)
}
}
for _, cp := range cps {
sortCallbackProcessor(cp)
}
var funcs = []*func(scope *Scope){}
var sortedFuncs = []*func(scope *Scope){}
for _, name := range sortedNames {
index := getRIndex(names, name)
if !cps[index].remove {
sortedFuncs = append(sortedFuncs, cps[index].processor)
}
}
for _, cp := range cps {
if sindex := getRIndex(sortedNames, cp.name); sindex == -1 {
if !cp.remove {
funcs = append(funcs, cp.processor)
}
}
}
return append(sortedFuncs, funcs...)
}
func (c *callback) sort() {
var creates, updates, deletes, queries, rowQueries []*callbackProcessor
for _, processor := range c.processors {
switch processor.typ {
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)
}
var DefaultCallback = &callback{processors: []*callbackProcessor{}}

View File

@@ -0,0 +1,110 @@
package gorm
import (
"fmt"
"strings"
)
func BeforeCreate(scope *Scope) {
scope.CallMethodWithErrorCheck("BeforeSave")
scope.CallMethodWithErrorCheck("BeforeCreate")
}
func UpdateTimeStampWhenCreate(scope *Scope) {
if !scope.HasError() {
now := NowFunc()
scope.SetColumn("CreatedAt", now)
scope.SetColumn("UpdatedAt", now)
}
}
func Create(scope *Scope) {
defer scope.Trace(NowFunc())
if !scope.HasError() {
// set create sql
var sqls, columns []string
fields := scope.Fields()
for _, field := range fields {
if scope.changeableField(field) {
if field.IsNormal {
if !field.IsPrimaryKey || (field.IsPrimaryKey && !field.IsBlank) {
if !field.IsBlank || !field.HasDefaultValue {
columns = append(columns, scope.Quote(field.DBName))
sqls = append(sqls, scope.AddToVars(field.Field.Interface()))
}
}
} else if relationship := field.Relationship; relationship != nil && relationship.Kind == "belongs_to" {
if relationField := fields[relationship.ForeignDBName]; !scope.changeableField(relationField) {
columns = append(columns, scope.Quote(relationField.DBName))
sqls = append(sqls, scope.AddToVars(relationField.Field.Interface()))
}
}
}
}
returningKey := "*"
primaryField := scope.PrimaryField()
if primaryField != nil {
returningKey = scope.Quote(primaryField.DBName)
}
if len(columns) == 0 {
scope.Raw(fmt.Sprintf("INSERT INTO %v DEFAULT VALUES %v",
scope.QuotedTableName(),
scope.Dialect().ReturningStr(scope.TableName(), returningKey),
))
} else {
scope.Raw(fmt.Sprintf(
"INSERT INTO %v (%v) VALUES (%v) %v",
scope.QuotedTableName(),
strings.Join(columns, ","),
strings.Join(sqls, ","),
scope.Dialect().ReturningStr(scope.TableName(), returningKey),
))
}
// execute create sql
if scope.Dialect().SupportLastInsertId() {
if result, err := scope.SqlDB().Exec(scope.Sql, scope.SqlVars...); scope.Err(err) == nil {
id, err := result.LastInsertId()
if scope.Err(err) == nil {
scope.db.RowsAffected, _ = result.RowsAffected()
if primaryField != nil && primaryField.IsBlank {
scope.Err(scope.SetColumn(primaryField, id))
}
}
}
} else {
if primaryField == nil {
if results, err := scope.SqlDB().Exec(scope.Sql, scope.SqlVars...); err == nil {
scope.db.RowsAffected, _ = results.RowsAffected()
} else {
scope.Err(err)
}
} else {
if err := scope.Err(scope.SqlDB().QueryRow(scope.Sql, scope.SqlVars...).Scan(primaryField.Field.Addr().Interface())); err == nil {
scope.db.RowsAffected = 1
} else {
scope.Err(err)
}
}
}
}
}
func AfterCreate(scope *Scope) {
scope.CallMethodWithErrorCheck("AfterCreate")
scope.CallMethodWithErrorCheck("AfterSave")
}
func init() {
DefaultCallback.Create().Register("gorm:begin_transaction", BeginTransaction)
DefaultCallback.Create().Register("gorm:before_create", BeforeCreate)
DefaultCallback.Create().Register("gorm:save_before_associations", SaveBeforeAssociations)
DefaultCallback.Create().Register("gorm:update_time_stamp_when_create", UpdateTimeStampWhenCreate)
DefaultCallback.Create().Register("gorm:create", Create)
DefaultCallback.Create().Register("gorm:save_after_associations", SaveAfterAssociations)
DefaultCallback.Create().Register("gorm:after_create", AfterCreate)
DefaultCallback.Create().Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
}

View File

@@ -0,0 +1,36 @@
package gorm
import "fmt"
func BeforeDelete(scope *Scope) {
scope.CallMethodWithErrorCheck("BeforeDelete")
}
func Delete(scope *Scope) {
if !scope.HasError() {
if !scope.Search.Unscoped && scope.HasColumn("DeletedAt") {
scope.Raw(
fmt.Sprintf("UPDATE %v SET deleted_at=%v %v",
scope.QuotedTableName(),
scope.AddToVars(NowFunc()),
scope.CombinedConditionSql(),
))
} else {
scope.Raw(fmt.Sprintf("DELETE FROM %v %v", scope.QuotedTableName(), scope.CombinedConditionSql()))
}
scope.Exec()
}
}
func AfterDelete(scope *Scope) {
scope.CallMethodWithErrorCheck("AfterDelete")
}
func init() {
DefaultCallback.Delete().Register("gorm:begin_transaction", BeginTransaction)
DefaultCallback.Delete().Register("gorm:before_delete", BeforeDelete)
DefaultCallback.Delete().Register("gorm:delete", Delete)
DefaultCallback.Delete().Register("gorm:after_delete", AfterDelete)
DefaultCallback.Delete().Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
}

View File

@@ -0,0 +1,118 @@
package gorm
import (
"errors"
"fmt"
"reflect"
)
func Query(scope *Scope) {
defer scope.Trace(NowFunc())
var (
isSlice bool
isPtr bool
anyRecordFound bool
destType reflect.Type
)
if orderBy, ok := scope.Get("gorm:order_by_primary_key"); ok {
if primaryKey := scope.PrimaryKey(); primaryKey != "" {
scope.Search.Order(fmt.Sprintf("%v.%v %v", scope.QuotedTableName(), scope.Quote(primaryKey), orderBy))
}
}
var dest = scope.IndirectValue()
if value, ok := scope.Get("gorm:query_destination"); ok {
dest = reflect.Indirect(reflect.ValueOf(value))
}
if kind := dest.Kind(); kind == reflect.Slice {
isSlice = true
destType = dest.Type().Elem()
dest.Set(reflect.Indirect(reflect.New(reflect.SliceOf(destType))))
if destType.Kind() == reflect.Ptr {
isPtr = true
destType = destType.Elem()
}
} else if kind != reflect.Struct {
scope.Err(errors.New("unsupported destination, should be slice or struct"))
return
}
scope.prepareQuerySql()
if !scope.HasError() {
rows, err := scope.SqlDB().Query(scope.Sql, scope.SqlVars...)
scope.db.RowsAffected = 0
if scope.Err(err) != nil {
return
}
defer rows.Close()
columns, _ := rows.Columns()
for rows.Next() {
scope.db.RowsAffected++
anyRecordFound = true
elem := dest
if isSlice {
elem = reflect.New(destType).Elem()
}
var values = make([]interface{}, len(columns))
fields := scope.New(elem.Addr().Interface()).Fields()
for index, column := range columns {
if field, ok := fields[column]; ok {
if field.Field.Kind() == reflect.Ptr {
values[index] = field.Field.Addr().Interface()
} else {
values[index] = reflect.New(reflect.PtrTo(field.Field.Type())).Interface()
}
} else {
var value interface{}
values[index] = &value
}
}
scope.Err(rows.Scan(values...))
for index, column := range columns {
value := values[index]
if field, ok := fields[column]; ok {
if field.Field.Kind() == reflect.Ptr {
field.Field.Set(reflect.ValueOf(value).Elem())
} else if v := reflect.ValueOf(value).Elem().Elem(); v.IsValid() {
field.Field.Set(v)
}
}
}
if isSlice {
if isPtr {
dest.Set(reflect.Append(dest, elem.Addr()))
} else {
dest.Set(reflect.Append(dest, elem))
}
}
}
if !anyRecordFound && !isSlice {
scope.Err(RecordNotFound)
}
}
}
func AfterQuery(scope *Scope) {
scope.CallMethodWithErrorCheck("AfterFind")
}
func init() {
DefaultCallback.Query().Register("gorm:query", Query)
DefaultCallback.Query().Register("gorm:after_query", AfterQuery)
DefaultCallback.Query().Register("gorm:preload", Preload)
}

View File

@@ -0,0 +1,76 @@
package gorm
import "reflect"
func BeginTransaction(scope *Scope) {
scope.Begin()
}
func CommitOrRollbackTransaction(scope *Scope) {
scope.CommitOrRollback()
}
func SaveBeforeAssociations(scope *Scope) {
if !scope.shouldSaveAssociations() {
return
}
for _, field := range scope.Fields() {
if scope.changeableField(field) && !field.IsBlank && !field.IsIgnored {
if relationship := field.Relationship; relationship != nil && relationship.Kind == "belongs_to" {
value := field.Field
scope.Err(scope.NewDB().Save(value.Addr().Interface()).Error)
if relationship.ForeignFieldName != "" {
scope.Err(scope.SetColumn(relationship.ForeignFieldName, scope.New(value.Addr().Interface()).PrimaryKeyValue()))
}
}
}
}
}
func SaveAfterAssociations(scope *Scope) {
if !scope.shouldSaveAssociations() {
return
}
for _, field := range scope.Fields() {
if scope.changeableField(field) && !field.IsBlank && !field.IsIgnored {
if relationship := field.Relationship; 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 relationship.JoinTableHandler == nil && relationship.ForeignFieldName != "" {
scope.Err(newScope.SetColumn(relationship.ForeignFieldName, scope.PrimaryKeyValue()))
}
if relationship.PolymorphicType != "" {
scope.Err(newScope.SetColumn(relationship.PolymorphicType, scope.TableName()))
}
scope.Err(newDB.Save(elem).Error)
if joinTableHandler := relationship.JoinTableHandler; joinTableHandler != nil {
scope.Err(joinTableHandler.Add(joinTableHandler, scope.NewDB(), scope.Value, newScope.Value))
}
}
default:
elem := value.Addr().Interface()
newScope := scope.New(elem)
if relationship.ForeignFieldName != "" {
scope.Err(newScope.SetColumn(relationship.ForeignFieldName, scope.PrimaryKeyValue()))
}
if relationship.PolymorphicType != "" {
scope.Err(newScope.SetColumn(relationship.PolymorphicType, scope.TableName()))
}
scope.Err(scope.NewDB().Save(elem).Error)
}
}
}
}
}

View File

@@ -0,0 +1,112 @@
package gorm
import (
"reflect"
"runtime"
"strings"
"testing"
)
func equalFuncs(funcs []*func(s *Scope), fnames []string) bool {
var names []string
for _, f := range funcs {
fnames := strings.Split(runtime.FuncForPC(reflect.ValueOf(*f).Pointer()).Name(), ".")
names = append(names, fnames[len(fnames)-1])
}
return reflect.DeepEqual(names, fnames)
}
func create(s *Scope) {}
func beforeCreate1(s *Scope) {}
func beforeCreate2(s *Scope) {}
func afterCreate1(s *Scope) {}
func afterCreate2(s *Scope) {}
func TestRegisterCallback(t *testing.T) {
var callback = &callback{processors: []*callbackProcessor{}}
callback.Create().Register("before_create1", beforeCreate1)
callback.Create().Register("before_create2", beforeCreate2)
callback.Create().Register("create", create)
callback.Create().Register("after_create1", afterCreate1)
callback.Create().Register("after_create2", afterCreate2)
if !equalFuncs(callback.creates, []string{"beforeCreate1", "beforeCreate2", "create", "afterCreate1", "afterCreate2"}) {
t.Errorf("register callback")
}
}
func TestRegisterCallbackWithOrder(t *testing.T) {
var callback1 = &callback{processors: []*callbackProcessor{}}
callback1.Create().Register("before_create1", beforeCreate1)
callback1.Create().Register("create", create)
callback1.Create().Register("after_create1", afterCreate1)
callback1.Create().Before("after_create1").Register("after_create2", afterCreate2)
if !equalFuncs(callback1.creates, []string{"beforeCreate1", "create", "afterCreate2", "afterCreate1"}) {
t.Errorf("register callback with order")
}
var callback2 = &callback{processors: []*callbackProcessor{}}
callback2.Update().Register("create", create)
callback2.Update().Before("create").Register("before_create1", beforeCreate1)
callback2.Update().After("after_create2").Register("after_create1", afterCreate1)
callback2.Update().Before("before_create1").Register("before_create2", beforeCreate2)
callback2.Update().Register("after_create2", afterCreate2)
if !equalFuncs(callback2.updates, []string{"beforeCreate2", "beforeCreate1", "create", "afterCreate2", "afterCreate1"}) {
t.Errorf("register callback with order")
}
}
func TestRegisterCallbackWithComplexOrder(t *testing.T) {
var callback1 = &callback{processors: []*callbackProcessor{}}
callback1.Query().Before("after_create1").After("before_create1").Register("create", create)
callback1.Query().Register("before_create1", beforeCreate1)
callback1.Query().Register("after_create1", afterCreate1)
if !equalFuncs(callback1.queries, []string{"beforeCreate1", "create", "afterCreate1"}) {
t.Errorf("register callback with order")
}
var callback2 = &callback{processors: []*callbackProcessor{}}
callback2.Delete().Before("after_create1").After("before_create1").Register("create", create)
callback2.Delete().Before("create").Register("before_create1", beforeCreate1)
callback2.Delete().After("before_create1").Register("before_create2", beforeCreate2)
callback2.Delete().Register("after_create1", afterCreate1)
callback2.Delete().After("after_create1").Register("after_create2", afterCreate2)
if !equalFuncs(callback2.deletes, []string{"beforeCreate1", "beforeCreate2", "create", "afterCreate1", "afterCreate2"}) {
t.Errorf("register callback with order")
}
}
func replaceCreate(s *Scope) {}
func TestReplaceCallback(t *testing.T) {
var callback = &callback{processors: []*callbackProcessor{}}
callback.Create().Before("after_create1").After("before_create1").Register("create", create)
callback.Create().Register("before_create1", beforeCreate1)
callback.Create().Register("after_create1", afterCreate1)
callback.Create().Replace("create", replaceCreate)
if !equalFuncs(callback.creates, []string{"beforeCreate1", "replaceCreate", "afterCreate1"}) {
t.Errorf("replace callback")
}
}
func TestRemoveCallback(t *testing.T) {
var callback = &callback{processors: []*callbackProcessor{}}
callback.Create().Before("after_create1").After("before_create1").Register("create", create)
callback.Create().Register("before_create1", beforeCreate1)
callback.Create().Register("after_create1", afterCreate1)
callback.Create().Remove("create")
if !equalFuncs(callback.creates, []string{"beforeCreate1", "afterCreate1"}) {
t.Errorf("remove callback")
}
}

View File

@@ -0,0 +1,96 @@
package gorm
import (
"fmt"
"strings"
)
func AssignUpdateAttributes(scope *Scope) {
if attrs, ok := scope.InstanceGet("gorm:update_interface"); ok {
if maps := convertInterfaceToMap(attrs); len(maps) > 0 {
protected, ok := scope.Get("gorm:ignore_protected_attrs")
_, updateColumn := scope.Get("gorm:update_column")
updateAttrs, hasUpdate := scope.updatedAttrsWithValues(maps, ok && protected.(bool))
if updateColumn {
scope.InstanceSet("gorm:update_attrs", maps)
} else if len(updateAttrs) > 0 {
scope.InstanceSet("gorm:update_attrs", updateAttrs)
} else if !hasUpdate {
scope.SkipLeft()
return
}
}
}
}
func BeforeUpdate(scope *Scope) {
if _, ok := scope.Get("gorm:update_column"); !ok {
scope.CallMethodWithErrorCheck("BeforeSave")
scope.CallMethodWithErrorCheck("BeforeUpdate")
}
}
func UpdateTimeStampWhenUpdate(scope *Scope) {
if _, ok := scope.Get("gorm:update_column"); !ok {
scope.SetColumn("UpdatedAt", NowFunc())
}
}
func Update(scope *Scope) {
if !scope.HasError() {
var sqls []string
if updateAttrs, ok := scope.InstanceGet("gorm:update_attrs"); ok {
for key, value := range updateAttrs.(map[string]interface{}) {
if scope.changeableDBColumn(key) {
sqls = append(sqls, fmt.Sprintf("%v = %v", scope.Quote(key), scope.AddToVars(value)))
}
}
} else {
fields := scope.Fields()
for _, field := range fields {
if scope.changeableField(field) && !field.IsPrimaryKey && field.IsNormal {
if !field.IsBlank || !field.HasDefaultValue {
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" {
if relationField := fields[relationship.ForeignDBName]; !scope.changeableField(relationField) {
if !relationField.IsBlank {
sqls = append(sqls, fmt.Sprintf("%v = %v", scope.Quote(relationField.DBName), scope.AddToVars(relationField.Field.Interface())))
}
}
}
}
}
if len(sqls) > 0 {
scope.Raw(fmt.Sprintf(
"UPDATE %v SET %v %v",
scope.QuotedTableName(),
strings.Join(sqls, ", "),
scope.CombinedConditionSql(),
))
scope.Exec()
}
}
}
func AfterUpdate(scope *Scope) {
if _, ok := scope.Get("gorm:update_column"); !ok {
scope.CallMethodWithErrorCheck("AfterUpdate")
scope.CallMethodWithErrorCheck("AfterSave")
}
}
func init() {
DefaultCallback.Update().Register("gorm:assign_update_attributes", AssignUpdateAttributes)
DefaultCallback.Update().Register("gorm:begin_transaction", BeginTransaction)
DefaultCallback.Update().Register("gorm:before_update", BeforeUpdate)
DefaultCallback.Update().Register("gorm:save_before_associations", SaveBeforeAssociations)
DefaultCallback.Update().Register("gorm:update_time_stamp_when_update", UpdateTimeStampWhenUpdate)
DefaultCallback.Update().Register("gorm:update", Update)
DefaultCallback.Update().Register("gorm:save_after_associations", SaveAfterAssociations)
DefaultCallback.Update().Register("gorm:after_update", AfterUpdate)
DefaultCallback.Update().Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
}

View File

@@ -0,0 +1,177 @@
package gorm_test
import (
"errors"
"github.com/jinzhu/gorm"
"reflect"
"testing"
)
func (s *Product) BeforeCreate() (err error) {
if s.Code == "Invalid" {
err = errors.New("invalid product")
}
s.BeforeCreateCallTimes = s.BeforeCreateCallTimes + 1
return
}
func (s *Product) BeforeUpdate() (err error) {
if s.Code == "dont_update" {
err = errors.New("can't update")
}
s.BeforeUpdateCallTimes = s.BeforeUpdateCallTimes + 1
return
}
func (s *Product) BeforeSave() (err error) {
if s.Code == "dont_save" {
err = errors.New("can't save")
}
s.BeforeSaveCallTimes = s.BeforeSaveCallTimes + 1
return
}
func (s *Product) AfterFind() {
s.AfterFindCallTimes = s.AfterFindCallTimes + 1
}
func (s *Product) AfterCreate(tx *gorm.DB) {
tx.Model(s).UpdateColumn(Product{AfterCreateCallTimes: s.AfterCreateCallTimes + 1})
}
func (s *Product) AfterUpdate() {
s.AfterUpdateCallTimes = s.AfterUpdateCallTimes + 1
}
func (s *Product) AfterSave() (err error) {
if s.Code == "after_save_error" {
err = errors.New("can't save")
}
s.AfterSaveCallTimes = s.AfterSaveCallTimes + 1
return
}
func (s *Product) BeforeDelete() (err error) {
if s.Code == "dont_delete" {
err = errors.New("can't delete")
}
s.BeforeDeleteCallTimes = s.BeforeDeleteCallTimes + 1
return
}
func (s *Product) AfterDelete() (err error) {
if s.Code == "after_delete_error" {
err = errors.New("can't delete")
}
s.AfterDeleteCallTimes = s.AfterDeleteCallTimes + 1
return
}
func (s *Product) GetCallTimes() []int64 {
return []int64{s.BeforeCreateCallTimes, s.BeforeSaveCallTimes, s.BeforeUpdateCallTimes, s.AfterCreateCallTimes, s.AfterSaveCallTimes, s.AfterUpdateCallTimes, s.BeforeDeleteCallTimes, s.AfterDeleteCallTimes, s.AfterFindCallTimes}
}
func TestRunCallbacks(t *testing.T) {
p := Product{Code: "unique_code", Price: 100}
DB.Save(&p)
if !reflect.DeepEqual(p.GetCallTimes(), []int64{1, 1, 0, 1, 1, 0, 0, 0, 0}) {
t.Errorf("Callbacks should be invoked successfully, %v", p.GetCallTimes())
}
DB.Where("Code = ?", "unique_code").First(&p)
if !reflect.DeepEqual(p.GetCallTimes(), []int64{1, 1, 0, 1, 0, 0, 0, 0, 1}) {
t.Errorf("After callbacks values are not saved, %v", p.GetCallTimes())
}
p.Price = 200
DB.Save(&p)
if !reflect.DeepEqual(p.GetCallTimes(), []int64{1, 2, 1, 1, 1, 1, 0, 0, 1}) {
t.Errorf("After update callbacks should be invoked successfully, %v", p.GetCallTimes())
}
var products []Product
DB.Find(&products, "code = ?", "unique_code")
if products[0].AfterFindCallTimes != 2 {
t.Errorf("AfterFind callbacks should work with slice")
}
DB.Where("Code = ?", "unique_code").First(&p)
if !reflect.DeepEqual(p.GetCallTimes(), []int64{1, 2, 1, 1, 0, 0, 0, 0, 2}) {
t.Errorf("After update callbacks values are not saved, %v", p.GetCallTimes())
}
DB.Delete(&p)
if !reflect.DeepEqual(p.GetCallTimes(), []int64{1, 2, 1, 1, 0, 0, 1, 1, 2}) {
t.Errorf("After delete callbacks should be invoked successfully, %v", p.GetCallTimes())
}
if DB.Where("Code = ?", "unique_code").First(&p).Error == nil {
t.Errorf("Can't find a deleted record")
}
}
func TestCallbacksWithErrors(t *testing.T) {
p := Product{Code: "Invalid", Price: 100}
if DB.Save(&p).Error == nil {
t.Errorf("An error from before create callbacks happened when create with invalid value")
}
if DB.Where("code = ?", "Invalid").First(&Product{}).Error == nil {
t.Errorf("Should not save record that have errors")
}
if DB.Save(&Product{Code: "dont_save", Price: 100}).Error == nil {
t.Errorf("An error from after create callbacks happened when create with invalid value")
}
p2 := Product{Code: "update_callback", Price: 100}
DB.Save(&p2)
p2.Code = "dont_update"
if DB.Save(&p2).Error == nil {
t.Errorf("An error from before update callbacks happened when update with invalid value")
}
if DB.Where("code = ?", "update_callback").First(&Product{}).Error != nil {
t.Errorf("Record Should not be updated due to errors happened in before update callback")
}
if DB.Where("code = ?", "dont_update").First(&Product{}).Error == nil {
t.Errorf("Record Should not be updated due to errors happened in before update callback")
}
p2.Code = "dont_save"
if DB.Save(&p2).Error == nil {
t.Errorf("An error from before save callbacks happened when update with invalid value")
}
p3 := Product{Code: "dont_delete", Price: 100}
DB.Save(&p3)
if DB.Delete(&p3).Error == nil {
t.Errorf("An error from before delete callbacks happened when delete")
}
if DB.Where("Code = ?", "dont_delete").First(&p3).Error != nil {
t.Errorf("An error from before delete callbacks happened")
}
p4 := Product{Code: "after_save_error", Price: 100}
DB.Save(&p4)
if err := DB.First(&Product{}, "code = ?", "after_save_error").Error; err == nil {
t.Errorf("Record should be reverted if get an error in after save callback")
}
p5 := Product{Code: "after_delete_error", Price: 100}
DB.Save(&p5)
if err := DB.First(&Product{}, "code = ?", "after_delete_error").Error; err != nil {
t.Errorf("Record should be found")
}
DB.Delete(&p5)
if err := DB.First(&Product{}, "code = ?", "after_delete_error").Error; err != nil {
t.Errorf("Record shouldn't be deleted because of an error happened in after delete callback")
}
}

View File

@@ -0,0 +1,101 @@
package gorm
import (
"fmt"
"reflect"
"strings"
"time"
)
type commonDialect struct{}
func (commonDialect) BinVar(i int) string {
return "$$" // ?
}
func (commonDialect) SupportLastInsertId() bool {
return true
}
func (commonDialect) HasTop() bool {
return false
}
func (commonDialect) SqlTag(value reflect.Value, size int, autoIncrease bool) string {
switch value.Kind() {
case reflect.Bool:
return "BOOLEAN"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
if autoIncrease {
return "INTEGER AUTO_INCREMENT"
}
return "INTEGER"
case reflect.Int64, reflect.Uint64:
if autoIncrease {
return "BIGINT AUTO_INCREMENT"
}
return "BIGINT"
case reflect.Float32, reflect.Float64:
return "FLOAT"
case reflect.String:
if size > 0 && size < 65532 {
return fmt.Sprintf("VARCHAR(%d)", size)
}
return "VARCHAR(65532)"
case reflect.Struct:
if _, ok := value.Interface().(time.Time); ok {
return "TIMESTAMP"
}
default:
if _, ok := value.Interface().([]byte); ok {
if size > 0 && size < 65532 {
return fmt.Sprintf("BINARY(%d)", size)
}
return "BINARY(65532)"
}
}
panic(fmt.Sprintf("invalid sql type %s (%s) for commonDialect", value.Type().Name(), value.Kind().String()))
}
func (commonDialect) ReturningStr(tableName, key string) string {
return ""
}
func (commonDialect) SelectFromDummyTable() string {
return ""
}
func (commonDialect) Quote(key string) string {
return fmt.Sprintf(`"%s"`, key)
}
func (commonDialect) databaseName(scope *Scope) string {
from := strings.Index(scope.db.parent.source, "/") + 1
to := strings.Index(scope.db.parent.source, "?")
if to == -1 {
to = len(scope.db.parent.source)
}
return scope.db.parent.source[from:to]
}
func (c commonDialect) HasTable(scope *Scope, tableName string) bool {
var count int
scope.NewDB().Raw("SELECT count(*) FROM INFORMATION_SCHEMA.TABLES WHERE table_name = ? AND table_schema = ?", tableName, c.databaseName(scope)).Row().Scan(&count)
return count > 0
}
func (c commonDialect) HasColumn(scope *Scope, tableName string, columnName string) bool {
var count int
scope.NewDB().Raw("SELECT count(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = ? AND table_name = ? AND column_name = ?", c.databaseName(scope), tableName, columnName).Row().Scan(&count)
return count > 0
}
func (commonDialect) HasIndex(scope *Scope, tableName string, indexName string) bool {
var count int
scope.NewDB().Raw("SELECT count(*) FROM INFORMATION_SCHEMA.STATISTICS where table_name = ? AND index_name = ?", tableName, indexName).Row().Scan(&count)
return count > 0
}
func (commonDialect) RemoveIndex(scope *Scope, indexName string) {
scope.NewDB().Exec(fmt.Sprintf("DROP INDEX %v ON %v", indexName, scope.QuotedTableName()))
}

View File

@@ -0,0 +1,159 @@
package gorm_test
import (
"reflect"
"testing"
"time"
)
func TestCreate(t *testing.T) {
float := 35.03554004971999
user := User{Name: "CreateUser", Age: 18, Birthday: time.Now(), UserNum: Num(111), PasswordHash: []byte{'f', 'a', 'k', '4'}, Latitude: float}
if !DB.NewRecord(user) || !DB.NewRecord(&user) {
t.Error("User should be new record before create")
}
if count := DB.Save(&user).RowsAffected; count != 1 {
t.Error("There should be one record be affected when create record")
}
if DB.NewRecord(user) || DB.NewRecord(&user) {
t.Error("User should not new record after save")
}
var newUser User
DB.First(&newUser, user.Id)
if !reflect.DeepEqual(newUser.PasswordHash, []byte{'f', 'a', 'k', '4'}) {
t.Errorf("User's PasswordHash should be saved ([]byte)")
}
if newUser.Age != 18 {
t.Errorf("User's Age should be saved (int)")
}
if newUser.UserNum != Num(111) {
t.Errorf("User's UserNum should be saved (custom type)")
}
if newUser.Latitude != float {
t.Errorf("Float64 should not be changed after save")
}
if user.CreatedAt.IsZero() {
t.Errorf("Should have created_at after create")
}
if newUser.CreatedAt.IsZero() {
t.Errorf("Should have created_at after create")
}
DB.Model(user).Update("name", "create_user_new_name")
DB.First(&user, user.Id)
if user.CreatedAt != newUser.CreatedAt {
t.Errorf("CreatedAt should not be changed after update")
}
}
func TestCreateWithNoGORMPrimayKey(t *testing.T) {
jt := JoinTable{From: 1, To: 2}
err := DB.Create(&jt).Error
if err != nil {
t.Errorf("No error should happen when create a record without a GORM primary key. But in the database this primary key exists and is the union of 2 or more fields\n But got: %s", err)
}
}
func TestCreateWithNoStdPrimaryKeyAndDefaultValues(t *testing.T) {
animal := Animal{Name: "Ferdinand"}
if DB.Save(&animal).Error != nil {
t.Errorf("No error should happen when create a record without std primary key")
}
if animal.Counter == 0 {
t.Errorf("No std primary key should be filled value after create")
}
if animal.Name != "Ferdinand" {
t.Errorf("Default value should be overrided")
}
// Test create with default value not overrided
an := Animal{From: "nerdz"}
if DB.Save(&an).Error != nil {
t.Errorf("No error should happen when create an record without std primary key")
}
// We must fetch the value again, to have the default fields updated
// (We can't do this in the update statements, since sql default can be expressions
// And be different from the fields' type (eg. a time.Time fiels has a default value of "now()"
DB.Model(Animal{}).Where(&Animal{Counter: an.Counter}).First(&an)
if an.Name != "galeone" {
t.Errorf("Default value should fill the field. But got %v", an.Name)
}
}
func TestAnonymousScanner(t *testing.T) {
user := User{Name: "anonymous_scanner", Role: Role{Name: "admin"}}
DB.Save(&user)
var user2 User
DB.First(&user2, "name = ?", "anonymous_scanner")
if user2.Role.Name != "admin" {
t.Errorf("Should be able to get anonymous scanner")
}
if !user2.IsAdmin() {
t.Errorf("Should be able to get anonymous scanner")
}
}
func TestAnonymousField(t *testing.T) {
user := User{Name: "anonymous_field", Company: Company{Name: "company"}}
DB.Save(&user)
var user2 User
DB.First(&user2, "name = ?", "anonymous_field")
DB.Model(&user2).Related(&user2.Company)
if user2.Company.Name != "company" {
t.Errorf("Should be able to get anonymous field")
}
}
func TestSelectWithCreate(t *testing.T) {
user := getPreparedUser("select_user", "select_with_create")
DB.Select("Name", "BillingAddress", "CreditCard", "Company", "Emails").Create(user)
var queryuser User
DB.Preload("BillingAddress").Preload("ShippingAddress").
Preload("CreditCard").Preload("Emails").Preload("Company").First(&queryuser, user.Id)
if queryuser.Name != user.Name || queryuser.Age == user.Age {
t.Errorf("Should only create users with name column")
}
if queryuser.BillingAddressID.Int64 == 0 || queryuser.ShippingAddressId != 0 ||
queryuser.CreditCard.ID == 0 || len(queryuser.Emails) == 0 {
t.Errorf("Should only create selected relationships")
}
}
func TestOmitWithCreate(t *testing.T) {
user := getPreparedUser("omit_user", "omit_with_create")
DB.Omit("Name", "BillingAddress", "CreditCard", "Company", "Emails").Create(user)
var queryuser User
DB.Preload("BillingAddress").Preload("ShippingAddress").
Preload("CreditCard").Preload("Emails").Preload("Company").First(&queryuser, user.Id)
if queryuser.Name == user.Name || queryuser.Age != user.Age {
t.Errorf("Should only create users with age column")
}
if queryuser.BillingAddressID.Int64 != 0 || queryuser.ShippingAddressId == 0 ||
queryuser.CreditCard.ID != 0 || len(queryuser.Emails) != 0 {
t.Errorf("Should not create omited relationships")
}
}

View File

@@ -0,0 +1,65 @@
package gorm_test
import (
"testing"
"time"
)
type CustomizeColumn struct {
ID int64 `gorm:"column:mapped_id; primary_key:yes"`
Name string `gorm:"column:mapped_name"`
Date time.Time `gorm:"column:mapped_time"`
}
// Make sure an ignored field does not interfere with another field's custom
// column name that matches the ignored field.
type CustomColumnAndIgnoredFieldClash struct {
Body string `sql:"-"`
RawBody string `gorm:"column:body"`
}
func TestCustomizeColumn(t *testing.T) {
col := "mapped_name"
DB.DropTable(&CustomizeColumn{})
DB.AutoMigrate(&CustomizeColumn{})
scope := DB.NewScope(&CustomizeColumn{})
if !scope.Dialect().HasColumn(scope, scope.TableName(), col) {
t.Errorf("CustomizeColumn should have column %s", col)
}
col = "mapped_id"
if scope.PrimaryKey() != col {
t.Errorf("CustomizeColumn should have primary key %s, but got %q", col, scope.PrimaryKey())
}
expected := "foo"
cc := CustomizeColumn{ID: 666, Name: expected, Date: time.Now()}
if count := DB.Create(&cc).RowsAffected; count != 1 {
t.Error("There should be one record be affected when create record")
}
var cc1 CustomizeColumn
DB.First(&cc1, 666)
if cc1.Name != expected {
t.Errorf("Failed to query CustomizeColumn")
}
cc.Name = "bar"
DB.Save(&cc)
var cc2 CustomizeColumn
DB.First(&cc2, 666)
if cc2.Name != "bar" {
t.Errorf("Failed to query CustomizeColumn")
}
}
func TestCustomColumnAndIgnoredFieldClash(t *testing.T) {
DB.DropTable(&CustomColumnAndIgnoredFieldClash{})
if err := DB.AutoMigrate(&CustomColumnAndIgnoredFieldClash{}).Error; err != nil {
t.Errorf("Should not raise error: %s", err)
}
}

View File

@@ -0,0 +1,68 @@
package gorm_test
import (
"testing"
"time"
)
func TestDelete(t *testing.T) {
user1, user2 := User{Name: "delete1"}, User{Name: "delete2"}
DB.Save(&user1)
DB.Save(&user2)
if DB.Delete(&user1).Error != nil {
t.Errorf("No error should happen when delete a record")
}
if !DB.Where("name = ?", user1.Name).First(&User{}).RecordNotFound() {
t.Errorf("User can't be found after delete")
}
if DB.Where("name = ?", user2.Name).First(&User{}).RecordNotFound() {
t.Errorf("Other users that not deleted should be found-able")
}
}
func TestInlineDelete(t *testing.T) {
user1, user2 := User{Name: "inline_delete1"}, User{Name: "inline_delete2"}
DB.Save(&user1)
DB.Save(&user2)
if DB.Delete(&User{}, user1.Id).Error != nil {
t.Errorf("No error should happen when delete a record")
} else if !DB.Where("name = ?", user1.Name).First(&User{}).RecordNotFound() {
t.Errorf("User can't be found after delete")
}
if DB.Delete(&User{}, "name = ?", user2.Name).Error != nil {
t.Errorf("No error should happen when delete a record")
} else if !DB.Where("name = ?", user2.Name).First(&User{}).RecordNotFound() {
t.Errorf("User can't be found after delete")
}
}
func TestSoftDelete(t *testing.T) {
type User struct {
Id int64
Name string
DeletedAt time.Time
}
DB.AutoMigrate(&User{})
user := User{Name: "soft_delete"}
DB.Save(&user)
DB.Delete(&user)
if DB.First(&User{}, "name = ?", user.Name).Error == nil {
t.Errorf("Can't find a soft deleted record")
}
if DB.Unscoped().First(&User{}, "name = ?", user.Name).Error != nil {
t.Errorf("Should be able to find soft deleted record with Unscoped")
}
DB.Unscoped().Delete(&user)
if !DB.Unscoped().First(&User{}, "name = ?", user.Name).RecordNotFound() {
t.Errorf("Can't find permanently deleted record")
}
}

View File

@@ -0,0 +1,40 @@
package gorm
import (
"fmt"
"reflect"
)
type Dialect interface {
BinVar(i int) string
SupportLastInsertId() bool
HasTop() bool
SqlTag(value reflect.Value, size int, autoIncrease bool) string
ReturningStr(tableName, key string) string
SelectFromDummyTable() string
Quote(key string) string
HasTable(scope *Scope, tableName string) bool
HasColumn(scope *Scope, tableName string, columnName string) bool
HasIndex(scope *Scope, tableName string, indexName string) bool
RemoveIndex(scope *Scope, indexName string)
}
func NewDialect(driver string) Dialect {
var d Dialect
switch driver {
case "postgres":
d = &postgres{}
case "foundation":
d = &foundation{}
case "mysql":
d = &mysql{}
case "sqlite3":
d = &sqlite3{}
case "mssql":
d = &mssql{}
default:
fmt.Printf("`%v` is not officially supported, running under compatibility mode.\n", driver)
d = &commonDialect{}
}
return d
}

View File

@@ -0,0 +1,68 @@
# Gorm Development
## Architecture
The most notable component of Gorm is`gorm.DB`, which hold database connection. It could be initialized like this:
db, err := gorm.Open("postgres", "user=gorm dbname=gorm sslmode=disable")
Gorm has chainable API, `gorm.DB` is the bridge of chains, it save related information and pass it to the next chain.
Lets use below code to explain how it works:
db.Where("name = ?", "jinzhu").Find(&users)
// equivalent code
newdb := db.Where("name =?", "jinzhu")
newdb.Find(&user)
`newdb` is `db`'s clone, in addition, it contains search conditions from the `Where` method.
`Find` is a query method, it creates a `Scope` instance, and pass it as argument to query callbacks.
There are four kinds of callbacks corresponds to sql's CURD: create callbacks, update callbacks, query callbacks, delete callbacks.
## Callbacks
### Register a new callback
func updateCreated(scope *Scope) {
if scope.HasColumn("Created") {
scope.SetColumn("Created", NowFunc())
}
}
db.Callback().Create().Register("update_created_at", updateCreated)
// register a callback for Create process
### Delete an existing callback
db.Callback().Create().Remove("gorm:create")
// delete callback `gorm:create` from Create callbacks
### Replace an existing callback
db.Callback().Create().Replace("gorm:create", newCreateFunction)
// replace callback `gorm:create` with new function `newCreateFunction` for Create process
### Register callback orders
db.Callback().Create().Before("gorm:create").Register("update_created_at", updateCreated)
db.Callback().Create().After("gorm:create").Register("update_created_at", updateCreated)
db.Callback().Query().After("gorm:query").Register("my_plugin:after_query", afterQuery)
db.Callback().Delete().After("gorm:delete").Register("my_plugin:after_delete", afterDelete)
db.Callback().Update().Before("gorm:update").Register("my_plugin:before_update", beforeUpdate)
db.Callback().Create().Before("gorm:create").After("gorm:before_create").Register("my_plugin:before_create", beforeCreate)
### Callback API
Gorm is powered by callbacks, so you could refer below links to learn how to write callbacks
[Create callbacks](https://github.com/jinzhu/gorm/blob/master/callback_create.go)
[Update callbacks](https://github.com/jinzhu/gorm/blob/master/callback_update.go)
[Query callbacks](https://github.com/jinzhu/gorm/blob/master/callback_query.go)
[Delete callbacks](https://github.com/jinzhu/gorm/blob/master/callback_delete.go)
View [https://github.com/jinzhu/gorm/blob/master/scope.go](https://github.com/jinzhu/gorm/blob/master/scope.go) for all available API

View File

@@ -0,0 +1,48 @@
package gorm_test
import "testing"
type BasePost struct {
Id int64
Title string
URL string
}
type HNPost struct {
BasePost
Upvotes int32
}
type EngadgetPost struct {
BasePost BasePost `gorm:"embedded"`
ImageUrl string
}
func TestSaveAndQueryEmbeddedStruct(t *testing.T) {
DB.Save(&HNPost{BasePost: BasePost{Title: "news"}})
DB.Save(&HNPost{BasePost: BasePost{Title: "hn_news"}})
var news HNPost
if err := DB.First(&news, "title = ?", "hn_news").Error; err != nil {
t.Errorf("no error should happen when query with embedded struct, but got %v", err)
} else if news.Title != "hn_news" {
t.Errorf("embedded struct's value should be scanned correctly")
}
DB.Save(&EngadgetPost{BasePost: BasePost{Title: "engadget_news"}})
var egNews EngadgetPost
if err := DB.First(&egNews, "title = ?", "engadget_news").Error; err != nil {
t.Errorf("no error should happen when query with embedded struct, but got %v", err)
} else if egNews.BasePost.Title != "engadget_news" {
t.Errorf("embedded struct's value should be scanned correctly")
}
if DB.NewScope(&HNPost{}).PrimaryField() == nil {
t.Errorf("primary key with embedded struct should works")
}
for _, field := range DB.NewScope(&HNPost{}).Fields() {
if field.Name == "BasePost" {
t.Errorf("scope Fields should not contain embedded struct")
}
}
}

11
Godeps/_workspace/src/github.com/jinzhu/gorm/errors.go generated vendored Normal file
View File

@@ -0,0 +1,11 @@
package gorm
import "errors"
var (
RecordNotFound = errors.New("record not found")
InvalidSql = errors.New("invalid sql")
NoNewAttrs = errors.New("no new attributes")
NoValidTransaction = errors.New("no valid transaction")
CantStartTransaction = errors.New("can't start transaction")
)

84
Godeps/_workspace/src/github.com/jinzhu/gorm/field.go generated vendored Normal file
View File

@@ -0,0 +1,84 @@
package gorm
import (
"database/sql"
"errors"
"reflect"
)
type Field struct {
*StructField
IsBlank bool
Field reflect.Value
}
func (field *Field) Set(value interface{}) error {
if !field.Field.IsValid() {
return errors.New("field value not valid")
}
if !field.Field.CanAddr() {
return errors.New("unaddressable value")
}
if rvalue, ok := value.(reflect.Value); ok {
value = rvalue.Interface()
}
if scanner, ok := field.Field.Addr().Interface().(sql.Scanner); ok {
if v, ok := value.(reflect.Value); ok {
if err := scanner.Scan(v.Interface()); err != nil {
return err
}
} else {
if err := scanner.Scan(value); err != nil {
return err
}
}
} else {
reflectValue, ok := value.(reflect.Value)
if !ok {
reflectValue = reflect.ValueOf(value)
}
if reflectValue.Type().ConvertibleTo(field.Field.Type()) {
field.Field.Set(reflectValue.Convert(field.Field.Type()))
} else {
return errors.New("could not convert argument")
}
}
field.IsBlank = isBlank(field.Field)
return nil
}
// Fields get value's fields
func (scope *Scope) Fields() map[string]*Field {
if scope.fields == nil {
fields := map[string]*Field{}
structFields := scope.GetStructFields()
indirectValue := scope.IndirectValue()
isStruct := indirectValue.Kind() == reflect.Struct
for _, structField := range structFields {
if isStruct {
fields[structField.DBName] = getField(indirectValue, structField)
} else {
fields[structField.DBName] = &Field{StructField: structField, IsBlank: true}
}
}
scope.fields = fields
}
return scope.fields
}
func getField(indirectValue reflect.Value, structField *StructField) *Field {
field := &Field{StructField: structField}
for _, name := range structField.Names {
indirectValue = reflect.Indirect(indirectValue).FieldByName(name)
}
field.Field = indirectValue
field.IsBlank = isBlank(indirectValue)
return field
}

View File

@@ -0,0 +1,78 @@
package gorm
import (
"fmt"
"reflect"
"time"
)
type foundation struct {
commonDialect
}
func (foundation) BinVar(i int) string {
return fmt.Sprintf("$%v", i)
}
func (foundation) SupportLastInsertId() bool {
return false
}
func (foundation) SqlTag(value reflect.Value, size int, autoIncrease bool) string {
switch value.Kind() {
case reflect.Bool:
return "boolean"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
if autoIncrease {
return "serial"
}
return "int"
case reflect.Int64, reflect.Uint64:
if autoIncrease {
return "bigserial"
}
return "bigint"
case reflect.Float32, reflect.Float64:
return "double"
case reflect.String:
if size > 0 && size < 65532 {
return fmt.Sprintf("varchar(%d)", size)
}
return "clob"
case reflect.Struct:
if _, ok := value.Interface().(time.Time); ok {
return "datetime"
}
default:
if _, ok := value.Interface().([]byte); ok {
return "blob"
}
}
panic(fmt.Sprintf("invalid sql type %s (%s) for foundation", value.Type().Name(), value.Kind().String()))
}
func (f foundation) ReturningStr(tableName, key string) string {
return fmt.Sprintf("RETURNING %v.%v", f.Quote(tableName), key)
}
func (foundation) HasTable(scope *Scope, tableName string) bool {
var count int
scope.NewDB().Raw("SELECT count(*) FROM INFORMATION_SCHEMA.tables WHERE table_schema = current_schema AND table_type = 'TABLE' AND table_name = ?", tableName).Row().Scan(&count)
return count > 0
}
func (foundation) HasColumn(scope *Scope, tableName string, columnName string) bool {
var count int
scope.NewDB().Raw("SELECT count(*) FROM INFORMATION_SCHEMA.columns WHERE table_schema = current_schema AND table_name = ? AND column_name = ?", tableName, columnName).Row().Scan(&count)
return count > 0
}
func (f foundation) RemoveIndex(scope *Scope, indexName string) {
scope.NewDB().Exec(fmt.Sprintf("DROP INDEX %v", f.Quote(indexName)))
}
func (foundation) HasIndex(scope *Scope, tableName string, indexName string) bool {
var count int
scope.NewDB().Raw("SELECT count(*) FROM INFORMATION_SCHEMA.indexes WHERE table_schema = current_schema AND table_name = ? AND index_name = ?", tableName, indexName).Row().Scan(&count)
return count > 0
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -0,0 +1,19 @@
package gorm
import "database/sql"
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
}

View File

@@ -0,0 +1,178 @@
package gorm
import (
"errors"
"fmt"
"reflect"
"strings"
)
type JoinTableHandlerInterface interface {
Setup(relationship *Relationship, tableName string, source reflect.Type, destination reflect.Type)
Table(db *DB) string
Add(handler JoinTableHandlerInterface, db *DB, source interface{}, destination interface{}) error
Delete(handler JoinTableHandlerInterface, db *DB, sources ...interface{}) error
JoinWith(handler JoinTableHandlerInterface, db *DB, source interface{}) *DB
SourceForeignKeys() []JoinTableForeignKey
DestinationForeignKeys() []JoinTableForeignKey
}
type JoinTableForeignKey struct {
DBName string
AssociationDBName string
}
type JoinTableSource struct {
ModelType reflect.Type
ForeignKeys []JoinTableForeignKey
}
type JoinTableHandler struct {
TableName string `sql:"-"`
Source JoinTableSource `sql:"-"`
Destination JoinTableSource `sql:"-"`
}
func (s *JoinTableHandler) SourceForeignKeys() []JoinTableForeignKey {
return s.Source.ForeignKeys
}
func (s *JoinTableHandler) DestinationForeignKeys() []JoinTableForeignKey {
return s.Destination.ForeignKeys
}
func (s *JoinTableHandler) Setup(relationship *Relationship, tableName string, source reflect.Type, destination reflect.Type) {
s.TableName = tableName
s.Source = JoinTableSource{ModelType: source}
sourceScope := &Scope{Value: reflect.New(source).Interface()}
sourcePrimaryFields := sourceScope.GetModelStruct().PrimaryFields
for _, primaryField := range sourcePrimaryFields {
if relationship.ForeignDBName == "" {
relationship.ForeignFieldName = source.Name() + primaryField.Name
relationship.ForeignDBName = ToDBName(relationship.ForeignFieldName)
}
var dbName string
if len(sourcePrimaryFields) == 1 || primaryField.DBName == "id" {
dbName = relationship.ForeignDBName
} else {
dbName = ToDBName(source.Name() + primaryField.Name)
}
s.Source.ForeignKeys = append(s.Source.ForeignKeys, JoinTableForeignKey{
DBName: dbName,
AssociationDBName: primaryField.DBName,
})
}
s.Destination = JoinTableSource{ModelType: destination}
destinationScope := &Scope{Value: reflect.New(destination).Interface()}
destinationPrimaryFields := destinationScope.GetModelStruct().PrimaryFields
for _, primaryField := range destinationPrimaryFields {
var dbName string
if len(sourcePrimaryFields) == 1 || primaryField.DBName == "id" {
dbName = relationship.AssociationForeignDBName
} else {
dbName = ToDBName(destinationScope.GetModelStruct().ModelType.Name() + primaryField.Name)
}
s.Destination.ForeignKeys = append(s.Destination.ForeignKeys, JoinTableForeignKey{
DBName: dbName,
AssociationDBName: primaryField.DBName,
})
}
}
func (s JoinTableHandler) Table(db *DB) string {
return s.TableName
}
func (s JoinTableHandler) GetSearchMap(db *DB, sources ...interface{}) map[string]interface{} {
values := map[string]interface{}{}
for _, source := range sources {
scope := db.NewScope(source)
modelType := scope.GetModelStruct().ModelType
if s.Source.ModelType == modelType {
for _, foreignKey := range s.Source.ForeignKeys {
values[foreignKey.DBName] = scope.Fields()[foreignKey.AssociationDBName].Field.Interface()
}
} else if s.Destination.ModelType == modelType {
for _, foreignKey := range s.Destination.ForeignKeys {
values[foreignKey.DBName] = scope.Fields()[foreignKey.AssociationDBName].Field.Interface()
}
}
}
return values
}
func (s JoinTableHandler) Add(handler JoinTableHandlerInterface, db *DB, source1 interface{}, source2 interface{}) error {
scope := db.NewScope("")
searchMap := s.GetSearchMap(db, source1, source2)
var assignColumns, binVars, conditions []string
var values []interface{}
for key, value := range searchMap {
assignColumns = append(assignColumns, 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 := 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
}
func (s JoinTableHandler) Delete(handler JoinTableHandlerInterface, db *DB, sources ...interface{}) error {
var conditions []string
var values []interface{}
for key, value := range s.GetSearchMap(db, sources...) {
conditions = append(conditions, fmt.Sprintf("%v = ?", key))
values = append(values, value)
}
return db.Table(handler.Table(db)).Where(strings.Join(conditions, " AND "), values...).Delete("").Error
}
func (s JoinTableHandler) JoinWith(handler JoinTableHandlerInterface, db *DB, source interface{}) *DB {
quotedTable := handler.Table(db)
scope := db.NewScope(source)
modelType := scope.GetModelStruct().ModelType
var joinConditions []string
var queryConditions []string
var values []interface{}
if s.Source.ModelType == modelType {
for _, foreignKey := range s.Destination.ForeignKeys {
destinationTableName := db.NewScope(reflect.New(s.Destination.ModelType).Interface()).QuotedTableName()
joinConditions = append(joinConditions, fmt.Sprintf("%v.%v = %v.%v", quotedTable, scope.Quote(foreignKey.DBName), destinationTableName, scope.Quote(foreignKey.AssociationDBName)))
}
for _, foreignKey := range s.Source.ForeignKeys {
queryConditions = append(queryConditions, fmt.Sprintf("%v.%v = ?", quotedTable, scope.Quote(foreignKey.DBName)))
values = append(values, scope.Fields()[foreignKey.AssociationDBName].Field.Interface())
}
return db.Joins(fmt.Sprintf("INNER JOIN %v ON %v", quotedTable, strings.Join(joinConditions, " AND "))).
Where(strings.Join(queryConditions, " AND "), values...)
} else {
db.Error = errors.New("wrong source type for join table handler")
return db
}
}

View File

@@ -0,0 +1,72 @@
package gorm_test
import (
"fmt"
"testing"
"time"
"github.com/jinzhu/gorm"
)
type Person struct {
Id int
Name string
Addresses []*Address `gorm:"many2many:person_addresses;"`
}
type PersonAddress struct {
gorm.JoinTableHandler
PersonID int
AddressID int
DeletedAt time.Time
CreatedAt time.Time
}
func (*PersonAddress) Add(handler gorm.JoinTableHandlerInterface, db *gorm.DB, foreignValue interface{}, associationValue interface{}) error {
return db.Where(map[string]interface{}{
"person_id": db.NewScope(foreignValue).PrimaryKeyValue(),
"address_id": db.NewScope(associationValue).PrimaryKeyValue(),
}).Assign(map[string]interface{}{
"person_id": foreignValue,
"address_id": associationValue,
"deleted_at": gorm.Expr("NULL"),
}).FirstOrCreate(&PersonAddress{}).Error
}
func (*PersonAddress) Delete(handler gorm.JoinTableHandlerInterface, db *gorm.DB, sources ...interface{}) error {
return db.Delete(&PersonAddress{}).Error
}
func (pa *PersonAddress) JoinWith(handler gorm.JoinTableHandlerInterface, db *gorm.DB, source interface{}) *gorm.DB {
table := pa.Table(db)
return db.Table(table).Joins("INNER JOIN person_addresses ON person_addresses.address_id = addresses.id").Where(fmt.Sprintf("%v.deleted_at IS NULL OR %v.deleted_at <= '0001-01-02'", table, table))
}
func TestJoinTable(t *testing.T) {
DB.Exec("drop table person_addresses;")
DB.AutoMigrate(&Person{})
DB.SetJoinTableHandler(&Person{}, "Addresses", &PersonAddress{})
address1 := &Address{Address1: "address 1"}
address2 := &Address{Address1: "address 2"}
person := &Person{Name: "person", Addresses: []*Address{address1, address2}}
DB.Save(person)
DB.Model(person).Association("Addresses").Delete(address1)
if DB.Find(&[]PersonAddress{}, "person_id = ?", person.Id).RowsAffected != 1 {
t.Errorf("Should found one address")
}
if DB.Model(person).Association("Addresses").Count() != 1 {
t.Errorf("Should found one address")
}
if DB.Unscoped().Find(&[]PersonAddress{}, "person_id = ?", person.Id).RowsAffected != 2 {
t.Errorf("Found two addresses with Unscoped")
}
if DB.Model(person).Association("Addresses").Clear(); DB.Model(person).Association("Addresses").Count() != 0 {
t.Errorf("Should deleted all addresses")
}
}

67
Godeps/_workspace/src/github.com/jinzhu/gorm/logger.go generated vendored Normal file
View File

@@ -0,0 +1,67 @@
package gorm
import (
"database/sql/driver"
"fmt"
"log"
"os"
"reflect"
"regexp"
"time"
)
type logger interface {
Print(v ...interface{})
}
type Logger struct {
*log.Logger
}
var defaultLogger = Logger{log.New(os.Stdout, "\r\n", 0)}
// Format log
var sqlRegexp = regexp.MustCompile(`(\$\d+)|\?`)
func (logger Logger) Print(values ...interface{}) {
if len(values) > 1 {
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
var formatedValues []interface{}
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 {
formatedValues = append(formatedValues, fmt.Sprintf("'%v'", t.Format(time.RFC3339)))
} else if b, ok := value.([]byte); ok {
formatedValues = append(formatedValues, fmt.Sprintf("'%v'", string(b)))
} else if r, ok := value.(driver.Valuer); ok {
if value, err := r.Value(); err == nil && value != nil {
formatedValues = append(formatedValues, fmt.Sprintf("'%v'", value))
} else {
formatedValues = append(formatedValues, "NULL")
}
} else {
formatedValues = append(formatedValues, fmt.Sprintf("'%v'", value))
}
} else {
formatedValues = append(formatedValues, fmt.Sprintf("'%v'", value))
}
}
messages = append(messages, fmt.Sprintf(sqlRegexp.ReplaceAllString(values[3].(string), "%v"), formatedValues...))
} else {
messages = append(messages, "\033[31;1m")
messages = append(messages, values[2:]...)
messages = append(messages, "\033[0m")
}
logger.Println(messages...)
}
}

496
Godeps/_workspace/src/github.com/jinzhu/gorm/main.go generated vendored Normal file
View File

@@ -0,0 +1,496 @@
package gorm
import (
"database/sql"
"errors"
"fmt"
"reflect"
"strings"
"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: return time.Now().UTC()
//
var NowFunc = func() time.Time {
return time.Now()
}
type DB struct {
Value interface{}
Error error
RowsAffected int64
callback *callback
db sqlCommon
parent *DB
search *search
logMode int
logger logger
dialect Dialect
singularTable bool
source string
values map[string]interface{}
joinTableHandlers map[string]JoinTableHandler
}
func Open(dialect string, args ...interface{}) (DB, error) {
var db DB
var err error
if len(args) == 0 {
err = errors.New("invalid database source")
} else {
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)
}
if driver == "foundation" {
driver = "postgres" // FoundationDB speaks a postgres-compatible protocol.
}
dbSql, err = sql.Open(driver, source)
case sqlCommon:
source = reflect.Indirect(reflect.ValueOf(value)).FieldByName("dsn").String()
dbSql = value
}
db = DB{
dialect: NewDialect(dialect),
logger: defaultLogger,
callback: DefaultCallback,
source: source,
values: map[string]interface{}{},
db: dbSql,
}
db.parent = &db
}
return db, err
}
func (s *DB) Close() error {
return s.parent.db.(*sql.DB).Close()
}
func (s *DB) DB() *sql.DB {
return s.db.(*sql.DB)
}
func (s *DB) New() *DB {
clone := s.clone()
clone.search = nil
clone.Value = nil
return clone
}
// NewScope create scope for callbacks, including DB's search information
func (db *DB) NewScope(value interface{}) *Scope {
dbClone := db.clone()
dbClone.Value = value
return &Scope{db: dbClone, Search: dbClone.search.clone(), Value: value}
}
// CommonDB Return the underlying sql.DB or sql.Tx instance.
// Use of this method is discouraged. It's mainly intended to allow
// coexistence with legacy non-GORM code.
func (s *DB) CommonDB() sqlCommon {
return s.db
}
func (s *DB) Callback() *callback {
s.parent.callback = s.parent.callback.clone()
return s.parent.callback
}
func (s *DB) SetLogger(l logger) {
s.parent.logger = l
}
func (s *DB) LogMode(enable bool) *DB {
if enable {
s.logMode = 2
} else {
s.logMode = 1
}
return s
}
func (s *DB) SingularTable(enable bool) {
modelStructs = map[reflect.Type]*ModelStruct{}
s.parent.singularTable = enable
}
func (s *DB) Where(query interface{}, args ...interface{}) *DB {
return s.clone().search.Where(query, args...).db
}
func (s *DB) Or(query interface{}, args ...interface{}) *DB {
return s.clone().search.Or(query, args...).db
}
func (s *DB) Not(query interface{}, args ...interface{}) *DB {
return s.clone().search.Not(query, args...).db
}
func (s *DB) Limit(value interface{}) *DB {
return s.clone().search.Limit(value).db
}
func (s *DB) Offset(value interface{}) *DB {
return s.clone().search.Offset(value).db
}
func (s *DB) Order(value string, reorder ...bool) *DB {
return s.clone().search.Order(value, reorder...).db
}
func (s *DB) Select(query interface{}, args ...interface{}) *DB {
return s.clone().search.Select(query, args...).db
}
func (s *DB) Omit(columns ...string) *DB {
return s.clone().search.Omit(columns...).db
}
func (s *DB) Group(query string) *DB {
return s.clone().search.Group(query).db
}
func (s *DB) Having(query string, values ...interface{}) *DB {
return s.clone().search.Having(query, values...).db
}
func (s *DB) Joins(query string) *DB {
return s.clone().search.Joins(query).db
}
func (s *DB) Scopes(funcs ...func(*DB) *DB) *DB {
for _, f := range funcs {
s = f(s)
}
return s
}
func (s *DB) Unscoped() *DB {
return s.clone().search.unscoped().db
}
func (s *DB) Attrs(attrs ...interface{}) *DB {
return s.clone().search.Attrs(attrs...).db
}
func (s *DB) Assign(attrs ...interface{}) *DB {
return s.clone().search.Assign(attrs...).db
}
func (s *DB) First(out interface{}, where ...interface{}) *DB {
newScope := s.clone().NewScope(out)
newScope.Search.Limit(1)
return newScope.Set("gorm:order_by_primary_key", "ASC").
inlineCondition(where...).callCallbacks(s.parent.callback.queries).db
}
func (s *DB) Last(out interface{}, where ...interface{}) *DB {
newScope := s.clone().NewScope(out)
newScope.Search.Limit(1)
return newScope.Set("gorm:order_by_primary_key", "DESC").
inlineCondition(where...).callCallbacks(s.parent.callback.queries).db
}
func (s *DB) Find(out interface{}, where ...interface{}) *DB {
return s.clone().NewScope(out).inlineCondition(where...).callCallbacks(s.parent.callback.queries).db
}
func (s *DB) Scan(dest interface{}) *DB {
return s.clone().NewScope(s.Value).Set("gorm:query_destination", dest).callCallbacks(s.parent.callback.queries).db
}
func (s *DB) Row() *sql.Row {
return s.NewScope(s.Value).row()
}
func (s *DB) Rows() (*sql.Rows, error) {
return s.NewScope(s.Value).rows()
}
func (s *DB) Pluck(column string, value interface{}) *DB {
return s.NewScope(s.Value).pluck(column, value).db
}
func (s *DB) Count(value interface{}) *DB {
return s.NewScope(s.Value).count(value).db
}
func (s *DB) Related(value interface{}, foreignKeys ...string) *DB {
return s.clone().NewScope(s.Value).related(value, foreignKeys...).db
}
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(convertInterfaceToMap(s.search.assignAttrs), false)
}
return c
}
func (s *DB) FirstOrCreate(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().callCallbacks(s.parent.callback.creates)
} else if len(c.search.assignAttrs) > 0 {
c.NewScope(out).InstanceSet("gorm:update_interface", s.search.assignAttrs).callCallbacks(s.parent.callback.updates)
}
return c
}
func (s *DB) Update(attrs ...interface{}) *DB {
return s.Updates(toSearchableMap(attrs...), true)
}
func (s *DB) Updates(values interface{}, ignoreProtectedAttrs ...bool) *DB {
return s.clone().NewScope(s.Value).
Set("gorm:ignore_protected_attrs", len(ignoreProtectedAttrs) > 0).
InstanceSet("gorm:update_interface", values).
callCallbacks(s.parent.callback.updates).db
}
func (s *DB) UpdateColumn(attrs ...interface{}) *DB {
return s.UpdateColumns(toSearchableMap(attrs...))
}
func (s *DB) UpdateColumns(values interface{}) *DB {
return s.clone().NewScope(s.Value).
Set("gorm:update_column", true).
Set("gorm:save_associations", false).
InstanceSet("gorm:update_interface", values).
callCallbacks(s.parent.callback.updates).db
}
func (s *DB) Save(value interface{}) *DB {
scope := s.clone().NewScope(value)
if scope.PrimaryKeyZero() {
return scope.callCallbacks(s.parent.callback.creates).db
}
return scope.callCallbacks(s.parent.callback.updates).db
}
func (s *DB) Create(value interface{}) *DB {
scope := s.clone().NewScope(value)
return scope.callCallbacks(s.parent.callback.creates).db
}
func (s *DB) Delete(value interface{}, where ...interface{}) *DB {
return s.clone().NewScope(value).inlineCondition(where...).callCallbacks(s.parent.callback.deletes).db
}
func (s *DB) Raw(sql string, values ...interface{}) *DB {
return s.clone().search.Raw(true).Where(sql, values...).db
}
func (s *DB) Exec(sql string, values ...interface{}) *DB {
scope := s.clone().NewScope(nil)
generatedSql := scope.buildWhereCondition(map[string]interface{}{"query": sql, "args": values})
generatedSql = strings.TrimSuffix(strings.TrimPrefix(generatedSql, "("), ")")
scope.Raw(generatedSql)
return scope.Exec().db
}
func (s *DB) Model(value interface{}) *DB {
c := s.clone()
c.Value = value
return c
}
func (s *DB) Table(name string) *DB {
clone := s.clone()
clone.search.Table(name)
clone.Value = nil
return clone
}
func (s *DB) Debug() *DB {
return s.clone().LogMode(true)
}
func (s *DB) Begin() *DB {
c := s.clone()
if db, ok := c.db.(sqlDb); ok {
tx, err := db.Begin()
c.db = interface{}(tx).(sqlCommon)
c.err(err)
} else {
c.err(CantStartTransaction)
}
return c
}
func (s *DB) Commit() *DB {
if db, ok := s.db.(sqlTx); ok {
s.err(db.Commit())
} else {
s.err(NoValidTransaction)
}
return s
}
func (s *DB) Rollback() *DB {
if db, ok := s.db.(sqlTx); ok {
s.err(db.Rollback())
} else {
s.err(NoValidTransaction)
}
return s
}
func (s *DB) NewRecord(value interface{}) bool {
return s.clone().NewScope(value).PrimaryKeyZero()
}
func (s *DB) RecordNotFound() bool {
return s.Error == RecordNotFound
}
// Migrations
func (s *DB) CreateTable(value interface{}) *DB {
return s.clone().NewScope(value).createTable().db
}
func (s *DB) DropTable(value interface{}) *DB {
return s.clone().NewScope(value).dropTable().db
}
func (s *DB) DropTableIfExists(value interface{}) *DB {
return s.clone().NewScope(value).dropTableIfExists().db
}
func (s *DB) HasTable(value interface{}) bool {
scope := s.clone().NewScope(value)
tableName := scope.TableName()
return scope.Dialect().HasTable(scope, tableName)
}
func (s *DB) AutoMigrate(values ...interface{}) *DB {
db := s.clone()
for _, value := range values {
db = db.NewScope(value).NeedPtr().autoMigrate().db
}
return db
}
func (s *DB) ModifyColumn(column string, typ string) *DB {
scope := s.clone().NewScope(s.Value)
scope.modifyColumn(column, typ)
return scope.db
}
func (s *DB) DropColumn(column string) *DB {
scope := s.clone().NewScope(s.Value)
scope.dropColumn(column)
return scope.db
}
func (s *DB) AddIndex(indexName string, column ...string) *DB {
scope := s.clone().NewScope(s.Value)
scope.addIndex(false, indexName, column...)
return scope.db
}
func (s *DB) AddUniqueIndex(indexName string, column ...string) *DB {
scope := s.clone().NewScope(s.Value)
scope.addIndex(true, indexName, column...)
return scope.db
}
func (s *DB) RemoveIndex(indexName string) *DB {
scope := s.clone().NewScope(s.Value)
scope.removeIndex(indexName)
return scope.db
}
/*
Add foreign key to the given scope
Example:
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.clone().NewScope(s.Value)
scope.addForeignKey(field, dest, onDelete, onUpdate)
return scope.db
}
func (s *DB) Association(column string) *Association {
var err error
scope := s.clone().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 || field.Relationship.ForeignFieldName == "" {
err = fmt.Errorf("invalid association %v for %v", column, scope.IndirectValue().Type())
} else {
return &Association{Scope: scope, Column: column, PrimaryKey: primaryField.Field.Interface(), Field: field}
}
} else {
err = fmt.Errorf("%v doesn't have column %v", scope.IndirectValue().Type(), column)
}
}
return &Association{Error: err}
}
func (s *DB) Preload(column string, conditions ...interface{}) *DB {
return s.clone().search.Preload(column, conditions...).db
}
// Set set value by name
func (s *DB) Set(name string, value interface{}) *DB {
return s.clone().InstantSet(name, value)
}
func (s *DB) InstantSet(name string, value interface{}) *DB {
s.values[name] = value
return s
}
// Get get value by name
func (s *DB) Get(name string) (value interface{}, ok bool) {
value, ok = s.values[name]
return
}
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 := parseTagSetting(field.Tag.Get("gorm"))["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(scope, table) {
s.Table(table).AutoMigrate(handler)
}
}
}
}
}

View File

@@ -0,0 +1,50 @@
package gorm
import "time"
func (s *DB) clone() *DB {
db := DB{db: s.db, parent: s.parent, logMode: s.logMode, values: map[string]interface{}{}, Value: s.Value, Error: s.Error}
for key, value := range s.values {
db.values[key] = value
}
if s.search == nil {
db.search = &search{}
} else {
db.search = s.search.clone()
}
db.search.db = &db
return &db
}
func (s *DB) err(err error) error {
if err != nil {
if err != RecordNotFound {
if s.logMode == 0 {
go s.print(fileWithLineNum(), err)
} else {
s.log(err)
}
}
s.Error = err
}
return err
}
func (s *DB) print(v ...interface{}) {
s.parent.logger.(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)
}
}

View File

@@ -0,0 +1,645 @@
package gorm_test
import (
"database/sql"
"database/sql/driver"
"fmt"
"strconv"
_ "github.com/denisenkom/go-mssqldb"
testdb "github.com/erikstmartin/go-testdb"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
"github.com/jinzhu/now"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
"os"
"testing"
"time"
)
var (
DB gorm.DB
t1, t2, t3, t4, t5 time.Time
)
func init() {
var err error
switch os.Getenv("GORM_DIALECT") {
case "mysql":
// CREATE USER 'gorm'@'localhost' IDENTIFIED BY 'gorm';
// CREATE DATABASE gorm;
// GRANT ALL ON gorm.* TO 'gorm'@'localhost';
fmt.Println("testing mysql...")
DB, err = gorm.Open("mysql", "gorm:gorm@/gorm?charset=utf8&parseTime=True")
case "postgres":
fmt.Println("testing postgres...")
DB, err = gorm.Open("postgres", "user=gorm DB.name=gorm sslmode=disable")
case "foundation":
fmt.Println("testing foundation...")
DB, err = gorm.Open("foundation", "dbname=gorm port=15432 sslmode=disable")
case "mssql":
fmt.Println("testing mssql...")
DB, err = gorm.Open("mssql", "server=SERVER_HERE;database=rogue;user id=USER_HERE;password=PW_HERE;port=1433")
default:
fmt.Println("testing sqlite3...")
DB, err = gorm.Open("sqlite3", "/tmp/gorm.db")
}
// DB.SetLogger(Logger{log.New(os.Stdout, "\r\n", 0)})
// DB.SetLogger(log.New(os.Stdout, "\r\n", 0))
DB.LogMode(true)
DB.LogMode(false)
if err != nil {
panic(fmt.Sprintf("No error should happen when connect database, but got %+v", err))
}
DB.DB().SetMaxIdleConns(10)
runMigration()
}
func TestStringPrimaryKey(t *testing.T) {
type UUIDStruct struct {
ID string `gorm:"primary_key"`
Name string
}
DB.AutoMigrate(&UUIDStruct{})
data := UUIDStruct{ID: "uuid", Name: "hello"}
if err := DB.Save(&data).Error; err != nil || data.ID != "uuid" {
t.Errorf("string primary key should not be populated")
}
}
func TestExceptionsWithInvalidSql(t *testing.T) {
var columns []string
if DB.Where("sdsd.zaaa = ?", "sd;;;aa").Pluck("aaa", &columns).Error == nil {
t.Errorf("Should got error with invalid SQL")
}
if DB.Model(&User{}).Where("sdsd.zaaa = ?", "sd;;;aa").Pluck("aaa", &columns).Error == nil {
t.Errorf("Should got error with invalid SQL")
}
if DB.Where("sdsd.zaaa = ?", "sd;;;aa").Find(&User{}).Error == nil {
t.Errorf("Should got error with invalid SQL")
}
var count1, count2 int64
DB.Model(&User{}).Count(&count1)
if count1 <= 0 {
t.Errorf("Should find some users")
}
if DB.Where("name = ?", "jinzhu; delete * from users").First(&User{}).Error == nil {
t.Errorf("Should got error with invalid SQL")
}
DB.Model(&User{}).Count(&count2)
if count1 != count2 {
t.Errorf("No user should not be deleted by invalid SQL")
}
}
func TestSetTable(t *testing.T) {
DB.Create(getPreparedUser("pluck_user1", "pluck_user"))
DB.Create(getPreparedUser("pluck_user2", "pluck_user"))
DB.Create(getPreparedUser("pluck_user3", "pluck_user"))
if err := DB.Table("users").Where("role = ?", "pluck_user").Pluck("age", &[]int{}).Error; err != nil {
t.Errorf("No errors should happen if set table for pluck", err.Error())
}
var users []User
if DB.Table("users").Find(&[]User{}).Error != nil {
t.Errorf("No errors should happen if set table for find")
}
if DB.Table("invalid_table").Find(&users).Error == nil {
t.Errorf("Should got error when table is set to an invalid table")
}
DB.Exec("drop table deleted_users;")
if DB.Table("deleted_users").CreateTable(&User{}).Error != nil {
t.Errorf("Create table with specified table")
}
DB.Table("deleted_users").Save(&User{Name: "DeletedUser"})
var deletedUsers []User
DB.Table("deleted_users").Find(&deletedUsers)
if len(deletedUsers) != 1 {
t.Errorf("Query from specified table")
}
DB.Save(getPreparedUser("normal_user", "reset_table"))
DB.Table("deleted_users").Save(getPreparedUser("deleted_user", "reset_table"))
var user1, user2, user3 User
DB.Where("role = ?", "reset_table").First(&user1).Table("deleted_users").First(&user2).Table("").First(&user3)
if (user1.Name != "normal_user") || (user2.Name != "deleted_user") || (user3.Name != "normal_user") {
t.Errorf("unset specified table with blank string")
}
}
type Order struct {
}
type Cart struct {
}
func (c Cart) TableName() string {
return "shopping_cart"
}
func TestHasTable(t *testing.T) {
type Foo struct {
Id int
Stuff string
}
DB.DropTable(&Foo{})
if ok := DB.HasTable(&Foo{}); ok {
t.Errorf("Table should not exist, but does")
}
if err := DB.CreateTable(&Foo{}).Error; err != nil {
t.Errorf("Table should be created")
}
if ok := DB.HasTable(&Foo{}); !ok {
t.Errorf("Table should exist, but HasTable informs it does not")
}
}
func TestTableName(t *testing.T) {
DB := DB.Model("")
if DB.NewScope(Order{}).TableName() != "orders" {
t.Errorf("Order's table name should be orders")
}
if DB.NewScope(&Order{}).TableName() != "orders" {
t.Errorf("&Order's table name should be orders")
}
if DB.NewScope([]Order{}).TableName() != "orders" {
t.Errorf("[]Order's table name should be orders")
}
if DB.NewScope(&[]Order{}).TableName() != "orders" {
t.Errorf("&[]Order's table name should be orders")
}
DB.SingularTable(true)
if DB.NewScope(Order{}).TableName() != "order" {
t.Errorf("Order's singular table name should be order")
}
if DB.NewScope(&Order{}).TableName() != "order" {
t.Errorf("&Order's singular table name should be order")
}
if DB.NewScope([]Order{}).TableName() != "order" {
t.Errorf("[]Order's singular table name should be order")
}
if DB.NewScope(&[]Order{}).TableName() != "order" {
t.Errorf("&[]Order's singular table name should be order")
}
if DB.NewScope(&Cart{}).TableName() != "shopping_cart" {
t.Errorf("&Cart's singular table name should be shopping_cart")
}
if DB.NewScope(Cart{}).TableName() != "shopping_cart" {
t.Errorf("Cart's singular table name should be shopping_cart")
}
if DB.NewScope(&[]Cart{}).TableName() != "shopping_cart" {
t.Errorf("&[]Cart's singular table name should be shopping_cart")
}
if DB.NewScope([]Cart{}).TableName() != "shopping_cart" {
t.Errorf("[]Cart's singular table name should be shopping_cart")
}
DB.SingularTable(false)
}
func TestSqlNullValue(t *testing.T) {
DB.DropTable(&NullValue{})
DB.AutoMigrate(&NullValue{})
if err := DB.Save(&NullValue{Name: sql.NullString{String: "hello", Valid: true},
Age: sql.NullInt64{Int64: 18, Valid: true},
Male: sql.NullBool{Bool: true, Valid: true},
Height: sql.NullFloat64{Float64: 100.11, Valid: true},
AddedAt: NullTime{Time: time.Now(), Valid: true},
}).Error; err != nil {
t.Errorf("Not error should raise when test null value")
}
var nv NullValue
DB.First(&nv, "name = ?", "hello")
if nv.Name.String != "hello" || nv.Age.Int64 != 18 || nv.Male.Bool != true || nv.Height.Float64 != 100.11 || nv.AddedAt.Valid != true {
t.Errorf("Should be able to fetch null value")
}
if err := DB.Save(&NullValue{Name: sql.NullString{String: "hello-2", Valid: true},
Age: sql.NullInt64{Int64: 18, Valid: false},
Male: sql.NullBool{Bool: true, Valid: true},
Height: sql.NullFloat64{Float64: 100.11, Valid: true},
AddedAt: NullTime{Time: time.Now(), Valid: false},
}).Error; err != nil {
t.Errorf("Not error should raise when test null value")
}
var nv2 NullValue
DB.First(&nv2, "name = ?", "hello-2")
if nv2.Name.String != "hello-2" || nv2.Age.Int64 != 0 || nv2.Male.Bool != true || nv2.Height.Float64 != 100.11 || nv2.AddedAt.Valid != false {
t.Errorf("Should be able to fetch null value")
}
if err := DB.Save(&NullValue{Name: sql.NullString{String: "hello-3", Valid: false},
Age: sql.NullInt64{Int64: 18, Valid: false},
Male: sql.NullBool{Bool: true, Valid: true},
Height: sql.NullFloat64{Float64: 100.11, Valid: true},
AddedAt: NullTime{Time: time.Now(), Valid: false},
}).Error; err == nil {
t.Errorf("Can't save because of name can't be null")
}
}
func TestTransaction(t *testing.T) {
tx := DB.Begin()
u := User{Name: "transcation"}
if err := tx.Save(&u).Error; err != nil {
t.Errorf("No error should raise")
}
if err := tx.First(&User{}, "name = ?", "transcation").Error; err != nil {
t.Errorf("Should find saved record")
}
if sqlTx, ok := tx.CommonDB().(*sql.Tx); !ok || sqlTx == nil {
t.Errorf("Should return the underlying sql.Tx")
}
tx.Rollback()
if err := tx.First(&User{}, "name = ?", "transcation").Error; err == nil {
t.Errorf("Should not find record after rollback")
}
tx2 := DB.Begin()
u2 := User{Name: "transcation-2"}
if err := tx2.Save(&u2).Error; err != nil {
t.Errorf("No error should raise")
}
if err := tx2.First(&User{}, "name = ?", "transcation-2").Error; err != nil {
t.Errorf("Should find saved record")
}
tx2.Commit()
if err := DB.First(&User{}, "name = ?", "transcation-2").Error; err != nil {
t.Errorf("Should be able to find committed record")
}
}
func TestRow(t *testing.T) {
user1 := User{Name: "RowUser1", Age: 1, Birthday: now.MustParse("2000-1-1")}
user2 := User{Name: "RowUser2", Age: 10, Birthday: now.MustParse("2010-1-1")}
user3 := User{Name: "RowUser3", Age: 20, Birthday: now.MustParse("2020-1-1")}
DB.Save(&user1).Save(&user2).Save(&user3)
row := DB.Table("users").Where("name = ?", user2.Name).Select("age").Row()
var age int64
row.Scan(&age)
if age != 10 {
t.Errorf("Scan with Row")
}
}
func TestRows(t *testing.T) {
user1 := User{Name: "RowsUser1", Age: 1, Birthday: now.MustParse("2000-1-1")}
user2 := User{Name: "RowsUser2", Age: 10, Birthday: now.MustParse("2010-1-1")}
user3 := User{Name: "RowsUser3", Age: 20, Birthday: now.MustParse("2020-1-1")}
DB.Save(&user1).Save(&user2).Save(&user3)
rows, err := DB.Table("users").Where("name = ? or name = ?", user2.Name, user3.Name).Select("name, age").Rows()
if err != nil {
t.Errorf("Not error should happen, but got")
}
count := 0
for rows.Next() {
var name string
var age int64
rows.Scan(&name, &age)
count++
}
if count != 2 {
t.Errorf("Should found two records with name 3")
}
}
func TestScan(t *testing.T) {
user1 := User{Name: "ScanUser1", Age: 1, Birthday: now.MustParse("2000-1-1")}
user2 := User{Name: "ScanUser2", Age: 10, Birthday: now.MustParse("2010-1-1")}
user3 := User{Name: "ScanUser3", Age: 20, Birthday: now.MustParse("2020-1-1")}
DB.Save(&user1).Save(&user2).Save(&user3)
type result struct {
Name string
Age int
}
var res result
DB.Table("users").Select("name, age").Where("name = ?", user3.Name).Scan(&res)
if res.Name != user3.Name {
t.Errorf("Scan into struct should work")
}
var doubleAgeRes result
DB.Table("users").Select("age + age as age").Where("name = ?", user3.Name).Scan(&doubleAgeRes)
if doubleAgeRes.Age != res.Age*2 {
t.Errorf("Scan double age as age")
}
var ress []result
DB.Table("users").Select("name, age").Where("name in (?)", []string{user2.Name, user3.Name}).Scan(&ress)
if len(ress) != 2 || ress[0].Name != user2.Name || ress[1].Name != user3.Name {
t.Errorf("Scan into struct map")
}
}
func TestRaw(t *testing.T) {
user1 := User{Name: "ExecRawSqlUser1", Age: 1, Birthday: now.MustParse("2000-1-1")}
user2 := User{Name: "ExecRawSqlUser2", Age: 10, Birthday: now.MustParse("2010-1-1")}
user3 := User{Name: "ExecRawSqlUser3", Age: 20, Birthday: now.MustParse("2020-1-1")}
DB.Save(&user1).Save(&user2).Save(&user3)
type result struct {
Name string
Email string
}
var ress []result
DB.Raw("SELECT name, age FROM users WHERE name = ? or name = ?", user2.Name, user3.Name).Scan(&ress)
if len(ress) != 2 || ress[0].Name != user2.Name || ress[1].Name != user3.Name {
t.Errorf("Raw with scan")
}
rows, _ := DB.Raw("select name, age from users where name = ?", user3.Name).Rows()
count := 0
for rows.Next() {
count++
}
if count != 1 {
t.Errorf("Raw with Rows should find one record with name 3")
}
DB.Exec("update users set name=? where name in (?)", "jinzhu", []string{user1.Name, user2.Name, user3.Name})
if DB.Where("name in (?)", []string{user1.Name, user2.Name, user3.Name}).First(&User{}).Error != gorm.RecordNotFound {
t.Error("Raw sql to update records")
}
}
func TestGroup(t *testing.T) {
rows, err := DB.Select("name").Table("users").Group("name").Rows()
if err == nil {
defer rows.Close()
for rows.Next() {
var name string
rows.Scan(&name)
}
} else {
t.Errorf("Should not raise any error")
}
}
func TestJoins(t *testing.T) {
type result struct {
Name string
Email string
}
user := User{
Name: "joins",
Emails: []Email{{Email: "join1@example.com"}, {Email: "join2@example.com"}},
}
DB.Save(&user)
var results []result
DB.Table("users").Select("name, email").Joins("left join emails on emails.user_id = users.id").Where("name = ?", "joins").Scan(&results)
if len(results) != 2 || results[0].Email != "join1@example.com" || results[1].Email != "join2@example.com" {
t.Errorf("Should find all two emails with Join")
}
}
func TestHaving(t *testing.T) {
rows, err := DB.Select("name, count(*) as total").Table("users").Group("name").Having("name IN (?)", []string{"2", "3"}).Rows()
if err == nil {
defer rows.Close()
for rows.Next() {
var name string
var total int64
rows.Scan(&name, &total)
if name == "2" && total != 1 {
t.Errorf("Should have one user having name 2")
}
if name == "3" && total != 2 {
t.Errorf("Should have two users having name 3")
}
}
} else {
t.Errorf("Should not raise any error")
}
}
func DialectHasTzSupport() bool {
// NB: mssql and FoundationDB do not support time zones.
if dialect := os.Getenv("GORM_DIALECT"); dialect == "mssql" || dialect == "foundation" {
return false
}
return true
}
func TestTimeWithZone(t *testing.T) {
var format = "2006-01-02 15:04:05 -0700"
var times []time.Time
GMT8, _ := time.LoadLocation("Asia/Shanghai")
times = append(times, time.Date(2013, 02, 19, 1, 51, 49, 123456789, GMT8))
times = append(times, time.Date(2013, 02, 18, 17, 51, 49, 123456789, time.UTC))
for index, vtime := range times {
name := "time_with_zone_" + strconv.Itoa(index)
user := User{Name: name, Birthday: vtime}
if !DialectHasTzSupport() {
// If our driver dialect doesn't support TZ's, just use UTC for everything here.
user.Birthday = vtime.UTC()
}
DB.Save(&user)
expectedBirthday := "2013-02-18 17:51:49 +0000"
foundBirthday := user.Birthday.UTC().Format(format)
if foundBirthday != expectedBirthday {
t.Errorf("User's birthday should not be changed after save for name=%s, expected bday=%+v but actual value=%+v", name, expectedBirthday, foundBirthday)
}
var findUser, findUser2, findUser3 User
DB.First(&findUser, "name = ?", name)
foundBirthday = findUser.Birthday.UTC().Format(format)
if foundBirthday != expectedBirthday {
t.Errorf("User's birthday should not be changed after find for name=%s, expected bday=%+v but actual value=%+v or %+v", name, expectedBirthday, foundBirthday)
}
if DB.Where("id = ? AND birthday >= ?", findUser.Id, user.Birthday.Add(-time.Minute)).First(&findUser2).RecordNotFound() {
t.Errorf("User should be found")
}
if !DB.Where("id = ? AND birthday >= ?", findUser.Id, user.Birthday.Add(time.Minute)).First(&findUser3).RecordNotFound() {
t.Errorf("User should not be found")
}
}
}
func TestHstore(t *testing.T) {
type Details struct {
Id int64
Bulk gorm.Hstore
}
if dialect := os.Getenv("GORM_DIALECT"); dialect != "postgres" {
t.Skip()
}
if err := DB.Exec("CREATE EXTENSION IF NOT EXISTS hstore").Error; err != nil {
fmt.Println("\033[31mHINT: Must be superuser to create hstore extension (ALTER USER gorm WITH SUPERUSER;)\033[0m")
panic(fmt.Sprintf("No error should happen when create hstore extension, but got %+v", err))
}
DB.Exec("drop table details")
if err := DB.CreateTable(&Details{}).Error; err != nil {
panic(fmt.Sprintf("No error should happen when create table, but got %+v", err))
}
bankAccountId, phoneNumber, opinion := "123456", "14151321232", "sharkbait"
bulk := map[string]*string{
"bankAccountId": &bankAccountId,
"phoneNumber": &phoneNumber,
"opinion": &opinion,
}
d := Details{Bulk: bulk}
DB.Save(&d)
var d2 Details
if err := DB.First(&d2).Error; err != nil {
t.Errorf("Got error when tried to fetch details: %+v", err)
}
for k := range bulk {
if r, ok := d2.Bulk[k]; ok {
if res, _ := bulk[k]; *res != *r {
t.Errorf("Details should be equal")
}
} else {
t.Errorf("Details should be existed")
}
}
}
func TestSetAndGet(t *testing.T) {
if value, ok := DB.Set("hello", "world").Get("hello"); !ok {
t.Errorf("Should be able to get setting after set")
} else {
if value.(string) != "world" {
t.Errorf("Setted value should not be changed")
}
}
if _, ok := DB.Get("non_existing"); ok {
t.Errorf("Get non existing key should return error")
}
}
func TestCompatibilityMode(t *testing.T) {
DB, _ := gorm.Open("testdb", "")
testdb.SetQueryFunc(func(query string) (driver.Rows, error) {
columns := []string{"id", "name", "age"}
result := `
1,Tim,20
2,Joe,25
3,Bob,30
`
return testdb.RowsFromCSVString(columns, result), nil
})
var users []User
DB.Find(&users)
if (users[0].Name != "Tim") || len(users) != 3 {
t.Errorf("Unexcepted result returned")
}
}
func TestOpenExistingDB(t *testing.T) {
DB.Save(&User{Name: "jnfeinstein"})
dialect := os.Getenv("GORM_DIALECT")
db, err := gorm.Open(dialect, DB.DB())
if err != nil {
t.Errorf("Should have wrapped the existing DB connection")
}
var user User
if db.Where("name = ?", "jnfeinstein").First(&user).Error == gorm.RecordNotFound {
t.Errorf("Should have found existing record")
}
}
func BenchmarkGorm(b *testing.B) {
b.N = 2000
for x := 0; x < b.N; x++ {
e := strconv.Itoa(x) + "benchmark@example.org"
email := BigEmail{Email: e, UserAgent: "pc", RegisteredAt: time.Now()}
// Insert
DB.Save(&email)
// Query
DB.First(&BigEmail{}, "email = ?", e)
// Update
DB.Model(&email).UpdateColumn("email", "new-"+e)
// Delete
DB.Delete(&email)
}
}
func BenchmarkRawSql(b *testing.B) {
DB, _ := sql.Open("postgres", "user=gorm DB.ame=gorm sslmode=disable")
DB.SetMaxIdleConns(10)
insertSql := "INSERT INTO emails (user_id,email,user_agent,registered_at,created_at,updated_at) VALUES ($1,$2,$3,$4,$5,$6) RETURNING id"
querySql := "SELECT * FROM emails WHERE email = $1 ORDER BY id LIMIT 1"
updateSql := "UPDATE emails SET email = $1, updated_at = $2 WHERE id = $3"
deleteSql := "DELETE FROM orders WHERE id = $1"
b.N = 2000
for x := 0; x < b.N; x++ {
var id int64
e := strconv.Itoa(x) + "benchmark@example.org"
email := BigEmail{Email: e, UserAgent: "pc", RegisteredAt: time.Now()}
// Insert
DB.QueryRow(insertSql, email.UserId, email.Email, email.UserAgent, email.RegisteredAt, time.Now(), time.Now()).Scan(&id)
// Query
rows, _ := DB.Query(querySql, email.Email)
rows.Close()
// Update
DB.Exec(updateSql, "new-"+e, time.Now(), id)
// Delete
DB.Exec(deleteSql, id)
}
}

View File

@@ -0,0 +1,123 @@
package gorm_test
import (
"fmt"
"testing"
"time"
)
func runMigration() {
if err := DB.DropTableIfExists(&User{}).Error; err != nil {
fmt.Printf("Got error when try to delete table users, %+v\n", err)
}
for _, table := range []string{"animals", "user_languages"} {
DB.Exec(fmt.Sprintf("drop table %v;", table))
}
values := []interface{}{&Product{}, &Email{}, &Address{}, &CreditCard{}, &Company{}, &Role{}, &Language{}, &HNPost{}, &EngadgetPost{}, &Animal{}, &User{}, &JoinTable{}}
for _, value := range values {
DB.DropTable(value)
}
if err := DB.AutoMigrate(values...).Error; err != nil {
panic(fmt.Sprintf("No error should happen when create table, but got %+v", err))
}
}
func TestIndexes(t *testing.T) {
if err := DB.Model(&Email{}).AddIndex("idx_email_email", "email").Error; err != nil {
t.Errorf("Got error when tried to create index: %+v", err)
}
scope := DB.NewScope(&Email{})
if !scope.Dialect().HasIndex(scope, scope.TableName(), "idx_email_email") {
t.Errorf("Email should have index idx_email_email")
}
if err := DB.Model(&Email{}).RemoveIndex("idx_email_email").Error; err != nil {
t.Errorf("Got error when tried to remove index: %+v", err)
}
if scope.Dialect().HasIndex(scope, scope.TableName(), "idx_email_email") {
t.Errorf("Email's index idx_email_email should be deleted")
}
if err := DB.Model(&Email{}).AddIndex("idx_email_email_and_user_id", "user_id", "email").Error; err != nil {
t.Errorf("Got error when tried to create index: %+v", err)
}
if !scope.Dialect().HasIndex(scope, scope.TableName(), "idx_email_email_and_user_id") {
t.Errorf("Email should have index idx_email_email_and_user_id")
}
if err := DB.Model(&Email{}).RemoveIndex("idx_email_email_and_user_id").Error; err != nil {
t.Errorf("Got error when tried to remove index: %+v", err)
}
if scope.Dialect().HasIndex(scope, scope.TableName(), "idx_email_email_and_user_id") {
t.Errorf("Email's index idx_email_email_and_user_id should be deleted")
}
if err := DB.Model(&Email{}).AddUniqueIndex("idx_email_email_and_user_id", "user_id", "email").Error; err != nil {
t.Errorf("Got error when tried to create index: %+v", err)
}
if !scope.Dialect().HasIndex(scope, scope.TableName(), "idx_email_email_and_user_id") {
t.Errorf("Email should have index idx_email_email_and_user_id")
}
if DB.Save(&User{Name: "unique_indexes", Emails: []Email{{Email: "user1@example.comiii"}, {Email: "user1@example.com"}, {Email: "user1@example.com"}}}).Error == nil {
t.Errorf("Should get to create duplicate record when having unique index")
}
if err := DB.Model(&Email{}).RemoveIndex("idx_email_email_and_user_id").Error; err != nil {
t.Errorf("Got error when tried to remove index: %+v", err)
}
if scope.Dialect().HasIndex(scope, scope.TableName(), "idx_email_email_and_user_id") {
t.Errorf("Email's index idx_email_email_and_user_id should be deleted")
}
if DB.Save(&User{Name: "unique_indexes", Emails: []Email{{Email: "user1@example.com"}, {Email: "user1@example.com"}}}).Error != nil {
t.Errorf("Should be able to create duplicated emails after remove unique index")
}
}
type BigEmail struct {
Id int64
UserId int64
Email string `sql:"index:idx_email_agent"`
UserAgent string `sql:"index:idx_email_agent"`
RegisteredAt time.Time `sql:"unique_index"`
CreatedAt time.Time
UpdatedAt time.Time
}
func (b BigEmail) TableName() string {
return "emails"
}
func TestAutoMigration(t *testing.T) {
DB.AutoMigrate(&Address{})
if err := DB.Table("emails").AutoMigrate(&BigEmail{}).Error; err != nil {
t.Errorf("Auto Migrate should not raise any error")
}
DB.Save(&BigEmail{Email: "jinzhu@example.org", UserAgent: "pc", RegisteredAt: time.Now()})
scope := DB.NewScope(&BigEmail{})
if !scope.Dialect().HasIndex(scope, scope.TableName(), "idx_email_agent") {
t.Errorf("Failed to create index")
}
if !scope.Dialect().HasIndex(scope, scope.TableName(), "uix_emails_registered_at") {
t.Errorf("Failed to create index")
}
var bigemail BigEmail
DB.First(&bigemail, "user_agent = ?", "pc")
if bigemail.Email != "jinzhu@example.org" || bigemail.UserAgent != "pc" || bigemail.RegisteredAt.IsZero() {
t.Error("Big Emails should be saved and fetched correctly")
}
}

10
Godeps/_workspace/src/github.com/jinzhu/gorm/model.go generated vendored Normal file
View File

@@ -0,0 +1,10 @@
package gorm
import "time"
type Model struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}

View File

@@ -0,0 +1,376 @@
package gorm
import (
"database/sql"
"fmt"
"go/ast"
"reflect"
"regexp"
"strconv"
"strings"
"time"
)
var modelStructs = map[reflect.Type]*ModelStruct{}
var DefaultTableNameHandler = func(db *DB, defaultTableName string) string {
return defaultTableName
}
type ModelStruct struct {
PrimaryFields []*StructField
StructFields []*StructField
ModelType reflect.Type
defaultTableName string
}
func (s ModelStruct) TableName(db *DB) string {
return DefaultTableNameHandler(db, s.defaultTableName)
}
type StructField struct {
DBName string
Name string
Names []string
IsPrimaryKey bool
IsNormal bool
IsIgnored bool
IsScanner bool
HasDefaultValue bool
Tag reflect.StructTag
Struct reflect.StructField
IsForeignKey bool
Relationship *Relationship
}
func (structField *StructField) clone() *StructField {
return &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,
Struct: structField.Struct,
IsForeignKey: structField.IsForeignKey,
Relationship: structField.Relationship,
}
}
type Relationship struct {
Kind string
PolymorphicType string
PolymorphicDBName string
ForeignFieldName string
ForeignDBName string
AssociationForeignFieldName string
AssociationForeignDBName string
JoinTableHandler JoinTableHandlerInterface
}
var pluralMapKeys = []*regexp.Regexp{regexp.MustCompile("ch$"), regexp.MustCompile("ss$"), regexp.MustCompile("sh$"), regexp.MustCompile("day$"), regexp.MustCompile("y$"), regexp.MustCompile("x$"), regexp.MustCompile("([^s])s?$")}
var pluralMapValues = []string{"ches", "sses", "shes", "days", "ies", "xes", "${1}s"}
func (scope *Scope) GetModelStruct() *ModelStruct {
var modelStruct ModelStruct
reflectValue := reflect.Indirect(reflect.ValueOf(scope.Value))
if !reflectValue.IsValid() {
return &modelStruct
}
if reflectValue.Kind() == reflect.Slice {
reflectValue = reflect.Indirect(reflect.New(reflectValue.Type().Elem()))
}
scopeType := reflectValue.Type()
if scopeType.Kind() == reflect.Ptr {
scopeType = scopeType.Elem()
}
if value, ok := modelStructs[scopeType]; ok {
return value
}
modelStruct.ModelType = scopeType
if scopeType.Kind() != reflect.Struct {
return &modelStruct
}
// Set tablename
type tabler interface {
TableName() string
}
if tabler, ok := reflect.New(scopeType).Interface().(interface {
TableName() string
}); ok {
modelStruct.defaultTableName = tabler.TableName()
} else {
name := ToDBName(scopeType.Name())
if scope.db == nil || !scope.db.parent.singularTable {
for index, reg := range pluralMapKeys {
if reg.MatchString(name) {
name = reg.ReplaceAllString(name, pluralMapValues[index])
}
}
}
modelStruct.defaultTableName = name
}
// Get all fields
fields := []*StructField{}
for i := 0; i < scopeType.NumField(); i++ {
if fieldStruct := scopeType.Field(i); ast.IsExported(fieldStruct.Name) {
field := &StructField{
Struct: fieldStruct,
Name: fieldStruct.Name,
Names: []string{fieldStruct.Name},
Tag: fieldStruct.Tag,
}
if fieldStruct.Tag.Get("sql") == "-" {
field.IsIgnored = true
} else {
sqlSettings := parseTagSetting(field.Tag.Get("sql"))
gormSettings := parseTagSetting(field.Tag.Get("gorm"))
if _, ok := gormSettings["PRIMARY_KEY"]; ok {
field.IsPrimaryKey = true
modelStruct.PrimaryFields = append(modelStruct.PrimaryFields, field)
}
if _, ok := sqlSettings["DEFAULT"]; ok {
field.HasDefaultValue = true
}
if value, ok := gormSettings["COLUMN"]; ok {
field.DBName = value
} else {
field.DBName = ToDBName(fieldStruct.Name)
}
}
fields = append(fields, field)
}
}
defer func() {
for _, field := range fields {
if !field.IsIgnored {
fieldStruct := field.Struct
fieldType, indirectType := fieldStruct.Type, fieldStruct.Type
if indirectType.Kind() == reflect.Ptr {
indirectType = indirectType.Elem()
}
if _, isScanner := reflect.New(fieldType).Interface().(sql.Scanner); isScanner {
field.IsScanner, field.IsNormal = true, true
}
if _, isTime := reflect.New(indirectType).Interface().(*time.Time); isTime {
field.IsNormal = true
}
if !field.IsNormal {
gormSettings := parseTagSetting(field.Tag.Get("gorm"))
toScope := scope.New(reflect.New(fieldStruct.Type).Interface())
getForeignField := func(column string, fields []*StructField) *StructField {
for _, field := range fields {
if field.Name == column || field.DBName == ToDBName(column) {
return field
}
}
return nil
}
var relationship = &Relationship{}
foreignKey := gormSettings["FOREIGNKEY"]
if polymorphic := gormSettings["POLYMORPHIC"]; polymorphic != "" {
if polymorphicField := getForeignField(polymorphic+"Id", toScope.GetStructFields()); polymorphicField != nil {
if polymorphicType := getForeignField(polymorphic+"Type", toScope.GetStructFields()); polymorphicType != nil {
relationship.ForeignFieldName = polymorphicField.Name
relationship.ForeignDBName = polymorphicField.DBName
relationship.PolymorphicType = polymorphicType.Name
relationship.PolymorphicDBName = polymorphicType.DBName
polymorphicType.IsForeignKey = true
polymorphicField.IsForeignKey = true
}
}
}
switch indirectType.Kind() {
case reflect.Slice:
elemType := indirectType.Elem()
if elemType.Kind() == reflect.Ptr {
elemType = elemType.Elem()
}
if elemType.Kind() == reflect.Struct {
if foreignKey == "" {
foreignKey = scopeType.Name() + "Id"
}
if many2many := gormSettings["MANY2MANY"]; many2many != "" {
relationship.Kind = "many_to_many"
associationForeignKey := gormSettings["ASSOCIATIONFOREIGNKEY"]
if associationForeignKey == "" {
associationForeignKey = elemType.Name() + "Id"
}
relationship.ForeignFieldName = foreignKey
relationship.ForeignDBName = ToDBName(foreignKey)
relationship.AssociationForeignFieldName = associationForeignKey
relationship.AssociationForeignDBName = ToDBName(associationForeignKey)
joinTableHandler := JoinTableHandler{}
joinTableHandler.Setup(relationship, many2many, scopeType, elemType)
relationship.JoinTableHandler = &joinTableHandler
field.Relationship = relationship
} else {
relationship.Kind = "has_many"
if foreignField := getForeignField(foreignKey, toScope.GetStructFields()); foreignField != nil {
relationship.ForeignFieldName = foreignField.Name
relationship.ForeignDBName = foreignField.DBName
foreignField.IsForeignKey = true
field.Relationship = relationship
} else if relationship.ForeignFieldName != "" {
field.Relationship = relationship
}
}
} else {
field.IsNormal = true
}
case reflect.Struct:
if _, ok := gormSettings["EMBEDDED"]; ok || fieldStruct.Anonymous {
for _, toField := range toScope.GetStructFields() {
toField = toField.clone()
toField.Names = append([]string{fieldStruct.Name}, toField.Names...)
modelStruct.StructFields = append(modelStruct.StructFields, toField)
if toField.IsPrimaryKey {
modelStruct.PrimaryFields = append(modelStruct.PrimaryFields, toField)
}
}
continue
} else {
belongsToForeignKey := foreignKey
if belongsToForeignKey == "" {
belongsToForeignKey = field.Name + "Id"
}
if foreignField := getForeignField(belongsToForeignKey, fields); foreignField != nil {
relationship.Kind = "belongs_to"
relationship.ForeignFieldName = foreignField.Name
relationship.ForeignDBName = foreignField.DBName
foreignField.IsForeignKey = true
field.Relationship = relationship
} else {
if foreignKey == "" {
foreignKey = modelStruct.ModelType.Name() + "Id"
}
relationship.Kind = "has_one"
if foreignField := getForeignField(foreignKey, toScope.GetStructFields()); foreignField != nil {
relationship.ForeignFieldName = foreignField.Name
relationship.ForeignDBName = foreignField.DBName
foreignField.IsForeignKey = true
field.Relationship = relationship
} else if relationship.ForeignFieldName != "" {
field.Relationship = relationship
}
}
}
default:
field.IsNormal = true
}
}
if field.IsNormal {
if len(modelStruct.PrimaryFields) == 0 && field.DBName == "id" {
field.IsPrimaryKey = true
modelStruct.PrimaryFields = append(modelStruct.PrimaryFields, field)
}
}
}
modelStruct.StructFields = append(modelStruct.StructFields, field)
}
}()
modelStructs[scopeType] = &modelStruct
return &modelStruct
}
func (scope *Scope) GetStructFields() (fields []*StructField) {
return scope.GetModelStruct().StructFields
}
func (scope *Scope) generateSqlTag(field *StructField) string {
var sqlType string
structType := field.Struct.Type
if structType.Kind() == reflect.Ptr {
structType = structType.Elem()
}
reflectValue := reflect.Indirect(reflect.New(structType))
sqlSettings := parseTagSetting(field.Tag.Get("sql"))
if value, ok := sqlSettings["TYPE"]; ok {
sqlType = value
}
additionalType := sqlSettings["NOT NULL"] + " " + sqlSettings["UNIQUE"]
if value, ok := sqlSettings["DEFAULT"]; ok {
additionalType = additionalType + " DEFAULT " + value
}
if field.IsScanner {
var getScannerValue func(reflect.Value)
getScannerValue = func(value reflect.Value) {
reflectValue = value
if _, isScanner := reflect.New(reflectValue.Type()).Interface().(sql.Scanner); isScanner && reflectValue.Kind() == reflect.Struct {
getScannerValue(reflectValue.Field(0))
}
}
getScannerValue(reflectValue)
}
if sqlType == "" {
var size = 255
if value, ok := sqlSettings["SIZE"]; ok {
size, _ = strconv.Atoi(value)
}
_, autoIncrease := sqlSettings["AUTO_INCREMENT"]
if field.IsPrimaryKey {
autoIncrease = true
}
sqlType = scope.Dialect().SqlTag(reflectValue, size, autoIncrease)
}
if strings.TrimSpace(additionalType) == "" {
return sqlType
} else {
return fmt.Sprintf("%v %v", sqlType, additionalType)
}
}
func parseTagSetting(str string) map[string]string {
tags := strings.Split(str, ";")
setting := map[string]string{}
for _, value := range tags {
v := strings.Split(value, ":")
k := strings.TrimSpace(strings.ToUpper(v[0]))
if len(v) == 2 {
setting[k] = v[1]
} else {
setting[k] = k
}
}
return setting
}

81
Godeps/_workspace/src/github.com/jinzhu/gorm/mssql.go generated vendored Normal file
View File

@@ -0,0 +1,81 @@
package gorm
import (
"fmt"
"reflect"
"strings"
"time"
)
type mssql struct {
commonDialect
}
func (mssql) HasTop() bool {
return true
}
func (mssql) SqlTag(value reflect.Value, size int, autoIncrease bool) string {
switch value.Kind() {
case reflect.Bool:
return "bit"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
if autoIncrease {
return "int IDENTITY(1,1)"
}
return "int"
case reflect.Int64, reflect.Uint64:
if autoIncrease {
return "bigint IDENTITY(1,1)"
}
return "bigint"
case reflect.Float32, reflect.Float64:
return "float"
case reflect.String:
if size > 0 && size < 65532 {
return fmt.Sprintf("nvarchar(%d)", size)
}
return "text"
case reflect.Struct:
if _, ok := value.Interface().(time.Time); ok {
return "datetime2"
}
default:
if _, ok := value.Interface().([]byte); ok {
if size > 0 && size < 65532 {
return fmt.Sprintf("varchar(%d)", size)
}
return "text"
}
}
panic(fmt.Sprintf("invalid sql type %s (%s) for mssql", value.Type().Name(), value.Kind().String()))
}
func (mssql) databaseName(scope *Scope) string {
dbStr := strings.Split(scope.db.parent.source, ";")
for _, value := range dbStr {
s := strings.Split(value, "=")
if s[0] == "database" {
return s[1]
}
}
return ""
}
func (s mssql) HasTable(scope *Scope, tableName string) bool {
var count int
scope.NewDB().Raw("SELECT count(*) FROM INFORMATION_SCHEMA.tables WHERE table_name = ? AND table_catalog = ?", tableName, s.databaseName(scope)).Row().Scan(&count)
return count > 0
}
func (s mssql) HasColumn(scope *Scope, tableName string, columnName string) bool {
var count int
scope.NewDB().Raw("SELECT count(*) FROM information_schema.columns WHERE table_catalog = ? AND table_name = ? AND column_name = ?", s.databaseName(scope), tableName, columnName).Row().Scan(&count)
return count > 0
}
func (mssql) HasIndex(scope *Scope, tableName string, indexName string) bool {
var count int
scope.NewDB().Raw("SELECT count(*) FROM sys.indexes WHERE name=? AND object_id=OBJECT_ID(?)", indexName, tableName).Row().Scan(&count)
return count > 0
}

View File

@@ -0,0 +1,46 @@
package gorm_test
import (
"fmt"
"os"
"testing"
)
type Blog struct {
ID uint `gorm:"primary_key"`
Locale string `gorm:"primary_key"`
Subject string
Body string
Tags []Tag `gorm:"many2many:blog_tags;"`
}
type Tag struct {
ID uint `gorm:"primary_key"`
Locale string `gorm:"primary_key"`
Value string
}
func TestManyToManyWithMultiPrimaryKeys(t *testing.T) {
if dialect := os.Getenv("GORM_DIALECT"); dialect != "" && dialect != "sqlite" {
DB.Exec(fmt.Sprintf("drop table blog_tags;"))
DB.AutoMigrate(&Blog{}, &Tag{})
blog := Blog{
Locale: "ZH",
Subject: "subject",
Body: "body",
Tags: []Tag{
{Locale: "ZH", Value: "tag1"},
{Locale: "ZH", Value: "tag2"},
},
}
DB.Save(&blog)
DB.Model(&blog).Association("Tags").Append([]Tag{{Locale: "ZH", Value: "tag3"}})
var tags []Tag
DB.Model(&blog).Related(&tags, "Tags")
if len(tags) != 3 {
t.Errorf("should found 3 tags with blog")
}
}
}

65
Godeps/_workspace/src/github.com/jinzhu/gorm/mysql.go generated vendored Normal file
View File

@@ -0,0 +1,65 @@
package gorm
import (
"fmt"
"reflect"
"time"
)
type mysql struct {
commonDialect
}
func (mysql) SqlTag(value reflect.Value, size int, autoIncrease bool) string {
switch value.Kind() {
case reflect.Bool:
return "boolean"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
if autoIncrease {
return "int AUTO_INCREMENT"
}
return "int"
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
if autoIncrease {
return "int unsigned AUTO_INCREMENT"
}
return "int unsigned"
case reflect.Int64:
if autoIncrease {
return "bigint AUTO_INCREMENT"
}
return "bigint"
case reflect.Uint64:
if autoIncrease {
return "bigint unsigned AUTO_INCREMENT"
}
return "bigint unsigned"
case reflect.Float32, reflect.Float64:
return "double"
case reflect.String:
if size > 0 && size < 65532 {
return fmt.Sprintf("varchar(%d)", size)
}
return "longtext"
case reflect.Struct:
if _, ok := value.Interface().(time.Time); ok {
return "timestamp NULL"
}
default:
if _, ok := value.Interface().([]byte); ok {
if size > 0 && size < 65532 {
return fmt.Sprintf("varbinary(%d)", size)
}
return "longblob"
}
}
panic(fmt.Sprintf("invalid sql type %s (%s) for mysql", value.Type().Name(), value.Kind().String()))
}
func (mysql) Quote(key string) string {
return fmt.Sprintf("`%s`", key)
}
func (mysql) SelectFromDummyTable() string {
return "FROM DUAL"
}

View File

@@ -0,0 +1,84 @@
package gorm_test
import "testing"
type PointerStruct struct {
ID int64
Name *string
Num *int
}
type NormalStruct struct {
ID int64
Name string
Num int
}
func TestPointerFields(t *testing.T) {
DB.DropTable(&PointerStruct{})
DB.AutoMigrate(&PointerStruct{})
var name = "pointer struct 1"
var num = 100
pointerStruct := PointerStruct{Name: &name, Num: &num}
if DB.Create(&pointerStruct).Error != nil {
t.Errorf("Failed to save pointer struct")
}
var pointerStructResult PointerStruct
if err := DB.First(&pointerStructResult, "id = ?", pointerStruct.ID).Error; err != nil || *pointerStructResult.Name != name || *pointerStructResult.Num != num {
t.Errorf("Failed to query saved pointer struct")
}
var tableName = DB.NewScope(&PointerStruct{}).TableName()
var normalStruct NormalStruct
DB.Table(tableName).First(&normalStruct)
if normalStruct.Name != name || normalStruct.Num != num {
t.Errorf("Failed to query saved Normal struct")
}
var nilPointerStruct = PointerStruct{}
if err := DB.Create(&nilPointerStruct).Error; err != nil {
t.Errorf("Failed to save nil pointer struct", err)
}
var pointerStruct2 PointerStruct
if err := DB.First(&pointerStruct2, "id = ?", nilPointerStruct.ID).Error; err != nil {
t.Errorf("Failed to query saved nil pointer struct", err)
}
var normalStruct2 NormalStruct
if err := DB.Table(tableName).First(&normalStruct2, "id = ?", nilPointerStruct.ID).Error; err != nil {
t.Errorf("Failed to query saved nil pointer struct", err)
}
var partialNilPointerStruct1 = PointerStruct{Num: &num}
if err := DB.Create(&partialNilPointerStruct1).Error; err != nil {
t.Errorf("Failed to save partial nil pointer struct", err)
}
var pointerStruct3 PointerStruct
if err := DB.First(&pointerStruct3, "id = ?", partialNilPointerStruct1.ID).Error; err != nil || *pointerStruct3.Num != num {
t.Errorf("Failed to query saved partial nil pointer struct", err)
}
var normalStruct3 NormalStruct
if err := DB.Table(tableName).First(&normalStruct3, "id = ?", partialNilPointerStruct1.ID).Error; err != nil || normalStruct3.Num != num {
t.Errorf("Failed to query saved partial pointer struct", err)
}
var partialNilPointerStruct2 = PointerStruct{Name: &name}
if err := DB.Create(&partialNilPointerStruct2).Error; err != nil {
t.Errorf("Failed to save partial nil pointer struct", err)
}
var pointerStruct4 PointerStruct
if err := DB.First(&pointerStruct4, "id = ?", partialNilPointerStruct2.ID).Error; err != nil || *pointerStruct4.Name != name {
t.Errorf("Failed to query saved partial nil pointer struct", err)
}
var normalStruct4 NormalStruct
if err := DB.Table(tableName).First(&normalStruct4, "id = ?", partialNilPointerStruct2.ID).Error; err != nil || normalStruct4.Name != name {
t.Errorf("Failed to query saved partial pointer struct", err)
}
}

View File

@@ -0,0 +1,56 @@
package gorm_test
import "testing"
type Cat struct {
Id int
Name string
Toy Toy `gorm:"polymorphic:Owner;"`
}
type Dog struct {
Id int
Name string
Toys []Toy `gorm:"polymorphic:Owner;"`
}
type Toy struct {
Id int
Name string
OwnerId int
OwnerType string
}
func TestPolymorphic(t *testing.T) {
DB.AutoMigrate(&Cat{})
DB.AutoMigrate(&Dog{})
DB.AutoMigrate(&Toy{})
cat := Cat{Name: "Mr. Bigglesworth", Toy: Toy{Name: "cat nip"}}
dog := Dog{Name: "Pluto", Toys: []Toy{Toy{Name: "orange ball"}, Toy{Name: "yellow ball"}}}
DB.Save(&cat).Save(&dog)
var catToys []Toy
if DB.Model(&cat).Related(&catToys, "Toy").RecordNotFound() {
t.Errorf("Did not find any has one polymorphic association")
} else if len(catToys) != 1 {
t.Errorf("Should have found only one polymorphic has one association")
} else if catToys[0].Name != cat.Toy.Name {
t.Errorf("Should have found the proper has one polymorphic association")
}
var dogToys []Toy
if DB.Model(&dog).Related(&dogToys, "Toys").RecordNotFound() {
t.Errorf("Did not find any polymorphic has many associations")
} else if len(dogToys) != len(dog.Toys) {
t.Errorf("Should have found all polymorphic has many associations")
}
if DB.Model(&cat).Association("Toy").Count() != 1 {
t.Errorf("Should return one polymorphic has one association")
}
if DB.Model(&dog).Association("Toys").Count() != 2 {
t.Errorf("Should return two polymorphic has many associations")
}
}

View File

@@ -0,0 +1,131 @@
package gorm
import (
"database/sql"
"database/sql/driver"
"fmt"
"reflect"
"time"
"github.com/lib/pq/hstore"
)
type postgres struct {
commonDialect
}
func (postgres) BinVar(i int) string {
return fmt.Sprintf("$%v", i)
}
func (postgres) SupportLastInsertId() bool {
return false
}
func (postgres) SqlTag(value reflect.Value, size int, autoIncrease bool) string {
switch value.Kind() {
case reflect.Bool:
return "boolean"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
if autoIncrease {
return "serial"
}
return "integer"
case reflect.Int64, reflect.Uint64:
if autoIncrease {
return "bigserial"
}
return "bigint"
case reflect.Float32, reflect.Float64:
return "numeric"
case reflect.String:
if size > 0 && size < 65532 {
return fmt.Sprintf("varchar(%d)", size)
}
return "text"
case reflect.Struct:
if _, ok := value.Interface().(time.Time); ok {
return "timestamp with time zone"
}
case reflect.Map:
if value.Type() == hstoreType {
return "hstore"
}
default:
if _, ok := value.Interface().([]byte); ok {
return "bytea"
}
}
panic(fmt.Sprintf("invalid sql type %s (%s) for postgres", value.Type().Name(), value.Kind().String()))
}
func (s postgres) ReturningStr(tableName, key string) string {
return fmt.Sprintf("RETURNING %v.%v", s.Quote(tableName), key)
}
func (postgres) HasTable(scope *Scope, tableName string) bool {
var count int
scope.NewDB().Raw("SELECT count(*) FROM INFORMATION_SCHEMA.tables WHERE table_name = ? AND table_type = 'BASE TABLE'", tableName).Row().Scan(&count)
return count > 0
}
func (postgres) HasColumn(scope *Scope, tableName string, columnName string) bool {
var count int
scope.NewDB().Raw("SELECT count(*) FROM INFORMATION_SCHEMA.columns WHERE table_name = ? AND column_name = ?", tableName, columnName).Row().Scan(&count)
return count > 0
}
func (postgres) RemoveIndex(scope *Scope, indexName string) {
scope.NewDB().Exec(fmt.Sprintf("DROP INDEX %v", indexName))
}
func (postgres) HasIndex(scope *Scope, tableName string, indexName string) bool {
var count int
scope.NewDB().Raw("SELECT count(*) FROM pg_indexes WHERE tablename = ? AND indexname = ?", tableName, indexName).Row().Scan(&count)
return count > 0
}
var hstoreType = reflect.TypeOf(Hstore{})
type Hstore map[string]*string
func (h Hstore) Value() (driver.Value, error) {
hstore := hstore.Hstore{Map: map[string]sql.NullString{}}
if len(h) == 0 {
return nil, nil
}
for key, value := range h {
var s sql.NullString
if value != nil {
s.String = *value
s.Valid = true
}
hstore.Map[key] = s
}
return hstore.Value()
}
func (h *Hstore) Scan(value interface{}) error {
hstore := hstore.Hstore{}
if err := hstore.Scan(value); err != nil {
return err
}
if len(hstore.Map) == 0 {
return nil
}
*h = Hstore{}
for k := range hstore.Map {
if hstore.Map[k].Valid {
s := hstore.Map[k].String
(*h)[k] = &s
} else {
(*h)[k] = nil
}
}
return nil
}

242
Godeps/_workspace/src/github.com/jinzhu/gorm/preload.go generated vendored Normal file
View File

@@ -0,0 +1,242 @@
package gorm
import (
"database/sql/driver"
"errors"
"fmt"
"reflect"
"strings"
)
func getRealValue(value reflect.Value, field string) interface{} {
result := reflect.Indirect(value).FieldByName(field).Interface()
if r, ok := result.(driver.Valuer); ok {
result, _ = r.Value()
}
return result
}
func equalAsString(a interface{}, b interface{}) bool {
return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b)
}
func Preload(scope *Scope) {
if scope.Search.preload == nil {
return
}
preloadMap := map[string]bool{}
fields := scope.Fields()
for _, preload := range scope.Search.preload {
schema, conditions := preload.schema, preload.conditions
keys := strings.Split(schema, ".")
currentScope := scope
currentFields := fields
originalConditions := conditions
conditions = []interface{}{}
for i, key := range keys {
var found bool
if preloadMap[strings.Join(keys[:i+1], ".")] {
goto nextLoop
}
if i == len(keys)-1 {
conditions = originalConditions
}
for _, field := range currentFields {
if field.Name != key || field.Relationship == nil {
continue
}
found = true
switch field.Relationship.Kind {
case "has_one":
currentScope.handleHasOnePreload(field, conditions)
case "has_many":
currentScope.handleHasManyPreload(field, conditions)
case "belongs_to":
currentScope.handleBelongsToPreload(field, conditions)
case "many_to_many":
fallthrough
default:
currentScope.Err(errors.New("not supported relation"))
}
break
}
if !found {
value := reflect.ValueOf(currentScope.Value)
if value.Kind() == reflect.Slice && value.Type().Elem().Kind() == reflect.Interface {
value = value.Index(0).Elem()
}
scope.Err(fmt.Errorf("can't find field %s in %s", key, value.Type()))
return
}
preloadMap[strings.Join(keys[:i+1], ".")] = true
nextLoop:
if i < len(keys)-1 {
currentScope = currentScope.getColumnsAsScope(key)
currentFields = currentScope.Fields()
}
}
}
}
func makeSlice(typ reflect.Type) interface{} {
if typ.Kind() == reflect.Slice {
typ = typ.Elem()
}
sliceType := reflect.SliceOf(typ)
slice := reflect.New(sliceType)
slice.Elem().Set(reflect.MakeSlice(sliceType, 0, 0))
return slice.Interface()
}
func (scope *Scope) handleHasOnePreload(field *Field, conditions []interface{}) {
primaryName := scope.PrimaryField().Name
primaryKeys := scope.getColumnAsArray(primaryName)
if len(primaryKeys) == 0 {
return
}
results := makeSlice(field.Struct.Type)
relation := field.Relationship
condition := fmt.Sprintf("%v IN (?)", scope.Quote(relation.ForeignDBName))
scope.Err(scope.NewDB().Where(condition, primaryKeys).Find(results, conditions...).Error)
resultValues := reflect.Indirect(reflect.ValueOf(results))
for i := 0; i < resultValues.Len(); i++ {
result := resultValues.Index(i)
if scope.IndirectValue().Kind() == reflect.Slice {
value := getRealValue(result, relation.ForeignFieldName)
objects := scope.IndirectValue()
for j := 0; j < objects.Len(); j++ {
if equalAsString(getRealValue(objects.Index(j), primaryName), value) {
reflect.Indirect(objects.Index(j)).FieldByName(field.Name).Set(result)
break
}
}
} else {
if err := scope.SetColumn(field, result); err != nil {
scope.Err(err)
return
}
}
}
}
func (scope *Scope) handleHasManyPreload(field *Field, conditions []interface{}) {
primaryName := scope.PrimaryField().Name
primaryKeys := scope.getColumnAsArray(primaryName)
if len(primaryKeys) == 0 {
return
}
results := makeSlice(field.Struct.Type)
relation := field.Relationship
condition := fmt.Sprintf("%v IN (?)", scope.Quote(relation.ForeignDBName))
scope.Err(scope.NewDB().Where(condition, primaryKeys).Find(results, conditions...).Error)
resultValues := reflect.Indirect(reflect.ValueOf(results))
if scope.IndirectValue().Kind() == reflect.Slice {
for i := 0; i < resultValues.Len(); i++ {
result := resultValues.Index(i)
value := getRealValue(result, relation.ForeignFieldName)
objects := scope.IndirectValue()
for j := 0; j < objects.Len(); j++ {
object := reflect.Indirect(objects.Index(j))
if equalAsString(getRealValue(object, primaryName), value) {
f := object.FieldByName(field.Name)
f.Set(reflect.Append(f, result))
break
}
}
}
} else {
scope.SetColumn(field, resultValues)
}
}
func (scope *Scope) handleBelongsToPreload(field *Field, conditions []interface{}) {
relation := field.Relationship
primaryKeys := scope.getColumnAsArray(relation.ForeignFieldName)
if len(primaryKeys) == 0 {
return
}
results := makeSlice(field.Struct.Type)
associationPrimaryKey := scope.New(results).PrimaryField().Name
scope.Err(scope.NewDB().Where(primaryKeys).Find(results, conditions...).Error)
resultValues := reflect.Indirect(reflect.ValueOf(results))
for i := 0; i < resultValues.Len(); i++ {
result := resultValues.Index(i)
if scope.IndirectValue().Kind() == reflect.Slice {
value := getRealValue(result, associationPrimaryKey)
objects := scope.IndirectValue()
for j := 0; j < objects.Len(); j++ {
object := reflect.Indirect(objects.Index(j))
if equalAsString(getRealValue(object, relation.ForeignFieldName), value) {
object.FieldByName(field.Name).Set(result)
}
}
} else {
scope.SetColumn(field, result)
}
}
}
func (scope *Scope) getColumnAsArray(column string) (columns []interface{}) {
values := scope.IndirectValue()
switch values.Kind() {
case reflect.Slice:
for i := 0; i < values.Len(); i++ {
columns = append(columns, reflect.Indirect(values.Index(i)).FieldByName(column).Interface())
}
case reflect.Struct:
return []interface{}{values.FieldByName(column).Interface()}
}
return
}
func (scope *Scope) getColumnsAsScope(column string) *Scope {
values := scope.IndirectValue()
switch values.Kind() {
case reflect.Slice:
modelType := values.Type().Elem()
if modelType.Kind() == reflect.Ptr {
modelType = modelType.Elem()
}
fieldStruct, _ := modelType.FieldByName(column)
var columns reflect.Value
if fieldStruct.Type.Kind() == reflect.Slice || fieldStruct.Type.Kind() == reflect.Ptr {
columns = reflect.New(reflect.SliceOf(reflect.PtrTo(fieldStruct.Type.Elem()))).Elem()
} else {
columns = reflect.New(reflect.SliceOf(reflect.PtrTo(fieldStruct.Type))).Elem()
}
for i := 0; i < values.Len(); i++ {
column := reflect.Indirect(values.Index(i)).FieldByName(column)
if column.Kind() == reflect.Ptr {
column = column.Elem()
}
if column.Kind() == reflect.Slice {
for i := 0; i < column.Len(); i++ {
columns = reflect.Append(columns, column.Index(i).Addr())
}
} else {
columns = reflect.Append(columns, column.Addr())
}
}
return scope.New(columns.Interface())
case reflect.Struct:
return scope.New(values.FieldByName(column).Addr().Interface())
}
return nil
}

View File

@@ -0,0 +1,609 @@
package gorm_test
import (
"encoding/json"
"reflect"
"testing"
)
func getPreloadUser(name string) *User {
return getPreparedUser(name, "Preload")
}
func checkUserHasPreloadData(user User, t *testing.T) {
u := getPreloadUser(user.Name)
if user.BillingAddress.Address1 != u.BillingAddress.Address1 {
t.Error("Failed to preload user's BillingAddress")
}
if user.ShippingAddress.Address1 != u.ShippingAddress.Address1 {
t.Error("Failed to preload user's ShippingAddress")
}
if user.CreditCard.Number != u.CreditCard.Number {
t.Error("Failed to preload user's CreditCard")
}
if user.Company.Name != u.Company.Name {
t.Error("Failed to preload user's Company")
}
if len(user.Emails) != len(u.Emails) {
t.Error("Failed to preload user's Emails")
} else {
var found int
for _, e1 := range u.Emails {
for _, e2 := range user.Emails {
if e1.Email == e2.Email {
found++
break
}
}
}
if found != len(u.Emails) {
t.Error("Failed to preload user's email details")
}
}
}
func TestPreload(t *testing.T) {
user1 := getPreloadUser("user1")
DB.Save(user1)
preloadDB := DB.Where("role = ?", "Preload").Preload("BillingAddress").Preload("ShippingAddress").
Preload("CreditCard").Preload("Emails").Preload("Company")
var user User
preloadDB.Find(&user)
checkUserHasPreloadData(user, t)
user2 := getPreloadUser("user2")
DB.Save(user2)
user3 := getPreloadUser("user3")
DB.Save(user3)
var users []User
preloadDB.Find(&users)
for _, user := range users {
checkUserHasPreloadData(user, t)
}
var users2 []*User
preloadDB.Find(&users2)
for _, user := range users2 {
checkUserHasPreloadData(*user, t)
}
var users3 []*User
preloadDB.Preload("Emails", "email = ?", user3.Emails[0].Email).Find(&users3)
for _, user := range users3 {
if user.Name == user3.Name {
if len(user.Emails) != 1 {
t.Errorf("should only preload one emails for user3 when with condition")
}
} else if len(user.Emails) != 0 {
t.Errorf("should not preload any emails for other users when with condition")
}
}
}
func TestNestedPreload1(t *testing.T) {
type (
Level1 struct {
ID uint
Value string
Level2ID uint
}
Level2 struct {
ID uint
Level1 Level1
Level3ID uint
}
Level3 struct {
ID uint
Name string
Level2 Level2
}
)
DB.DropTableIfExists(&Level3{})
DB.DropTableIfExists(&Level2{})
DB.DropTableIfExists(&Level1{})
if err := DB.AutoMigrate(&Level3{}, &Level2{}, &Level1{}).Error; err != nil {
panic(err)
}
want := Level3{Level2: Level2{Level1: Level1{Value: "value"}}}
if err := DB.Create(&want).Error; err != nil {
panic(err)
}
var got Level3
if err := DB.Preload("Level2").Preload("Level2.Level1").Find(&got).Error; err != nil {
panic(err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %s; want %s", toJSONString(got), toJSONString(want))
}
}
func TestNestedPreload2(t *testing.T) {
type (
Level1 struct {
ID uint
Value string
Level2ID uint
}
Level2 struct {
ID uint
Level1s []*Level1
Level3ID uint
}
Level3 struct {
ID uint
Name string
Level2s []Level2
}
)
DB.DropTableIfExists(&Level3{})
DB.DropTableIfExists(&Level2{})
DB.DropTableIfExists(&Level1{})
if err := DB.AutoMigrate(&Level3{}, &Level2{}, &Level1{}).Error; err != nil {
panic(err)
}
want := Level3{
Level2s: []Level2{
{
Level1s: []*Level1{
&Level1{Value: "value1"},
&Level1{Value: "value2"},
},
},
{
Level1s: []*Level1{
&Level1{Value: "value3"},
},
},
},
}
if err := DB.Create(&want).Error; err != nil {
panic(err)
}
var got Level3
if err := DB.Preload("Level2s.Level1s").Find(&got).Error; err != nil {
panic(err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %s; want %s", toJSONString(got), toJSONString(want))
}
}
func TestNestedPreload3(t *testing.T) {
type (
Level1 struct {
ID uint
Value string
Level2ID uint
}
Level2 struct {
ID uint
Level1 Level1
Level3ID uint
}
Level3 struct {
Name string
ID uint
Level2s []Level2
}
)
DB.DropTableIfExists(&Level3{})
DB.DropTableIfExists(&Level2{})
DB.DropTableIfExists(&Level1{})
if err := DB.AutoMigrate(&Level3{}, &Level2{}, &Level1{}).Error; err != nil {
panic(err)
}
want := Level3{
Level2s: []Level2{
{Level1: Level1{Value: "value1"}},
{Level1: Level1{Value: "value2"}},
},
}
if err := DB.Create(&want).Error; err != nil {
panic(err)
}
var got Level3
if err := DB.Preload("Level2s.Level1").Find(&got).Error; err != nil {
panic(err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %s; want %s", toJSONString(got), toJSONString(want))
}
}
func TestNestedPreload4(t *testing.T) {
type (
Level1 struct {
ID uint
Value string
Level2ID uint
}
Level2 struct {
ID uint
Level1s []Level1
Level3ID uint
}
Level3 struct {
ID uint
Name string
Level2 Level2
}
)
DB.DropTableIfExists(&Level3{})
DB.DropTableIfExists(&Level2{})
DB.DropTableIfExists(&Level1{})
if err := DB.AutoMigrate(&Level3{}, &Level2{}, &Level1{}).Error; err != nil {
panic(err)
}
want := Level3{
Level2: Level2{
Level1s: []Level1{
Level1{Value: "value1"},
Level1{Value: "value2"},
},
},
}
if err := DB.Create(&want).Error; err != nil {
panic(err)
}
var got Level3
if err := DB.Preload("Level2.Level1s").Find(&got).Error; err != nil {
panic(err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %s; want %s", toJSONString(got), toJSONString(want))
}
}
// Slice: []Level3
func TestNestedPreload5(t *testing.T) {
type (
Level1 struct {
ID uint
Value string
Level2ID uint
}
Level2 struct {
ID uint
Level1 Level1
Level3ID uint
}
Level3 struct {
ID uint
Name string
Level2 Level2
}
)
DB.DropTableIfExists(&Level3{})
DB.DropTableIfExists(&Level2{})
DB.DropTableIfExists(&Level1{})
if err := DB.AutoMigrate(&Level3{}, &Level2{}, &Level1{}).Error; err != nil {
panic(err)
}
want := make([]Level3, 2)
want[0] = Level3{Level2: Level2{Level1: Level1{Value: "value"}}}
if err := DB.Create(&want[0]).Error; err != nil {
panic(err)
}
want[1] = Level3{Level2: Level2{Level1: Level1{Value: "value2"}}}
if err := DB.Create(&want[1]).Error; err != nil {
panic(err)
}
var got []Level3
if err := DB.Preload("Level2").Preload("Level2.Level1").Find(&got).Error; err != nil {
panic(err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %s; want %s", toJSONString(got), toJSONString(want))
}
}
func TestNestedPreload6(t *testing.T) {
type (
Level1 struct {
ID uint
Value string
Level2ID uint
}
Level2 struct {
ID uint
Level1s []Level1
Level3ID uint
}
Level3 struct {
ID uint
Name string
Level2s []Level2
}
)
DB.DropTableIfExists(&Level3{})
DB.DropTableIfExists(&Level2{})
DB.DropTableIfExists(&Level1{})
if err := DB.AutoMigrate(&Level3{}, &Level2{}, &Level1{}).Error; err != nil {
panic(err)
}
want := make([]Level3, 2)
want[0] = Level3{
Level2s: []Level2{
{
Level1s: []Level1{
{Value: "value1"},
{Value: "value2"},
},
},
{
Level1s: []Level1{
{Value: "value3"},
},
},
},
}
if err := DB.Create(&want[0]).Error; err != nil {
panic(err)
}
want[1] = Level3{
Level2s: []Level2{
{
Level1s: []Level1{
{Value: "value3"},
{Value: "value4"},
},
},
{
Level1s: []Level1{
{Value: "value5"},
},
},
},
}
if err := DB.Create(&want[1]).Error; err != nil {
panic(err)
}
var got []Level3
if err := DB.Preload("Level2s.Level1s").Find(&got).Error; err != nil {
panic(err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %s; want %s", toJSONString(got), toJSONString(want))
}
}
func TestNestedPreload7(t *testing.T) {
type (
Level1 struct {
ID uint
Value string
Level2ID uint
}
Level2 struct {
ID uint
Level1 Level1
Level3ID uint
}
Level3 struct {
ID uint
Name string
Level2s []Level2
}
)
DB.DropTableIfExists(&Level3{})
DB.DropTableIfExists(&Level2{})
DB.DropTableIfExists(&Level1{})
if err := DB.AutoMigrate(&Level3{}, &Level2{}, &Level1{}).Error; err != nil {
panic(err)
}
want := make([]Level3, 2)
want[0] = Level3{
Level2s: []Level2{
{Level1: Level1{Value: "value1"}},
{Level1: Level1{Value: "value2"}},
},
}
if err := DB.Create(&want[0]).Error; err != nil {
panic(err)
}
want[1] = Level3{
Level2s: []Level2{
{Level1: Level1{Value: "value3"}},
{Level1: Level1{Value: "value4"}},
},
}
if err := DB.Create(&want[1]).Error; err != nil {
panic(err)
}
var got []Level3
if err := DB.Preload("Level2s.Level1").Find(&got).Error; err != nil {
panic(err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %s; want %s", toJSONString(got), toJSONString(want))
}
}
func TestNestedPreload8(t *testing.T) {
type (
Level1 struct {
ID uint
Value string
Level2ID uint
}
Level2 struct {
ID uint
Level1s []Level1
Level3ID uint
}
Level3 struct {
ID uint
Name string
Level2 Level2
}
)
DB.DropTableIfExists(&Level3{})
DB.DropTableIfExists(&Level2{})
DB.DropTableIfExists(&Level1{})
if err := DB.AutoMigrate(&Level3{}, &Level2{}, &Level1{}).Error; err != nil {
panic(err)
}
want := make([]Level3, 2)
want[0] = Level3{
Level2: Level2{
Level1s: []Level1{
Level1{Value: "value1"},
Level1{Value: "value2"},
},
},
}
if err := DB.Create(&want[0]).Error; err != nil {
panic(err)
}
want[1] = Level3{
Level2: Level2{
Level1s: []Level1{
Level1{Value: "value3"},
Level1{Value: "value4"},
},
},
}
if err := DB.Create(&want[1]).Error; err != nil {
panic(err)
}
var got []Level3
if err := DB.Preload("Level2.Level1s").Find(&got).Error; err != nil {
panic(err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %s; want %s", toJSONString(got), toJSONString(want))
}
}
func TestNestedPreload9(t *testing.T) {
type (
Level0 struct {
ID uint
Value string
Level1ID uint
}
Level1 struct {
ID uint
Value string
Level2ID uint
Level2_1ID uint
Level0s []Level0
}
Level2 struct {
ID uint
Level1s []Level1
Level3ID uint
}
Level2_1 struct {
ID uint
Level1s []Level1
Level3ID uint
}
Level3 struct {
ID uint
Name string
Level2 Level2
Level2_1 Level2_1
}
)
DB.DropTableIfExists(&Level3{})
DB.DropTableIfExists(&Level2{})
DB.DropTableIfExists(&Level2_1{})
DB.DropTableIfExists(&Level1{})
DB.DropTableIfExists(&Level0{})
if err := DB.AutoMigrate(&Level3{}, &Level2{}, &Level1{}, &Level2_1{}, &Level0{}).Error; err != nil {
panic(err)
}
want := make([]Level3, 2)
want[0] = Level3{
Level2: Level2{
Level1s: []Level1{
Level1{Value: "value1"},
Level1{Value: "value2"},
},
},
Level2_1: Level2_1{
Level1s: []Level1{
Level1{
Value: "value1-1",
Level0s: []Level0{{Value: "Level0-1"}},
},
Level1{
Value: "value2-2",
Level0s: []Level0{{Value: "Level0-2"}},
},
},
},
}
if err := DB.Create(&want[0]).Error; err != nil {
panic(err)
}
want[1] = Level3{
Level2: Level2{
Level1s: []Level1{
Level1{Value: "value3"},
Level1{Value: "value4"},
},
},
Level2_1: Level2_1{
Level1s: []Level1{
Level1{Value: "value3-3"},
Level1{Value: "value4-4"},
},
},
}
if err := DB.Create(&want[1]).Error; err != nil {
panic(err)
}
var got []Level3
if err := DB.Preload("Level2").Preload("Level2.Level1s").Preload("Level2_1").Preload("Level2_1.Level1s").Preload("Level2_1.Level1s.Level0s").Find(&got).Error; err != nil {
panic(err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("got %s; want %s", toJSONString(got), toJSONString(want))
}
}
func toJSONString(v interface{}) []byte {
r, _ := json.MarshalIndent(v, "", " ")
return r
}

View File

@@ -0,0 +1,580 @@
package gorm_test
import (
"fmt"
"reflect"
"github.com/jinzhu/now"
"testing"
"time"
)
func TestFirstAndLast(t *testing.T) {
DB.Save(&User{Name: "user1", Emails: []Email{{Email: "user1@example.com"}}})
DB.Save(&User{Name: "user2", Emails: []Email{{Email: "user2@example.com"}}})
var user1, user2, user3, user4 User
DB.First(&user1)
DB.Order("id").Limit(1).Find(&user2)
DB.Last(&user3)
DB.Order("id desc").Limit(1).Find(&user4)
if user1.Id != user2.Id || user3.Id != user4.Id {
t.Errorf("First and Last should by order by primary key")
}
var users []User
DB.First(&users)
if len(users) != 1 {
t.Errorf("Find first record as slice")
}
if DB.Joins("left join emails on emails.user_id = users.id").First(&User{}).Error != nil {
t.Errorf("Should not raise any error when order with Join table")
}
}
func TestFirstAndLastWithNoStdPrimaryKey(t *testing.T) {
DB.Save(&Animal{Name: "animal1"})
DB.Save(&Animal{Name: "animal2"})
var animal1, animal2, animal3, animal4 Animal
DB.First(&animal1)
DB.Order("counter").Limit(1).Find(&animal2)
DB.Last(&animal3)
DB.Order("counter desc").Limit(1).Find(&animal4)
if animal1.Counter != animal2.Counter || animal3.Counter != animal4.Counter {
t.Errorf("First and Last should work correctly")
}
}
func TestUIntPrimaryKey(t *testing.T) {
var animal Animal
DB.First(&animal, uint64(1))
if animal.Counter != 1 {
t.Errorf("Fetch a record from with a non-int primary key should work, but failed")
}
DB.Model(Animal{}).Where(Animal{Counter: uint64(2)}).Scan(&animal)
if animal.Counter != 2 {
t.Errorf("Fetch a record from with a non-int primary key should work, but failed")
}
}
func TestFindAsSliceOfPointers(t *testing.T) {
DB.Save(&User{Name: "user"})
var users []User
DB.Find(&users)
var userPointers []*User
DB.Find(&userPointers)
if len(users) == 0 || len(users) != len(userPointers) {
t.Errorf("Find slice of pointers")
}
}
func TestSearchWithPlainSQL(t *testing.T) {
user1 := User{Name: "PlainSqlUser1", Age: 1, Birthday: now.MustParse("2000-1-1")}
user2 := User{Name: "PlainSqlUser2", Age: 10, Birthday: now.MustParse("2010-1-1")}
user3 := User{Name: "PlainSqlUser3", Age: 20, Birthday: now.MustParse("2020-1-1")}
DB.Save(&user1).Save(&user2).Save(&user3)
scopedb := DB.Where("name LIKE ?", "%PlainSqlUser%")
if DB.Where("name = ?", user1.Name).First(&User{}).RecordNotFound() {
t.Errorf("Search with plain SQL")
}
if DB.Where("name LIKE ?", "%"+user1.Name+"%").First(&User{}).RecordNotFound() {
t.Errorf("Search with plan SQL (regexp)")
}
var users []User
DB.Find(&users, "name LIKE ? and age > ?", "%PlainSqlUser%", 1)
if len(users) != 2 {
t.Errorf("Should found 2 users that age > 1, but got %v", len(users))
}
DB.Where("name LIKE ?", "%PlainSqlUser%").Where("age >= ?", 1).Find(&users)
if len(users) != 3 {
t.Errorf("Should found 3 users that age >= 1, but got %v", len(users))
}
scopedb.Where("age <> ?", 20).Find(&users)
if len(users) != 2 {
t.Errorf("Should found 2 users age != 20, but got %v", len(users))
}
scopedb.Where("birthday > ?", now.MustParse("2000-1-1")).Find(&users)
if len(users) != 2 {
t.Errorf("Should found 2 users's birthday > 2000-1-1, but got %v", len(users))
}
scopedb.Where("birthday > ?", "2002-10-10").Find(&users)
if len(users) != 2 {
t.Errorf("Should found 2 users's birthday >= 2002-10-10, but got %v", len(users))
}
scopedb.Where("birthday >= ?", "2010-1-1").Where("birthday < ?", "2020-1-1").Find(&users)
if len(users) != 1 {
t.Errorf("Should found 1 users's birthday < 2020-1-1 and >= 2010-1-1, but got %v", len(users))
}
DB.Where("name in (?)", []string{user1.Name, user2.Name}).Find(&users)
if len(users) != 2 {
t.Errorf("Should found 2 users, but got %v", len(users))
}
DB.Where("id in (?)", []int64{user1.Id, user2.Id, user3.Id}).Find(&users)
if len(users) != 3 {
t.Errorf("Should found 3 users, but got %v", len(users))
}
DB.Where("id in (?)", user1.Id).Find(&users)
if len(users) != 1 {
t.Errorf("Should found 1 users, but got %v", len(users))
}
if DB.Where("name = ?", "none existing").Find(&[]User{}).RecordNotFound() {
t.Errorf("Should not get RecordNotFound error when looking for none existing records")
}
}
func TestSearchWithStruct(t *testing.T) {
user1 := User{Name: "StructSearchUser1", Age: 1, Birthday: now.MustParse("2000-1-1")}
user2 := User{Name: "StructSearchUser2", Age: 10, Birthday: now.MustParse("2010-1-1")}
user3 := User{Name: "StructSearchUser3", Age: 20, Birthday: now.MustParse("2020-1-1")}
DB.Save(&user1).Save(&user2).Save(&user3)
if DB.Where(user1.Id).First(&User{}).RecordNotFound() {
t.Errorf("Search with primary key")
}
if DB.First(&User{}, user1.Id).RecordNotFound() {
t.Errorf("Search with primary key as inline condition")
}
if DB.First(&User{}, fmt.Sprintf("%v", user1.Id)).RecordNotFound() {
t.Errorf("Search with primary key as inline condition")
}
var users []User
DB.Where([]int64{user1.Id, user2.Id, user3.Id}).Find(&users)
if len(users) != 3 {
t.Errorf("Should found 3 users when search with primary keys, but got %v", len(users))
}
var user User
DB.First(&user, &User{Name: user1.Name})
if user.Id == 0 || user.Name != user1.Name {
t.Errorf("Search first record with inline pointer of struct")
}
DB.First(&user, User{Name: user1.Name})
if user.Id == 0 || user.Name != user.Name {
t.Errorf("Search first record with inline struct")
}
DB.Where(&User{Name: user1.Name}).First(&user)
if user.Id == 0 || user.Name != user1.Name {
t.Errorf("Search first record with where struct")
}
DB.Find(&users, &User{Name: user2.Name})
if len(users) != 1 {
t.Errorf("Search all records with inline struct")
}
}
func TestSearchWithMap(t *testing.T) {
user1 := User{Name: "MapSearchUser1", Age: 1, Birthday: now.MustParse("2000-1-1")}
user2 := User{Name: "MapSearchUser2", Age: 10, Birthday: now.MustParse("2010-1-1")}
user3 := User{Name: "MapSearchUser3", Age: 20, Birthday: now.MustParse("2020-1-1")}
DB.Save(&user1).Save(&user2).Save(&user3)
var user User
DB.First(&user, map[string]interface{}{"name": user1.Name})
if user.Id == 0 || user.Name != user1.Name {
t.Errorf("Search first record with inline map")
}
user = User{}
DB.Where(map[string]interface{}{"name": user2.Name}).First(&user)
if user.Id == 0 || user.Name != user2.Name {
t.Errorf("Search first record with where map")
}
var users []User
DB.Where(map[string]interface{}{"name": user3.Name}).Find(&users)
if len(users) != 1 {
t.Errorf("Search all records with inline map")
}
DB.Find(&users, map[string]interface{}{"name": user3.Name})
if len(users) != 1 {
t.Errorf("Search all records with inline map")
}
}
func TestSearchWithEmptyChain(t *testing.T) {
user1 := User{Name: "ChainSearchUser1", Age: 1, Birthday: now.MustParse("2000-1-1")}
user2 := User{Name: "ChainearchUser2", Age: 10, Birthday: now.MustParse("2010-1-1")}
user3 := User{Name: "ChainearchUser3", Age: 20, Birthday: now.MustParse("2020-1-1")}
DB.Save(&user1).Save(&user2).Save(&user3)
if DB.Where("").Where("").First(&User{}).Error != nil {
t.Errorf("Should not raise any error if searching with empty strings")
}
if DB.Where(&User{}).Where("name = ?", user1.Name).First(&User{}).Error != nil {
t.Errorf("Should not raise any error if searching with empty struct")
}
if DB.Where(map[string]interface{}{}).Where("name = ?", user1.Name).First(&User{}).Error != nil {
t.Errorf("Should not raise any error if searching with empty map")
}
}
func TestSelect(t *testing.T) {
user1 := User{Name: "SelectUser1"}
DB.Save(&user1)
var user User
DB.Where("name = ?", user1.Name).Select("name").Find(&user)
if user.Id != 0 {
t.Errorf("Should not have ID because only selected name, %+v", user.Id)
}
if user.Name != user1.Name {
t.Errorf("Should have user Name when selected it")
}
}
func TestOrderAndPluck(t *testing.T) {
user1 := User{Name: "OrderPluckUser1", Age: 1}
user2 := User{Name: "OrderPluckUser2", Age: 10}
user3 := User{Name: "OrderPluckUser3", Age: 20}
DB.Save(&user1).Save(&user2).Save(&user3)
scopedb := DB.Model(&User{}).Where("name like ?", "%OrderPluckUser%")
var ages []int64
scopedb.Order("age desc").Pluck("age", &ages)
if ages[0] != 20 {
t.Errorf("The first age should be 20 when order with age desc")
}
var ages1, ages2 []int64
scopedb.Order("age desc").Pluck("age", &ages1).Pluck("age", &ages2)
if !reflect.DeepEqual(ages1, ages2) {
t.Errorf("The first order is the primary order")
}
var ages3, ages4 []int64
scopedb.Model(&User{}).Order("age desc").Pluck("age", &ages3).Order("age", true).Pluck("age", &ages4)
if reflect.DeepEqual(ages3, ages4) {
t.Errorf("Reorder should work")
}
var names []string
var ages5 []int64
scopedb.Model(User{}).Order("name").Order("age desc").Pluck("age", &ages5).Pluck("name", &names)
if names != nil && ages5 != nil {
if !(names[0] == user1.Name && names[1] == user2.Name && names[2] == user3.Name && ages5[2] == 20) {
t.Errorf("Order with multiple orders")
}
} else {
t.Errorf("Order with multiple orders")
}
DB.Model(User{}).Select("name, age").Find(&[]User{})
}
func TestLimit(t *testing.T) {
user1 := User{Name: "LimitUser1", Age: 1}
user2 := User{Name: "LimitUser2", Age: 10}
user3 := User{Name: "LimitUser3", Age: 20}
user4 := User{Name: "LimitUser4", Age: 10}
user5 := User{Name: "LimitUser5", Age: 20}
DB.Save(&user1).Save(&user2).Save(&user3).Save(&user4).Save(&user5)
var users1, users2, users3 []User
DB.Order("age desc").Limit(3).Find(&users1).Limit(5).Find(&users2).Limit(-1).Find(&users3)
if len(users1) != 3 || len(users2) != 5 || len(users3) <= 5 {
t.Errorf("Limit should works")
}
}
func TestOffset(t *testing.T) {
for i := 0; i < 20; i++ {
DB.Save(&User{Name: fmt.Sprintf("OffsetUser%v", i)})
}
var users1, users2, users3, users4 []User
DB.Limit(100).Order("age desc").Find(&users1).Offset(3).Find(&users2).Offset(5).Find(&users3).Offset(-1).Find(&users4)
if (len(users1) != len(users4)) || (len(users1)-len(users2) != 3) || (len(users1)-len(users3) != 5) {
t.Errorf("Offset should work")
}
}
func TestOr(t *testing.T) {
user1 := User{Name: "OrUser1", Age: 1}
user2 := User{Name: "OrUser2", Age: 10}
user3 := User{Name: "OrUser3", Age: 20}
DB.Save(&user1).Save(&user2).Save(&user3)
var users []User
DB.Where("name = ?", user1.Name).Or("name = ?", user2.Name).Find(&users)
if len(users) != 2 {
t.Errorf("Find users with or")
}
}
func TestCount(t *testing.T) {
user1 := User{Name: "CountUser1", Age: 1}
user2 := User{Name: "CountUser2", Age: 10}
user3 := User{Name: "CountUser3", Age: 20}
DB.Save(&user1).Save(&user2).Save(&user3)
var count, count1, count2 int64
var users []User
if err := DB.Where("name = ?", user1.Name).Or("name = ?", user3.Name).Find(&users).Count(&count).Error; err != nil {
t.Errorf(fmt.Sprintf("Count should work, but got err %v", err))
}
if count != int64(len(users)) {
t.Errorf("Count() method should get correct value")
}
DB.Model(&User{}).Where("name = ?", user1.Name).Count(&count1).Or("name in (?)", []string{user2.Name, user3.Name}).Count(&count2)
if count1 != 1 || count2 != 3 {
t.Errorf("Multiple count in chain")
}
}
func TestNot(t *testing.T) {
DB.Create(getPreparedUser("user1", "not"))
DB.Create(getPreparedUser("user2", "not"))
DB.Create(getPreparedUser("user3", "not"))
DB.Create(getPreparedUser("user4", "not"))
DB := DB.Where("role = ?", "not")
var users1, users2, users3, users4, users5, users6, users7, users8 []User
if DB.Find(&users1).RowsAffected != 4 {
t.Errorf("should find 4 not users")
}
DB.Not(users1[0].Id).Find(&users2)
if len(users1)-len(users2) != 1 {
t.Errorf("Should ignore the first users with Not")
}
DB.Not([]int{}).Find(&users3)
if len(users1)-len(users3) != 0 {
t.Errorf("Should find all users with a blank condition")
}
var name3Count int64
DB.Table("users").Where("name = ?", "user3").Count(&name3Count)
DB.Not("name", "user3").Find(&users4)
if len(users1)-len(users4) != int(name3Count) {
t.Errorf("Should find all users's name not equal 3")
}
DB.Not("name = ?", "user3").Find(&users4)
if len(users1)-len(users4) != int(name3Count) {
t.Errorf("Should find all users's name not equal 3")
}
DB.Not("name <> ?", "user3").Find(&users4)
if len(users4) != int(name3Count) {
t.Errorf("Should find all users's name not equal 3")
}
DB.Not(User{Name: "user3"}).Find(&users5)
if len(users1)-len(users5) != int(name3Count) {
t.Errorf("Should find all users's name not equal 3")
}
DB.Not(map[string]interface{}{"name": "user3"}).Find(&users6)
if len(users1)-len(users6) != int(name3Count) {
t.Errorf("Should find all users's name not equal 3")
}
DB.Not("name", []string{"user3"}).Find(&users7)
if len(users1)-len(users7) != int(name3Count) {
t.Errorf("Should find all users's name not equal 3")
}
var name2Count int64
DB.Table("users").Where("name = ?", "user2").Count(&name2Count)
DB.Not("name", []string{"user3", "user2"}).Find(&users8)
if len(users1)-len(users8) != (int(name3Count) + int(name2Count)) {
t.Errorf("Should find all users's name not equal 3")
}
}
func TestFillSmallerStruct(t *testing.T) {
user1 := User{Name: "SmallerUser", Age: 100}
DB.Save(&user1)
type SimpleUser struct {
Name string
Id int64
UpdatedAt time.Time
CreatedAt time.Time
}
var simpleUser SimpleUser
DB.Table("users").Where("name = ?", user1.Name).First(&simpleUser)
if simpleUser.Id == 0 || simpleUser.Name == "" {
t.Errorf("Should fill data correctly into smaller struct")
}
}
func TestFindOrInitialize(t *testing.T) {
var user1, user2, user3, user4, user5, user6 User
DB.Where(&User{Name: "find or init", Age: 33}).FirstOrInit(&user1)
if user1.Name != "find or init" || user1.Id != 0 || user1.Age != 33 {
t.Errorf("user should be initialized with search value")
}
DB.Where(User{Name: "find or init", Age: 33}).FirstOrInit(&user2)
if user2.Name != "find or init" || user2.Id != 0 || user2.Age != 33 {
t.Errorf("user should be initialized with search value")
}
DB.FirstOrInit(&user3, map[string]interface{}{"name": "find or init 2"})
if user3.Name != "find or init 2" || user3.Id != 0 {
t.Errorf("user should be initialized with inline search value")
}
DB.Where(&User{Name: "find or init"}).Attrs(User{Age: 44}).FirstOrInit(&user4)
if user4.Name != "find or init" || user4.Id != 0 || user4.Age != 44 {
t.Errorf("user should be initialized with search value and attrs")
}
DB.Where(&User{Name: "find or init"}).Assign("age", 44).FirstOrInit(&user4)
if user4.Name != "find or init" || user4.Id != 0 || user4.Age != 44 {
t.Errorf("user should be initialized with search value and assign attrs")
}
DB.Save(&User{Name: "find or init", Age: 33})
DB.Where(&User{Name: "find or init"}).Attrs("age", 44).FirstOrInit(&user5)
if user5.Name != "find or init" || user5.Id == 0 || user5.Age != 33 {
t.Errorf("user should be found and not initialized by Attrs")
}
DB.Where(&User{Name: "find or init", Age: 33}).FirstOrInit(&user6)
if user6.Name != "find or init" || user6.Id == 0 || user6.Age != 33 {
t.Errorf("user should be found with FirstOrInit")
}
DB.Where(&User{Name: "find or init"}).Assign(User{Age: 44}).FirstOrInit(&user6)
if user6.Name != "find or init" || user6.Id == 0 || user6.Age != 44 {
t.Errorf("user should be found and updated with assigned attrs")
}
}
func TestFindOrCreate(t *testing.T) {
var user1, user2, user3, user4, user5, user6, user7, user8 User
DB.Where(&User{Name: "find or create", Age: 33}).FirstOrCreate(&user1)
if user1.Name != "find or create" || user1.Id == 0 || user1.Age != 33 {
t.Errorf("user should be created with search value")
}
DB.Where(&User{Name: "find or create", Age: 33}).FirstOrCreate(&user2)
if user1.Id != user2.Id || user2.Name != "find or create" || user2.Id == 0 || user2.Age != 33 {
t.Errorf("user should be created with search value")
}
DB.FirstOrCreate(&user3, map[string]interface{}{"name": "find or create 2"})
if user3.Name != "find or create 2" || user3.Id == 0 {
t.Errorf("user should be created with inline search value")
}
DB.Where(&User{Name: "find or create 3"}).Attrs("age", 44).FirstOrCreate(&user4)
if user4.Name != "find or create 3" || user4.Id == 0 || user4.Age != 44 {
t.Errorf("user should be created with search value and attrs")
}
updatedAt1 := user4.UpdatedAt
DB.Where(&User{Name: "find or create 3"}).Assign("age", 55).FirstOrCreate(&user4)
if updatedAt1.Format(time.RFC3339Nano) == user4.UpdatedAt.Format(time.RFC3339Nano) {
t.Errorf("UpdateAt should be changed when update values with assign")
}
DB.Where(&User{Name: "find or create 4"}).Assign(User{Age: 44}).FirstOrCreate(&user4)
if user4.Name != "find or create 4" || user4.Id == 0 || user4.Age != 44 {
t.Errorf("user should be created with search value and assigned attrs")
}
DB.Where(&User{Name: "find or create"}).Attrs("age", 44).FirstOrInit(&user5)
if user5.Name != "find or create" || user5.Id == 0 || user5.Age != 33 {
t.Errorf("user should be found and not initialized by Attrs")
}
DB.Where(&User{Name: "find or create"}).Assign(User{Age: 44}).FirstOrCreate(&user6)
if user6.Name != "find or create" || user6.Id == 0 || user6.Age != 44 {
t.Errorf("user should be found and updated with assigned attrs")
}
DB.Where(&User{Name: "find or create"}).Find(&user7)
if user7.Name != "find or create" || user7.Id == 0 || user7.Age != 44 {
t.Errorf("user should be found and updated with assigned attrs")
}
DB.Where(&User{Name: "find or create embedded struct"}).Assign(User{Age: 44, CreditCard: CreditCard{Number: "1231231231"}, Emails: []Email{{Email: "jinzhu@assign_embedded_struct.com"}, {Email: "jinzhu-2@assign_embedded_struct.com"}}}).FirstOrCreate(&user8)
if DB.Where("email = ?", "jinzhu-2@assign_embedded_struct.com").First(&Email{}).RecordNotFound() {
t.Errorf("embedded struct email should be saved")
}
if DB.Where("email = ?", "1231231231").First(&CreditCard{}).RecordNotFound() {
t.Errorf("embedded struct credit card should be saved")
}
}
func TestSelectWithEscapedFieldName(t *testing.T) {
user1 := User{Name: "EscapedFieldNameUser", Age: 1}
user2 := User{Name: "EscapedFieldNameUser", Age: 10}
user3 := User{Name: "EscapedFieldNameUser", Age: 20}
DB.Save(&user1).Save(&user2).Save(&user3)
var names []string
DB.Model(User{}).Where(&User{Name: "EscapedFieldNameUser"}).Pluck("\"name\"", &names)
if len(names) != 3 {
t.Errorf("Expected 3 name, but got: %d", len(names))
}
}
func TestSelectWithVariables(t *testing.T) {
DB.Save(&User{Name: "jinzhu"})
rows, _ := DB.Table("users").Select("? as fake", "name").Rows()
if !rows.Next() {
t.Errorf("Should have returned at least one row")
} else {
columns, _ := rows.Columns()
if !reflect.DeepEqual(columns, []string{"fake"}) {
t.Errorf("Should only contains one column")
}
}
}
func TestSelectWithArrayInput(t *testing.T) {
DB.Save(&User{Name: "jinzhu", Age: 42})
var user User
DB.Select([]string{"name", "age"}).Where("age = 42 AND name = 'jinzhu'").First(&user)
if user.Name != "jinzhu" || user.Age != 42 {
t.Errorf("Should have selected both age and name")
}
}

443
Godeps/_workspace/src/github.com/jinzhu/gorm/scope.go generated vendored Normal file
View File

@@ -0,0 +1,443 @@
package gorm
import (
"errors"
"fmt"
"strings"
"time"
"reflect"
)
type Scope struct {
Search *search
Value interface{}
Sql string
SqlVars []interface{}
db *DB
indirectValue *reflect.Value
instanceId string
primaryKeyField *Field
skipLeft bool
fields map[string]*Field
selectAttrs *[]string
}
func (scope *Scope) IndirectValue() reflect.Value {
if scope.indirectValue == nil {
value := reflect.Indirect(reflect.ValueOf(scope.Value))
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
scope.indirectValue = &value
}
return *scope.indirectValue
}
func (scope *Scope) NeedPtr() *Scope {
reflectKind := reflect.ValueOf(scope.Value).Kind()
if !((reflectKind == reflect.Invalid) || (reflectKind == reflect.Ptr)) {
err := fmt.Errorf("%v %v\n", fileWithLineNum(), "using unaddressable value")
scope.Err(err)
fmt.Printf(err.Error())
}
return scope
}
// New create a new Scope without search information
func (scope *Scope) New(value interface{}) *Scope {
return &Scope{db: scope.NewDB(), Search: &search{}, Value: value}
}
// NewDB create a new DB without search information
func (scope *Scope) NewDB() *DB {
if scope.db != nil {
db := scope.db.clone()
db.search = nil
db.Value = nil
return db
}
return nil
}
func (scope *Scope) DB() *DB {
return scope.db
}
// SqlDB return *sql.DB
func (scope *Scope) SqlDB() sqlCommon {
return scope.db.db
}
// SkipLeft skip remaining callbacks
func (scope *Scope) SkipLeft() {
scope.skipLeft = true
}
// Quote used to quote database column name according to database dialect
func (scope *Scope) Quote(str string) string {
if strings.Index(str, ".") != -1 {
newStrs := []string{}
for _, str := range strings.Split(str, ".") {
newStrs = append(newStrs, scope.Dialect().Quote(str))
}
return strings.Join(newStrs, ".")
} else {
return scope.Dialect().Quote(str)
}
}
// Dialect get dialect
func (scope *Scope) Dialect() Dialect {
return scope.db.parent.dialect
}
// Err write error
func (scope *Scope) Err(err error) error {
if err != nil {
scope.db.err(err)
}
return err
}
// Log print log message
func (scope *Scope) Log(v ...interface{}) {
scope.db.log(v...)
}
// HasError check if there are any error
func (scope *Scope) HasError() bool {
return scope.db.Error != nil
}
func (scope *Scope) PrimaryFields() []*Field {
var fields = []*Field{}
for _, field := range scope.GetModelStruct().PrimaryFields {
fields = append(fields, scope.Fields()[field.DBName])
}
return fields
}
func (scope *Scope) PrimaryField() *Field {
if primaryFields := scope.GetModelStruct().PrimaryFields; len(primaryFields) > 0 {
if len(primaryFields) > 1 {
if field, ok := scope.Fields()["id"]; ok {
return field
}
}
return scope.Fields()[primaryFields[0].DBName]
}
return nil
}
// PrimaryKey get the primary key's column name
func (scope *Scope) PrimaryKey() string {
if field := scope.PrimaryField(); field != nil {
return field.DBName
}
return ""
}
// PrimaryKeyZero check the primary key is blank or not
func (scope *Scope) PrimaryKeyZero() bool {
field := scope.PrimaryField()
return field == nil || field.IsBlank
}
// PrimaryKeyValue get the primary key's value
func (scope *Scope) PrimaryKeyValue() interface{} {
if field := scope.PrimaryField(); field != nil && field.Field.IsValid() {
return field.Field.Interface()
}
return 0
}
// HasColumn to check if has column
func (scope *Scope) HasColumn(column string) bool {
for _, field := range scope.GetStructFields() {
if field.IsNormal && (field.Name == column || field.DBName == column) {
return true
}
}
return false
}
// SetColumn to set the column's value
func (scope *Scope) SetColumn(column interface{}, value interface{}) error {
if field, ok := column.(*Field); ok {
return field.Set(value)
} else if name, ok := column.(string); ok {
if field, ok := scope.Fields()[name]; ok {
return field.Set(value)
}
dbName := ToDBName(name)
if field, ok := scope.Fields()[dbName]; ok {
return field.Set(value)
}
if field, ok := scope.FieldByName(name); ok {
return field.Set(value)
}
}
return errors.New("could not convert column to field")
}
func (scope *Scope) CallMethod(name string, checkError bool) {
if scope.Value == nil || (checkError && scope.HasError()) {
return
}
call := func(value interface{}) {
if fm := reflect.ValueOf(value).MethodByName(name); fm.IsValid() {
switch f := fm.Interface().(type) {
case func():
f()
case func(s *Scope):
f(scope)
case func(s *DB):
f(scope.NewDB())
case func() error:
scope.Err(f())
case func(s *Scope) error:
scope.Err(f(scope))
case func(s *DB) error:
scope.Err(f(scope.NewDB()))
default:
scope.Err(fmt.Errorf("unsupported function %v", name))
}
}
}
if values := scope.IndirectValue(); values.Kind() == reflect.Slice {
for i := 0; i < values.Len(); i++ {
call(values.Index(i).Addr().Interface())
}
} else {
call(scope.Value)
}
}
func (scope *Scope) CallMethodWithErrorCheck(name string) {
scope.CallMethod(name, true)
}
// AddToVars add value as sql's vars, gorm will escape them
func (scope *Scope) AddToVars(value interface{}) string {
if expr, ok := value.(*expr); ok {
exp := expr.expr
for _, arg := range expr.args {
exp = strings.Replace(exp, "?", scope.AddToVars(arg), 1)
}
return exp
} else {
scope.SqlVars = append(scope.SqlVars, value)
return scope.Dialect().BinVar(len(scope.SqlVars))
}
}
type tabler interface {
TableName() string
}
type dbTabler interface {
TableName(*DB) string
}
// TableName get table name
func (scope *Scope) TableName() string {
if scope.Search != nil && len(scope.Search.tableName) > 0 {
return scope.Search.tableName
}
if tabler, ok := scope.Value.(tabler); ok {
return tabler.TableName()
}
if tabler, ok := scope.Value.(dbTabler); ok {
return tabler.TableName(scope.db)
}
return scope.GetModelStruct().TableName(scope.db.Model(scope.Value))
}
func (scope *Scope) QuotedTableName() (name string) {
if scope.Search != nil && len(scope.Search.tableName) > 0 {
if strings.Index(scope.Search.tableName, " ") != -1 {
return scope.Search.tableName
}
return scope.Quote(scope.Search.tableName)
} else {
return scope.Quote(scope.TableName())
}
}
// CombinedConditionSql get combined condition sql
func (scope *Scope) CombinedConditionSql() string {
return scope.joinsSql() + scope.whereSql() + scope.groupSql() +
scope.havingSql() + scope.orderSql() + scope.limitSql() + scope.offsetSql()
}
func (scope *Scope) FieldByName(name string) (field *Field, ok bool) {
for _, field := range scope.Fields() {
if field.Name == name || field.DBName == name {
return field, true
}
}
return nil, false
}
// Raw set sql
func (scope *Scope) Raw(sql string) *Scope {
scope.Sql = strings.Replace(sql, "$$", "?", -1)
return scope
}
// Exec invoke sql
func (scope *Scope) Exec() *Scope {
defer scope.Trace(NowFunc())
if !scope.HasError() {
if result, err := scope.SqlDB().Exec(scope.Sql, scope.SqlVars...); scope.Err(err) == nil {
if count, err := result.RowsAffected(); scope.Err(err) == nil {
scope.db.RowsAffected = count
}
}
}
return scope
}
// Set set value by name
func (scope *Scope) Set(name string, value interface{}) *Scope {
scope.db.InstantSet(name, value)
return scope
}
// Get get value by name
func (scope *Scope) Get(name string) (interface{}, bool) {
return scope.db.Get(name)
}
// InstanceId get InstanceId for scope
func (scope *Scope) InstanceId() string {
if scope.instanceId == "" {
scope.instanceId = fmt.Sprintf("%v%v", &scope, &scope.db)
}
return scope.instanceId
}
func (scope *Scope) InstanceSet(name string, value interface{}) *Scope {
return scope.Set(name+scope.InstanceId(), value)
}
func (scope *Scope) InstanceGet(name string) (interface{}, bool) {
return scope.Get(name + scope.InstanceId())
}
// Trace print sql log
func (scope *Scope) Trace(t time.Time) {
if len(scope.Sql) > 0 {
scope.db.slog(scope.Sql, t, scope.SqlVars...)
}
}
// Begin start a transaction
func (scope *Scope) Begin() *Scope {
if db, ok := scope.SqlDB().(sqlDb); ok {
if tx, err := db.Begin(); err == nil {
scope.db.db = interface{}(tx).(sqlCommon)
scope.InstanceSet("gorm:started_transaction", true)
}
}
return scope
}
// CommitOrRollback commit current transaction if there is no error, otherwise rollback it
func (scope *Scope) CommitOrRollback() *Scope {
if _, ok := scope.InstanceGet("gorm:started_transaction"); ok {
if db, ok := scope.db.db.(sqlTx); ok {
if scope.HasError() {
db.Rollback()
} else {
db.Commit()
}
scope.db.db = scope.db.parent.db
}
}
return scope
}
func (scope *Scope) SelectAttrs() []string {
if scope.selectAttrs == nil {
attrs := []string{}
for _, value := range scope.Search.selects {
if str, ok := value.(string); ok {
attrs = append(attrs, str)
} else if strs, ok := value.([]string); ok {
attrs = append(attrs, strs...)
} else if strs, ok := value.([]interface{}); ok {
for _, str := range strs {
attrs = append(attrs, fmt.Sprintf("%v", str))
}
}
}
scope.selectAttrs = &attrs
}
return *scope.selectAttrs
}
func (scope *Scope) OmitAttrs() []string {
return scope.Search.omits
}
func (scope *Scope) changeableDBColumn(column string) bool {
selectAttrs := scope.SelectAttrs()
omitAttrs := scope.OmitAttrs()
if len(selectAttrs) > 0 {
for _, attr := range selectAttrs {
if column == ToDBName(attr) {
return true
}
}
return false
}
for _, attr := range omitAttrs {
if column == ToDBName(attr) {
return false
}
}
return true
}
func (scope *Scope) changeableField(field *Field) bool {
selectAttrs := scope.SelectAttrs()
omitAttrs := scope.OmitAttrs()
if len(selectAttrs) > 0 {
for _, attr := range selectAttrs {
if field.Name == attr || field.DBName == attr {
return true
}
}
return false
}
for _, attr := range omitAttrs {
if field.Name == attr || field.DBName == attr {
return false
}
}
return !field.IsIgnored
}
func (scope *Scope) shouldSaveAssociations() bool {
saveAssociations, ok := scope.Get("gorm:save_associations")
if ok && !saveAssociations.(bool) {
return false
}
return true
}

View File

@@ -0,0 +1,596 @@
package gorm
import (
"database/sql"
"database/sql/driver"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
)
func (scope *Scope) primaryCondition(value interface{}) string {
return fmt.Sprintf("(%v = %v)", scope.Quote(scope.PrimaryKey()), value)
}
func (scope *Scope) buildWhereCondition(clause map[string]interface{}) (str string) {
switch value := clause["query"].(type) {
case string:
// if string is number
if regexp.MustCompile("^\\s*\\d+\\s*$").MatchString(value) {
id, _ := strconv.Atoi(value)
return scope.primaryCondition(scope.AddToVars(id))
} else if value != "" {
str = fmt.Sprintf("(%v)", value)
}
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, sql.NullInt64:
return scope.primaryCondition(scope.AddToVars(value))
case []int, []int8, []int16, []int32, []int64, []uint, []uint8, []uint16, []uint32, []uint64, []string, []interface{}:
str = fmt.Sprintf("(%v in (?))", scope.Quote(scope.PrimaryKey()))
clause["args"] = []interface{}{value}
case map[string]interface{}:
var sqls []string
for key, value := range value {
sqls = append(sqls, fmt.Sprintf("(%v = %v)", scope.Quote(key), scope.AddToVars(value)))
}
return strings.Join(sqls, " AND ")
case interface{}:
var sqls []string
for _, field := range scope.New(value).Fields() {
if !field.IsIgnored && !field.IsBlank {
sqls = append(sqls, fmt.Sprintf("(%v = %v)", scope.Quote(field.DBName), scope.AddToVars(field.Field.Interface())))
}
}
return strings.Join(sqls, " AND ")
}
args := clause["args"].([]interface{})
for _, arg := range args {
switch reflect.ValueOf(arg).Kind() {
case reflect.Slice: // For where("id in (?)", []int64{1,2})
values := reflect.ValueOf(arg)
var tempMarks []string
for i := 0; i < values.Len(); i++ {
tempMarks = append(tempMarks, scope.AddToVars(values.Index(i).Interface()))
}
str = strings.Replace(str, "?", strings.Join(tempMarks, ","), 1)
default:
if valuer, ok := interface{}(arg).(driver.Valuer); ok {
arg, _ = valuer.Value()
}
str = strings.Replace(str, "?", scope.AddToVars(arg), 1)
}
}
return
}
func (scope *Scope) buildNotCondition(clause map[string]interface{}) (str string) {
var notEqualSql string
var primaryKey = scope.PrimaryKey()
switch value := clause["query"].(type) {
case string:
// is number
if regexp.MustCompile("^\\s*\\d+\\s*$").MatchString(value) {
id, _ := strconv.Atoi(value)
return fmt.Sprintf("(%v <> %v)", scope.Quote(primaryKey), id)
} else if regexp.MustCompile("(?i) (=|<>|>|<|LIKE|IS) ").MatchString(value) {
str = fmt.Sprintf(" NOT (%v) ", value)
notEqualSql = fmt.Sprintf("NOT (%v)", value)
} else {
str = fmt.Sprintf("(%v NOT IN (?))", scope.Quote(value))
notEqualSql = fmt.Sprintf("(%v <> ?)", scope.Quote(value))
}
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, sql.NullInt64:
return fmt.Sprintf("(%v <> %v)", scope.Quote(primaryKey), value)
case []int, []int8, []int16, []int32, []int64, []uint, []uint8, []uint16, []uint32, []uint64, []string:
if reflect.ValueOf(value).Len() > 0 {
str = fmt.Sprintf("(%v NOT IN (?))", scope.Quote(primaryKey))
clause["args"] = []interface{}{value}
}
return ""
case map[string]interface{}:
var sqls []string
for key, value := range value {
sqls = append(sqls, fmt.Sprintf("(%v <> %v)", scope.Quote(key), scope.AddToVars(value)))
}
return strings.Join(sqls, " AND ")
case interface{}:
var sqls []string
for _, field := range scope.New(value).Fields() {
if !field.IsBlank {
sqls = append(sqls, fmt.Sprintf("(%v <> %v)", scope.Quote(field.DBName), scope.AddToVars(field.Field.Interface())))
}
}
return strings.Join(sqls, " AND ")
}
args := clause["args"].([]interface{})
for _, arg := range args {
switch reflect.ValueOf(arg).Kind() {
case reflect.Slice: // For where("id in (?)", []int64{1,2})
values := reflect.ValueOf(arg)
var tempMarks []string
for i := 0; i < values.Len(); i++ {
tempMarks = append(tempMarks, scope.AddToVars(values.Index(i).Interface()))
}
str = strings.Replace(str, "?", strings.Join(tempMarks, ","), 1)
default:
if scanner, ok := interface{}(arg).(driver.Valuer); ok {
arg, _ = scanner.Value()
}
str = strings.Replace(notEqualSql, "?", scope.AddToVars(arg), 1)
}
}
return
}
func (scope *Scope) buildSelectQuery(clause map[string]interface{}) (str string) {
switch value := clause["query"].(type) {
case string:
str = value
case []string:
str = strings.Join(value, ", ")
}
args := clause["args"].([]interface{})
for _, arg := range args {
switch reflect.ValueOf(arg).Kind() {
case reflect.Slice:
values := reflect.ValueOf(arg)
var tempMarks []string
for i := 0; i < values.Len(); i++ {
tempMarks = append(tempMarks, scope.AddToVars(values.Index(i).Interface()))
}
str = strings.Replace(str, "?", strings.Join(tempMarks, ","), 1)
default:
if valuer, ok := interface{}(arg).(driver.Valuer); ok {
arg, _ = valuer.Value()
}
str = strings.Replace(str, "?", scope.Dialect().Quote(fmt.Sprintf("%v", arg)), 1)
}
}
return
}
func (scope *Scope) whereSql() (sql string) {
var primaryConditions, andConditions, orConditions []string
if !scope.Search.Unscoped && scope.Fields()["deleted_at"] != nil {
sql := fmt.Sprintf("(%v.deleted_at IS NULL OR %v.deleted_at <= '0001-01-02')", scope.QuotedTableName(), scope.QuotedTableName())
primaryConditions = append(primaryConditions, sql)
}
if !scope.PrimaryKeyZero() {
primaryConditions = append(primaryConditions, scope.primaryCondition(scope.AddToVars(scope.PrimaryKeyValue())))
}
for _, clause := range scope.Search.whereConditions {
if sql := scope.buildWhereCondition(clause); sql != "" {
andConditions = append(andConditions, sql)
}
}
for _, clause := range scope.Search.orConditions {
if sql := scope.buildWhereCondition(clause); sql != "" {
orConditions = append(orConditions, sql)
}
}
for _, clause := range scope.Search.notConditions {
if sql := scope.buildNotCondition(clause); sql != "" {
andConditions = append(andConditions, sql)
}
}
orSql := strings.Join(orConditions, " OR ")
combinedSql := strings.Join(andConditions, " AND ")
if len(combinedSql) > 0 {
if len(orSql) > 0 {
combinedSql = combinedSql + " OR " + orSql
}
} else {
combinedSql = orSql
}
if len(primaryConditions) > 0 {
sql = "WHERE " + strings.Join(primaryConditions, " AND ")
if len(combinedSql) > 0 {
sql = sql + " AND (" + combinedSql + ")"
}
} else if len(combinedSql) > 0 {
sql = "WHERE " + combinedSql
}
return
}
func (scope *Scope) selectSql() string {
if len(scope.Search.selects) == 0 {
return "*"
}
return scope.buildSelectQuery(scope.Search.selects)
}
func (scope *Scope) orderSql() string {
if len(scope.Search.orders) == 0 {
return ""
}
return " ORDER BY " + strings.Join(scope.Search.orders, ",")
}
func (scope *Scope) limitSql() string {
if !scope.Dialect().HasTop() {
if len(scope.Search.limit) == 0 {
return ""
}
return " LIMIT " + scope.Search.limit
}
return ""
}
func (scope *Scope) topSql() string {
if scope.Dialect().HasTop() && len(scope.Search.offset) == 0 {
if len(scope.Search.limit) == 0 {
return ""
}
return " TOP(" + scope.Search.limit + ")"
}
return ""
}
func (scope *Scope) offsetSql() string {
if len(scope.Search.offset) == 0 {
return ""
}
if scope.Dialect().HasTop() {
sql := " OFFSET " + scope.Search.offset + " ROW "
if len(scope.Search.limit) > 0 {
sql += "FETCH NEXT " + scope.Search.limit + " ROWS ONLY"
}
return sql
}
return " OFFSET " + scope.Search.offset
}
func (scope *Scope) groupSql() string {
if len(scope.Search.group) == 0 {
return ""
}
return " GROUP BY " + scope.Search.group
}
func (scope *Scope) havingSql() string {
if scope.Search.havingCondition == nil {
return ""
}
return " HAVING " + scope.buildWhereCondition(scope.Search.havingCondition)
}
func (scope *Scope) joinsSql() string {
return scope.Search.joins + " "
}
func (scope *Scope) prepareQuerySql() {
if scope.Search.raw {
scope.Raw(strings.TrimSuffix(strings.TrimPrefix(scope.CombinedConditionSql(), " WHERE ("), ")"))
} else {
scope.Raw(fmt.Sprintf("SELECT %v %v FROM %v %v", scope.topSql(), scope.selectSql(), scope.QuotedTableName(), scope.CombinedConditionSql()))
}
return
}
func (scope *Scope) inlineCondition(values ...interface{}) *Scope {
if len(values) > 0 {
scope.Search.Where(values[0], values[1:]...)
}
return scope
}
func (scope *Scope) callCallbacks(funcs []*func(s *Scope)) *Scope {
for _, f := range funcs {
(*f)(scope)
if scope.skipLeft {
break
}
}
return scope
}
func (scope *Scope) updatedAttrsWithValues(values map[string]interface{}, ignoreProtectedAttrs bool) (results map[string]interface{}, hasUpdate bool) {
if !scope.IndirectValue().CanAddr() {
return values, true
}
var hasExpr bool
fields := scope.Fields()
for key, value := range values {
if field, ok := fields[ToDBName(key)]; ok && field.Field.IsValid() {
if !reflect.DeepEqual(field.Field, reflect.ValueOf(value)) {
if _, ok := value.(*expr); ok {
hasExpr = true
} else if !equalAsString(field.Field.Interface(), value) {
hasUpdate = true
field.Set(value)
}
}
}
}
if hasExpr {
var updateMap = map[string]interface{}{}
for key, value := range fields {
if v, ok := values[key]; ok {
updateMap[key] = v
} else {
updateMap[key] = value.Field.Interface()
}
}
return updateMap, true
}
return
}
func (scope *Scope) row() *sql.Row {
defer scope.Trace(NowFunc())
scope.callCallbacks(scope.db.parent.callback.rowQueries)
scope.prepareQuerySql()
return scope.SqlDB().QueryRow(scope.Sql, scope.SqlVars...)
}
func (scope *Scope) rows() (*sql.Rows, error) {
defer scope.Trace(NowFunc())
scope.callCallbacks(scope.db.parent.callback.rowQueries)
scope.prepareQuerySql()
return scope.SqlDB().Query(scope.Sql, scope.SqlVars...)
}
func (scope *Scope) initialize() *Scope {
for _, clause := range scope.Search.whereConditions {
scope.updatedAttrsWithValues(convertInterfaceToMap(clause["query"]), false)
}
scope.updatedAttrsWithValues(convertInterfaceToMap(scope.Search.initAttrs), false)
scope.updatedAttrsWithValues(convertInterfaceToMap(scope.Search.assignAttrs), false)
return scope
}
func (scope *Scope) pluck(column string, value interface{}) *Scope {
dest := reflect.Indirect(reflect.ValueOf(value))
scope.Search.Select(column)
if dest.Kind() != reflect.Slice {
scope.Err(fmt.Errorf("results should be a slice, not %s", dest.Kind()))
return scope
}
rows, err := scope.rows()
if scope.Err(err) == nil {
defer rows.Close()
for rows.Next() {
elem := reflect.New(dest.Type().Elem()).Interface()
scope.Err(rows.Scan(elem))
dest.Set(reflect.Append(dest, reflect.ValueOf(elem).Elem()))
}
}
return scope
}
func (scope *Scope) count(value interface{}) *Scope {
scope.Search.Select("count(*)")
scope.Err(scope.row().Scan(value))
return scope
}
func (scope *Scope) typeName() string {
value := scope.IndirectValue()
if value.Kind() == reflect.Slice {
return value.Type().Elem().Name()
}
return value.Type().Name()
}
func (scope *Scope) related(value interface{}, foreignKeys ...string) *Scope {
toScope := scope.db.NewScope(value)
fromFields := scope.Fields()
toFields := toScope.Fields()
for _, foreignKey := range append(foreignKeys, toScope.typeName()+"Id", scope.typeName()+"Id") {
var fromField, toField *Field
if field, ok := scope.FieldByName(foreignKey); ok {
fromField = field
} else {
fromField = fromFields[ToDBName(foreignKey)]
}
if field, ok := toScope.FieldByName(foreignKey); ok {
toField = field
} else {
toField = toFields[ToDBName(foreignKey)]
}
if fromField != nil {
if relationship := fromField.Relationship; relationship != nil {
if relationship.Kind == "many_to_many" {
joinTableHandler := relationship.JoinTableHandler
scope.Err(joinTableHandler.JoinWith(joinTableHandler, toScope.db, scope.Value).Find(value).Error)
} else if relationship.Kind == "belongs_to" {
sql := fmt.Sprintf("%v = ?", scope.Quote(toScope.PrimaryKey()))
foreignKeyValue := fromFields[relationship.ForeignDBName].Field.Interface()
scope.Err(toScope.db.Where(sql, foreignKeyValue).Find(value).Error)
} else if relationship.Kind == "has_many" || relationship.Kind == "has_one" {
sql := fmt.Sprintf("%v = ?", scope.Quote(relationship.ForeignDBName))
query := toScope.db.Where(sql, scope.PrimaryKeyValue())
if relationship.PolymorphicType != "" {
query = query.Where(fmt.Sprintf("%v = ?", scope.Quote(relationship.PolymorphicDBName)), scope.TableName())
}
scope.Err(query.Find(value).Error)
}
} else {
sql := fmt.Sprintf("%v = ?", scope.Quote(toScope.PrimaryKey()))
scope.Err(toScope.db.Where(sql, fromField.Field.Interface()).Find(value).Error)
}
return scope
} else if toField != nil {
sql := fmt.Sprintf("%v = ?", scope.Quote(toField.DBName))
scope.Err(toScope.db.Where(sql, scope.PrimaryKeyValue()).Find(value).Error)
return scope
}
}
scope.Err(fmt.Errorf("invalid association %v", foreignKeys))
return scope
}
func (scope *Scope) createJoinTable(field *StructField) {
if relationship := field.Relationship; relationship != nil && relationship.JoinTableHandler != nil {
joinTableHandler := relationship.JoinTableHandler
joinTable := joinTableHandler.Table(scope.db)
if !scope.Dialect().HasTable(scope, joinTable) {
toScope := &Scope{Value: reflect.New(field.Struct.Type).Interface()}
var sqlTypes []string
for _, s := range []*Scope{scope, toScope} {
for _, primaryField := range s.GetModelStruct().PrimaryFields {
value := reflect.Indirect(reflect.New(primaryField.Struct.Type))
primaryKeySqlType := scope.Dialect().SqlTag(value, 255, false)
dbName := ToDBName(s.GetModelStruct().ModelType.Name() + primaryField.Name)
sqlTypes = append(sqlTypes, scope.Quote(dbName)+" "+primaryKeySqlType)
}
}
scope.Err(scope.NewDB().Exec(fmt.Sprintf("CREATE TABLE %v (%v)", scope.Quote(joinTable), strings.Join(sqlTypes, ","))).Error)
}
scope.NewDB().Table(joinTable).AutoMigrate(joinTableHandler)
}
}
func (scope *Scope) createTable() *Scope {
var tags []string
var primaryKeys []string
for _, field := range scope.GetStructFields() {
if field.IsNormal {
sqlTag := scope.generateSqlTag(field)
tags = append(tags, scope.Quote(field.DBName)+" "+sqlTag)
}
if field.IsPrimaryKey {
primaryKeys = append(primaryKeys, scope.Quote(field.DBName))
}
scope.createJoinTable(field)
}
var primaryKeyStr string
if len(primaryKeys) > 0 {
primaryKeyStr = fmt.Sprintf(", PRIMARY KEY (%v)", strings.Join(primaryKeys, ","))
}
scope.Raw(fmt.Sprintf("CREATE TABLE %v (%v %v)", scope.QuotedTableName(), strings.Join(tags, ","), primaryKeyStr)).Exec()
return scope
}
func (scope *Scope) dropTable() *Scope {
scope.Raw(fmt.Sprintf("DROP TABLE %v", scope.QuotedTableName())).Exec()
return scope
}
func (scope *Scope) dropTableIfExists() *Scope {
if scope.Dialect().HasTable(scope, scope.TableName()) {
scope.dropTable()
}
return scope
}
func (scope *Scope) modifyColumn(column string, typ string) {
scope.Raw(fmt.Sprintf("ALTER TABLE %v MODIFY %v %v", scope.QuotedTableName(), scope.Quote(column), typ)).Exec()
}
func (scope *Scope) dropColumn(column string) {
scope.Raw(fmt.Sprintf("ALTER TABLE %v DROP COLUMN %v", scope.QuotedTableName(), scope.Quote(column))).Exec()
}
func (scope *Scope) addIndex(unique bool, indexName string, column ...string) {
if scope.Dialect().HasIndex(scope, scope.TableName(), indexName) {
return
}
var columns []string
for _, name := range column {
if regexp.MustCompile("^[a-zA-Z]+$").MatchString(name) {
columns = append(columns, scope.Quote(name))
} else {
columns = append(columns, name)
}
}
sqlCreate := "CREATE INDEX"
if unique {
sqlCreate = "CREATE UNIQUE INDEX"
}
scope.Raw(fmt.Sprintf("%s %v ON %v(%v);", sqlCreate, indexName, scope.QuotedTableName(), strings.Join(columns, ", "))).Exec()
}
func (scope *Scope) addForeignKey(field string, dest string, onDelete string, onUpdate string) {
var table = scope.TableName()
var keyName = fmt.Sprintf("%s_%s_foreign", table, field)
var query = `ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s ON DELETE %s ON UPDATE %s;`
scope.Raw(fmt.Sprintf(query, scope.QuotedTableName(), scope.Quote(keyName), scope.Quote(field), scope.Quote(dest), onDelete, onUpdate)).Exec()
}
func (scope *Scope) removeIndex(indexName string) {
scope.Dialect().RemoveIndex(scope, indexName)
}
func (scope *Scope) autoMigrate() *Scope {
tableName := scope.TableName()
quotedTableName := scope.QuotedTableName()
if !scope.Dialect().HasTable(scope, tableName) {
scope.createTable()
} else {
for _, field := range scope.GetStructFields() {
if !scope.Dialect().HasColumn(scope, tableName, field.DBName) {
if field.IsNormal {
sqlTag := scope.generateSqlTag(field)
scope.Raw(fmt.Sprintf("ALTER TABLE %v ADD %v %v;", quotedTableName, scope.Quote(field.DBName), sqlTag)).Exec()
}
}
scope.createJoinTable(field)
}
}
scope.autoIndex()
return scope
}
func (scope *Scope) autoIndex() *Scope {
var indexes = map[string][]string{}
var uniqueIndexes = map[string][]string{}
for _, field := range scope.GetStructFields() {
sqlSettings := parseTagSetting(field.Tag.Get("sql"))
if name, ok := sqlSettings["INDEX"]; ok {
if name == "INDEX" {
name = fmt.Sprintf("idx_%v_%v", scope.TableName(), field.DBName)
}
indexes[name] = append(indexes[name], field.DBName)
}
if name, ok := sqlSettings["UNIQUE_INDEX"]; ok {
if name == "UNIQUE_INDEX" {
name = fmt.Sprintf("uix_%v_%v", scope.TableName(), field.DBName)
}
uniqueIndexes[name] = append(uniqueIndexes[name], field.DBName)
}
}
for name, columns := range indexes {
scope.addIndex(false, name, columns...)
}
for name, columns := range uniqueIndexes {
scope.addIndex(true, name, columns...)
}
return scope
}

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