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:
33
Godeps/Godeps.json
generated
33
Godeps/Godeps.json
generated
@@ -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"
|
||||
|
||||
@@ -20,3 +20,4 @@ _cgo_export.*
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
@@ -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
|
||||
741
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/README.md
generated
vendored
Normal file
741
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/README.md
generated
vendored
Normal 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
|
||||
68
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aes/ecb.go
generated
vendored
Normal file
68
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aes/ecb.go
generated
vendored
Normal 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:]
|
||||
}
|
||||
}
|
||||
38
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aes/ecb_test.go
generated
vendored
Normal file
38
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aes/ecb_test.go
generated
vendored
Normal 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})
|
||||
}
|
||||
113
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aes/key_wrap.go
generated
vendored
Normal file
113
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aes/key_wrap.go
generated
vendored
Normal 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
|
||||
}
|
||||
240
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aes/key_wrap_test.go
generated
vendored
Normal file
240
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aes/key_wrap_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
108
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aes_cbc_hmac.go
generated
vendored
Normal file
108
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aes_cbc_hmac.go
generated
vendored
Normal 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];
|
||||
}
|
||||
|
||||
94
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aes_gcm.go
generated
vendored
Normal file
94
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aes_gcm.go
generated
vendored
Normal 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
|
||||
}
|
||||
|
||||
128
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aes_gcm_kw.go
generated
vendored
Normal file
128
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aes_gcm_kw.go
generated
vendored
Normal 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")
|
||||
}
|
||||
64
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aeskw.go
generated
vendored
Normal file
64
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/aeskw.go
generated
vendored
Normal 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")
|
||||
}
|
||||
116
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/arrays/arrays.go
generated
vendored
Normal file
116
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/arrays/arrays.go
generated
vendored
Normal 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()
|
||||
}
|
||||
80
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/arrays/arrays_test.go
generated
vendored
Normal file
80
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/arrays/arrays_test.go
generated
vendored
Normal 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 })
|
||||
}
|
||||
31
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/base64url/base64url.go
generated
vendored
Normal file
31
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/base64url/base64url.go
generated
vendored
Normal 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
|
||||
}
|
||||
40
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/base64url/base64url_test.go
generated
vendored
Normal file
40
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/base64url/base64url_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
34
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/compact/compact.go
generated
vendored
Normal file
34
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/compact/compact.go
generated
vendored
Normal 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,".")
|
||||
}
|
||||
90
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/compact/compact_test.go
generated
vendored
Normal file
90
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/compact/compact_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
|
||||
|
||||
39
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/deflate.go
generated
vendored
Normal file
39
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/deflate.go
generated
vendored
Normal 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
|
||||
}
|
||||
|
||||
|
||||
39
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/direct.go
generated
vendored
Normal file
39
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/direct.go
generated
vendored
Normal 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")
|
||||
}
|
||||
151
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/ecdh.go
generated
vendored
Normal file
151
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/ecdh.go
generated
vendored
Normal 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)
|
||||
}
|
||||
42
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/ecdh_aeskw.go
generated
vendored
Normal file
42
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/ecdh_aeskw.go
generated
vendored
Normal 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)
|
||||
}
|
||||
76
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/ecdsa_using_sha.go
generated
vendored
Normal file
76
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/ecdsa_using_sha.go
generated
vendored
Normal 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'")
|
||||
}
|
||||
13
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/hmac.go
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/hmac.go
generated
vendored
Normal 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)
|
||||
}
|
||||
46
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/hmac_using_sha.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/hmac_using_sha.go
generated
vendored
Normal 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")
|
||||
}
|
||||
317
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/jose.go
generated
vendored
Normal file
317
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/jose.go
generated
vendored
Normal 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
|
||||
}
|
||||
2245
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/jose_test.go
generated
vendored
Normal file
2245
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/jose_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
39
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/kdf/nist_sp800_56a.go
generated
vendored
Normal file
39
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/kdf/nist_sp800_56a.go
generated
vendored
Normal 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]
|
||||
}
|
||||
57
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/kdf/nist_sp800_56a_test.go
generated
vendored
Normal file
57
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/kdf/nist_sp800_56a_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
62
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/kdf/pbkdf2.go
generated
vendored
Normal file
62
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/kdf/pbkdf2.go
generated
vendored
Normal 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
|
||||
}
|
||||
112
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/kdf/pbkdf2_test.go
generated
vendored
Normal file
112
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/kdf/pbkdf2_test.go
generated
vendored
Normal 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 })
|
||||
}
|
||||
|
||||
14
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_cert.pem
generated
vendored
Normal file
14
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_cert.pem
generated
vendored
Normal 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-----
|
||||
5
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_private.key
generated
vendored
Normal file
5
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_private.key
generated
vendored
Normal 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-----
|
||||
5
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_private.pem
generated
vendored
Normal file
5
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_private.pem
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgD/fZeBClL+0pGHDy
|
||||
NJ2vyK8GYzIP+9sbkpPezdc8CtyhRANCAASqGi1pEudyh+Nw//zeAGIYSQG/H/q/
|
||||
x6Xz2TIg3GN4hbD5BS1f4/vEitpui+TpmtJyggzox5D55N9kjq0X/hyy
|
||||
-----END PRIVATE KEY-----
|
||||
4
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_public.key
generated
vendored
Normal file
4
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/ecc/ec_public.key
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqhotaRLncofjcP/83gBiGEkBvx/6
|
||||
v8el89kyINxjeIWw+QUtX+P7xIrabovk6ZrScoIM6MeQ+eTfZI6tF/4csg==
|
||||
-----END PUBLIC KEY-----
|
||||
88
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/ecc/ecc.go
generated
vendored
Normal file
88
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/ecc/ecc.go
generated
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
113
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/ecc/ecc_test.go
generated
vendored
Normal file
113
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/ecc/ecc_test.go
generated
vendored
Normal 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
|
||||
}
|
||||
16
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/rsa/priv.key
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/rsa/priv.key
generated
vendored
Normal 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-----
|
||||
15
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/rsa/priv.pem
generated
vendored
Normal file
15
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/rsa/priv.pem
generated
vendored
Normal 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-----
|
||||
6
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/rsa/pub.key
generated
vendored
Normal file
6
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/rsa/pub.key
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8fexkhwhpHr71ErFzLHDXftPD
|
||||
IzvAW5K885Q7qJCc1b0EKa0hBWAjtaI6dVIXgCJnkLzHtcAFqxjX2xcN+I0AkpL/
|
||||
JYli4eWh6hz61PA04CrC/l4xxmjMLIPMuvKRJma6clfzePclypYdwtTy5xSa05ma
|
||||
tDxy6jlyDTbzFhb+4wIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
12
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/rsa/pub.pem
generated
vendored
Normal file
12
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/rsa/pub.pem
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBjTCB9wIEchk3tjANBgkqhkiG9w0BAQUFADAOMQwwCgYDVQQDEwNqd3QwHhcN
|
||||
MTQwMTA4MTM0NDUxWhcNMTUwMjA3MjAwMDAwWjAOMQwwCgYDVQQDEwNqd3QwgZ8w
|
||||
DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALx97GSHCGkevvUSsXMscNd+08MjO8Bb
|
||||
krzzlDuokJzVvQQprSEFYCO1ojp1UheAImeQvMe1wAWrGNfbFw34jQCSkv8liWLh
|
||||
5aHqHPrU8DTgKsL+XjHGaMwsg8y68pEmZrpyV/N49yXKlh3C1PLnFJrTmZq0PHLq
|
||||
OXINNvMWFv7jAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAJcAJ4zK0ZZLUHRdxhQdj
|
||||
e0Xv7fPYosE7gV0apbPJXnuhU1XH4XKFQQNYWyxjwfFSjghAyyZqlfcWgl0STOXx
|
||||
JnFfEbSQL1EB5xKj9e1taMd+84mFXkVNvhN3wphe3EbPr9M99BRLic/MSGAqnJRS
|
||||
OPIdhqg16dZmPJ4kCG8lLmc=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
62
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/rsa/rsa.go
generated
vendored
Normal file
62
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/rsa/rsa.go
generated
vendored
Normal 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
|
||||
}
|
||||
85
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/rsa/rsa_test.go
generated
vendored
Normal file
85
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/keys/rsa/rsa_test.go
generated
vendored
Normal 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
|
||||
}
|
||||
23
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/padding/align.go
generated
vendored
Normal file
23
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/padding/align.go
generated
vendored
Normal 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...)
|
||||
}
|
||||
52
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/padding/align_test.go
generated
vendored
Normal file
52
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/padding/align_test.go
generated
vendored
Normal 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})
|
||||
}
|
||||
38
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/padding/pkcs7.go
generated
vendored
Normal file
38
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/padding/pkcs7.go
generated
vendored
Normal 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
|
||||
}
|
||||
109
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/padding/pkcs7_test.go
generated
vendored
Normal file
109
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/padding/pkcs7_test.go
generated
vendored
Normal 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})
|
||||
}
|
||||
96
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/pbse2_hmac_aeskw.go
generated
vendored
Normal file
96
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/pbse2_hmac_aeskw.go
generated
vendored
Normal 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()
|
||||
}
|
||||
}
|
||||
38
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/plaintext.go
generated
vendored
Normal file
38
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/plaintext.go
generated
vendored
Normal 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
|
||||
}
|
||||
57
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/rsa_oaep.go
generated
vendored
Normal file
57
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/rsa_oaep.go
generated
vendored
Normal 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()
|
||||
}
|
||||
}
|
||||
41
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/rsa_pkcs1v15.go
generated
vendored
Normal file
41
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/rsa_pkcs1v15.go
generated
vendored
Normal 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'")
|
||||
}
|
||||
50
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/rsa_using_sha.go
generated
vendored
Normal file
50
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/rsa_using_sha.go
generated
vendored
Normal 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)
|
||||
}
|
||||
43
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/rsapss_using_sha.go
generated
vendored
Normal file
43
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/rsapss_using_sha.go
generated
vendored
Normal 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'")
|
||||
}
|
||||
24
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/sha.go
generated
vendored
Normal file
24
Godeps/_workspace/src/github.com/dvsekhvalnov/jose2go/sha.go
generated
vendored
Normal 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()
|
||||
}
|
||||
}
|
||||
6
Godeps/_workspace/src/github.com/endophage/gotuf/store/errors.go
generated
vendored
6
Godeps/_workspace/src/github.com/endophage/gotuf/store/errors.go
generated
vendored
@@ -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"
|
||||
}
|
||||
|
||||
3
Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore.go
generated
vendored
3
Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore.go
generated
vendored
@@ -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
21
Godeps/_workspace/src/github.com/jinzhu/gorm/License
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-NOW Jinzhu <wosmvp@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
1224
Godeps/_workspace/src/github.com/jinzhu/gorm/README.md
generated
vendored
Normal file
1224
Godeps/_workspace/src/github.com/jinzhu/gorm/README.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
185
Godeps/_workspace/src/github.com/jinzhu/gorm/association.go
generated
vendored
Normal file
185
Godeps/_workspace/src/github.com/jinzhu/gorm/association.go
generated
vendored
Normal 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
|
||||
}
|
||||
262
Godeps/_workspace/src/github.com/jinzhu/gorm/association_test.go
generated
vendored
Normal file
262
Godeps/_workspace/src/github.com/jinzhu/gorm/association_test.go
generated
vendored
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
200
Godeps/_workspace/src/github.com/jinzhu/gorm/callback.go
generated
vendored
Normal file
200
Godeps/_workspace/src/github.com/jinzhu/gorm/callback.go
generated
vendored
Normal 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{}}
|
||||
110
Godeps/_workspace/src/github.com/jinzhu/gorm/callback_create.go
generated
vendored
Normal file
110
Godeps/_workspace/src/github.com/jinzhu/gorm/callback_create.go
generated
vendored
Normal 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)
|
||||
}
|
||||
36
Godeps/_workspace/src/github.com/jinzhu/gorm/callback_delete.go
generated
vendored
Normal file
36
Godeps/_workspace/src/github.com/jinzhu/gorm/callback_delete.go
generated
vendored
Normal 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)
|
||||
}
|
||||
118
Godeps/_workspace/src/github.com/jinzhu/gorm/callback_query.go
generated
vendored
Normal file
118
Godeps/_workspace/src/github.com/jinzhu/gorm/callback_query.go
generated
vendored
Normal 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)
|
||||
}
|
||||
76
Godeps/_workspace/src/github.com/jinzhu/gorm/callback_shared.go
generated
vendored
Normal file
76
Godeps/_workspace/src/github.com/jinzhu/gorm/callback_shared.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
112
Godeps/_workspace/src/github.com/jinzhu/gorm/callback_test.go
generated
vendored
Normal file
112
Godeps/_workspace/src/github.com/jinzhu/gorm/callback_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
}
|
||||
96
Godeps/_workspace/src/github.com/jinzhu/gorm/callback_update.go
generated
vendored
Normal file
96
Godeps/_workspace/src/github.com/jinzhu/gorm/callback_update.go
generated
vendored
Normal 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)
|
||||
}
|
||||
177
Godeps/_workspace/src/github.com/jinzhu/gorm/callbacks_test.go
generated
vendored
Normal file
177
Godeps/_workspace/src/github.com/jinzhu/gorm/callbacks_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
}
|
||||
101
Godeps/_workspace/src/github.com/jinzhu/gorm/common_dialect.go
generated
vendored
Normal file
101
Godeps/_workspace/src/github.com/jinzhu/gorm/common_dialect.go
generated
vendored
Normal 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()))
|
||||
}
|
||||
159
Godeps/_workspace/src/github.com/jinzhu/gorm/create_test.go
generated
vendored
Normal file
159
Godeps/_workspace/src/github.com/jinzhu/gorm/create_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
}
|
||||
65
Godeps/_workspace/src/github.com/jinzhu/gorm/customize_column_test.go
generated
vendored
Normal file
65
Godeps/_workspace/src/github.com/jinzhu/gorm/customize_column_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
68
Godeps/_workspace/src/github.com/jinzhu/gorm/delete_test.go
generated
vendored
Normal file
68
Godeps/_workspace/src/github.com/jinzhu/gorm/delete_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
}
|
||||
40
Godeps/_workspace/src/github.com/jinzhu/gorm/dialect.go
generated
vendored
Normal file
40
Godeps/_workspace/src/github.com/jinzhu/gorm/dialect.go
generated
vendored
Normal 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
|
||||
}
|
||||
68
Godeps/_workspace/src/github.com/jinzhu/gorm/doc/development.md
generated
vendored
Normal file
68
Godeps/_workspace/src/github.com/jinzhu/gorm/doc/development.md
generated
vendored
Normal 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
|
||||
48
Godeps/_workspace/src/github.com/jinzhu/gorm/embedded_struct_test.go
generated
vendored
Normal file
48
Godeps/_workspace/src/github.com/jinzhu/gorm/embedded_struct_test.go
generated
vendored
Normal 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
11
Godeps/_workspace/src/github.com/jinzhu/gorm/errors.go
generated
vendored
Normal 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
84
Godeps/_workspace/src/github.com/jinzhu/gorm/field.go
generated
vendored
Normal 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
|
||||
}
|
||||
78
Godeps/_workspace/src/github.com/jinzhu/gorm/foundation.go
generated
vendored
Normal file
78
Godeps/_workspace/src/github.com/jinzhu/gorm/foundation.go
generated
vendored
Normal 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
|
||||
}
|
||||
BIN
Godeps/_workspace/src/github.com/jinzhu/gorm/images/logger.png
generated
vendored
Normal file
BIN
Godeps/_workspace/src/github.com/jinzhu/gorm/images/logger.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
19
Godeps/_workspace/src/github.com/jinzhu/gorm/interface.go
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/jinzhu/gorm/interface.go
generated
vendored
Normal 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
|
||||
}
|
||||
178
Godeps/_workspace/src/github.com/jinzhu/gorm/join_table_handler.go
generated
vendored
Normal file
178
Godeps/_workspace/src/github.com/jinzhu/gorm/join_table_handler.go
generated
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
72
Godeps/_workspace/src/github.com/jinzhu/gorm/join_table_test.go
generated
vendored
Normal file
72
Godeps/_workspace/src/github.com/jinzhu/gorm/join_table_test.go
generated
vendored
Normal 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
67
Godeps/_workspace/src/github.com/jinzhu/gorm/logger.go
generated
vendored
Normal 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
496
Godeps/_workspace/src/github.com/jinzhu/gorm/main.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Godeps/_workspace/src/github.com/jinzhu/gorm/main_private.go
generated
vendored
Normal file
50
Godeps/_workspace/src/github.com/jinzhu/gorm/main_private.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
645
Godeps/_workspace/src/github.com/jinzhu/gorm/main_test.go
generated
vendored
Normal file
645
Godeps/_workspace/src/github.com/jinzhu/gorm/main_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
123
Godeps/_workspace/src/github.com/jinzhu/gorm/migration_test.go
generated
vendored
Normal file
123
Godeps/_workspace/src/github.com/jinzhu/gorm/migration_test.go
generated
vendored
Normal 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
10
Godeps/_workspace/src/github.com/jinzhu/gorm/model.go
generated
vendored
Normal 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
|
||||
}
|
||||
376
Godeps/_workspace/src/github.com/jinzhu/gorm/model_struct.go
generated
vendored
Normal file
376
Godeps/_workspace/src/github.com/jinzhu/gorm/model_struct.go
generated
vendored
Normal 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
81
Godeps/_workspace/src/github.com/jinzhu/gorm/mssql.go
generated
vendored
Normal 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
|
||||
}
|
||||
46
Godeps/_workspace/src/github.com/jinzhu/gorm/multi_primary_keys_test.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/jinzhu/gorm/multi_primary_keys_test.go
generated
vendored
Normal 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
65
Godeps/_workspace/src/github.com/jinzhu/gorm/mysql.go
generated
vendored
Normal 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"
|
||||
}
|
||||
84
Godeps/_workspace/src/github.com/jinzhu/gorm/pointer_test.go
generated
vendored
Normal file
84
Godeps/_workspace/src/github.com/jinzhu/gorm/pointer_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
56
Godeps/_workspace/src/github.com/jinzhu/gorm/polymorphic_test.go
generated
vendored
Normal file
56
Godeps/_workspace/src/github.com/jinzhu/gorm/polymorphic_test.go
generated
vendored
Normal 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")
|
||||
}
|
||||
}
|
||||
131
Godeps/_workspace/src/github.com/jinzhu/gorm/postgres.go
generated
vendored
Normal file
131
Godeps/_workspace/src/github.com/jinzhu/gorm/postgres.go
generated
vendored
Normal 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
242
Godeps/_workspace/src/github.com/jinzhu/gorm/preload.go
generated
vendored
Normal 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
|
||||
}
|
||||
609
Godeps/_workspace/src/github.com/jinzhu/gorm/preload_test.go
generated
vendored
Normal file
609
Godeps/_workspace/src/github.com/jinzhu/gorm/preload_test.go
generated
vendored
Normal 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
|
||||
}
|
||||
580
Godeps/_workspace/src/github.com/jinzhu/gorm/query_test.go
generated
vendored
Normal file
580
Godeps/_workspace/src/github.com/jinzhu/gorm/query_test.go
generated
vendored
Normal 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
443
Godeps/_workspace/src/github.com/jinzhu/gorm/scope.go
generated
vendored
Normal 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
|
||||
}
|
||||
596
Godeps/_workspace/src/github.com/jinzhu/gorm/scope_private.go
generated
vendored
Normal file
596
Godeps/_workspace/src/github.com/jinzhu/gorm/scope_private.go
generated
vendored
Normal 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
Reference in New Issue
Block a user