Migrated to dep instead of govendor
This commit is contained in:
parent
0a2eae120e
commit
9c0c9474e8
92 changed files with 14968 additions and 14649 deletions
133
Gopkg.lock
generated
Normal file
133
Gopkg.lock
generated
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/davecgh/go-spew"
|
||||||
|
packages = ["spew"]
|
||||||
|
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||||
|
version = "v1.1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/dgrijalva/jwt-go"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "a539ee1a749a2b895533f979515ac7e6e0f5b650"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/go-ini/ini"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "358ee7663966325963d4e8b2e1fbd570c5195153"
|
||||||
|
version = "v1.38.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/go-sql-driver/mysql"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "ee359f95877bdef36cbb602711e49b6f0becfca9"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/go-xorm/builder"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "c8871c857d2555fbfbd8524f895be5386d3d8836"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/go-xorm/core"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "f43c33d9a48db006417a7ac4c16b08897e3e1458"
|
||||||
|
version = "v0.5.8"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/go-xorm/xorm"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "29d4a0330a00b9be468b70e3fb0f74109348c358"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/labstack/echo"
|
||||||
|
packages = [
|
||||||
|
".",
|
||||||
|
"middleware"
|
||||||
|
]
|
||||||
|
revision = "1049c9613cd371b7ea8f219404c9a821734781ed"
|
||||||
|
version = "v3.1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/labstack/gommon"
|
||||||
|
packages = [
|
||||||
|
"bytes",
|
||||||
|
"color",
|
||||||
|
"log",
|
||||||
|
"random"
|
||||||
|
]
|
||||||
|
revision = "57409ada9da0f2afad6664c49502f8c50fbd8476"
|
||||||
|
version = "0.2.3"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/mattn/go-colorable"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "ad5389df28cdac544c99bd7b9161a0b5b6ca9d1b"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/mattn/go-isatty"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "a5cdd64afdee435007ee3e9f6ed4684af949d568"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/mattn/go-sqlite3"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "25ecb14adfc7543176f7d85291ec7dba82c6f7e4"
|
||||||
|
version = "v1.9.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/pmezard/go-difflib"
|
||||||
|
packages = ["difflib"]
|
||||||
|
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||||
|
version = "v1.0.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/stretchr/testify"
|
||||||
|
packages = ["assert"]
|
||||||
|
revision = "87b1dfb5b2fa649f52695dd9eae19abe404a4308"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/valyala/bytebufferpool"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/valyala/fasttemplate"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "dcecefd839c4193db0d35b88ec65b4c12d360ab0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "golang.org/x/crypto"
|
||||||
|
packages = [
|
||||||
|
"acme",
|
||||||
|
"acme/autocert",
|
||||||
|
"bcrypt",
|
||||||
|
"blowfish"
|
||||||
|
]
|
||||||
|
revision = "9419663f5a44be8b34ca85f08abc5fe1be11f8a3"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "golang.org/x/sys"
|
||||||
|
packages = ["unix"]
|
||||||
|
revision = "314a259e304ff91bd6985da2a7149bbf91237993"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "gopkg.in/testfixtures.v2"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "1d98c34adfb14dbedeef37127968233b5d960f02"
|
||||||
|
version = "v2.4.5"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "gopkg.in/yaml.v2"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4"
|
||||||
|
version = "v2.0.0"
|
||||||
|
|
||||||
|
[solve-meta]
|
||||||
|
analyzer-name = "dep"
|
||||||
|
analyzer-version = 1
|
||||||
|
inputs-digest = "aec97fcf68fb5a8fdc8e80f6aeab7b4301c46eec4f911c8e79d2206a806424bd"
|
||||||
|
solver-name = "gps-cdcl"
|
||||||
|
solver-version = 1
|
50
Gopkg.toml
Normal file
50
Gopkg.toml
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Gopkg.toml example
|
||||||
|
#
|
||||||
|
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||||
|
# for detailed Gopkg.toml documentation.
|
||||||
|
#
|
||||||
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
# version = "1.0.0"
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project2"
|
||||||
|
# branch = "dev"
|
||||||
|
# source = "github.com/myfork/project2"
|
||||||
|
#
|
||||||
|
# [[override]]
|
||||||
|
# name = "github.com/x/y"
|
||||||
|
# version = "2.4.0"
|
||||||
|
#
|
||||||
|
# [prune]
|
||||||
|
# non-go = false
|
||||||
|
# go-tests = true
|
||||||
|
# unused-packages = true
|
||||||
|
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/go-ini/ini"
|
||||||
|
version = "1.38.1"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/go-xorm/core"
|
||||||
|
version = "0.5.7"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/labstack/echo"
|
||||||
|
version = "3.1.0"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/mattn/go-sqlite3"
|
||||||
|
version = "1.5.0"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "gopkg.in/testfixtures.v2"
|
||||||
|
version = "2.4.3"
|
||||||
|
|
||||||
|
[prune]
|
||||||
|
go-tests = true
|
||||||
|
unused-packages = true
|
2
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
2
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
|
@ -2,7 +2,7 @@ ISC License
|
||||||
|
|
||||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
copyright notice and this permission notice appear in all copies.
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
|
6
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
6
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
|
@ -41,9 +41,9 @@ var (
|
||||||
// after commit 82f48826c6c7 which changed the format again to mirror
|
// after commit 82f48826c6c7 which changed the format again to mirror
|
||||||
// the original format. Code in the init function updates these offsets
|
// the original format. Code in the init function updates these offsets
|
||||||
// as necessary.
|
// as necessary.
|
||||||
offsetPtr = ptrSize
|
offsetPtr = uintptr(ptrSize)
|
||||||
offsetScalar = uintptr(0)
|
offsetScalar = uintptr(0)
|
||||||
offsetFlag = ptrSize * 2
|
offsetFlag = uintptr(ptrSize * 2)
|
||||||
|
|
||||||
// flagKindWidth and flagKindShift indicate various bits that the
|
// flagKindWidth and flagKindShift indicate various bits that the
|
||||||
// reflect package uses internally to track kind information.
|
// reflect package uses internally to track kind information.
|
||||||
|
@ -58,7 +58,7 @@ var (
|
||||||
// changed their positions. Code in the init function updates these
|
// changed their positions. Code in the init function updates these
|
||||||
// flags as necessary.
|
// flags as necessary.
|
||||||
flagKindWidth = uintptr(5)
|
flagKindWidth = uintptr(5)
|
||||||
flagKindShift = flagKindWidth - 1
|
flagKindShift = uintptr(flagKindWidth - 1)
|
||||||
flagRO = uintptr(1 << 0)
|
flagRO = uintptr(1 << 0)
|
||||||
flagIndir = uintptr(1 << 1)
|
flagIndir = uintptr(1 << 1)
|
||||||
)
|
)
|
||||||
|
|
2
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
2
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
|
@ -180,7 +180,7 @@ func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||||
w.Write(closeParenBytes)
|
w.Write(closeParenBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
|
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
|
||||||
// prefix to Writer w.
|
// prefix to Writer w.
|
||||||
func printHexPtr(w io.Writer, p uintptr) {
|
func printHexPtr(w io.Writer, p uintptr) {
|
||||||
// Null pointer.
|
// Null pointer.
|
||||||
|
|
10
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
10
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
|
@ -35,16 +35,16 @@ var (
|
||||||
|
|
||||||
// cCharRE is a regular expression that matches a cgo char.
|
// cCharRE is a regular expression that matches a cgo char.
|
||||||
// It is used to detect character arrays to hexdump them.
|
// It is used to detect character arrays to hexdump them.
|
||||||
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
|
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
|
||||||
|
|
||||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||||
// char. It is used to detect unsigned character arrays to hexdump
|
// char. It is used to detect unsigned character arrays to hexdump
|
||||||
// them.
|
// them.
|
||||||
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
|
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
|
||||||
|
|
||||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||||
// It is used to detect uint8_t arrays to hexdump them.
|
// It is used to detect uint8_t arrays to hexdump them.
|
||||||
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
|
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
|
||||||
)
|
)
|
||||||
|
|
||||||
// dumpState contains information about the state of a dump operation.
|
// dumpState contains information about the state of a dump operation.
|
||||||
|
@ -143,10 +143,10 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||||
// Display dereferenced value.
|
// Display dereferenced value.
|
||||||
d.w.Write(openParenBytes)
|
d.w.Write(openParenBytes)
|
||||||
switch {
|
switch {
|
||||||
case nilFound:
|
case nilFound == true:
|
||||||
d.w.Write(nilAngleBytes)
|
d.w.Write(nilAngleBytes)
|
||||||
|
|
||||||
case cycleFound:
|
case cycleFound == true:
|
||||||
d.w.Write(circularBytes)
|
d.w.Write(circularBytes)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
4
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
4
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
|
@ -182,10 +182,10 @@ func (f *formatState) formatPtr(v reflect.Value) {
|
||||||
|
|
||||||
// Display dereferenced value.
|
// Display dereferenced value.
|
||||||
switch {
|
switch {
|
||||||
case nilFound:
|
case nilFound == true:
|
||||||
f.fs.Write(nilAngleBytes)
|
f.fs.Write(nilAngleBytes)
|
||||||
|
|
||||||
case cycleFound:
|
case cycleFound == true:
|
||||||
f.fs.Write(circularShortBytes)
|
f.fs.Write(circularShortBytes)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
175
vendor/github.com/garyburd/redigo/LICENSE
generated
vendored
175
vendor/github.com/garyburd/redigo/LICENSE
generated
vendored
|
@ -1,175 +0,0 @@
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
54
vendor/github.com/garyburd/redigo/internal/commandinfo.go
generated
vendored
54
vendor/github.com/garyburd/redigo/internal/commandinfo.go
generated
vendored
|
@ -1,54 +0,0 @@
|
||||||
// Copyright 2014 Gary Burd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package internal // import "github.com/garyburd/redigo/internal"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
WatchState = 1 << iota
|
|
||||||
MultiState
|
|
||||||
SubscribeState
|
|
||||||
MonitorState
|
|
||||||
)
|
|
||||||
|
|
||||||
type CommandInfo struct {
|
|
||||||
Set, Clear int
|
|
||||||
}
|
|
||||||
|
|
||||||
var commandInfos = map[string]CommandInfo{
|
|
||||||
"WATCH": {Set: WatchState},
|
|
||||||
"UNWATCH": {Clear: WatchState},
|
|
||||||
"MULTI": {Set: MultiState},
|
|
||||||
"EXEC": {Clear: WatchState | MultiState},
|
|
||||||
"DISCARD": {Clear: WatchState | MultiState},
|
|
||||||
"PSUBSCRIBE": {Set: SubscribeState},
|
|
||||||
"SUBSCRIBE": {Set: SubscribeState},
|
|
||||||
"MONITOR": {Set: MonitorState},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
for n, ci := range commandInfos {
|
|
||||||
commandInfos[strings.ToLower(n)] = ci
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func LookupCommandInfo(commandName string) CommandInfo {
|
|
||||||
if ci, ok := commandInfos[commandName]; ok {
|
|
||||||
return ci
|
|
||||||
}
|
|
||||||
return commandInfos[strings.ToUpper(commandName)]
|
|
||||||
}
|
|
651
vendor/github.com/garyburd/redigo/redis/conn.go
generated
vendored
651
vendor/github.com/garyburd/redigo/redis/conn.go
generated
vendored
|
@ -1,651 +0,0 @@
|
||||||
// Copyright 2012 Gary Burd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// conn is the low-level implementation of Conn
|
|
||||||
type conn struct {
|
|
||||||
// Shared
|
|
||||||
mu sync.Mutex
|
|
||||||
pending int
|
|
||||||
err error
|
|
||||||
conn net.Conn
|
|
||||||
|
|
||||||
// Read
|
|
||||||
readTimeout time.Duration
|
|
||||||
br *bufio.Reader
|
|
||||||
|
|
||||||
// Write
|
|
||||||
writeTimeout time.Duration
|
|
||||||
bw *bufio.Writer
|
|
||||||
|
|
||||||
// Scratch space for formatting argument length.
|
|
||||||
// '*' or '$', length, "\r\n"
|
|
||||||
lenScratch [32]byte
|
|
||||||
|
|
||||||
// Scratch space for formatting integers and floats.
|
|
||||||
numScratch [40]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialTimeout acts like Dial but takes timeouts for establishing the
|
|
||||||
// connection to the server, writing a command and reading a reply.
|
|
||||||
//
|
|
||||||
// Deprecated: Use Dial with options instead.
|
|
||||||
func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) {
|
|
||||||
return Dial(network, address,
|
|
||||||
DialConnectTimeout(connectTimeout),
|
|
||||||
DialReadTimeout(readTimeout),
|
|
||||||
DialWriteTimeout(writeTimeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialOption specifies an option for dialing a Redis server.
|
|
||||||
type DialOption struct {
|
|
||||||
f func(*dialOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dialOptions struct {
|
|
||||||
readTimeout time.Duration
|
|
||||||
writeTimeout time.Duration
|
|
||||||
dialer *net.Dialer
|
|
||||||
dial func(network, addr string) (net.Conn, error)
|
|
||||||
db int
|
|
||||||
password string
|
|
||||||
useTLS bool
|
|
||||||
skipVerify bool
|
|
||||||
tlsConfig *tls.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialReadTimeout specifies the timeout for reading a single command reply.
|
|
||||||
func DialReadTimeout(d time.Duration) DialOption {
|
|
||||||
return DialOption{func(do *dialOptions) {
|
|
||||||
do.readTimeout = d
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialWriteTimeout specifies the timeout for writing a single command.
|
|
||||||
func DialWriteTimeout(d time.Duration) DialOption {
|
|
||||||
return DialOption{func(do *dialOptions) {
|
|
||||||
do.writeTimeout = d
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialConnectTimeout specifies the timeout for connecting to the Redis server when
|
|
||||||
// no DialNetDial option is specified.
|
|
||||||
func DialConnectTimeout(d time.Duration) DialOption {
|
|
||||||
return DialOption{func(do *dialOptions) {
|
|
||||||
do.dialer.Timeout = d
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server
|
|
||||||
// when no DialNetDial option is specified.
|
|
||||||
// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then
|
|
||||||
// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected.
|
|
||||||
func DialKeepAlive(d time.Duration) DialOption {
|
|
||||||
return DialOption{func(do *dialOptions) {
|
|
||||||
do.dialer.KeepAlive = d
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialNetDial specifies a custom dial function for creating TCP
|
|
||||||
// connections, otherwise a net.Dialer customized via the other options is used.
|
|
||||||
// DialNetDial overrides DialConnectTimeout and DialKeepAlive.
|
|
||||||
func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
|
|
||||||
return DialOption{func(do *dialOptions) {
|
|
||||||
do.dial = dial
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialDatabase specifies the database to select when dialing a connection.
|
|
||||||
func DialDatabase(db int) DialOption {
|
|
||||||
return DialOption{func(do *dialOptions) {
|
|
||||||
do.db = db
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialPassword specifies the password to use when connecting to
|
|
||||||
// the Redis server.
|
|
||||||
func DialPassword(password string) DialOption {
|
|
||||||
return DialOption{func(do *dialOptions) {
|
|
||||||
do.password = password
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialTLSConfig specifies the config to use when a TLS connection is dialed.
|
|
||||||
// Has no effect when not dialing a TLS connection.
|
|
||||||
func DialTLSConfig(c *tls.Config) DialOption {
|
|
||||||
return DialOption{func(do *dialOptions) {
|
|
||||||
do.tlsConfig = c
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialTLSSkipVerify disables server name verification when connecting over
|
|
||||||
// TLS. Has no effect when not dialing a TLS connection.
|
|
||||||
func DialTLSSkipVerify(skip bool) DialOption {
|
|
||||||
return DialOption{func(do *dialOptions) {
|
|
||||||
do.skipVerify = skip
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialUseTLS specifies whether TLS should be used when connecting to the
|
|
||||||
// server. This option is ignore by DialURL.
|
|
||||||
func DialUseTLS(useTLS bool) DialOption {
|
|
||||||
return DialOption{func(do *dialOptions) {
|
|
||||||
do.useTLS = useTLS
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial connects to the Redis server at the given network and
|
|
||||||
// address using the specified options.
|
|
||||||
func Dial(network, address string, options ...DialOption) (Conn, error) {
|
|
||||||
do := dialOptions{
|
|
||||||
dialer: &net.Dialer{
|
|
||||||
KeepAlive: time.Minute * 5,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, option := range options {
|
|
||||||
option.f(&do)
|
|
||||||
}
|
|
||||||
if do.dial == nil {
|
|
||||||
do.dial = do.dialer.Dial
|
|
||||||
}
|
|
||||||
|
|
||||||
netConn, err := do.dial(network, address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if do.useTLS {
|
|
||||||
tlsConfig := cloneTLSClientConfig(do.tlsConfig, do.skipVerify)
|
|
||||||
if tlsConfig.ServerName == "" {
|
|
||||||
host, _, err := net.SplitHostPort(address)
|
|
||||||
if err != nil {
|
|
||||||
netConn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig.ServerName = host
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConn := tls.Client(netConn, tlsConfig)
|
|
||||||
if err := tlsConn.Handshake(); err != nil {
|
|
||||||
netConn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
netConn = tlsConn
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &conn{
|
|
||||||
conn: netConn,
|
|
||||||
bw: bufio.NewWriter(netConn),
|
|
||||||
br: bufio.NewReader(netConn),
|
|
||||||
readTimeout: do.readTimeout,
|
|
||||||
writeTimeout: do.writeTimeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
if do.password != "" {
|
|
||||||
if _, err := c.Do("AUTH", do.password); err != nil {
|
|
||||||
netConn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if do.db != 0 {
|
|
||||||
if _, err := c.Do("SELECT", do.db); err != nil {
|
|
||||||
netConn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`)
|
|
||||||
|
|
||||||
// DialURL connects to a Redis server at the given URL using the Redis
|
|
||||||
// URI scheme. URLs should follow the draft IANA specification for the
|
|
||||||
// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis).
|
|
||||||
func DialURL(rawurl string, options ...DialOption) (Conn, error) {
|
|
||||||
u, err := url.Parse(rawurl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Scheme != "redis" && u.Scheme != "rediss" {
|
|
||||||
return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
// As per the IANA draft spec, the host defaults to localhost and
|
|
||||||
// the port defaults to 6379.
|
|
||||||
host, port, err := net.SplitHostPort(u.Host)
|
|
||||||
if err != nil {
|
|
||||||
// assume port is missing
|
|
||||||
host = u.Host
|
|
||||||
port = "6379"
|
|
||||||
}
|
|
||||||
if host == "" {
|
|
||||||
host = "localhost"
|
|
||||||
}
|
|
||||||
address := net.JoinHostPort(host, port)
|
|
||||||
|
|
||||||
if u.User != nil {
|
|
||||||
password, isSet := u.User.Password()
|
|
||||||
if isSet {
|
|
||||||
options = append(options, DialPassword(password))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match := pathDBRegexp.FindStringSubmatch(u.Path)
|
|
||||||
if len(match) == 2 {
|
|
||||||
db := 0
|
|
||||||
if len(match[1]) > 0 {
|
|
||||||
db, err = strconv.Atoi(match[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if db != 0 {
|
|
||||||
options = append(options, DialDatabase(db))
|
|
||||||
}
|
|
||||||
} else if u.Path != "" {
|
|
||||||
return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
options = append(options, DialUseTLS(u.Scheme == "rediss"))
|
|
||||||
|
|
||||||
return Dial("tcp", address, options...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConn returns a new Redigo connection for the given net connection.
|
|
||||||
func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn {
|
|
||||||
return &conn{
|
|
||||||
conn: netConn,
|
|
||||||
bw: bufio.NewWriter(netConn),
|
|
||||||
br: bufio.NewReader(netConn),
|
|
||||||
readTimeout: readTimeout,
|
|
||||||
writeTimeout: writeTimeout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) Close() error {
|
|
||||||
c.mu.Lock()
|
|
||||||
err := c.err
|
|
||||||
if c.err == nil {
|
|
||||||
c.err = errors.New("redigo: closed")
|
|
||||||
err = c.conn.Close()
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) fatal(err error) error {
|
|
||||||
c.mu.Lock()
|
|
||||||
if c.err == nil {
|
|
||||||
c.err = err
|
|
||||||
// Close connection to force errors on subsequent calls and to unblock
|
|
||||||
// other reader or writer.
|
|
||||||
c.conn.Close()
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) Err() error {
|
|
||||||
c.mu.Lock()
|
|
||||||
err := c.err
|
|
||||||
c.mu.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) writeLen(prefix byte, n int) error {
|
|
||||||
c.lenScratch[len(c.lenScratch)-1] = '\n'
|
|
||||||
c.lenScratch[len(c.lenScratch)-2] = '\r'
|
|
||||||
i := len(c.lenScratch) - 3
|
|
||||||
for {
|
|
||||||
c.lenScratch[i] = byte('0' + n%10)
|
|
||||||
i -= 1
|
|
||||||
n = n / 10
|
|
||||||
if n == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.lenScratch[i] = prefix
|
|
||||||
_, err := c.bw.Write(c.lenScratch[i:])
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) writeString(s string) error {
|
|
||||||
c.writeLen('$', len(s))
|
|
||||||
c.bw.WriteString(s)
|
|
||||||
_, err := c.bw.WriteString("\r\n")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) writeBytes(p []byte) error {
|
|
||||||
c.writeLen('$', len(p))
|
|
||||||
c.bw.Write(p)
|
|
||||||
_, err := c.bw.WriteString("\r\n")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) writeInt64(n int64) error {
|
|
||||||
return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) writeFloat64(n float64) error {
|
|
||||||
return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) writeCommand(cmd string, args []interface{}) error {
|
|
||||||
c.writeLen('*', 1+len(args))
|
|
||||||
if err := c.writeString(cmd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, arg := range args {
|
|
||||||
if err := c.writeArg(arg, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) {
|
|
||||||
switch arg := arg.(type) {
|
|
||||||
case string:
|
|
||||||
return c.writeString(arg)
|
|
||||||
case []byte:
|
|
||||||
return c.writeBytes(arg)
|
|
||||||
case int:
|
|
||||||
return c.writeInt64(int64(arg))
|
|
||||||
case int64:
|
|
||||||
return c.writeInt64(arg)
|
|
||||||
case float64:
|
|
||||||
return c.writeFloat64(arg)
|
|
||||||
case bool:
|
|
||||||
if arg {
|
|
||||||
return c.writeString("1")
|
|
||||||
} else {
|
|
||||||
return c.writeString("0")
|
|
||||||
}
|
|
||||||
case nil:
|
|
||||||
return c.writeString("")
|
|
||||||
case Argument:
|
|
||||||
if argumentTypeOK {
|
|
||||||
return c.writeArg(arg.RedisArg(), false)
|
|
||||||
}
|
|
||||||
// See comment in default clause below.
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fmt.Fprint(&buf, arg)
|
|
||||||
return c.writeBytes(buf.Bytes())
|
|
||||||
default:
|
|
||||||
// This default clause is intended to handle builtin numeric types.
|
|
||||||
// The function should return an error for other types, but this is not
|
|
||||||
// done for compatibility with previous versions of the package.
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fmt.Fprint(&buf, arg)
|
|
||||||
return c.writeBytes(buf.Bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type protocolError string
|
|
||||||
|
|
||||||
func (pe protocolError) Error() string {
|
|
||||||
return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) readLine() ([]byte, error) {
|
|
||||||
p, err := c.br.ReadSlice('\n')
|
|
||||||
if err == bufio.ErrBufferFull {
|
|
||||||
return nil, protocolError("long response line")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
i := len(p) - 2
|
|
||||||
if i < 0 || p[i] != '\r' {
|
|
||||||
return nil, protocolError("bad response line terminator")
|
|
||||||
}
|
|
||||||
return p[:i], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseLen parses bulk string and array lengths.
|
|
||||||
func parseLen(p []byte) (int, error) {
|
|
||||||
if len(p) == 0 {
|
|
||||||
return -1, protocolError("malformed length")
|
|
||||||
}
|
|
||||||
|
|
||||||
if p[0] == '-' && len(p) == 2 && p[1] == '1' {
|
|
||||||
// handle $-1 and $-1 null replies.
|
|
||||||
return -1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var n int
|
|
||||||
for _, b := range p {
|
|
||||||
n *= 10
|
|
||||||
if b < '0' || b > '9' {
|
|
||||||
return -1, protocolError("illegal bytes in length")
|
|
||||||
}
|
|
||||||
n += int(b - '0')
|
|
||||||
}
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseInt parses an integer reply.
|
|
||||||
func parseInt(p []byte) (interface{}, error) {
|
|
||||||
if len(p) == 0 {
|
|
||||||
return 0, protocolError("malformed integer")
|
|
||||||
}
|
|
||||||
|
|
||||||
var negate bool
|
|
||||||
if p[0] == '-' {
|
|
||||||
negate = true
|
|
||||||
p = p[1:]
|
|
||||||
if len(p) == 0 {
|
|
||||||
return 0, protocolError("malformed integer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var n int64
|
|
||||||
for _, b := range p {
|
|
||||||
n *= 10
|
|
||||||
if b < '0' || b > '9' {
|
|
||||||
return 0, protocolError("illegal bytes in length")
|
|
||||||
}
|
|
||||||
n += int64(b - '0')
|
|
||||||
}
|
|
||||||
|
|
||||||
if negate {
|
|
||||||
n = -n
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
okReply interface{} = "OK"
|
|
||||||
pongReply interface{} = "PONG"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *conn) readReply() (interface{}, error) {
|
|
||||||
line, err := c.readLine()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(line) == 0 {
|
|
||||||
return nil, protocolError("short response line")
|
|
||||||
}
|
|
||||||
switch line[0] {
|
|
||||||
case '+':
|
|
||||||
switch {
|
|
||||||
case len(line) == 3 && line[1] == 'O' && line[2] == 'K':
|
|
||||||
// Avoid allocation for frequent "+OK" response.
|
|
||||||
return okReply, nil
|
|
||||||
case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G':
|
|
||||||
// Avoid allocation in PING command benchmarks :)
|
|
||||||
return pongReply, nil
|
|
||||||
default:
|
|
||||||
return string(line[1:]), nil
|
|
||||||
}
|
|
||||||
case '-':
|
|
||||||
return Error(string(line[1:])), nil
|
|
||||||
case ':':
|
|
||||||
return parseInt(line[1:])
|
|
||||||
case '$':
|
|
||||||
n, err := parseLen(line[1:])
|
|
||||||
if n < 0 || err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p := make([]byte, n)
|
|
||||||
_, err = io.ReadFull(c.br, p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if line, err := c.readLine(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if len(line) != 0 {
|
|
||||||
return nil, protocolError("bad bulk string format")
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
case '*':
|
|
||||||
n, err := parseLen(line[1:])
|
|
||||||
if n < 0 || err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r := make([]interface{}, n)
|
|
||||||
for i := range r {
|
|
||||||
r[i], err = c.readReply()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
return nil, protocolError("unexpected response line")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) Send(cmd string, args ...interface{}) error {
|
|
||||||
c.mu.Lock()
|
|
||||||
c.pending += 1
|
|
||||||
c.mu.Unlock()
|
|
||||||
if c.writeTimeout != 0 {
|
|
||||||
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
|
||||||
}
|
|
||||||
if err := c.writeCommand(cmd, args); err != nil {
|
|
||||||
return c.fatal(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) Flush() error {
|
|
||||||
if c.writeTimeout != 0 {
|
|
||||||
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
|
||||||
}
|
|
||||||
if err := c.bw.Flush(); err != nil {
|
|
||||||
return c.fatal(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) Receive() (reply interface{}, err error) {
|
|
||||||
if c.readTimeout != 0 {
|
|
||||||
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
|
|
||||||
}
|
|
||||||
if reply, err = c.readReply(); err != nil {
|
|
||||||
return nil, c.fatal(err)
|
|
||||||
}
|
|
||||||
// When using pub/sub, the number of receives can be greater than the
|
|
||||||
// number of sends. To enable normal use of the connection after
|
|
||||||
// unsubscribing from all channels, we do not decrement pending to a
|
|
||||||
// negative value.
|
|
||||||
//
|
|
||||||
// The pending field is decremented after the reply is read to handle the
|
|
||||||
// case where Receive is called before Send.
|
|
||||||
c.mu.Lock()
|
|
||||||
if c.pending > 0 {
|
|
||||||
c.pending -= 1
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
if err, ok := reply.(Error); ok {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
pending := c.pending
|
|
||||||
c.pending = 0
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
if cmd == "" && pending == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.writeTimeout != 0 {
|
|
||||||
c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd != "" {
|
|
||||||
if err := c.writeCommand(cmd, args); err != nil {
|
|
||||||
return nil, c.fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.bw.Flush(); err != nil {
|
|
||||||
return nil, c.fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.readTimeout != 0 {
|
|
||||||
c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd == "" {
|
|
||||||
reply := make([]interface{}, pending)
|
|
||||||
for i := range reply {
|
|
||||||
r, e := c.readReply()
|
|
||||||
if e != nil {
|
|
||||||
return nil, c.fatal(e)
|
|
||||||
}
|
|
||||||
reply[i] = r
|
|
||||||
}
|
|
||||||
return reply, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var reply interface{}
|
|
||||||
for i := 0; i <= pending; i++ {
|
|
||||||
var e error
|
|
||||||
if reply, e = c.readReply(); e != nil {
|
|
||||||
return nil, c.fatal(e)
|
|
||||||
}
|
|
||||||
if e, ok := reply.(Error); ok && err == nil {
|
|
||||||
err = e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reply, err
|
|
||||||
}
|
|
177
vendor/github.com/garyburd/redigo/redis/doc.go
generated
vendored
177
vendor/github.com/garyburd/redigo/redis/doc.go
generated
vendored
|
@ -1,177 +0,0 @@
|
||||||
// Copyright 2012 Gary Burd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
// Package redis is a client for the Redis database.
|
|
||||||
//
|
|
||||||
// The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more
|
|
||||||
// documentation about this package.
|
|
||||||
//
|
|
||||||
// Connections
|
|
||||||
//
|
|
||||||
// The Conn interface is the primary interface for working with Redis.
|
|
||||||
// Applications create connections by calling the Dial, DialWithTimeout or
|
|
||||||
// NewConn functions. In the future, functions will be added for creating
|
|
||||||
// sharded and other types of connections.
|
|
||||||
//
|
|
||||||
// The application must call the connection Close method when the application
|
|
||||||
// is done with the connection.
|
|
||||||
//
|
|
||||||
// Executing Commands
|
|
||||||
//
|
|
||||||
// The Conn interface has a generic method for executing Redis commands:
|
|
||||||
//
|
|
||||||
// Do(commandName string, args ...interface{}) (reply interface{}, err error)
|
|
||||||
//
|
|
||||||
// The Redis command reference (http://redis.io/commands) lists the available
|
|
||||||
// commands. An example of using the Redis APPEND command is:
|
|
||||||
//
|
|
||||||
// n, err := conn.Do("APPEND", "key", "value")
|
|
||||||
//
|
|
||||||
// The Do method converts command arguments to bulk strings for transmission
|
|
||||||
// to the server as follows:
|
|
||||||
//
|
|
||||||
// Go Type Conversion
|
|
||||||
// []byte Sent as is
|
|
||||||
// string Sent as is
|
|
||||||
// int, int64 strconv.FormatInt(v)
|
|
||||||
// float64 strconv.FormatFloat(v, 'g', -1, 64)
|
|
||||||
// bool true -> "1", false -> "0"
|
|
||||||
// nil ""
|
|
||||||
// all other types fmt.Fprint(w, v)
|
|
||||||
//
|
|
||||||
// Redis command reply types are represented using the following Go types:
|
|
||||||
//
|
|
||||||
// Redis type Go type
|
|
||||||
// error redis.Error
|
|
||||||
// integer int64
|
|
||||||
// simple string string
|
|
||||||
// bulk string []byte or nil if value not present.
|
|
||||||
// array []interface{} or nil if value not present.
|
|
||||||
//
|
|
||||||
// Use type assertions or the reply helper functions to convert from
|
|
||||||
// interface{} to the specific Go type for the command result.
|
|
||||||
//
|
|
||||||
// Pipelining
|
|
||||||
//
|
|
||||||
// Connections support pipelining using the Send, Flush and Receive methods.
|
|
||||||
//
|
|
||||||
// Send(commandName string, args ...interface{}) error
|
|
||||||
// Flush() error
|
|
||||||
// Receive() (reply interface{}, err error)
|
|
||||||
//
|
|
||||||
// Send writes the command to the connection's output buffer. Flush flushes the
|
|
||||||
// connection's output buffer to the server. Receive reads a single reply from
|
|
||||||
// the server. The following example shows a simple pipeline.
|
|
||||||
//
|
|
||||||
// c.Send("SET", "foo", "bar")
|
|
||||||
// c.Send("GET", "foo")
|
|
||||||
// c.Flush()
|
|
||||||
// c.Receive() // reply from SET
|
|
||||||
// v, err = c.Receive() // reply from GET
|
|
||||||
//
|
|
||||||
// The Do method combines the functionality of the Send, Flush and Receive
|
|
||||||
// methods. The Do method starts by writing the command and flushing the output
|
|
||||||
// buffer. Next, the Do method receives all pending replies including the reply
|
|
||||||
// for the command just sent by Do. If any of the received replies is an error,
|
|
||||||
// then Do returns the error. If there are no errors, then Do returns the last
|
|
||||||
// reply. If the command argument to the Do method is "", then the Do method
|
|
||||||
// will flush the output buffer and receive pending replies without sending a
|
|
||||||
// command.
|
|
||||||
//
|
|
||||||
// Use the Send and Do methods to implement pipelined transactions.
|
|
||||||
//
|
|
||||||
// c.Send("MULTI")
|
|
||||||
// c.Send("INCR", "foo")
|
|
||||||
// c.Send("INCR", "bar")
|
|
||||||
// r, err := c.Do("EXEC")
|
|
||||||
// fmt.Println(r) // prints [1, 1]
|
|
||||||
//
|
|
||||||
// Concurrency
|
|
||||||
//
|
|
||||||
// Connections support one concurrent caller to the Receive method and one
|
|
||||||
// concurrent caller to the Send and Flush methods. No other concurrency is
|
|
||||||
// supported including concurrent calls to the Do method.
|
|
||||||
//
|
|
||||||
// For full concurrent access to Redis, use the thread-safe Pool to get, use
|
|
||||||
// and release a connection from within a goroutine. Connections returned from
|
|
||||||
// a Pool have the concurrency restrictions described in the previous
|
|
||||||
// paragraph.
|
|
||||||
//
|
|
||||||
// Publish and Subscribe
|
|
||||||
//
|
|
||||||
// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers.
|
|
||||||
//
|
|
||||||
// c.Send("SUBSCRIBE", "example")
|
|
||||||
// c.Flush()
|
|
||||||
// for {
|
|
||||||
// reply, err := c.Receive()
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// // process pushed message
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// The PubSubConn type wraps a Conn with convenience methods for implementing
|
|
||||||
// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods
|
|
||||||
// send and flush a subscription management command. The receive method
|
|
||||||
// converts a pushed message to convenient types for use in a type switch.
|
|
||||||
//
|
|
||||||
// psc := redis.PubSubConn{Conn: c}
|
|
||||||
// psc.Subscribe("example")
|
|
||||||
// for {
|
|
||||||
// switch v := psc.Receive().(type) {
|
|
||||||
// case redis.Message:
|
|
||||||
// fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
|
|
||||||
// case redis.Subscription:
|
|
||||||
// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
|
|
||||||
// case error:
|
|
||||||
// return v
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Reply Helpers
|
|
||||||
//
|
|
||||||
// The Bool, Int, Bytes, String, Strings and Values functions convert a reply
|
|
||||||
// to a value of a specific type. To allow convenient wrapping of calls to the
|
|
||||||
// connection Do and Receive methods, the functions take a second argument of
|
|
||||||
// type error. If the error is non-nil, then the helper function returns the
|
|
||||||
// error. If the error is nil, the function converts the reply to the specified
|
|
||||||
// type:
|
|
||||||
//
|
|
||||||
// exists, err := redis.Bool(c.Do("EXISTS", "foo"))
|
|
||||||
// if err != nil {
|
|
||||||
// // handle error return from c.Do or type conversion error.
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// The Scan function converts elements of a array reply to Go types:
|
|
||||||
//
|
|
||||||
// var value1 int
|
|
||||||
// var value2 string
|
|
||||||
// reply, err := redis.Values(c.Do("MGET", "key1", "key2"))
|
|
||||||
// if err != nil {
|
|
||||||
// // handle error
|
|
||||||
// }
|
|
||||||
// if _, err := redis.Scan(reply, &value1, &value2); err != nil {
|
|
||||||
// // handle error
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Errors
|
|
||||||
//
|
|
||||||
// Connection methods return error replies from the server as type redis.Error.
|
|
||||||
//
|
|
||||||
// Call the connection Err() method to determine if the connection encountered
|
|
||||||
// non-recoverable error such as a network error or protocol parsing error. If
|
|
||||||
// Err() returns a non-nil value, then the connection is not usable and should
|
|
||||||
// be closed.
|
|
||||||
package redis // import "github.com/garyburd/redigo/redis"
|
|
33
vendor/github.com/garyburd/redigo/redis/go17.go
generated
vendored
33
vendor/github.com/garyburd/redigo/redis/go17.go
generated
vendored
|
@ -1,33 +0,0 @@
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package redis
|
|
||||||
|
|
||||||
import "crypto/tls"
|
|
||||||
|
|
||||||
// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case
|
|
||||||
func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config {
|
|
||||||
if cfg == nil {
|
|
||||||
return &tls.Config{InsecureSkipVerify: skipVerify}
|
|
||||||
}
|
|
||||||
return &tls.Config{
|
|
||||||
Rand: cfg.Rand,
|
|
||||||
Time: cfg.Time,
|
|
||||||
Certificates: cfg.Certificates,
|
|
||||||
NameToCertificate: cfg.NameToCertificate,
|
|
||||||
GetCertificate: cfg.GetCertificate,
|
|
||||||
RootCAs: cfg.RootCAs,
|
|
||||||
NextProtos: cfg.NextProtos,
|
|
||||||
ServerName: cfg.ServerName,
|
|
||||||
ClientAuth: cfg.ClientAuth,
|
|
||||||
ClientCAs: cfg.ClientCAs,
|
|
||||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
|
||||||
CipherSuites: cfg.CipherSuites,
|
|
||||||
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
|
|
||||||
ClientSessionCache: cfg.ClientSessionCache,
|
|
||||||
MinVersion: cfg.MinVersion,
|
|
||||||
MaxVersion: cfg.MaxVersion,
|
|
||||||
CurvePreferences: cfg.CurvePreferences,
|
|
||||||
DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled,
|
|
||||||
Renegotiation: cfg.Renegotiation,
|
|
||||||
}
|
|
||||||
}
|
|
117
vendor/github.com/garyburd/redigo/redis/log.go
generated
vendored
117
vendor/github.com/garyburd/redigo/redis/log.go
generated
vendored
|
@ -1,117 +0,0 @@
|
||||||
// Copyright 2012 Gary Burd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewLoggingConn returns a logging wrapper around a connection.
|
|
||||||
func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn {
|
|
||||||
if prefix != "" {
|
|
||||||
prefix = prefix + "."
|
|
||||||
}
|
|
||||||
return &loggingConn{conn, logger, prefix}
|
|
||||||
}
|
|
||||||
|
|
||||||
type loggingConn struct {
|
|
||||||
Conn
|
|
||||||
logger *log.Logger
|
|
||||||
prefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *loggingConn) Close() error {
|
|
||||||
err := c.Conn.Close()
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err)
|
|
||||||
c.logger.Output(2, buf.String())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) {
|
|
||||||
const chop = 32
|
|
||||||
switch v := v.(type) {
|
|
||||||
case []byte:
|
|
||||||
if len(v) > chop {
|
|
||||||
fmt.Fprintf(buf, "%q...", v[:chop])
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(buf, "%q", v)
|
|
||||||
}
|
|
||||||
case string:
|
|
||||||
if len(v) > chop {
|
|
||||||
fmt.Fprintf(buf, "%q...", v[:chop])
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(buf, "%q", v)
|
|
||||||
}
|
|
||||||
case []interface{}:
|
|
||||||
if len(v) == 0 {
|
|
||||||
buf.WriteString("[]")
|
|
||||||
} else {
|
|
||||||
sep := "["
|
|
||||||
fin := "]"
|
|
||||||
if len(v) > chop {
|
|
||||||
v = v[:chop]
|
|
||||||
fin = "...]"
|
|
||||||
}
|
|
||||||
for _, vv := range v {
|
|
||||||
buf.WriteString(sep)
|
|
||||||
c.printValue(buf, vv)
|
|
||||||
sep = ", "
|
|
||||||
}
|
|
||||||
buf.WriteString(fin)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
fmt.Fprint(buf, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fmt.Fprintf(&buf, "%s%s(", c.prefix, method)
|
|
||||||
if method != "Receive" {
|
|
||||||
buf.WriteString(commandName)
|
|
||||||
for _, arg := range args {
|
|
||||||
buf.WriteString(", ")
|
|
||||||
c.printValue(&buf, arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.WriteString(") -> (")
|
|
||||||
if method != "Send" {
|
|
||||||
c.printValue(&buf, reply)
|
|
||||||
buf.WriteString(", ")
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&buf, "%v)", err)
|
|
||||||
c.logger.Output(3, buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) {
|
|
||||||
reply, err := c.Conn.Do(commandName, args...)
|
|
||||||
c.print("Do", commandName, args, reply, err)
|
|
||||||
return reply, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *loggingConn) Send(commandName string, args ...interface{}) error {
|
|
||||||
err := c.Conn.Send(commandName, args...)
|
|
||||||
c.print("Send", commandName, args, nil, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *loggingConn) Receive() (interface{}, error) {
|
|
||||||
reply, err := c.Conn.Receive()
|
|
||||||
c.print("Receive", "", nil, reply, err)
|
|
||||||
return reply, err
|
|
||||||
}
|
|
442
vendor/github.com/garyburd/redigo/redis/pool.go
generated
vendored
442
vendor/github.com/garyburd/redigo/redis/pool.go
generated
vendored
|
@ -1,442 +0,0 @@
|
||||||
// Copyright 2012 Gary Burd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"container/list"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha1"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/garyburd/redigo/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
var nowFunc = time.Now // for testing
|
|
||||||
|
|
||||||
// ErrPoolExhausted is returned from a pool connection method (Do, Send,
|
|
||||||
// Receive, Flush, Err) when the maximum number of database connections in the
|
|
||||||
// pool has been reached.
|
|
||||||
var ErrPoolExhausted = errors.New("redigo: connection pool exhausted")
|
|
||||||
|
|
||||||
var (
|
|
||||||
errPoolClosed = errors.New("redigo: connection pool closed")
|
|
||||||
errConnClosed = errors.New("redigo: connection closed")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Pool maintains a pool of connections. The application calls the Get method
|
|
||||||
// to get a connection from the pool and the connection's Close method to
|
|
||||||
// return the connection's resources to the pool.
|
|
||||||
//
|
|
||||||
// The following example shows how to use a pool in a web application. The
|
|
||||||
// application creates a pool at application startup and makes it available to
|
|
||||||
// request handlers using a package level variable. The pool configuration used
|
|
||||||
// here is an example, not a recommendation.
|
|
||||||
//
|
|
||||||
// func newPool(addr string) *redis.Pool {
|
|
||||||
// return &redis.Pool{
|
|
||||||
// MaxIdle: 3,
|
|
||||||
// IdleTimeout: 240 * time.Second,
|
|
||||||
// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) },
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var (
|
|
||||||
// pool *redis.Pool
|
|
||||||
// redisServer = flag.String("redisServer", ":6379", "")
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// func main() {
|
|
||||||
// flag.Parse()
|
|
||||||
// pool = newPool(*redisServer)
|
|
||||||
// ...
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// A request handler gets a connection from the pool and closes the connection
|
|
||||||
// when the handler is done:
|
|
||||||
//
|
|
||||||
// func serveHome(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// conn := pool.Get()
|
|
||||||
// defer conn.Close()
|
|
||||||
// ...
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Use the Dial function to authenticate connections with the AUTH command or
|
|
||||||
// select a database with the SELECT command:
|
|
||||||
//
|
|
||||||
// pool := &redis.Pool{
|
|
||||||
// // Other pool configuration not shown in this example.
|
|
||||||
// Dial: func () (redis.Conn, error) {
|
|
||||||
// c, err := redis.Dial("tcp", server)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// if _, err := c.Do("AUTH", password); err != nil {
|
|
||||||
// c.Close()
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// if _, err := c.Do("SELECT", db); err != nil {
|
|
||||||
// c.Close()
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// return c, nil
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Use the TestOnBorrow function to check the health of an idle connection
|
|
||||||
// before the connection is returned to the application. This example PINGs
|
|
||||||
// connections that have been idle more than a minute:
|
|
||||||
//
|
|
||||||
// pool := &redis.Pool{
|
|
||||||
// // Other pool configuration not shown in this example.
|
|
||||||
// TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
|
||||||
// if time.Since(t) < time.Minute {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
// _, err := c.Do("PING")
|
|
||||||
// return err
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
type Pool struct {
|
|
||||||
// Dial is an application supplied function for creating and configuring a
|
|
||||||
// connection.
|
|
||||||
//
|
|
||||||
// The connection returned from Dial must not be in a special state
|
|
||||||
// (subscribed to pubsub channel, transaction started, ...).
|
|
||||||
Dial func() (Conn, error)
|
|
||||||
|
|
||||||
// TestOnBorrow is an optional application supplied function for checking
|
|
||||||
// the health of an idle connection before the connection is used again by
|
|
||||||
// the application. Argument t is the time that the connection was returned
|
|
||||||
// to the pool. If the function returns an error, then the connection is
|
|
||||||
// closed.
|
|
||||||
TestOnBorrow func(c Conn, t time.Time) error
|
|
||||||
|
|
||||||
// Maximum number of idle connections in the pool.
|
|
||||||
MaxIdle int
|
|
||||||
|
|
||||||
// Maximum number of connections allocated by the pool at a given time.
|
|
||||||
// When zero, there is no limit on the number of connections in the pool.
|
|
||||||
MaxActive int
|
|
||||||
|
|
||||||
// Close connections after remaining idle for this duration. If the value
|
|
||||||
// is zero, then idle connections are not closed. Applications should set
|
|
||||||
// the timeout to a value less than the server's timeout.
|
|
||||||
IdleTimeout time.Duration
|
|
||||||
|
|
||||||
// If Wait is true and the pool is at the MaxActive limit, then Get() waits
|
|
||||||
// for a connection to be returned to the pool before returning.
|
|
||||||
Wait bool
|
|
||||||
|
|
||||||
// mu protects fields defined below.
|
|
||||||
mu sync.Mutex
|
|
||||||
cond *sync.Cond
|
|
||||||
closed bool
|
|
||||||
active int
|
|
||||||
|
|
||||||
// Stack of idleConn with most recently used at the front.
|
|
||||||
idle list.List
|
|
||||||
}
|
|
||||||
|
|
||||||
type idleConn struct {
|
|
||||||
c Conn
|
|
||||||
t time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPool creates a new pool.
|
|
||||||
//
|
|
||||||
// Deprecated: Initialize the Pool directory as shown in the example.
|
|
||||||
func NewPool(newFn func() (Conn, error), maxIdle int) *Pool {
|
|
||||||
return &Pool{Dial: newFn, MaxIdle: maxIdle}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get gets a connection. The application must close the returned connection.
|
|
||||||
// This method always returns a valid connection so that applications can defer
|
|
||||||
// error handling to the first use of the connection. If there is an error
|
|
||||||
// getting an underlying connection, then the connection Err, Do, Send, Flush
|
|
||||||
// and Receive methods return that error.
|
|
||||||
func (p *Pool) Get() Conn {
|
|
||||||
c, err := p.get()
|
|
||||||
if err != nil {
|
|
||||||
return errorConnection{err}
|
|
||||||
}
|
|
||||||
return &pooledConnection{p: p, c: c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PoolStats contains pool statistics.
|
|
||||||
type PoolStats struct {
|
|
||||||
// ActiveCount is the number of connections in the pool. The count includes idle connections and connections in use.
|
|
||||||
ActiveCount int
|
|
||||||
// IdleCount is the number of idle connections in the pool.
|
|
||||||
IdleCount int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stats returns pool's statistics.
|
|
||||||
func (p *Pool) Stats() PoolStats {
|
|
||||||
p.mu.Lock()
|
|
||||||
stats := PoolStats{
|
|
||||||
ActiveCount: p.active,
|
|
||||||
IdleCount: p.idle.Len(),
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
|
||||||
|
|
||||||
return stats
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActiveCount returns the number of connections in the pool. The count includes idle connections and connections in use.
|
|
||||||
func (p *Pool) ActiveCount() int {
|
|
||||||
p.mu.Lock()
|
|
||||||
active := p.active
|
|
||||||
p.mu.Unlock()
|
|
||||||
return active
|
|
||||||
}
|
|
||||||
|
|
||||||
// IdleCount returns the number of idle connections in the pool.
|
|
||||||
func (p *Pool) IdleCount() int {
|
|
||||||
p.mu.Lock()
|
|
||||||
idle := p.idle.Len()
|
|
||||||
p.mu.Unlock()
|
|
||||||
return idle
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close releases the resources used by the pool.
|
|
||||||
func (p *Pool) Close() error {
|
|
||||||
p.mu.Lock()
|
|
||||||
idle := p.idle
|
|
||||||
p.idle.Init()
|
|
||||||
p.closed = true
|
|
||||||
p.active -= idle.Len()
|
|
||||||
if p.cond != nil {
|
|
||||||
p.cond.Broadcast()
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
|
||||||
for e := idle.Front(); e != nil; e = e.Next() {
|
|
||||||
e.Value.(idleConn).c.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// release decrements the active count and signals waiters. The caller must
|
|
||||||
// hold p.mu during the call.
|
|
||||||
func (p *Pool) release() {
|
|
||||||
p.active -= 1
|
|
||||||
if p.cond != nil {
|
|
||||||
p.cond.Signal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get prunes stale connections and returns a connection from the idle list or
|
|
||||||
// creates a new connection.
|
|
||||||
func (p *Pool) get() (Conn, error) {
|
|
||||||
p.mu.Lock()
|
|
||||||
|
|
||||||
// Prune stale connections.
|
|
||||||
|
|
||||||
if timeout := p.IdleTimeout; timeout > 0 {
|
|
||||||
for i, n := 0, p.idle.Len(); i < n; i++ {
|
|
||||||
e := p.idle.Back()
|
|
||||||
if e == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ic := e.Value.(idleConn)
|
|
||||||
if ic.t.Add(timeout).After(nowFunc()) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
p.idle.Remove(e)
|
|
||||||
p.release()
|
|
||||||
p.mu.Unlock()
|
|
||||||
ic.c.Close()
|
|
||||||
p.mu.Lock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
// Get idle connection.
|
|
||||||
|
|
||||||
for i, n := 0, p.idle.Len(); i < n; i++ {
|
|
||||||
e := p.idle.Front()
|
|
||||||
if e == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ic := e.Value.(idleConn)
|
|
||||||
p.idle.Remove(e)
|
|
||||||
test := p.TestOnBorrow
|
|
||||||
p.mu.Unlock()
|
|
||||||
if test == nil || test(ic.c, ic.t) == nil {
|
|
||||||
return ic.c, nil
|
|
||||||
}
|
|
||||||
ic.c.Close()
|
|
||||||
p.mu.Lock()
|
|
||||||
p.release()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for pool closed before dialing a new connection.
|
|
||||||
|
|
||||||
if p.closed {
|
|
||||||
p.mu.Unlock()
|
|
||||||
return nil, errors.New("redigo: get on closed pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial new connection if under limit.
|
|
||||||
|
|
||||||
if p.MaxActive == 0 || p.active < p.MaxActive {
|
|
||||||
dial := p.Dial
|
|
||||||
p.active += 1
|
|
||||||
p.mu.Unlock()
|
|
||||||
c, err := dial()
|
|
||||||
if err != nil {
|
|
||||||
p.mu.Lock()
|
|
||||||
p.release()
|
|
||||||
p.mu.Unlock()
|
|
||||||
c = nil
|
|
||||||
}
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !p.Wait {
|
|
||||||
p.mu.Unlock()
|
|
||||||
return nil, ErrPoolExhausted
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.cond == nil {
|
|
||||||
p.cond = sync.NewCond(&p.mu)
|
|
||||||
}
|
|
||||||
p.cond.Wait()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pool) put(c Conn, forceClose bool) error {
|
|
||||||
err := c.Err()
|
|
||||||
p.mu.Lock()
|
|
||||||
if !p.closed && err == nil && !forceClose {
|
|
||||||
p.idle.PushFront(idleConn{t: nowFunc(), c: c})
|
|
||||||
if p.idle.Len() > p.MaxIdle {
|
|
||||||
c = p.idle.Remove(p.idle.Back()).(idleConn).c
|
|
||||||
} else {
|
|
||||||
c = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c == nil {
|
|
||||||
if p.cond != nil {
|
|
||||||
p.cond.Signal()
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
p.release()
|
|
||||||
p.mu.Unlock()
|
|
||||||
return c.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type pooledConnection struct {
|
|
||||||
p *Pool
|
|
||||||
c Conn
|
|
||||||
state int
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
sentinel []byte
|
|
||||||
sentinelOnce sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
func initSentinel() {
|
|
||||||
p := make([]byte, 64)
|
|
||||||
if _, err := rand.Read(p); err == nil {
|
|
||||||
sentinel = p
|
|
||||||
} else {
|
|
||||||
h := sha1.New()
|
|
||||||
io.WriteString(h, "Oops, rand failed. Use time instead.")
|
|
||||||
io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10))
|
|
||||||
sentinel = h.Sum(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *pooledConnection) Close() error {
|
|
||||||
c := pc.c
|
|
||||||
if _, ok := c.(errorConnection); ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
pc.c = errorConnection{errConnClosed}
|
|
||||||
|
|
||||||
if pc.state&internal.MultiState != 0 {
|
|
||||||
c.Send("DISCARD")
|
|
||||||
pc.state &^= (internal.MultiState | internal.WatchState)
|
|
||||||
} else if pc.state&internal.WatchState != 0 {
|
|
||||||
c.Send("UNWATCH")
|
|
||||||
pc.state &^= internal.WatchState
|
|
||||||
}
|
|
||||||
if pc.state&internal.SubscribeState != 0 {
|
|
||||||
c.Send("UNSUBSCRIBE")
|
|
||||||
c.Send("PUNSUBSCRIBE")
|
|
||||||
// To detect the end of the message stream, ask the server to echo
|
|
||||||
// a sentinel value and read until we see that value.
|
|
||||||
sentinelOnce.Do(initSentinel)
|
|
||||||
c.Send("ECHO", sentinel)
|
|
||||||
c.Flush()
|
|
||||||
for {
|
|
||||||
p, err := c.Receive()
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) {
|
|
||||||
pc.state &^= internal.SubscribeState
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.Do("")
|
|
||||||
pc.p.put(c, pc.state != 0)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *pooledConnection) Err() error {
|
|
||||||
return pc.c.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
|
||||||
ci := internal.LookupCommandInfo(commandName)
|
|
||||||
pc.state = (pc.state | ci.Set) &^ ci.Clear
|
|
||||||
return pc.c.Do(commandName, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *pooledConnection) Send(commandName string, args ...interface{}) error {
|
|
||||||
ci := internal.LookupCommandInfo(commandName)
|
|
||||||
pc.state = (pc.state | ci.Set) &^ ci.Clear
|
|
||||||
return pc.c.Send(commandName, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *pooledConnection) Flush() error {
|
|
||||||
return pc.c.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *pooledConnection) Receive() (reply interface{}, err error) {
|
|
||||||
return pc.c.Receive()
|
|
||||||
}
|
|
||||||
|
|
||||||
type errorConnection struct{ err error }
|
|
||||||
|
|
||||||
func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err }
|
|
||||||
func (ec errorConnection) Send(string, ...interface{}) error { return ec.err }
|
|
||||||
func (ec errorConnection) Err() error { return ec.err }
|
|
||||||
func (ec errorConnection) Close() error { return ec.err }
|
|
||||||
func (ec errorConnection) Flush() error { return ec.err }
|
|
||||||
func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err }
|
|
31
vendor/github.com/garyburd/redigo/redis/pre_go17.go
generated
vendored
31
vendor/github.com/garyburd/redigo/redis/pre_go17.go
generated
vendored
|
@ -1,31 +0,0 @@
|
||||||
// +build !go1.7
|
|
||||||
|
|
||||||
package redis
|
|
||||||
|
|
||||||
import "crypto/tls"
|
|
||||||
|
|
||||||
// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case
|
|
||||||
func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config {
|
|
||||||
if cfg == nil {
|
|
||||||
return &tls.Config{InsecureSkipVerify: skipVerify}
|
|
||||||
}
|
|
||||||
return &tls.Config{
|
|
||||||
Rand: cfg.Rand,
|
|
||||||
Time: cfg.Time,
|
|
||||||
Certificates: cfg.Certificates,
|
|
||||||
NameToCertificate: cfg.NameToCertificate,
|
|
||||||
GetCertificate: cfg.GetCertificate,
|
|
||||||
RootCAs: cfg.RootCAs,
|
|
||||||
NextProtos: cfg.NextProtos,
|
|
||||||
ServerName: cfg.ServerName,
|
|
||||||
ClientAuth: cfg.ClientAuth,
|
|
||||||
ClientCAs: cfg.ClientCAs,
|
|
||||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
|
||||||
CipherSuites: cfg.CipherSuites,
|
|
||||||
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
|
|
||||||
ClientSessionCache: cfg.ClientSessionCache,
|
|
||||||
MinVersion: cfg.MinVersion,
|
|
||||||
MaxVersion: cfg.MaxVersion,
|
|
||||||
CurvePreferences: cfg.CurvePreferences,
|
|
||||||
}
|
|
||||||
}
|
|
144
vendor/github.com/garyburd/redigo/redis/pubsub.go
generated
vendored
144
vendor/github.com/garyburd/redigo/redis/pubsub.go
generated
vendored
|
@ -1,144 +0,0 @@
|
||||||
// Copyright 2012 Gary Burd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package redis
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
// Subscription represents a subscribe or unsubscribe notification.
|
|
||||||
type Subscription struct {
|
|
||||||
// Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe"
|
|
||||||
Kind string
|
|
||||||
|
|
||||||
// The channel that was changed.
|
|
||||||
Channel string
|
|
||||||
|
|
||||||
// The current number of subscriptions for connection.
|
|
||||||
Count int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message represents a message notification.
|
|
||||||
type Message struct {
|
|
||||||
// The originating channel.
|
|
||||||
Channel string
|
|
||||||
|
|
||||||
// The message data.
|
|
||||||
Data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// PMessage represents a pmessage notification.
|
|
||||||
type PMessage struct {
|
|
||||||
// The matched pattern.
|
|
||||||
Pattern string
|
|
||||||
|
|
||||||
// The originating channel.
|
|
||||||
Channel string
|
|
||||||
|
|
||||||
// The message data.
|
|
||||||
Data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pong represents a pubsub pong notification.
|
|
||||||
type Pong struct {
|
|
||||||
Data string
|
|
||||||
}
|
|
||||||
|
|
||||||
// PubSubConn wraps a Conn with convenience methods for subscribers.
|
|
||||||
type PubSubConn struct {
|
|
||||||
Conn Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the connection.
|
|
||||||
func (c PubSubConn) Close() error {
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe subscribes the connection to the specified channels.
|
|
||||||
func (c PubSubConn) Subscribe(channel ...interface{}) error {
|
|
||||||
c.Conn.Send("SUBSCRIBE", channel...)
|
|
||||||
return c.Conn.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// PSubscribe subscribes the connection to the given patterns.
|
|
||||||
func (c PubSubConn) PSubscribe(channel ...interface{}) error {
|
|
||||||
c.Conn.Send("PSUBSCRIBE", channel...)
|
|
||||||
return c.Conn.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsubscribe unsubscribes the connection from the given channels, or from all
|
|
||||||
// of them if none is given.
|
|
||||||
func (c PubSubConn) Unsubscribe(channel ...interface{}) error {
|
|
||||||
c.Conn.Send("UNSUBSCRIBE", channel...)
|
|
||||||
return c.Conn.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUnsubscribe unsubscribes the connection from the given patterns, or from all
|
|
||||||
// of them if none is given.
|
|
||||||
func (c PubSubConn) PUnsubscribe(channel ...interface{}) error {
|
|
||||||
c.Conn.Send("PUNSUBSCRIBE", channel...)
|
|
||||||
return c.Conn.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ping sends a PING to the server with the specified data.
|
|
||||||
//
|
|
||||||
// The connection must be subscribed to at least one channel or pattern when
|
|
||||||
// calling this method.
|
|
||||||
func (c PubSubConn) Ping(data string) error {
|
|
||||||
c.Conn.Send("PING", data)
|
|
||||||
return c.Conn.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive returns a pushed message as a Subscription, Message, PMessage, Pong
|
|
||||||
// or error. The return value is intended to be used directly in a type switch
|
|
||||||
// as illustrated in the PubSubConn example.
|
|
||||||
func (c PubSubConn) Receive() interface{} {
|
|
||||||
reply, err := Values(c.Conn.Receive())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var kind string
|
|
||||||
reply, err = Scan(reply, &kind)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case "message":
|
|
||||||
var m Message
|
|
||||||
if _, err := Scan(reply, &m.Channel, &m.Data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
case "pmessage":
|
|
||||||
var pm PMessage
|
|
||||||
if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pm
|
|
||||||
case "subscribe", "psubscribe", "unsubscribe", "punsubscribe":
|
|
||||||
s := Subscription{Kind: kind}
|
|
||||||
if _, err := Scan(reply, &s.Channel, &s.Count); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
case "pong":
|
|
||||||
var p Pong
|
|
||||||
if _, err := Scan(reply, &p.Data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
return errors.New("redigo: unknown pubsub notification")
|
|
||||||
}
|
|
61
vendor/github.com/garyburd/redigo/redis/redis.go
generated
vendored
61
vendor/github.com/garyburd/redigo/redis/redis.go
generated
vendored
|
@ -1,61 +0,0 @@
|
||||||
// Copyright 2012 Gary Burd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package redis
|
|
||||||
|
|
||||||
// Error represents an error returned in a command reply.
|
|
||||||
type Error string
|
|
||||||
|
|
||||||
func (err Error) Error() string { return string(err) }
|
|
||||||
|
|
||||||
// Conn represents a connection to a Redis server.
|
|
||||||
type Conn interface {
|
|
||||||
// Close closes the connection.
|
|
||||||
Close() error
|
|
||||||
|
|
||||||
// Err returns a non-nil value when the connection is not usable.
|
|
||||||
Err() error
|
|
||||||
|
|
||||||
// Do sends a command to the server and returns the received reply.
|
|
||||||
Do(commandName string, args ...interface{}) (reply interface{}, err error)
|
|
||||||
|
|
||||||
// Send writes the command to the client's output buffer.
|
|
||||||
Send(commandName string, args ...interface{}) error
|
|
||||||
|
|
||||||
// Flush flushes the output buffer to the Redis server.
|
|
||||||
Flush() error
|
|
||||||
|
|
||||||
// Receive receives a single reply from the Redis server
|
|
||||||
Receive() (reply interface{}, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Argument is the interface implemented by an object which wants to control how
|
|
||||||
// the object is converted to Redis bulk strings.
|
|
||||||
type Argument interface {
|
|
||||||
// RedisArg returns a value to be encoded as a bulk string per the
|
|
||||||
// conversions listed in the section 'Executing Commands'.
|
|
||||||
// Implementations should typically return a []byte or string.
|
|
||||||
RedisArg() interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scanner is implemented by an object which wants to control its value is
|
|
||||||
// interpreted when read from Redis.
|
|
||||||
type Scanner interface {
|
|
||||||
// RedisScan assigns a value from a Redis value. The argument src is one of
|
|
||||||
// the reply types listed in the section `Executing Commands`.
|
|
||||||
//
|
|
||||||
// An error should be returned if the value cannot be stored without
|
|
||||||
// loss of information.
|
|
||||||
RedisScan(src interface{}) error
|
|
||||||
}
|
|
479
vendor/github.com/garyburd/redigo/redis/reply.go
generated
vendored
479
vendor/github.com/garyburd/redigo/redis/reply.go
generated
vendored
|
@ -1,479 +0,0 @@
|
||||||
// Copyright 2012 Gary Burd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrNil indicates that a reply value is nil.
|
|
||||||
var ErrNil = errors.New("redigo: nil returned")
|
|
||||||
|
|
||||||
// Int is a helper that converts a command reply to an integer. If err is not
|
|
||||||
// equal to nil, then Int returns 0, err. Otherwise, Int converts the
|
|
||||||
// reply to an int as follows:
|
|
||||||
//
|
|
||||||
// Reply type Result
|
|
||||||
// integer int(reply), nil
|
|
||||||
// bulk string parsed reply, nil
|
|
||||||
// nil 0, ErrNil
|
|
||||||
// other 0, error
|
|
||||||
func Int(reply interface{}, err error) (int, error) {
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
switch reply := reply.(type) {
|
|
||||||
case int64:
|
|
||||||
x := int(reply)
|
|
||||||
if int64(x) != reply {
|
|
||||||
return 0, strconv.ErrRange
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
case []byte:
|
|
||||||
n, err := strconv.ParseInt(string(reply), 10, 0)
|
|
||||||
return int(n), err
|
|
||||||
case nil:
|
|
||||||
return 0, ErrNil
|
|
||||||
case Error:
|
|
||||||
return 0, reply
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64 is a helper that converts a command reply to 64 bit integer. If err is
|
|
||||||
// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the
|
|
||||||
// reply to an int64 as follows:
|
|
||||||
//
|
|
||||||
// Reply type Result
|
|
||||||
// integer reply, nil
|
|
||||||
// bulk string parsed reply, nil
|
|
||||||
// nil 0, ErrNil
|
|
||||||
// other 0, error
|
|
||||||
func Int64(reply interface{}, err error) (int64, error) {
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
switch reply := reply.(type) {
|
|
||||||
case int64:
|
|
||||||
return reply, nil
|
|
||||||
case []byte:
|
|
||||||
n, err := strconv.ParseInt(string(reply), 10, 64)
|
|
||||||
return n, err
|
|
||||||
case nil:
|
|
||||||
return 0, ErrNil
|
|
||||||
case Error:
|
|
||||||
return 0, reply
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply)
|
|
||||||
}
|
|
||||||
|
|
||||||
var errNegativeInt = errors.New("redigo: unexpected value for Uint64")
|
|
||||||
|
|
||||||
// Uint64 is a helper that converts a command reply to 64 bit integer. If err is
|
|
||||||
// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the
|
|
||||||
// reply to an int64 as follows:
|
|
||||||
//
|
|
||||||
// Reply type Result
|
|
||||||
// integer reply, nil
|
|
||||||
// bulk string parsed reply, nil
|
|
||||||
// nil 0, ErrNil
|
|
||||||
// other 0, error
|
|
||||||
func Uint64(reply interface{}, err error) (uint64, error) {
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
switch reply := reply.(type) {
|
|
||||||
case int64:
|
|
||||||
if reply < 0 {
|
|
||||||
return 0, errNegativeInt
|
|
||||||
}
|
|
||||||
return uint64(reply), nil
|
|
||||||
case []byte:
|
|
||||||
n, err := strconv.ParseUint(string(reply), 10, 64)
|
|
||||||
return n, err
|
|
||||||
case nil:
|
|
||||||
return 0, ErrNil
|
|
||||||
case Error:
|
|
||||||
return 0, reply
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64 is a helper that converts a command reply to 64 bit float. If err is
|
|
||||||
// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts
|
|
||||||
// the reply to an int as follows:
|
|
||||||
//
|
|
||||||
// Reply type Result
|
|
||||||
// bulk string parsed reply, nil
|
|
||||||
// nil 0, ErrNil
|
|
||||||
// other 0, error
|
|
||||||
func Float64(reply interface{}, err error) (float64, error) {
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
switch reply := reply.(type) {
|
|
||||||
case []byte:
|
|
||||||
n, err := strconv.ParseFloat(string(reply), 64)
|
|
||||||
return n, err
|
|
||||||
case nil:
|
|
||||||
return 0, ErrNil
|
|
||||||
case Error:
|
|
||||||
return 0, reply
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String is a helper that converts a command reply to a string. If err is not
|
|
||||||
// equal to nil, then String returns "", err. Otherwise String converts the
|
|
||||||
// reply to a string as follows:
|
|
||||||
//
|
|
||||||
// Reply type Result
|
|
||||||
// bulk string string(reply), nil
|
|
||||||
// simple string reply, nil
|
|
||||||
// nil "", ErrNil
|
|
||||||
// other "", error
|
|
||||||
func String(reply interface{}, err error) (string, error) {
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
switch reply := reply.(type) {
|
|
||||||
case []byte:
|
|
||||||
return string(reply), nil
|
|
||||||
case string:
|
|
||||||
return reply, nil
|
|
||||||
case nil:
|
|
||||||
return "", ErrNil
|
|
||||||
case Error:
|
|
||||||
return "", reply
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes is a helper that converts a command reply to a slice of bytes. If err
|
|
||||||
// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts
|
|
||||||
// the reply to a slice of bytes as follows:
|
|
||||||
//
|
|
||||||
// Reply type Result
|
|
||||||
// bulk string reply, nil
|
|
||||||
// simple string []byte(reply), nil
|
|
||||||
// nil nil, ErrNil
|
|
||||||
// other nil, error
|
|
||||||
func Bytes(reply interface{}, err error) ([]byte, error) {
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch reply := reply.(type) {
|
|
||||||
case []byte:
|
|
||||||
return reply, nil
|
|
||||||
case string:
|
|
||||||
return []byte(reply), nil
|
|
||||||
case nil:
|
|
||||||
return nil, ErrNil
|
|
||||||
case Error:
|
|
||||||
return nil, reply
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool is a helper that converts a command reply to a boolean. If err is not
|
|
||||||
// equal to nil, then Bool returns false, err. Otherwise Bool converts the
|
|
||||||
// reply to boolean as follows:
|
|
||||||
//
|
|
||||||
// Reply type Result
|
|
||||||
// integer value != 0, nil
|
|
||||||
// bulk string strconv.ParseBool(reply)
|
|
||||||
// nil false, ErrNil
|
|
||||||
// other false, error
|
|
||||||
func Bool(reply interface{}, err error) (bool, error) {
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
switch reply := reply.(type) {
|
|
||||||
case int64:
|
|
||||||
return reply != 0, nil
|
|
||||||
case []byte:
|
|
||||||
return strconv.ParseBool(string(reply))
|
|
||||||
case nil:
|
|
||||||
return false, ErrNil
|
|
||||||
case Error:
|
|
||||||
return false, reply
|
|
||||||
}
|
|
||||||
return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiBulk is a helper that converts an array command reply to a []interface{}.
|
|
||||||
//
|
|
||||||
// Deprecated: Use Values instead.
|
|
||||||
func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) }
|
|
||||||
|
|
||||||
// Values is a helper that converts an array command reply to a []interface{}.
|
|
||||||
// If err is not equal to nil, then Values returns nil, err. Otherwise, Values
|
|
||||||
// converts the reply as follows:
|
|
||||||
//
|
|
||||||
// Reply type Result
|
|
||||||
// array reply, nil
|
|
||||||
// nil nil, ErrNil
|
|
||||||
// other nil, error
|
|
||||||
func Values(reply interface{}, err error) ([]interface{}, error) {
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch reply := reply.(type) {
|
|
||||||
case []interface{}:
|
|
||||||
return reply, nil
|
|
||||||
case nil:
|
|
||||||
return nil, ErrNil
|
|
||||||
case Error:
|
|
||||||
return nil, reply
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch reply := reply.(type) {
|
|
||||||
case []interface{}:
|
|
||||||
makeSlice(len(reply))
|
|
||||||
for i := range reply {
|
|
||||||
if reply[i] == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := assign(i, reply[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case nil:
|
|
||||||
return ErrNil
|
|
||||||
case Error:
|
|
||||||
return reply
|
|
||||||
}
|
|
||||||
return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Float64s is a helper that converts an array command reply to a []float64. If
|
|
||||||
// err is not equal to nil, then Float64s returns nil, err. Nil array items are
|
|
||||||
// converted to 0 in the output slice. Floats64 returns an error if an array
|
|
||||||
// item is not a bulk string or nil.
|
|
||||||
func Float64s(reply interface{}, err error) ([]float64, error) {
|
|
||||||
var result []float64
|
|
||||||
err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error {
|
|
||||||
p, ok := v.([]byte)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v)
|
|
||||||
}
|
|
||||||
f, err := strconv.ParseFloat(string(p), 64)
|
|
||||||
result[i] = f
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strings is a helper that converts an array command reply to a []string. If
|
|
||||||
// err is not equal to nil, then Strings returns nil, err. Nil array items are
|
|
||||||
// converted to "" in the output slice. Strings returns an error if an array
|
|
||||||
// item is not a bulk string or nil.
|
|
||||||
func Strings(reply interface{}, err error) ([]string, error) {
|
|
||||||
var result []string
|
|
||||||
err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error {
|
|
||||||
switch v := v.(type) {
|
|
||||||
case string:
|
|
||||||
result[i] = v
|
|
||||||
return nil
|
|
||||||
case []byte:
|
|
||||||
result[i] = string(v)
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ByteSlices is a helper that converts an array command reply to a [][]byte.
|
|
||||||
// If err is not equal to nil, then ByteSlices returns nil, err. Nil array
|
|
||||||
// items are stay nil. ByteSlices returns an error if an array item is not a
|
|
||||||
// bulk string or nil.
|
|
||||||
func ByteSlices(reply interface{}, err error) ([][]byte, error) {
|
|
||||||
var result [][]byte
|
|
||||||
err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error {
|
|
||||||
p, ok := v.([]byte)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v)
|
|
||||||
}
|
|
||||||
result[i] = p
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64s is a helper that converts an array command reply to a []int64.
|
|
||||||
// If err is not equal to nil, then Int64s returns nil, err. Nil array
|
|
||||||
// items are stay nil. Int64s returns an error if an array item is not a
|
|
||||||
// bulk string or nil.
|
|
||||||
func Int64s(reply interface{}, err error) ([]int64, error) {
|
|
||||||
var result []int64
|
|
||||||
err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error {
|
|
||||||
switch v := v.(type) {
|
|
||||||
case int64:
|
|
||||||
result[i] = v
|
|
||||||
return nil
|
|
||||||
case []byte:
|
|
||||||
n, err := strconv.ParseInt(string(v), 10, 64)
|
|
||||||
result[i] = n
|
|
||||||
return err
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ints is a helper that converts an array command reply to a []in.
|
|
||||||
// If err is not equal to nil, then Ints returns nil, err. Nil array
|
|
||||||
// items are stay nil. Ints returns an error if an array item is not a
|
|
||||||
// bulk string or nil.
|
|
||||||
func Ints(reply interface{}, err error) ([]int, error) {
|
|
||||||
var result []int
|
|
||||||
err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error {
|
|
||||||
switch v := v.(type) {
|
|
||||||
case int64:
|
|
||||||
n := int(v)
|
|
||||||
if int64(n) != v {
|
|
||||||
return strconv.ErrRange
|
|
||||||
}
|
|
||||||
result[i] = n
|
|
||||||
return nil
|
|
||||||
case []byte:
|
|
||||||
n, err := strconv.Atoi(string(v))
|
|
||||||
result[i] = n
|
|
||||||
return err
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringMap is a helper that converts an array of strings (alternating key, value)
|
|
||||||
// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format.
|
|
||||||
// Requires an even number of values in result.
|
|
||||||
func StringMap(result interface{}, err error) (map[string]string, error) {
|
|
||||||
values, err := Values(result, err)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(values)%2 != 0 {
|
|
||||||
return nil, errors.New("redigo: StringMap expects even number of values result")
|
|
||||||
}
|
|
||||||
m := make(map[string]string, len(values)/2)
|
|
||||||
for i := 0; i < len(values); i += 2 {
|
|
||||||
key, okKey := values[i].([]byte)
|
|
||||||
value, okValue := values[i+1].([]byte)
|
|
||||||
if !okKey || !okValue {
|
|
||||||
return nil, errors.New("redigo: StringMap key not a bulk string value")
|
|
||||||
}
|
|
||||||
m[string(key)] = string(value)
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntMap is a helper that converts an array of strings (alternating key, value)
|
|
||||||
// into a map[string]int. The HGETALL commands return replies in this format.
|
|
||||||
// Requires an even number of values in result.
|
|
||||||
func IntMap(result interface{}, err error) (map[string]int, error) {
|
|
||||||
values, err := Values(result, err)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(values)%2 != 0 {
|
|
||||||
return nil, errors.New("redigo: IntMap expects even number of values result")
|
|
||||||
}
|
|
||||||
m := make(map[string]int, len(values)/2)
|
|
||||||
for i := 0; i < len(values); i += 2 {
|
|
||||||
key, ok := values[i].([]byte)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("redigo: IntMap key not a bulk string value")
|
|
||||||
}
|
|
||||||
value, err := Int(values[i+1], nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m[string(key)] = value
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64Map is a helper that converts an array of strings (alternating key, value)
|
|
||||||
// into a map[string]int64. The HGETALL commands return replies in this format.
|
|
||||||
// Requires an even number of values in result.
|
|
||||||
func Int64Map(result interface{}, err error) (map[string]int64, error) {
|
|
||||||
values, err := Values(result, err)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(values)%2 != 0 {
|
|
||||||
return nil, errors.New("redigo: Int64Map expects even number of values result")
|
|
||||||
}
|
|
||||||
m := make(map[string]int64, len(values)/2)
|
|
||||||
for i := 0; i < len(values); i += 2 {
|
|
||||||
key, ok := values[i].([]byte)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("redigo: Int64Map key not a bulk string value")
|
|
||||||
}
|
|
||||||
value, err := Int64(values[i+1], nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m[string(key)] = value
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Positions is a helper that converts an array of positions (lat, long)
|
|
||||||
// into a [][2]float64. The GEOPOS command returns replies in this format.
|
|
||||||
func Positions(result interface{}, err error) ([]*[2]float64, error) {
|
|
||||||
values, err := Values(result, err)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
positions := make([]*[2]float64, len(values))
|
|
||||||
for i := range values {
|
|
||||||
if values[i] == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p, ok := values[i].([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("redigo: unexpected element type for interface slice, got type %T", values[i])
|
|
||||||
}
|
|
||||||
if len(p) != 2 {
|
|
||||||
return nil, fmt.Errorf("redigo: unexpected number of values for a member position, got %d", len(p))
|
|
||||||
}
|
|
||||||
lat, err := Float64(p[0], nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
long, err := Float64(p[1], nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
positions[i] = &[2]float64{lat, long}
|
|
||||||
}
|
|
||||||
return positions, nil
|
|
||||||
}
|
|
585
vendor/github.com/garyburd/redigo/redis/scan.go
generated
vendored
585
vendor/github.com/garyburd/redigo/redis/scan.go
generated
vendored
|
@ -1,585 +0,0 @@
|
||||||
// Copyright 2012 Gary Burd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ensureLen(d reflect.Value, n int) {
|
|
||||||
if n > d.Cap() {
|
|
||||||
d.Set(reflect.MakeSlice(d.Type(), n, n))
|
|
||||||
} else {
|
|
||||||
d.SetLen(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cannotConvert(d reflect.Value, s interface{}) error {
|
|
||||||
var sname string
|
|
||||||
switch s.(type) {
|
|
||||||
case string:
|
|
||||||
sname = "Redis simple string"
|
|
||||||
case Error:
|
|
||||||
sname = "Redis error"
|
|
||||||
case int64:
|
|
||||||
sname = "Redis integer"
|
|
||||||
case []byte:
|
|
||||||
sname = "Redis bulk string"
|
|
||||||
case []interface{}:
|
|
||||||
sname = "Redis array"
|
|
||||||
default:
|
|
||||||
sname = reflect.TypeOf(s).String()
|
|
||||||
}
|
|
||||||
return fmt.Errorf("cannot convert from %s to %s", sname, d.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertAssignBulkString(d reflect.Value, s []byte) (err error) {
|
|
||||||
switch d.Type().Kind() {
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
var x float64
|
|
||||||
x, err = strconv.ParseFloat(string(s), d.Type().Bits())
|
|
||||||
d.SetFloat(x)
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
var x int64
|
|
||||||
x, err = strconv.ParseInt(string(s), 10, d.Type().Bits())
|
|
||||||
d.SetInt(x)
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
var x uint64
|
|
||||||
x, err = strconv.ParseUint(string(s), 10, d.Type().Bits())
|
|
||||||
d.SetUint(x)
|
|
||||||
case reflect.Bool:
|
|
||||||
var x bool
|
|
||||||
x, err = strconv.ParseBool(string(s))
|
|
||||||
d.SetBool(x)
|
|
||||||
case reflect.String:
|
|
||||||
d.SetString(string(s))
|
|
||||||
case reflect.Slice:
|
|
||||||
if d.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
err = cannotConvert(d, s)
|
|
||||||
} else {
|
|
||||||
d.SetBytes(s)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err = cannotConvert(d, s)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertAssignInt(d reflect.Value, s int64) (err error) {
|
|
||||||
switch d.Type().Kind() {
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
d.SetInt(s)
|
|
||||||
if d.Int() != s {
|
|
||||||
err = strconv.ErrRange
|
|
||||||
d.SetInt(0)
|
|
||||||
}
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
if s < 0 {
|
|
||||||
err = strconv.ErrRange
|
|
||||||
} else {
|
|
||||||
x := uint64(s)
|
|
||||||
d.SetUint(x)
|
|
||||||
if d.Uint() != x {
|
|
||||||
err = strconv.ErrRange
|
|
||||||
d.SetUint(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.Bool:
|
|
||||||
d.SetBool(s != 0)
|
|
||||||
default:
|
|
||||||
err = cannotConvert(d, s)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertAssignValue(d reflect.Value, s interface{}) (err error) {
|
|
||||||
if d.Kind() != reflect.Ptr {
|
|
||||||
if d.CanAddr() {
|
|
||||||
d2 := d.Addr()
|
|
||||||
if d2.CanInterface() {
|
|
||||||
if scanner, ok := d2.Interface().(Scanner); ok {
|
|
||||||
return scanner.RedisScan(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if d.CanInterface() {
|
|
||||||
// Already a reflect.Ptr
|
|
||||||
if d.IsNil() {
|
|
||||||
d.Set(reflect.New(d.Type().Elem()))
|
|
||||||
}
|
|
||||||
if scanner, ok := d.Interface().(Scanner); ok {
|
|
||||||
return scanner.RedisScan(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch s := s.(type) {
|
|
||||||
case []byte:
|
|
||||||
err = convertAssignBulkString(d, s)
|
|
||||||
case int64:
|
|
||||||
err = convertAssignInt(d, s)
|
|
||||||
default:
|
|
||||||
err = cannotConvert(d, s)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertAssignArray(d reflect.Value, s []interface{}) error {
|
|
||||||
if d.Type().Kind() != reflect.Slice {
|
|
||||||
return cannotConvert(d, s)
|
|
||||||
}
|
|
||||||
ensureLen(d, len(s))
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
if err := convertAssignValue(d.Index(i), s[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertAssign(d interface{}, s interface{}) (err error) {
|
|
||||||
if scanner, ok := d.(Scanner); ok {
|
|
||||||
return scanner.RedisScan(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the most common destination types using type switches and
|
|
||||||
// fall back to reflection for all other types.
|
|
||||||
switch s := s.(type) {
|
|
||||||
case nil:
|
|
||||||
// ignore
|
|
||||||
case []byte:
|
|
||||||
switch d := d.(type) {
|
|
||||||
case *string:
|
|
||||||
*d = string(s)
|
|
||||||
case *int:
|
|
||||||
*d, err = strconv.Atoi(string(s))
|
|
||||||
case *bool:
|
|
||||||
*d, err = strconv.ParseBool(string(s))
|
|
||||||
case *[]byte:
|
|
||||||
*d = s
|
|
||||||
case *interface{}:
|
|
||||||
*d = s
|
|
||||||
case nil:
|
|
||||||
// skip value
|
|
||||||
default:
|
|
||||||
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
|
||||||
err = cannotConvert(d, s)
|
|
||||||
} else {
|
|
||||||
err = convertAssignBulkString(d.Elem(), s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case int64:
|
|
||||||
switch d := d.(type) {
|
|
||||||
case *int:
|
|
||||||
x := int(s)
|
|
||||||
if int64(x) != s {
|
|
||||||
err = strconv.ErrRange
|
|
||||||
x = 0
|
|
||||||
}
|
|
||||||
*d = x
|
|
||||||
case *bool:
|
|
||||||
*d = s != 0
|
|
||||||
case *interface{}:
|
|
||||||
*d = s
|
|
||||||
case nil:
|
|
||||||
// skip value
|
|
||||||
default:
|
|
||||||
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
|
||||||
err = cannotConvert(d, s)
|
|
||||||
} else {
|
|
||||||
err = convertAssignInt(d.Elem(), s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case string:
|
|
||||||
switch d := d.(type) {
|
|
||||||
case *string:
|
|
||||||
*d = s
|
|
||||||
case *interface{}:
|
|
||||||
*d = s
|
|
||||||
case nil:
|
|
||||||
// skip value
|
|
||||||
default:
|
|
||||||
err = cannotConvert(reflect.ValueOf(d), s)
|
|
||||||
}
|
|
||||||
case []interface{}:
|
|
||||||
switch d := d.(type) {
|
|
||||||
case *[]interface{}:
|
|
||||||
*d = s
|
|
||||||
case *interface{}:
|
|
||||||
*d = s
|
|
||||||
case nil:
|
|
||||||
// skip value
|
|
||||||
default:
|
|
||||||
if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
|
|
||||||
err = cannotConvert(d, s)
|
|
||||||
} else {
|
|
||||||
err = convertAssignArray(d.Elem(), s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case Error:
|
|
||||||
err = s
|
|
||||||
default:
|
|
||||||
err = cannotConvert(reflect.ValueOf(d), s)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan copies from src to the values pointed at by dest.
|
|
||||||
//
|
|
||||||
// Scan uses RedisScan if available otherwise:
|
|
||||||
//
|
|
||||||
// The values pointed at by dest must be an integer, float, boolean, string,
|
|
||||||
// []byte, interface{} or slices of these types. Scan uses the standard strconv
|
|
||||||
// package to convert bulk strings to numeric and boolean types.
|
|
||||||
//
|
|
||||||
// If a dest value is nil, then the corresponding src value is skipped.
|
|
||||||
//
|
|
||||||
// If a src element is nil, then the corresponding dest value is not modified.
|
|
||||||
//
|
|
||||||
// To enable easy use of Scan in a loop, Scan returns the slice of src
|
|
||||||
// following the copied values.
|
|
||||||
func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) {
|
|
||||||
if len(src) < len(dest) {
|
|
||||||
return nil, errors.New("redigo.Scan: array short")
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
for i, d := range dest {
|
|
||||||
err = convertAssign(d, src[i])
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return src[len(dest):], err
|
|
||||||
}
|
|
||||||
|
|
||||||
type fieldSpec struct {
|
|
||||||
name string
|
|
||||||
index []int
|
|
||||||
omitEmpty bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type structSpec struct {
|
|
||||||
m map[string]*fieldSpec
|
|
||||||
l []*fieldSpec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss *structSpec) fieldSpec(name []byte) *fieldSpec {
|
|
||||||
return ss.m[string(name)]
|
|
||||||
}
|
|
||||||
|
|
||||||
func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) {
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
|
||||||
f := t.Field(i)
|
|
||||||
switch {
|
|
||||||
case f.PkgPath != "" && !f.Anonymous:
|
|
||||||
// Ignore unexported fields.
|
|
||||||
case f.Anonymous:
|
|
||||||
// TODO: Handle pointers. Requires change to decoder and
|
|
||||||
// protection against infinite recursion.
|
|
||||||
if f.Type.Kind() == reflect.Struct {
|
|
||||||
compileStructSpec(f.Type, depth, append(index, i), ss)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
fs := &fieldSpec{name: f.Name}
|
|
||||||
tag := f.Tag.Get("redis")
|
|
||||||
p := strings.Split(tag, ",")
|
|
||||||
if len(p) > 0 {
|
|
||||||
if p[0] == "-" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(p[0]) > 0 {
|
|
||||||
fs.name = p[0]
|
|
||||||
}
|
|
||||||
for _, s := range p[1:] {
|
|
||||||
switch s {
|
|
||||||
case "omitempty":
|
|
||||||
fs.omitEmpty = true
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d, found := depth[fs.name]
|
|
||||||
if !found {
|
|
||||||
d = 1 << 30
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case len(index) == d:
|
|
||||||
// At same depth, remove from result.
|
|
||||||
delete(ss.m, fs.name)
|
|
||||||
j := 0
|
|
||||||
for i := 0; i < len(ss.l); i++ {
|
|
||||||
if fs.name != ss.l[i].name {
|
|
||||||
ss.l[j] = ss.l[i]
|
|
||||||
j += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ss.l = ss.l[:j]
|
|
||||||
case len(index) < d:
|
|
||||||
fs.index = make([]int, len(index)+1)
|
|
||||||
copy(fs.index, index)
|
|
||||||
fs.index[len(index)] = i
|
|
||||||
depth[fs.name] = len(index)
|
|
||||||
ss.m[fs.name] = fs
|
|
||||||
ss.l = append(ss.l, fs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
structSpecMutex sync.RWMutex
|
|
||||||
structSpecCache = make(map[reflect.Type]*structSpec)
|
|
||||||
defaultFieldSpec = &fieldSpec{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func structSpecForType(t reflect.Type) *structSpec {
|
|
||||||
|
|
||||||
structSpecMutex.RLock()
|
|
||||||
ss, found := structSpecCache[t]
|
|
||||||
structSpecMutex.RUnlock()
|
|
||||||
if found {
|
|
||||||
return ss
|
|
||||||
}
|
|
||||||
|
|
||||||
structSpecMutex.Lock()
|
|
||||||
defer structSpecMutex.Unlock()
|
|
||||||
ss, found = structSpecCache[t]
|
|
||||||
if found {
|
|
||||||
return ss
|
|
||||||
}
|
|
||||||
|
|
||||||
ss = &structSpec{m: make(map[string]*fieldSpec)}
|
|
||||||
compileStructSpec(t, make(map[string]int), nil, ss)
|
|
||||||
structSpecCache[t] = ss
|
|
||||||
return ss
|
|
||||||
}
|
|
||||||
|
|
||||||
var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct")
|
|
||||||
|
|
||||||
// ScanStruct scans alternating names and values from src to a struct. The
|
|
||||||
// HGETALL and CONFIG GET commands return replies in this format.
|
|
||||||
//
|
|
||||||
// ScanStruct uses exported field names to match values in the response. Use
|
|
||||||
// 'redis' field tag to override the name:
|
|
||||||
//
|
|
||||||
// Field int `redis:"myName"`
|
|
||||||
//
|
|
||||||
// Fields with the tag redis:"-" are ignored.
|
|
||||||
//
|
|
||||||
// Each field uses RedisScan if available otherwise:
|
|
||||||
// Integer, float, boolean, string and []byte fields are supported. Scan uses the
|
|
||||||
// standard strconv package to convert bulk string values to numeric and
|
|
||||||
// boolean types.
|
|
||||||
//
|
|
||||||
// If a src element is nil, then the corresponding field is not modified.
|
|
||||||
func ScanStruct(src []interface{}, dest interface{}) error {
|
|
||||||
d := reflect.ValueOf(dest)
|
|
||||||
if d.Kind() != reflect.Ptr || d.IsNil() {
|
|
||||||
return errScanStructValue
|
|
||||||
}
|
|
||||||
d = d.Elem()
|
|
||||||
if d.Kind() != reflect.Struct {
|
|
||||||
return errScanStructValue
|
|
||||||
}
|
|
||||||
ss := structSpecForType(d.Type())
|
|
||||||
|
|
||||||
if len(src)%2 != 0 {
|
|
||||||
return errors.New("redigo.ScanStruct: number of values not a multiple of 2")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(src); i += 2 {
|
|
||||||
s := src[i+1]
|
|
||||||
if s == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name, ok := src[i].([]byte)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i)
|
|
||||||
}
|
|
||||||
fs := ss.fieldSpec(name)
|
|
||||||
if fs == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
|
|
||||||
return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ScanSlice scans src to the slice pointed to by dest. The elements the dest
|
|
||||||
// slice must be integer, float, boolean, string, struct or pointer to struct
|
|
||||||
// values.
|
|
||||||
//
|
|
||||||
// Struct fields must be integer, float, boolean or string values. All struct
|
|
||||||
// fields are used unless a subset is specified using fieldNames.
|
|
||||||
func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error {
|
|
||||||
d := reflect.ValueOf(dest)
|
|
||||||
if d.Kind() != reflect.Ptr || d.IsNil() {
|
|
||||||
return errScanSliceValue
|
|
||||||
}
|
|
||||||
d = d.Elem()
|
|
||||||
if d.Kind() != reflect.Slice {
|
|
||||||
return errScanSliceValue
|
|
||||||
}
|
|
||||||
|
|
||||||
isPtr := false
|
|
||||||
t := d.Type().Elem()
|
|
||||||
if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
|
|
||||||
isPtr = true
|
|
||||||
t = t.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Kind() != reflect.Struct {
|
|
||||||
ensureLen(d, len(src))
|
|
||||||
for i, s := range src {
|
|
||||||
if s == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := convertAssignValue(d.Index(i), s); err != nil {
|
|
||||||
return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ss := structSpecForType(t)
|
|
||||||
fss := ss.l
|
|
||||||
if len(fieldNames) > 0 {
|
|
||||||
fss = make([]*fieldSpec, len(fieldNames))
|
|
||||||
for i, name := range fieldNames {
|
|
||||||
fss[i] = ss.m[name]
|
|
||||||
if fss[i] == nil {
|
|
||||||
return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fss) == 0 {
|
|
||||||
return errors.New("redigo.ScanSlice: no struct fields")
|
|
||||||
}
|
|
||||||
|
|
||||||
n := len(src) / len(fss)
|
|
||||||
if n*len(fss) != len(src) {
|
|
||||||
return errors.New("redigo.ScanSlice: length not a multiple of struct field count")
|
|
||||||
}
|
|
||||||
|
|
||||||
ensureLen(d, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
d := d.Index(i)
|
|
||||||
if isPtr {
|
|
||||||
if d.IsNil() {
|
|
||||||
d.Set(reflect.New(t))
|
|
||||||
}
|
|
||||||
d = d.Elem()
|
|
||||||
}
|
|
||||||
for j, fs := range fss {
|
|
||||||
s := src[i*len(fss)+j]
|
|
||||||
if s == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
|
|
||||||
return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Args is a helper for constructing command arguments from structured values.
|
|
||||||
type Args []interface{}
|
|
||||||
|
|
||||||
// Add returns the result of appending value to args.
|
|
||||||
func (args Args) Add(value ...interface{}) Args {
|
|
||||||
return append(args, value...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddFlat returns the result of appending the flattened value of v to args.
|
|
||||||
//
|
|
||||||
// Maps are flattened by appending the alternating keys and map values to args.
|
|
||||||
//
|
|
||||||
// Slices are flattened by appending the slice elements to args.
|
|
||||||
//
|
|
||||||
// Structs are flattened by appending the alternating names and values of
|
|
||||||
// exported fields to args. If v is a nil struct pointer, then nothing is
|
|
||||||
// appended. The 'redis' field tag overrides struct field names. See ScanStruct
|
|
||||||
// for more information on the use of the 'redis' field tag.
|
|
||||||
//
|
|
||||||
// Other types are appended to args as is.
|
|
||||||
func (args Args) AddFlat(v interface{}) Args {
|
|
||||||
rv := reflect.ValueOf(v)
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
args = flattenStruct(args, rv)
|
|
||||||
case reflect.Slice:
|
|
||||||
for i := 0; i < rv.Len(); i++ {
|
|
||||||
args = append(args, rv.Index(i).Interface())
|
|
||||||
}
|
|
||||||
case reflect.Map:
|
|
||||||
for _, k := range rv.MapKeys() {
|
|
||||||
args = append(args, k.Interface(), rv.MapIndex(k).Interface())
|
|
||||||
}
|
|
||||||
case reflect.Ptr:
|
|
||||||
if rv.Type().Elem().Kind() == reflect.Struct {
|
|
||||||
if !rv.IsNil() {
|
|
||||||
args = flattenStruct(args, rv.Elem())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
args = append(args, v)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
args = append(args, v)
|
|
||||||
}
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
func flattenStruct(args Args, v reflect.Value) Args {
|
|
||||||
ss := structSpecForType(v.Type())
|
|
||||||
for _, fs := range ss.l {
|
|
||||||
fv := v.FieldByIndex(fs.index)
|
|
||||||
if fs.omitEmpty {
|
|
||||||
var empty = false
|
|
||||||
switch fv.Kind() {
|
|
||||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
|
||||||
empty = fv.Len() == 0
|
|
||||||
case reflect.Bool:
|
|
||||||
empty = !fv.Bool()
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
empty = fv.Int() == 0
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
empty = fv.Uint() == 0
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
empty = fv.Float() == 0
|
|
||||||
case reflect.Interface, reflect.Ptr:
|
|
||||||
empty = fv.IsNil()
|
|
||||||
}
|
|
||||||
if empty {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
args = append(args, fs.name, fv.Interface())
|
|
||||||
}
|
|
||||||
return args
|
|
||||||
}
|
|
91
vendor/github.com/garyburd/redigo/redis/script.go
generated
vendored
91
vendor/github.com/garyburd/redigo/redis/script.go
generated
vendored
|
@ -1,91 +0,0 @@
|
||||||
// Copyright 2012 Gary Burd
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
|
||||||
// not use this file except in compliance with the License. You may obtain
|
|
||||||
// a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
// License for the specific language governing permissions and limitations
|
|
||||||
// under the License.
|
|
||||||
|
|
||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Script encapsulates the source, hash and key count for a Lua script. See
|
|
||||||
// http://redis.io/commands/eval for information on scripts in Redis.
|
|
||||||
type Script struct {
|
|
||||||
keyCount int
|
|
||||||
src string
|
|
||||||
hash string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewScript returns a new script object. If keyCount is greater than or equal
|
|
||||||
// to zero, then the count is automatically inserted in the EVAL command
|
|
||||||
// argument list. If keyCount is less than zero, then the application supplies
|
|
||||||
// the count as the first value in the keysAndArgs argument to the Do, Send and
|
|
||||||
// SendHash methods.
|
|
||||||
func NewScript(keyCount int, src string) *Script {
|
|
||||||
h := sha1.New()
|
|
||||||
io.WriteString(h, src)
|
|
||||||
return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} {
|
|
||||||
var args []interface{}
|
|
||||||
if s.keyCount < 0 {
|
|
||||||
args = make([]interface{}, 1+len(keysAndArgs))
|
|
||||||
args[0] = spec
|
|
||||||
copy(args[1:], keysAndArgs)
|
|
||||||
} else {
|
|
||||||
args = make([]interface{}, 2+len(keysAndArgs))
|
|
||||||
args[0] = spec
|
|
||||||
args[1] = s.keyCount
|
|
||||||
copy(args[2:], keysAndArgs)
|
|
||||||
}
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash returns the script hash.
|
|
||||||
func (s *Script) Hash() string {
|
|
||||||
return s.hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do evaluates the script. Under the covers, Do optimistically evaluates the
|
|
||||||
// script using the EVALSHA command. If the command fails because the script is
|
|
||||||
// not loaded, then Do evaluates the script using the EVAL command (thus
|
|
||||||
// causing the script to load).
|
|
||||||
func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) {
|
|
||||||
v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...)
|
|
||||||
if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") {
|
|
||||||
v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...)
|
|
||||||
}
|
|
||||||
return v, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendHash evaluates the script without waiting for the reply. The script is
|
|
||||||
// evaluated with the EVALSHA command. The application must ensure that the
|
|
||||||
// script is loaded by a previous call to Send, Do or Load methods.
|
|
||||||
func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error {
|
|
||||||
return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send evaluates the script without waiting for the reply.
|
|
||||||
func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error {
|
|
||||||
return c.Send("EVAL", s.args(s.src, keysAndArgs)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load loads the script without evaluating it.
|
|
||||||
func (s *Script) Load(c Conn) error {
|
|
||||||
_, err := c.Do("SCRIPT", "LOAD", s.src)
|
|
||||||
return err
|
|
||||||
}
|
|
2
vendor/github.com/go-ini/ini/LICENSE
generated
vendored
2
vendor/github.com/go-ini/ini/LICENSE
generated
vendored
|
@ -176,7 +176,7 @@ recommend that a file or class name and description of purpose be included on
|
||||||
the same "printed page" as the copyright notice for easier identification within
|
the same "printed page" as the copyright notice for easier identification within
|
||||||
third-party archives.
|
third-party archives.
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
Copyright 2014 Unknwon
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
5
vendor/github.com/go-ini/ini/Makefile
generated
vendored
5
vendor/github.com/go-ini/ini/Makefile
generated
vendored
|
@ -1,4 +1,4 @@
|
||||||
.PHONY: build test bench vet
|
.PHONY: build test bench vet coverage
|
||||||
|
|
||||||
build: vet bench
|
build: vet bench
|
||||||
|
|
||||||
|
@ -10,3 +10,6 @@ bench:
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
go vet
|
go vet
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out
|
||||||
|
|
722
vendor/github.com/go-ini/ini/README.md
generated
vendored
722
vendor/github.com/go-ini/ini/README.md
generated
vendored
|
@ -1,15 +1,13 @@
|
||||||
INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://sourcegraph.com/github.com/go-ini/ini/-/badge.svg)](https://sourcegraph.com/github.com/go-ini/ini?badge)
|
INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg)](https://sourcegraph.com/github.com/go-ini/ini)
|
||||||
===
|
===
|
||||||
|
|
||||||
![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
|
![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
|
||||||
|
|
||||||
Package ini provides INI file read and write functionality in Go.
|
Package ini provides INI file read and write functionality in Go.
|
||||||
|
|
||||||
[简体中文](README_ZH.md)
|
## Features
|
||||||
|
|
||||||
## Feature
|
- Load from multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
|
||||||
|
|
||||||
- Load multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
|
|
||||||
- Read with recursion values.
|
- Read with recursion values.
|
||||||
- Read with parent-child sections.
|
- Read with parent-child sections.
|
||||||
- Read with auto-increment key names.
|
- Read with auto-increment key names.
|
||||||
|
@ -24,722 +22,22 @@ Package ini provides INI file read and write functionality in Go.
|
||||||
|
|
||||||
To use a tagged revision:
|
To use a tagged revision:
|
||||||
|
|
||||||
go get gopkg.in/ini.v1
|
```sh
|
||||||
|
$ go get gopkg.in/ini.v1
|
||||||
|
```
|
||||||
|
|
||||||
To use with latest changes:
|
To use with latest changes:
|
||||||
|
|
||||||
go get github.com/go-ini/ini
|
```sh
|
||||||
|
$ go get github.com/go-ini/ini
|
||||||
|
```
|
||||||
|
|
||||||
Please add `-u` flag to update in the future.
|
Please add `-u` flag to update in the future.
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
If you want to test on your machine, please apply `-t` flag:
|
|
||||||
|
|
||||||
go get -t gopkg.in/ini.v1
|
|
||||||
|
|
||||||
Please add `-u` flag to update in the future.
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
### Loading from data sources
|
|
||||||
|
|
||||||
A **Data Source** is either raw data in type `[]byte`, a file name with type `string` or `io.ReadCloser`. You can load **as many data sources as you want**. Passing other types will simply return an error.
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
|
|
||||||
```
|
|
||||||
|
|
||||||
Or start with an empty object:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg := ini.Empty()
|
|
||||||
```
|
|
||||||
|
|
||||||
When you cannot decide how many data sources to load at the beginning, you will still be able to **Append()** them later.
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := cfg.Append("other file", []byte("other raw data"))
|
|
||||||
```
|
|
||||||
|
|
||||||
If you have a list of files with possibilities that some of them may not available at the time, and you don't know exactly which ones, you can use `LooseLoad` to ignore nonexistent files without returning error.
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.LooseLoad("filename", "filename_404")
|
|
||||||
```
|
|
||||||
|
|
||||||
The cool thing is, whenever the file is available to load while you're calling `Reload` method, it will be counted as usual.
|
|
||||||
|
|
||||||
#### Ignore cases of key name
|
|
||||||
|
|
||||||
When you do not care about cases of section and key names, you can use `InsensitiveLoad` to force all names to be lowercased while parsing.
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.InsensitiveLoad("filename")
|
|
||||||
//...
|
|
||||||
|
|
||||||
// sec1 and sec2 are the exactly same section object
|
|
||||||
sec1, err := cfg.GetSection("Section")
|
|
||||||
sec2, err := cfg.GetSection("SecTIOn")
|
|
||||||
|
|
||||||
// key1 and key2 are the exactly same key object
|
|
||||||
key1, err := sec1.GetKey("Key")
|
|
||||||
key2, err := sec2.GetKey("KeY")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### MySQL-like boolean key
|
|
||||||
|
|
||||||
MySQL's configuration allows a key without value as follows:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[mysqld]
|
|
||||||
...
|
|
||||||
skip-host-cache
|
|
||||||
skip-name-resolve
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, this is considered as missing value. But if you know you're going to deal with those cases, you can assign advanced load options:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
|
|
||||||
```
|
|
||||||
|
|
||||||
The value of those keys are always `true`, and when you save to a file, it will keep in the same foramt as you read.
|
|
||||||
|
|
||||||
To generate such keys in your program, you could use `NewBooleanKey`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
key, err := sec.NewBooleanKey("skip-host-cache")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Comment
|
|
||||||
|
|
||||||
Take care that following format will be treated as comment:
|
|
||||||
|
|
||||||
1. Line begins with `#` or `;`
|
|
||||||
2. Words after `#` or `;`
|
|
||||||
3. Words after section name (i.e words after `[some section name]`)
|
|
||||||
|
|
||||||
If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```.
|
|
||||||
|
|
||||||
Alternatively, you can use following `LoadOptions` to completely ignore inline comments:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, "app.ini"))
|
|
||||||
```
|
|
||||||
|
|
||||||
### Working with sections
|
|
||||||
|
|
||||||
To get a section, you would need to:
|
|
||||||
|
|
||||||
```go
|
|
||||||
section, err := cfg.GetSection("section name")
|
|
||||||
```
|
|
||||||
|
|
||||||
For a shortcut for default section, just give an empty string as name:
|
|
||||||
|
|
||||||
```go
|
|
||||||
section, err := cfg.GetSection("")
|
|
||||||
```
|
|
||||||
|
|
||||||
When you're pretty sure the section exists, following code could make your life easier:
|
|
||||||
|
|
||||||
```go
|
|
||||||
section := cfg.Section("section name")
|
|
||||||
```
|
|
||||||
|
|
||||||
What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
|
|
||||||
|
|
||||||
To create a new section:
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := cfg.NewSection("new section")
|
|
||||||
```
|
|
||||||
|
|
||||||
To get a list of sections or section names:
|
|
||||||
|
|
||||||
```go
|
|
||||||
sections := cfg.Sections()
|
|
||||||
names := cfg.SectionStrings()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Working with keys
|
|
||||||
|
|
||||||
To get a key under a section:
|
|
||||||
|
|
||||||
```go
|
|
||||||
key, err := cfg.Section("").GetKey("key name")
|
|
||||||
```
|
|
||||||
|
|
||||||
Same rule applies to key operations:
|
|
||||||
|
|
||||||
```go
|
|
||||||
key := cfg.Section("").Key("key name")
|
|
||||||
```
|
|
||||||
|
|
||||||
To check if a key exists:
|
|
||||||
|
|
||||||
```go
|
|
||||||
yes := cfg.Section("").HasKey("key name")
|
|
||||||
```
|
|
||||||
|
|
||||||
To create a new key:
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := cfg.Section("").NewKey("name", "value")
|
|
||||||
```
|
|
||||||
|
|
||||||
To get a list of keys or key names:
|
|
||||||
|
|
||||||
```go
|
|
||||||
keys := cfg.Section("").Keys()
|
|
||||||
names := cfg.Section("").KeyStrings()
|
|
||||||
```
|
|
||||||
|
|
||||||
To get a clone hash of keys and corresponding values:
|
|
||||||
|
|
||||||
```go
|
|
||||||
hash := cfg.Section("").KeysHash()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Working with values
|
|
||||||
|
|
||||||
To get a string value:
|
|
||||||
|
|
||||||
```go
|
|
||||||
val := cfg.Section("").Key("key name").String()
|
|
||||||
```
|
|
||||||
|
|
||||||
To validate key value on the fly:
|
|
||||||
|
|
||||||
```go
|
|
||||||
val := cfg.Section("").Key("key name").Validate(func(in string) string {
|
|
||||||
if len(in) == 0 {
|
|
||||||
return "default"
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
If you do not want any auto-transformation (such as recursive read) for the values, you can get raw value directly (this way you get much better performance):
|
|
||||||
|
|
||||||
```go
|
|
||||||
val := cfg.Section("").Key("key name").Value()
|
|
||||||
```
|
|
||||||
|
|
||||||
To check if raw value exists:
|
|
||||||
|
|
||||||
```go
|
|
||||||
yes := cfg.Section("").HasValue("test value")
|
|
||||||
```
|
|
||||||
|
|
||||||
To get value with types:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// For boolean values:
|
|
||||||
// true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
|
|
||||||
// false when value is: 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
|
|
||||||
v, err = cfg.Section("").Key("BOOL").Bool()
|
|
||||||
v, err = cfg.Section("").Key("FLOAT64").Float64()
|
|
||||||
v, err = cfg.Section("").Key("INT").Int()
|
|
||||||
v, err = cfg.Section("").Key("INT64").Int64()
|
|
||||||
v, err = cfg.Section("").Key("UINT").Uint()
|
|
||||||
v, err = cfg.Section("").Key("UINT64").Uint64()
|
|
||||||
v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
|
|
||||||
v, err = cfg.Section("").Key("TIME").Time() // RFC3339
|
|
||||||
|
|
||||||
v = cfg.Section("").Key("BOOL").MustBool()
|
|
||||||
v = cfg.Section("").Key("FLOAT64").MustFloat64()
|
|
||||||
v = cfg.Section("").Key("INT").MustInt()
|
|
||||||
v = cfg.Section("").Key("INT64").MustInt64()
|
|
||||||
v = cfg.Section("").Key("UINT").MustUint()
|
|
||||||
v = cfg.Section("").Key("UINT64").MustUint64()
|
|
||||||
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
|
|
||||||
v = cfg.Section("").Key("TIME").MustTime() // RFC3339
|
|
||||||
|
|
||||||
// Methods start with Must also accept one argument for default value
|
|
||||||
// when key not found or fail to parse value to given type.
|
|
||||||
// Except method MustString, which you have to pass a default value.
|
|
||||||
|
|
||||||
v = cfg.Section("").Key("String").MustString("default")
|
|
||||||
v = cfg.Section("").Key("BOOL").MustBool(true)
|
|
||||||
v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
|
|
||||||
v = cfg.Section("").Key("INT").MustInt(10)
|
|
||||||
v = cfg.Section("").Key("INT64").MustInt64(99)
|
|
||||||
v = cfg.Section("").Key("UINT").MustUint(3)
|
|
||||||
v = cfg.Section("").Key("UINT64").MustUint64(6)
|
|
||||||
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
|
|
||||||
v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
|
|
||||||
```
|
|
||||||
|
|
||||||
What if my value is three-line long?
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[advance]
|
|
||||||
ADDRESS = """404 road,
|
|
||||||
NotFound, State, 5000
|
|
||||||
Earth"""
|
|
||||||
```
|
|
||||||
|
|
||||||
Not a problem!
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("advance").Key("ADDRESS").String()
|
|
||||||
|
|
||||||
/* --- start ---
|
|
||||||
404 road,
|
|
||||||
NotFound, State, 5000
|
|
||||||
Earth
|
|
||||||
------ end --- */
|
|
||||||
```
|
|
||||||
|
|
||||||
That's cool, how about continuation lines?
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[advance]
|
|
||||||
two_lines = how about \
|
|
||||||
continuation lines?
|
|
||||||
lots_of_lines = 1 \
|
|
||||||
2 \
|
|
||||||
3 \
|
|
||||||
4
|
|
||||||
```
|
|
||||||
|
|
||||||
Piece of cake!
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
|
|
||||||
cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
|
|
||||||
```
|
|
||||||
|
|
||||||
Well, I hate continuation lines, how do I disable that?
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.LoadSources(ini.LoadOptions{
|
|
||||||
IgnoreContinuation: true,
|
|
||||||
}, "filename")
|
|
||||||
```
|
|
||||||
|
|
||||||
Holy crap!
|
|
||||||
|
|
||||||
Note that single quotes around values will be stripped:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
foo = "some value" // foo: some value
|
|
||||||
bar = 'some value' // bar: some value
|
|
||||||
```
|
|
||||||
|
|
||||||
That's all? Hmm, no.
|
|
||||||
|
|
||||||
#### Helper methods of working with values
|
|
||||||
|
|
||||||
To get value with given candidates:
|
|
||||||
|
|
||||||
```go
|
|
||||||
v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
|
|
||||||
v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
|
|
||||||
v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
|
|
||||||
v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
|
|
||||||
v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
|
|
||||||
v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
|
|
||||||
v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
|
|
||||||
v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
|
|
||||||
```
|
|
||||||
|
|
||||||
Default value will be presented if value of key is not in candidates you given, and default value does not need be one of candidates.
|
|
||||||
|
|
||||||
To validate value in a given range:
|
|
||||||
|
|
||||||
```go
|
|
||||||
vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
|
|
||||||
vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
|
|
||||||
vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
|
|
||||||
vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
|
|
||||||
vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
|
|
||||||
vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
|
|
||||||
vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Auto-split values into a slice
|
|
||||||
|
|
||||||
To use zero value of type for invalid inputs:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
|
|
||||||
// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
|
|
||||||
vals = cfg.Section("").Key("STRINGS").Strings(",")
|
|
||||||
vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
|
|
||||||
vals = cfg.Section("").Key("INTS").Ints(",")
|
|
||||||
vals = cfg.Section("").Key("INT64S").Int64s(",")
|
|
||||||
vals = cfg.Section("").Key("UINTS").Uints(",")
|
|
||||||
vals = cfg.Section("").Key("UINT64S").Uint64s(",")
|
|
||||||
vals = cfg.Section("").Key("TIMES").Times(",")
|
|
||||||
```
|
|
||||||
|
|
||||||
To exclude invalid values out of result slice:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
|
|
||||||
// Input: how, 2.2, are, you -> [2.2]
|
|
||||||
vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
|
|
||||||
vals = cfg.Section("").Key("INTS").ValidInts(",")
|
|
||||||
vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
|
|
||||||
vals = cfg.Section("").Key("UINTS").ValidUints(",")
|
|
||||||
vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
|
|
||||||
vals = cfg.Section("").Key("TIMES").ValidTimes(",")
|
|
||||||
```
|
|
||||||
|
|
||||||
Or to return nothing but error when have invalid inputs:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
|
|
||||||
// Input: how, 2.2, are, you -> error
|
|
||||||
vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
|
|
||||||
vals = cfg.Section("").Key("INTS").StrictInts(",")
|
|
||||||
vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
|
|
||||||
vals = cfg.Section("").Key("UINTS").StrictUints(",")
|
|
||||||
vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
|
|
||||||
vals = cfg.Section("").Key("TIMES").StrictTimes(",")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Save your configuration
|
|
||||||
|
|
||||||
Finally, it's time to save your configuration to somewhere.
|
|
||||||
|
|
||||||
A typical way to save configuration is writing it to a file:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// ...
|
|
||||||
err = cfg.SaveTo("my.ini")
|
|
||||||
err = cfg.SaveToIndent("my.ini", "\t")
|
|
||||||
```
|
|
||||||
|
|
||||||
Another way to save is writing to a `io.Writer` interface:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// ...
|
|
||||||
cfg.WriteTo(writer)
|
|
||||||
cfg.WriteToIndent(writer, "\t")
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, spaces are used to align "=" sign between key and values, to disable that:
|
|
||||||
|
|
||||||
```go
|
|
||||||
ini.PrettyFormat = false
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced Usage
|
|
||||||
|
|
||||||
### Recursive Values
|
|
||||||
|
|
||||||
For all value of keys, there is a special syntax `%(<name>)s`, where `<name>` is the key name in same section or default section, and `%(<name>)s` will be replaced by corresponding value(empty string if key not found). You can use this syntax at most 99 level of recursions.
|
|
||||||
|
|
||||||
```ini
|
|
||||||
NAME = ini
|
|
||||||
|
|
||||||
[author]
|
|
||||||
NAME = Unknwon
|
|
||||||
GITHUB = https://github.com/%(NAME)s
|
|
||||||
|
|
||||||
[package]
|
|
||||||
FULL_NAME = github.com/go-ini/%(NAME)s
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
|
|
||||||
cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parent-child Sections
|
|
||||||
|
|
||||||
You can use `.` in section name to indicate parent-child relationship between two or more sections. If the key not found in the child section, library will try again on its parent section until there is no parent section.
|
|
||||||
|
|
||||||
```ini
|
|
||||||
NAME = ini
|
|
||||||
VERSION = v1
|
|
||||||
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
|
|
||||||
|
|
||||||
[package]
|
|
||||||
CLONE_URL = https://%(IMPORT_PATH)s
|
|
||||||
|
|
||||||
[package.sub]
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Retrieve parent keys available to a child section
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Unparseable Sections
|
|
||||||
|
|
||||||
Sometimes, you have sections that do not contain key-value pairs but raw content, to handle such case, you can use `LoadOptions.UnparsableSections`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
|
|
||||||
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
|
|
||||||
|
|
||||||
body := cfg.Section("COMMENTS").Body()
|
|
||||||
|
|
||||||
/* --- start ---
|
|
||||||
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
|
|
||||||
------ end --- */
|
|
||||||
```
|
|
||||||
|
|
||||||
### Auto-increment Key Names
|
|
||||||
|
|
||||||
If key name is `-` in data source, then it would be seen as special syntax for auto-increment key name start from 1, and every section is independent on counter.
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[features]
|
|
||||||
-: Support read/write comments of keys and sections
|
|
||||||
-: Support auto-increment of key names
|
|
||||||
-: Support load multiple files to overwrite key values
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Map To Struct
|
|
||||||
|
|
||||||
Want more objective way to play with INI? Cool.
|
|
||||||
|
|
||||||
```ini
|
|
||||||
Name = Unknwon
|
|
||||||
age = 21
|
|
||||||
Male = true
|
|
||||||
Born = 1993-01-01T20:17:05Z
|
|
||||||
|
|
||||||
[Note]
|
|
||||||
Content = Hi is a good man!
|
|
||||||
Cities = HangZhou, Boston
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Note struct {
|
|
||||||
Content string
|
|
||||||
Cities []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Person struct {
|
|
||||||
Name string
|
|
||||||
Age int `ini:"age"`
|
|
||||||
Male bool
|
|
||||||
Born time.Time
|
|
||||||
Note
|
|
||||||
Created time.Time `ini:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cfg, err := ini.Load("path/to/ini")
|
|
||||||
// ...
|
|
||||||
p := new(Person)
|
|
||||||
err = cfg.MapTo(p)
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// Things can be simpler.
|
|
||||||
err = ini.MapTo(p, "path/to/ini")
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// Just map a section? Fine.
|
|
||||||
n := new(Note)
|
|
||||||
err = cfg.Section("Note").MapTo(n)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Can I have default value for field? Absolutely.
|
|
||||||
|
|
||||||
Assign it before you map to struct. It will keep the value as it is if the key is not presented or got wrong type.
|
|
||||||
|
|
||||||
```go
|
|
||||||
// ...
|
|
||||||
p := &Person{
|
|
||||||
Name: "Joe",
|
|
||||||
}
|
|
||||||
// ...
|
|
||||||
```
|
|
||||||
|
|
||||||
It's really cool, but what's the point if you can't give me my file back from struct?
|
|
||||||
|
|
||||||
### Reflect From Struct
|
|
||||||
|
|
||||||
Why not?
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Embeded struct {
|
|
||||||
Dates []time.Time `delim:"|"`
|
|
||||||
Places []string `ini:"places,omitempty"`
|
|
||||||
None []int `ini:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Author struct {
|
|
||||||
Name string `ini:"NAME"`
|
|
||||||
Male bool
|
|
||||||
Age int
|
|
||||||
GPA float64
|
|
||||||
NeverMind string `ini:"-"`
|
|
||||||
*Embeded
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
a := &Author{"Unknwon", true, 21, 2.8, "",
|
|
||||||
&Embeded{
|
|
||||||
[]time.Time{time.Now(), time.Now()},
|
|
||||||
[]string{"HangZhou", "Boston"},
|
|
||||||
[]int{},
|
|
||||||
}}
|
|
||||||
cfg := ini.Empty()
|
|
||||||
err = ini.ReflectFrom(cfg, a)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
So, what do I get?
|
|
||||||
|
|
||||||
```ini
|
|
||||||
NAME = Unknwon
|
|
||||||
Male = true
|
|
||||||
Age = 21
|
|
||||||
GPA = 2.8
|
|
||||||
|
|
||||||
[Embeded]
|
|
||||||
Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
|
|
||||||
places = HangZhou,Boston
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Name Mapper
|
|
||||||
|
|
||||||
To save your time and make your code cleaner, this library supports [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) between struct field and actual section and key name.
|
|
||||||
|
|
||||||
There are 2 built-in name mappers:
|
|
||||||
|
|
||||||
- `AllCapsUnderscore`: it converts to format `ALL_CAPS_UNDERSCORE` then match section or key.
|
|
||||||
- `TitleUnderscore`: it converts to format `title_underscore` then match section or key.
|
|
||||||
|
|
||||||
To use them:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Info struct {
|
|
||||||
PackageName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
|
|
||||||
// ...
|
|
||||||
|
|
||||||
cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
|
|
||||||
// ...
|
|
||||||
info := new(Info)
|
|
||||||
cfg.NameMapper = ini.AllCapsUnderscore
|
|
||||||
err = cfg.MapTo(info)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
|
|
||||||
|
|
||||||
#### Value Mapper
|
|
||||||
|
|
||||||
To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Env struct {
|
|
||||||
Foo string `ini:"foo"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
|
|
||||||
cfg.ValueMapper = os.ExpandEnv
|
|
||||||
// ...
|
|
||||||
env := &Env{}
|
|
||||||
err = cfg.Section("env").MapTo(env)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`.
|
|
||||||
|
|
||||||
#### Other Notes On Map/Reflect
|
|
||||||
|
|
||||||
Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Child struct {
|
|
||||||
Age string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Parent struct {
|
|
||||||
Name string
|
|
||||||
Child
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
City string
|
|
||||||
Parent
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Example configuration:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
City = Boston
|
|
||||||
|
|
||||||
[Parent]
|
|
||||||
Name = Unknwon
|
|
||||||
|
|
||||||
[Child]
|
|
||||||
Age = 21
|
|
||||||
```
|
|
||||||
|
|
||||||
What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Child struct {
|
|
||||||
Age string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Parent struct {
|
|
||||||
Name string
|
|
||||||
Child `ini:"Parent"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
City string
|
|
||||||
Parent
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Example configuration:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
City = Boston
|
|
||||||
|
|
||||||
[Parent]
|
|
||||||
Name = Unknwon
|
|
||||||
Age = 21
|
|
||||||
```
|
|
||||||
|
|
||||||
## Getting Help
|
## Getting Help
|
||||||
|
|
||||||
|
- [Getting Started](https://ini.unknwon.io/docs/intro/getting_started)
|
||||||
- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
|
- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
|
||||||
- [File An Issue](https://github.com/go-ini/ini/issues/new)
|
|
||||||
|
|
||||||
## FAQs
|
|
||||||
|
|
||||||
### What does `BlockMode` field do?
|
|
||||||
|
|
||||||
By default, library lets you read and write values so we need a locker to make sure your data is safe. But in cases that you are very sure about only reading data through the library, you can set `cfg.BlockMode = false` to speed up read operations about **50-70%** faster.
|
|
||||||
|
|
||||||
### Why another INI library?
|
|
||||||
|
|
||||||
Many people are using my another INI library [goconfig](https://github.com/Unknwon/goconfig), so the reason for this one is I would like to make more Go style code. Also when you set `cfg.BlockMode = false`, this one is about **10-30%** faster.
|
|
||||||
|
|
||||||
To make those changes I have to confirm API broken, so it's safer to keep it in another place and start using `gopkg.in` to version my package at this time.(PS: shorter import path)
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
733
vendor/github.com/go-ini/ini/README_ZH.md
generated
vendored
733
vendor/github.com/go-ini/ini/README_ZH.md
generated
vendored
|
@ -1,733 +0,0 @@
|
||||||
本包提供了 Go 语言中读写 INI 文件的功能。
|
|
||||||
|
|
||||||
## 功能特性
|
|
||||||
|
|
||||||
- 支持覆盖加载多个数据源(`[]byte`、文件和 `io.ReadCloser`)
|
|
||||||
- 支持递归读取键值
|
|
||||||
- 支持读取父子分区
|
|
||||||
- 支持读取自增键名
|
|
||||||
- 支持读取多行的键值
|
|
||||||
- 支持大量辅助方法
|
|
||||||
- 支持在读取时直接转换为 Go 语言类型
|
|
||||||
- 支持读取和 **写入** 分区和键的注释
|
|
||||||
- 轻松操作分区、键值和注释
|
|
||||||
- 在保存文件时分区和键值会保持原有的顺序
|
|
||||||
|
|
||||||
## 下载安装
|
|
||||||
|
|
||||||
使用一个特定版本:
|
|
||||||
|
|
||||||
go get gopkg.in/ini.v1
|
|
||||||
|
|
||||||
使用最新版:
|
|
||||||
|
|
||||||
go get github.com/go-ini/ini
|
|
||||||
|
|
||||||
如需更新请添加 `-u` 选项。
|
|
||||||
|
|
||||||
### 测试安装
|
|
||||||
|
|
||||||
如果您想要在自己的机器上运行测试,请使用 `-t` 标记:
|
|
||||||
|
|
||||||
go get -t gopkg.in/ini.v1
|
|
||||||
|
|
||||||
如需更新请添加 `-u` 选项。
|
|
||||||
|
|
||||||
## 开始使用
|
|
||||||
|
|
||||||
### 从数据源加载
|
|
||||||
|
|
||||||
一个 **数据源** 可以是 `[]byte` 类型的原始数据,`string` 类型的文件路径或 `io.ReadCloser`。您可以加载 **任意多个** 数据源。如果您传递其它类型的数据源,则会直接返回错误。
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
|
|
||||||
```
|
|
||||||
|
|
||||||
或者从一个空白的文件开始:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg := ini.Empty()
|
|
||||||
```
|
|
||||||
|
|
||||||
当您在一开始无法决定需要加载哪些数据源时,仍可以使用 **Append()** 在需要的时候加载它们。
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := cfg.Append("other file", []byte("other raw data"))
|
|
||||||
```
|
|
||||||
|
|
||||||
当您想要加载一系列文件,但是不能够确定其中哪些文件是不存在的,可以通过调用函数 `LooseLoad` 来忽略它们(`Load` 会因为文件不存在而返回错误):
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.LooseLoad("filename", "filename_404")
|
|
||||||
```
|
|
||||||
|
|
||||||
更牛逼的是,当那些之前不存在的文件在重新调用 `Reload` 方法的时候突然出现了,那么它们会被正常加载。
|
|
||||||
|
|
||||||
#### 忽略键名的大小写
|
|
||||||
|
|
||||||
有时候分区和键的名称大小写混合非常烦人,这个时候就可以通过 `InsensitiveLoad` 将所有分区和键名在读取里强制转换为小写:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.InsensitiveLoad("filename")
|
|
||||||
//...
|
|
||||||
|
|
||||||
// sec1 和 sec2 指向同一个分区对象
|
|
||||||
sec1, err := cfg.GetSection("Section")
|
|
||||||
sec2, err := cfg.GetSection("SecTIOn")
|
|
||||||
|
|
||||||
// key1 和 key2 指向同一个键对象
|
|
||||||
key1, err := sec1.GetKey("Key")
|
|
||||||
key2, err := sec2.GetKey("KeY")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 类似 MySQL 配置中的布尔值键
|
|
||||||
|
|
||||||
MySQL 的配置文件中会出现没有具体值的布尔类型的键:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[mysqld]
|
|
||||||
...
|
|
||||||
skip-host-cache
|
|
||||||
skip-name-resolve
|
|
||||||
```
|
|
||||||
|
|
||||||
默认情况下这被认为是缺失值而无法完成解析,但可以通过高级的加载选项对它们进行处理:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
|
|
||||||
```
|
|
||||||
|
|
||||||
这些键的值永远为 `true`,且在保存到文件时也只会输出键名。
|
|
||||||
|
|
||||||
如果您想要通过程序来生成此类键,则可以使用 `NewBooleanKey`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
key, err := sec.NewBooleanKey("skip-host-cache")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 关于注释
|
|
||||||
|
|
||||||
下述几种情况的内容将被视为注释:
|
|
||||||
|
|
||||||
1. 所有以 `#` 或 `;` 开头的行
|
|
||||||
2. 所有在 `#` 或 `;` 之后的内容
|
|
||||||
3. 分区标签后的文字 (即 `[分区名]` 之后的内容)
|
|
||||||
|
|
||||||
如果你希望使用包含 `#` 或 `;` 的值,请使用 ``` ` ``` 或 ``` """ ``` 进行包覆。
|
|
||||||
|
|
||||||
除此之外,您还可以通过 `LoadOptions` 完全忽略行内注释:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, "app.ini"))
|
|
||||||
```
|
|
||||||
|
|
||||||
### 操作分区(Section)
|
|
||||||
|
|
||||||
获取指定分区:
|
|
||||||
|
|
||||||
```go
|
|
||||||
section, err := cfg.GetSection("section name")
|
|
||||||
```
|
|
||||||
|
|
||||||
如果您想要获取默认分区,则可以用空字符串代替分区名:
|
|
||||||
|
|
||||||
```go
|
|
||||||
section, err := cfg.GetSection("")
|
|
||||||
```
|
|
||||||
|
|
||||||
当您非常确定某个分区是存在的,可以使用以下简便方法:
|
|
||||||
|
|
||||||
```go
|
|
||||||
section := cfg.Section("section name")
|
|
||||||
```
|
|
||||||
|
|
||||||
如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。
|
|
||||||
|
|
||||||
创建一个分区:
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := cfg.NewSection("new section")
|
|
||||||
```
|
|
||||||
|
|
||||||
获取所有分区对象或名称:
|
|
||||||
|
|
||||||
```go
|
|
||||||
sections := cfg.Sections()
|
|
||||||
names := cfg.SectionStrings()
|
|
||||||
```
|
|
||||||
|
|
||||||
### 操作键(Key)
|
|
||||||
|
|
||||||
获取某个分区下的键:
|
|
||||||
|
|
||||||
```go
|
|
||||||
key, err := cfg.Section("").GetKey("key name")
|
|
||||||
```
|
|
||||||
|
|
||||||
和分区一样,您也可以直接获取键而忽略错误处理:
|
|
||||||
|
|
||||||
```go
|
|
||||||
key := cfg.Section("").Key("key name")
|
|
||||||
```
|
|
||||||
|
|
||||||
判断某个键是否存在:
|
|
||||||
|
|
||||||
```go
|
|
||||||
yes := cfg.Section("").HasKey("key name")
|
|
||||||
```
|
|
||||||
|
|
||||||
创建一个新的键:
|
|
||||||
|
|
||||||
```go
|
|
||||||
err := cfg.Section("").NewKey("name", "value")
|
|
||||||
```
|
|
||||||
|
|
||||||
获取分区下的所有键或键名:
|
|
||||||
|
|
||||||
```go
|
|
||||||
keys := cfg.Section("").Keys()
|
|
||||||
names := cfg.Section("").KeyStrings()
|
|
||||||
```
|
|
||||||
|
|
||||||
获取分区下的所有键值对的克隆:
|
|
||||||
|
|
||||||
```go
|
|
||||||
hash := cfg.Section("").KeysHash()
|
|
||||||
```
|
|
||||||
|
|
||||||
### 操作键值(Value)
|
|
||||||
|
|
||||||
获取一个类型为字符串(string)的值:
|
|
||||||
|
|
||||||
```go
|
|
||||||
val := cfg.Section("").Key("key name").String()
|
|
||||||
```
|
|
||||||
|
|
||||||
获取值的同时通过自定义函数进行处理验证:
|
|
||||||
|
|
||||||
```go
|
|
||||||
val := cfg.Section("").Key("key name").Validate(func(in string) string {
|
|
||||||
if len(in) == 0 {
|
|
||||||
return "default"
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
如果您不需要任何对值的自动转变功能(例如递归读取),可以直接获取原值(这种方式性能最佳):
|
|
||||||
|
|
||||||
```go
|
|
||||||
val := cfg.Section("").Key("key name").Value()
|
|
||||||
```
|
|
||||||
|
|
||||||
判断某个原值是否存在:
|
|
||||||
|
|
||||||
```go
|
|
||||||
yes := cfg.Section("").HasValue("test value")
|
|
||||||
```
|
|
||||||
|
|
||||||
获取其它类型的值:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// 布尔值的规则:
|
|
||||||
// true 当值为:1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
|
|
||||||
// false 当值为:0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
|
|
||||||
v, err = cfg.Section("").Key("BOOL").Bool()
|
|
||||||
v, err = cfg.Section("").Key("FLOAT64").Float64()
|
|
||||||
v, err = cfg.Section("").Key("INT").Int()
|
|
||||||
v, err = cfg.Section("").Key("INT64").Int64()
|
|
||||||
v, err = cfg.Section("").Key("UINT").Uint()
|
|
||||||
v, err = cfg.Section("").Key("UINT64").Uint64()
|
|
||||||
v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
|
|
||||||
v, err = cfg.Section("").Key("TIME").Time() // RFC3339
|
|
||||||
|
|
||||||
v = cfg.Section("").Key("BOOL").MustBool()
|
|
||||||
v = cfg.Section("").Key("FLOAT64").MustFloat64()
|
|
||||||
v = cfg.Section("").Key("INT").MustInt()
|
|
||||||
v = cfg.Section("").Key("INT64").MustInt64()
|
|
||||||
v = cfg.Section("").Key("UINT").MustUint()
|
|
||||||
v = cfg.Section("").Key("UINT64").MustUint64()
|
|
||||||
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
|
|
||||||
v = cfg.Section("").Key("TIME").MustTime() // RFC3339
|
|
||||||
|
|
||||||
// 由 Must 开头的方法名允许接收一个相同类型的参数来作为默认值,
|
|
||||||
// 当键不存在或者转换失败时,则会直接返回该默认值。
|
|
||||||
// 但是,MustString 方法必须传递一个默认值。
|
|
||||||
|
|
||||||
v = cfg.Seciont("").Key("String").MustString("default")
|
|
||||||
v = cfg.Section("").Key("BOOL").MustBool(true)
|
|
||||||
v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
|
|
||||||
v = cfg.Section("").Key("INT").MustInt(10)
|
|
||||||
v = cfg.Section("").Key("INT64").MustInt64(99)
|
|
||||||
v = cfg.Section("").Key("UINT").MustUint(3)
|
|
||||||
v = cfg.Section("").Key("UINT64").MustUint64(6)
|
|
||||||
v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
|
|
||||||
v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
|
|
||||||
```
|
|
||||||
|
|
||||||
如果我的值有好多行怎么办?
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[advance]
|
|
||||||
ADDRESS = """404 road,
|
|
||||||
NotFound, State, 5000
|
|
||||||
Earth"""
|
|
||||||
```
|
|
||||||
|
|
||||||
嗯哼?小 case!
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("advance").Key("ADDRESS").String()
|
|
||||||
|
|
||||||
/* --- start ---
|
|
||||||
404 road,
|
|
||||||
NotFound, State, 5000
|
|
||||||
Earth
|
|
||||||
------ end --- */
|
|
||||||
```
|
|
||||||
|
|
||||||
赞爆了!那要是我属于一行的内容写不下想要写到第二行怎么办?
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[advance]
|
|
||||||
two_lines = how about \
|
|
||||||
continuation lines?
|
|
||||||
lots_of_lines = 1 \
|
|
||||||
2 \
|
|
||||||
3 \
|
|
||||||
4
|
|
||||||
```
|
|
||||||
|
|
||||||
简直是小菜一碟!
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
|
|
||||||
cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
|
|
||||||
```
|
|
||||||
|
|
||||||
可是我有时候觉得两行连在一起特别没劲,怎么才能不自动连接两行呢?
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := ini.LoadSources(ini.LoadOptions{
|
|
||||||
IgnoreContinuation: true,
|
|
||||||
}, "filename")
|
|
||||||
```
|
|
||||||
|
|
||||||
哇靠给力啊!
|
|
||||||
|
|
||||||
需要注意的是,值两侧的单引号会被自动剔除:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
foo = "some value" // foo: some value
|
|
||||||
bar = 'some value' // bar: some value
|
|
||||||
```
|
|
||||||
|
|
||||||
这就是全部了?哈哈,当然不是。
|
|
||||||
|
|
||||||
#### 操作键值的辅助方法
|
|
||||||
|
|
||||||
获取键值时设定候选值:
|
|
||||||
|
|
||||||
```go
|
|
||||||
v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
|
|
||||||
v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
|
|
||||||
v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
|
|
||||||
v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
|
|
||||||
v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
|
|
||||||
v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
|
|
||||||
v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
|
|
||||||
v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
|
|
||||||
```
|
|
||||||
|
|
||||||
如果获取到的值不是候选值的任意一个,则会返回默认值,而默认值不需要是候选值中的一员。
|
|
||||||
|
|
||||||
验证获取的值是否在指定范围内:
|
|
||||||
|
|
||||||
```go
|
|
||||||
vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
|
|
||||||
vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
|
|
||||||
vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
|
|
||||||
vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
|
|
||||||
vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
|
|
||||||
vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
|
|
||||||
vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
|
|
||||||
```
|
|
||||||
|
|
||||||
##### 自动分割键值到切片(slice)
|
|
||||||
|
|
||||||
当存在无效输入时,使用零值代替:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
|
|
||||||
// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
|
|
||||||
vals = cfg.Section("").Key("STRINGS").Strings(",")
|
|
||||||
vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
|
|
||||||
vals = cfg.Section("").Key("INTS").Ints(",")
|
|
||||||
vals = cfg.Section("").Key("INT64S").Int64s(",")
|
|
||||||
vals = cfg.Section("").Key("UINTS").Uints(",")
|
|
||||||
vals = cfg.Section("").Key("UINT64S").Uint64s(",")
|
|
||||||
vals = cfg.Section("").Key("TIMES").Times(",")
|
|
||||||
```
|
|
||||||
|
|
||||||
从结果切片中剔除无效输入:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
|
|
||||||
// Input: how, 2.2, are, you -> [2.2]
|
|
||||||
vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
|
|
||||||
vals = cfg.Section("").Key("INTS").ValidInts(",")
|
|
||||||
vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
|
|
||||||
vals = cfg.Section("").Key("UINTS").ValidUints(",")
|
|
||||||
vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
|
|
||||||
vals = cfg.Section("").Key("TIMES").ValidTimes(",")
|
|
||||||
```
|
|
||||||
|
|
||||||
当存在无效输入时,直接返回错误:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
|
|
||||||
// Input: how, 2.2, are, you -> error
|
|
||||||
vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
|
|
||||||
vals = cfg.Section("").Key("INTS").StrictInts(",")
|
|
||||||
vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
|
|
||||||
vals = cfg.Section("").Key("UINTS").StrictUints(",")
|
|
||||||
vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
|
|
||||||
vals = cfg.Section("").Key("TIMES").StrictTimes(",")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 保存配置
|
|
||||||
|
|
||||||
终于到了这个时刻,是时候保存一下配置了。
|
|
||||||
|
|
||||||
比较原始的做法是输出配置到某个文件:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// ...
|
|
||||||
err = cfg.SaveTo("my.ini")
|
|
||||||
err = cfg.SaveToIndent("my.ini", "\t")
|
|
||||||
```
|
|
||||||
|
|
||||||
另一个比较高级的做法是写入到任何实现 `io.Writer` 接口的对象中:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// ...
|
|
||||||
cfg.WriteTo(writer)
|
|
||||||
cfg.WriteToIndent(writer, "\t")
|
|
||||||
```
|
|
||||||
|
|
||||||
默认情况下,空格将被用于对齐键值之间的等号以美化输出结果,以下代码可以禁用该功能:
|
|
||||||
|
|
||||||
```go
|
|
||||||
ini.PrettyFormat = false
|
|
||||||
```
|
|
||||||
|
|
||||||
## 高级用法
|
|
||||||
|
|
||||||
### 递归读取键值
|
|
||||||
|
|
||||||
在获取所有键值的过程中,特殊语法 `%(<name>)s` 会被应用,其中 `<name>` 可以是相同分区或者默认分区下的键名。字符串 `%(<name>)s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。
|
|
||||||
|
|
||||||
```ini
|
|
||||||
NAME = ini
|
|
||||||
|
|
||||||
[author]
|
|
||||||
NAME = Unknwon
|
|
||||||
GITHUB = https://github.com/%(NAME)s
|
|
||||||
|
|
||||||
[package]
|
|
||||||
FULL_NAME = github.com/go-ini/%(NAME)s
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
|
|
||||||
cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
|
|
||||||
```
|
|
||||||
|
|
||||||
### 读取父子分区
|
|
||||||
|
|
||||||
您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。
|
|
||||||
|
|
||||||
```ini
|
|
||||||
NAME = ini
|
|
||||||
VERSION = v1
|
|
||||||
IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
|
|
||||||
|
|
||||||
[package]
|
|
||||||
CLONE_URL = https://%(IMPORT_PATH)s
|
|
||||||
|
|
||||||
[package.sub]
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 获取上级父分区下的所有键名
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 无法解析的分区
|
|
||||||
|
|
||||||
如果遇到一些比较特殊的分区,它们不包含常见的键值对,而是没有固定格式的纯文本,则可以使用 `LoadOptions.UnparsableSections` 进行处理:
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
|
|
||||||
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
|
|
||||||
|
|
||||||
body := cfg.Section("COMMENTS").Body()
|
|
||||||
|
|
||||||
/* --- start ---
|
|
||||||
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
|
|
||||||
------ end --- */
|
|
||||||
```
|
|
||||||
|
|
||||||
### 读取自增键名
|
|
||||||
|
|
||||||
如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[features]
|
|
||||||
-: Support read/write comments of keys and sections
|
|
||||||
-: Support auto-increment of key names
|
|
||||||
-: Support load multiple files to overwrite key values
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 映射到结构
|
|
||||||
|
|
||||||
想要使用更加面向对象的方式玩转 INI 吗?好主意。
|
|
||||||
|
|
||||||
```ini
|
|
||||||
Name = Unknwon
|
|
||||||
age = 21
|
|
||||||
Male = true
|
|
||||||
Born = 1993-01-01T20:17:05Z
|
|
||||||
|
|
||||||
[Note]
|
|
||||||
Content = Hi is a good man!
|
|
||||||
Cities = HangZhou, Boston
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Note struct {
|
|
||||||
Content string
|
|
||||||
Cities []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Person struct {
|
|
||||||
Name string
|
|
||||||
Age int `ini:"age"`
|
|
||||||
Male bool
|
|
||||||
Born time.Time
|
|
||||||
Note
|
|
||||||
Created time.Time `ini:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cfg, err := ini.Load("path/to/ini")
|
|
||||||
// ...
|
|
||||||
p := new(Person)
|
|
||||||
err = cfg.MapTo(p)
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// 一切竟可以如此的简单。
|
|
||||||
err = ini.MapTo(p, "path/to/ini")
|
|
||||||
// ...
|
|
||||||
|
|
||||||
// 嗯哼?只需要映射一个分区吗?
|
|
||||||
n := new(Note)
|
|
||||||
err = cfg.Section("Note").MapTo(n)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
结构的字段怎么设置默认值呢?很简单,只要在映射之前对指定字段进行赋值就可以了。如果键未找到或者类型错误,该值不会发生改变。
|
|
||||||
|
|
||||||
```go
|
|
||||||
// ...
|
|
||||||
p := &Person{
|
|
||||||
Name: "Joe",
|
|
||||||
}
|
|
||||||
// ...
|
|
||||||
```
|
|
||||||
|
|
||||||
这样玩 INI 真的好酷啊!然而,如果不能还给我原来的配置文件,有什么卵用?
|
|
||||||
|
|
||||||
### 从结构反射
|
|
||||||
|
|
||||||
可是,我有说不能吗?
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Embeded struct {
|
|
||||||
Dates []time.Time `delim:"|"`
|
|
||||||
Places []string `ini:"places,omitempty"`
|
|
||||||
None []int `ini:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Author struct {
|
|
||||||
Name string `ini:"NAME"`
|
|
||||||
Male bool
|
|
||||||
Age int
|
|
||||||
GPA float64
|
|
||||||
NeverMind string `ini:"-"`
|
|
||||||
*Embeded
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
a := &Author{"Unknwon", true, 21, 2.8, "",
|
|
||||||
&Embeded{
|
|
||||||
[]time.Time{time.Now(), time.Now()},
|
|
||||||
[]string{"HangZhou", "Boston"},
|
|
||||||
[]int{},
|
|
||||||
}}
|
|
||||||
cfg := ini.Empty()
|
|
||||||
err = ini.ReflectFrom(cfg, a)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
瞧瞧,奇迹发生了。
|
|
||||||
|
|
||||||
```ini
|
|
||||||
NAME = Unknwon
|
|
||||||
Male = true
|
|
||||||
Age = 21
|
|
||||||
GPA = 2.8
|
|
||||||
|
|
||||||
[Embeded]
|
|
||||||
Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
|
|
||||||
places = HangZhou,Boston
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 名称映射器(Name Mapper)
|
|
||||||
|
|
||||||
为了节省您的时间并简化代码,本库支持类型为 [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。
|
|
||||||
|
|
||||||
目前有 2 款内置的映射器:
|
|
||||||
|
|
||||||
- `AllCapsUnderscore`:该映射器将字段名转换至格式 `ALL_CAPS_UNDERSCORE` 后再去匹配分区名和键名。
|
|
||||||
- `TitleUnderscore`:该映射器将字段名转换至格式 `title_underscore` 后再去匹配分区名和键名。
|
|
||||||
|
|
||||||
使用方法:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Info struct{
|
|
||||||
PackageName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
|
|
||||||
// ...
|
|
||||||
|
|
||||||
cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
|
|
||||||
// ...
|
|
||||||
info := new(Info)
|
|
||||||
cfg.NameMapper = ini.AllCapsUnderscore
|
|
||||||
err = cfg.MapTo(info)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
使用函数 `ini.ReflectFromWithMapper` 时也可应用相同的规则。
|
|
||||||
|
|
||||||
#### 值映射器(Value Mapper)
|
|
||||||
|
|
||||||
值映射器允许使用一个自定义函数自动展开值的具体内容,例如:运行时获取环境变量:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Env struct {
|
|
||||||
Foo string `ini:"foo"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
|
|
||||||
cfg.ValueMapper = os.ExpandEnv
|
|
||||||
// ...
|
|
||||||
env := &Env{}
|
|
||||||
err = cfg.Section("env").MapTo(env)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
本例中,`env.Foo` 将会是运行时所获取到环境变量 `MY_VAR` 的值。
|
|
||||||
|
|
||||||
#### 映射/反射的其它说明
|
|
||||||
|
|
||||||
任何嵌入的结构都会被默认认作一个不同的分区,并且不会自动产生所谓的父子分区关联:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Child struct {
|
|
||||||
Age string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Parent struct {
|
|
||||||
Name string
|
|
||||||
Child
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
City string
|
|
||||||
Parent
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
示例配置文件:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
City = Boston
|
|
||||||
|
|
||||||
[Parent]
|
|
||||||
Name = Unknwon
|
|
||||||
|
|
||||||
[Child]
|
|
||||||
Age = 21
|
|
||||||
```
|
|
||||||
|
|
||||||
很好,但是,我就是要嵌入结构也在同一个分区。好吧,你爹是李刚!
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Child struct {
|
|
||||||
Age string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Parent struct {
|
|
||||||
Name string
|
|
||||||
Child `ini:"Parent"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
City string
|
|
||||||
Parent
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
示例配置文件:
|
|
||||||
|
|
||||||
```ini
|
|
||||||
City = Boston
|
|
||||||
|
|
||||||
[Parent]
|
|
||||||
Name = Unknwon
|
|
||||||
Age = 21
|
|
||||||
```
|
|
||||||
|
|
||||||
## 获取帮助
|
|
||||||
|
|
||||||
- [API 文档](https://gowalker.org/gopkg.in/ini.v1)
|
|
||||||
- [创建工单](https://github.com/go-ini/ini/issues/new)
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
### 字段 `BlockMode` 是什么?
|
|
||||||
|
|
||||||
默认情况下,本库会在您进行读写操作时采用锁机制来确保数据时间。但在某些情况下,您非常确定只进行读操作。此时,您可以通过设置 `cfg.BlockMode = false` 来将读操作提升大约 **50-70%** 的性能。
|
|
||||||
|
|
||||||
### 为什么要写另一个 INI 解析库?
|
|
||||||
|
|
||||||
许多人都在使用我的 [goconfig](https://github.com/Unknwon/goconfig) 来完成对 INI 文件的操作,但我希望使用更加 Go 风格的代码。并且当您设置 `cfg.BlockMode = false` 时,会有大约 **10-30%** 的性能提升。
|
|
||||||
|
|
||||||
为了做出这些改变,我必须对 API 进行破坏,所以新开一个仓库是最安全的做法。除此之外,本库直接使用 `gopkg.in` 来进行版本化发布。(其实真相是导入路径更短了)
|
|
403
vendor/github.com/go-ini/ini/ini.go
generated
vendored
403
vendor/github.com/go-ini/ini/ini.go
generated
vendored
|
@ -1,3 +1,5 @@
|
||||||
|
// +build go1.6
|
||||||
|
|
||||||
// Copyright 2014 Unknwon
|
// Copyright 2014 Unknwon
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||||
|
@ -17,15 +19,12 @@ package ini
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -35,7 +34,7 @@ const (
|
||||||
|
|
||||||
// Maximum allowed depth when recursively substituing variable names.
|
// Maximum allowed depth when recursively substituing variable names.
|
||||||
_DEPTH_VALUES = 99
|
_DEPTH_VALUES = 99
|
||||||
_VERSION = "1.28.2"
|
_VERSION = "1.38.1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version returns current package version literal.
|
// Version returns current package version literal.
|
||||||
|
@ -56,6 +55,9 @@ var (
|
||||||
// or reduce all possible spaces for compact format.
|
// or reduce all possible spaces for compact format.
|
||||||
PrettyFormat = true
|
PrettyFormat = true
|
||||||
|
|
||||||
|
// Place spaces around "=" sign even when PrettyFormat is false
|
||||||
|
PrettyEqual = false
|
||||||
|
|
||||||
// Explicitly write DEFAULT section header
|
// Explicitly write DEFAULT section header
|
||||||
DefaultHeader = false
|
DefaultHeader = false
|
||||||
|
|
||||||
|
@ -92,18 +94,6 @@ func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
|
||||||
return os.Open(s.name)
|
return os.Open(s.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
type bytesReadCloser struct {
|
|
||||||
reader io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rc *bytesReadCloser) Read(p []byte) (n int, err error) {
|
|
||||||
return rc.reader.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rc *bytesReadCloser) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sourceData represents an object that contains content in memory.
|
// sourceData represents an object that contains content in memory.
|
||||||
type sourceData struct {
|
type sourceData struct {
|
||||||
data []byte
|
data []byte
|
||||||
|
@ -122,38 +112,6 @@ func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
|
||||||
return s.reader, nil
|
return s.reader, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// File represents a combination of a or more INI file(s) in memory.
|
|
||||||
type File struct {
|
|
||||||
// Should make things safe, but sometimes doesn't matter.
|
|
||||||
BlockMode bool
|
|
||||||
// Make sure data is safe in multiple goroutines.
|
|
||||||
lock sync.RWMutex
|
|
||||||
|
|
||||||
// Allow combination of multiple data sources.
|
|
||||||
dataSources []dataSource
|
|
||||||
// Actual data is stored here.
|
|
||||||
sections map[string]*Section
|
|
||||||
|
|
||||||
// To keep data in order.
|
|
||||||
sectionList []string
|
|
||||||
|
|
||||||
options LoadOptions
|
|
||||||
|
|
||||||
NameMapper
|
|
||||||
ValueMapper
|
|
||||||
}
|
|
||||||
|
|
||||||
// newFile initializes File object with given data sources.
|
|
||||||
func newFile(dataSources []dataSource, opts LoadOptions) *File {
|
|
||||||
return &File{
|
|
||||||
BlockMode: true,
|
|
||||||
dataSources: dataSources,
|
|
||||||
sections: make(map[string]*Section),
|
|
||||||
sectionList: make([]string, 0, 10),
|
|
||||||
options: opts,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDataSource(source interface{}) (dataSource, error) {
|
func parseDataSource(source interface{}) (dataSource, error) {
|
||||||
switch s := source.(type) {
|
switch s := source.(type) {
|
||||||
case string:
|
case string:
|
||||||
|
@ -176,12 +134,34 @@ type LoadOptions struct {
|
||||||
IgnoreContinuation bool
|
IgnoreContinuation bool
|
||||||
// IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
|
// IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
|
||||||
IgnoreInlineComment bool
|
IgnoreInlineComment bool
|
||||||
|
// SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
|
||||||
|
SkipUnrecognizableLines bool
|
||||||
// AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
|
// AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
|
||||||
// This type of keys are mostly used in my.cnf.
|
// This type of keys are mostly used in my.cnf.
|
||||||
AllowBooleanKeys bool
|
AllowBooleanKeys bool
|
||||||
// AllowShadows indicates whether to keep track of keys with same name under same section.
|
// AllowShadows indicates whether to keep track of keys with same name under same section.
|
||||||
AllowShadows bool
|
AllowShadows bool
|
||||||
// Some INI formats allow group blocks that store a block of raw content that doesn't otherwise
|
// AllowNestedValues indicates whether to allow AWS-like nested values.
|
||||||
|
// Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values
|
||||||
|
AllowNestedValues bool
|
||||||
|
// AllowPythonMultilineValues indicates whether to allow Python-like multi-line values.
|
||||||
|
// Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
|
||||||
|
// Relevant quote: Values can also span multiple lines, as long as they are indented deeper
|
||||||
|
// than the first line of the value.
|
||||||
|
AllowPythonMultilineValues bool
|
||||||
|
// SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value.
|
||||||
|
// Docs: https://docs.python.org/2/library/configparser.html
|
||||||
|
// Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names.
|
||||||
|
// In the latter case, they need to be preceded by a whitespace character to be recognized as a comment.
|
||||||
|
SpaceBeforeInlineComment bool
|
||||||
|
// UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format
|
||||||
|
// when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value"
|
||||||
|
UnescapeValueDoubleQuotes bool
|
||||||
|
// UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format
|
||||||
|
// when value is NOT surrounded by any quotes.
|
||||||
|
// Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all.
|
||||||
|
UnescapeValueCommentSymbols bool
|
||||||
|
// UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise
|
||||||
// conform to key/value pairs. Specify the names of those blocks here.
|
// conform to key/value pairs. Specify the names of those blocks here.
|
||||||
UnparseableSections []string
|
UnparseableSections []string
|
||||||
}
|
}
|
||||||
|
@ -229,328 +209,3 @@ func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
|
||||||
func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
|
func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
|
||||||
return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
|
return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty returns an empty file object.
|
|
||||||
func Empty() *File {
|
|
||||||
// Ignore error here, we sure our data is good.
|
|
||||||
f, _ := Load([]byte(""))
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSection creates a new section.
|
|
||||||
func (f *File) NewSection(name string) (*Section, error) {
|
|
||||||
if len(name) == 0 {
|
|
||||||
return nil, errors.New("error creating new section: empty section name")
|
|
||||||
} else if f.options.Insensitive && name != DEFAULT_SECTION {
|
|
||||||
name = strings.ToLower(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.BlockMode {
|
|
||||||
f.lock.Lock()
|
|
||||||
defer f.lock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if inSlice(name, f.sectionList) {
|
|
||||||
return f.sections[name], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
f.sectionList = append(f.sectionList, name)
|
|
||||||
f.sections[name] = newSection(f, name)
|
|
||||||
return f.sections[name], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRawSection creates a new section with an unparseable body.
|
|
||||||
func (f *File) NewRawSection(name, body string) (*Section, error) {
|
|
||||||
section, err := f.NewSection(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
section.isRawSection = true
|
|
||||||
section.rawBody = body
|
|
||||||
return section, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSections creates a list of sections.
|
|
||||||
func (f *File) NewSections(names ...string) (err error) {
|
|
||||||
for _, name := range names {
|
|
||||||
if _, err = f.NewSection(name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSection returns section by given name.
|
|
||||||
func (f *File) GetSection(name string) (*Section, error) {
|
|
||||||
if len(name) == 0 {
|
|
||||||
name = DEFAULT_SECTION
|
|
||||||
} else if f.options.Insensitive {
|
|
||||||
name = strings.ToLower(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.BlockMode {
|
|
||||||
f.lock.RLock()
|
|
||||||
defer f.lock.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
sec := f.sections[name]
|
|
||||||
if sec == nil {
|
|
||||||
return nil, fmt.Errorf("section '%s' does not exist", name)
|
|
||||||
}
|
|
||||||
return sec, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section assumes named section exists and returns a zero-value when not.
|
|
||||||
func (f *File) Section(name string) *Section {
|
|
||||||
sec, err := f.GetSection(name)
|
|
||||||
if err != nil {
|
|
||||||
// Note: It's OK here because the only possible error is empty section name,
|
|
||||||
// but if it's empty, this piece of code won't be executed.
|
|
||||||
sec, _ = f.NewSection(name)
|
|
||||||
return sec
|
|
||||||
}
|
|
||||||
return sec
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section returns list of Section.
|
|
||||||
func (f *File) Sections() []*Section {
|
|
||||||
sections := make([]*Section, len(f.sectionList))
|
|
||||||
for i := range f.sectionList {
|
|
||||||
sections[i] = f.Section(f.sectionList[i])
|
|
||||||
}
|
|
||||||
return sections
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChildSections returns a list of child sections of given section name.
|
|
||||||
func (f *File) ChildSections(name string) []*Section {
|
|
||||||
return f.Section(name).ChildSections()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SectionStrings returns list of section names.
|
|
||||||
func (f *File) SectionStrings() []string {
|
|
||||||
list := make([]string, len(f.sectionList))
|
|
||||||
copy(list, f.sectionList)
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteSection deletes a section.
|
|
||||||
func (f *File) DeleteSection(name string) {
|
|
||||||
if f.BlockMode {
|
|
||||||
f.lock.Lock()
|
|
||||||
defer f.lock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(name) == 0 {
|
|
||||||
name = DEFAULT_SECTION
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, s := range f.sectionList {
|
|
||||||
if s == name {
|
|
||||||
f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
|
|
||||||
delete(f.sections, name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) reload(s dataSource) error {
|
|
||||||
r, err := s.ReadCloser()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
return f.parse(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload reloads and parses all data sources.
|
|
||||||
func (f *File) Reload() (err error) {
|
|
||||||
for _, s := range f.dataSources {
|
|
||||||
if err = f.reload(s); err != nil {
|
|
||||||
// In loose mode, we create an empty default section for nonexistent files.
|
|
||||||
if os.IsNotExist(err) && f.options.Loose {
|
|
||||||
f.parse(bytes.NewBuffer(nil))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append appends one or more data sources and reloads automatically.
|
|
||||||
func (f *File) Append(source interface{}, others ...interface{}) error {
|
|
||||||
ds, err := parseDataSource(source)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.dataSources = append(f.dataSources, ds)
|
|
||||||
for _, s := range others {
|
|
||||||
ds, err = parseDataSource(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.dataSources = append(f.dataSources, ds)
|
|
||||||
}
|
|
||||||
return f.Reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
|
|
||||||
equalSign := "="
|
|
||||||
if PrettyFormat {
|
|
||||||
equalSign = " = "
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use buffer to make sure target is safe until finish encoding.
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
for i, sname := range f.sectionList {
|
|
||||||
sec := f.Section(sname)
|
|
||||||
if len(sec.Comment) > 0 {
|
|
||||||
if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
|
|
||||||
sec.Comment = "; " + sec.Comment
|
|
||||||
}
|
|
||||||
if _, err := buf.WriteString(sec.Comment + LineBreak); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if i > 0 || DefaultHeader {
|
|
||||||
if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Write nothing if default section is empty
|
|
||||||
if len(sec.keyList) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sec.isRawSection {
|
|
||||||
if _, err := buf.WriteString(sec.rawBody); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count and generate alignment length and buffer spaces using the
|
|
||||||
// longest key. Keys may be modifed if they contain certain characters so
|
|
||||||
// we need to take that into account in our calculation.
|
|
||||||
alignLength := 0
|
|
||||||
if PrettyFormat {
|
|
||||||
for _, kname := range sec.keyList {
|
|
||||||
keyLength := len(kname)
|
|
||||||
// First case will surround key by ` and second by """
|
|
||||||
if strings.ContainsAny(kname, "\"=:") {
|
|
||||||
keyLength += 2
|
|
||||||
} else if strings.Contains(kname, "`") {
|
|
||||||
keyLength += 6
|
|
||||||
}
|
|
||||||
|
|
||||||
if keyLength > alignLength {
|
|
||||||
alignLength = keyLength
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
alignSpaces := bytes.Repeat([]byte(" "), alignLength)
|
|
||||||
|
|
||||||
KEY_LIST:
|
|
||||||
for _, kname := range sec.keyList {
|
|
||||||
key := sec.Key(kname)
|
|
||||||
if len(key.Comment) > 0 {
|
|
||||||
if len(indent) > 0 && sname != DEFAULT_SECTION {
|
|
||||||
buf.WriteString(indent)
|
|
||||||
}
|
|
||||||
if key.Comment[0] != '#' && key.Comment[0] != ';' {
|
|
||||||
key.Comment = "; " + key.Comment
|
|
||||||
}
|
|
||||||
if _, err := buf.WriteString(key.Comment + LineBreak); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(indent) > 0 && sname != DEFAULT_SECTION {
|
|
||||||
buf.WriteString(indent)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case key.isAutoIncrement:
|
|
||||||
kname = "-"
|
|
||||||
case strings.ContainsAny(kname, "\"=:"):
|
|
||||||
kname = "`" + kname + "`"
|
|
||||||
case strings.Contains(kname, "`"):
|
|
||||||
kname = `"""` + kname + `"""`
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, val := range key.ValueWithShadows() {
|
|
||||||
if _, err := buf.WriteString(kname); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.isBooleanType {
|
|
||||||
if kname != sec.keyList[len(sec.keyList)-1] {
|
|
||||||
buf.WriteString(LineBreak)
|
|
||||||
}
|
|
||||||
continue KEY_LIST
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write out alignment spaces before "=" sign
|
|
||||||
if PrettyFormat {
|
|
||||||
buf.Write(alignSpaces[:alignLength-len(kname)])
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case key value contains "\n", "`", "\"", "#" or ";"
|
|
||||||
if strings.ContainsAny(val, "\n`") {
|
|
||||||
val = `"""` + val + `"""`
|
|
||||||
} else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
|
|
||||||
val = "`" + val + "`"
|
|
||||||
}
|
|
||||||
if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if PrettySection {
|
|
||||||
// Put a line between sections
|
|
||||||
if _, err := buf.WriteString(LineBreak); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteToIndent writes content into io.Writer with given indention.
|
|
||||||
// If PrettyFormat has been set to be true,
|
|
||||||
// it will align "=" sign with spaces under each section.
|
|
||||||
func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) {
|
|
||||||
buf, err := f.writeToBuffer(indent)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return buf.WriteTo(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteTo writes file content into io.Writer.
|
|
||||||
func (f *File) WriteTo(w io.Writer) (int64, error) {
|
|
||||||
return f.WriteToIndent(w, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveToIndent writes content to file system with given value indention.
|
|
||||||
func (f *File) SaveToIndent(filename, indent string) error {
|
|
||||||
// Note: Because we are truncating with os.Create,
|
|
||||||
// so it's safer to save to a temporary file location and rename afte done.
|
|
||||||
buf, err := f.writeToBuffer(indent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ioutil.WriteFile(filename, buf.Bytes(), 0666)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveTo writes content to file system.
|
|
||||||
func (f *File) SaveTo(filename string) error {
|
|
||||||
return f.SaveToIndent(filename, "")
|
|
||||||
}
|
|
||||||
|
|
64
vendor/github.com/go-ini/ini/key.go
generated
vendored
64
vendor/github.com/go-ini/ini/key.go
generated
vendored
|
@ -15,6 +15,7 @@
|
||||||
package ini
|
package ini
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -25,6 +26,7 @@ import (
|
||||||
// Key represents a key under a section.
|
// Key represents a key under a section.
|
||||||
type Key struct {
|
type Key struct {
|
||||||
s *Section
|
s *Section
|
||||||
|
Comment string
|
||||||
name string
|
name string
|
||||||
value string
|
value string
|
||||||
isAutoIncrement bool
|
isAutoIncrement bool
|
||||||
|
@ -33,7 +35,7 @@ type Key struct {
|
||||||
isShadow bool
|
isShadow bool
|
||||||
shadows []*Key
|
shadows []*Key
|
||||||
|
|
||||||
Comment string
|
nestedValues []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// newKey simply return a key object with given values.
|
// newKey simply return a key object with given values.
|
||||||
|
@ -66,6 +68,22 @@ func (k *Key) AddShadow(val string) error {
|
||||||
return k.addShadow(val)
|
return k.addShadow(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *Key) addNestedValue(val string) error {
|
||||||
|
if k.isAutoIncrement || k.isBooleanType {
|
||||||
|
return errors.New("cannot add nested value to auto-increment or boolean key")
|
||||||
|
}
|
||||||
|
|
||||||
|
k.nestedValues = append(k.nestedValues, val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) AddNestedValue(val string) error {
|
||||||
|
if !k.s.f.options.AllowNestedValues {
|
||||||
|
return errors.New("nested value is not allowed")
|
||||||
|
}
|
||||||
|
return k.addNestedValue(val)
|
||||||
|
}
|
||||||
|
|
||||||
// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
|
// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
|
||||||
type ValueMapper func(string) string
|
type ValueMapper func(string) string
|
||||||
|
|
||||||
|
@ -92,6 +110,12 @@ func (k *Key) ValueWithShadows() []string {
|
||||||
return vals
|
return vals
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NestedValues returns nested values stored in the key.
|
||||||
|
// It is possible returned value is nil if no nested values stored in the key.
|
||||||
|
func (k *Key) NestedValues() []string {
|
||||||
|
return k.nestedValues
|
||||||
|
}
|
||||||
|
|
||||||
// transformValue takes a raw value and transforms to its final string.
|
// transformValue takes a raw value and transforms to its final string.
|
||||||
func (k *Key) transformValue(val string) string {
|
func (k *Key) transformValue(val string) string {
|
||||||
if k.s.f.ValueMapper != nil {
|
if k.s.f.ValueMapper != nil {
|
||||||
|
@ -114,7 +138,7 @@ func (k *Key) transformValue(val string) string {
|
||||||
|
|
||||||
// Search in the same section.
|
// Search in the same section.
|
||||||
nk, err := k.s.GetKey(noption)
|
nk, err := k.s.GetKey(noption)
|
||||||
if err != nil {
|
if err != nil || k == nk {
|
||||||
// Search again in default section.
|
// Search again in default section.
|
||||||
nk, _ = k.s.f.Section("").GetKey(noption)
|
nk, _ = k.s.f.Section("").GetKey(noption)
|
||||||
}
|
}
|
||||||
|
@ -444,11 +468,39 @@ func (k *Key) Strings(delim string) []string {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
vals := strings.Split(str, delim)
|
runes := []rune(str)
|
||||||
for i := range vals {
|
vals := make([]string, 0, 2)
|
||||||
// vals[i] = k.transformValue(strings.TrimSpace(vals[i]))
|
var buf bytes.Buffer
|
||||||
vals[i] = strings.TrimSpace(vals[i])
|
escape := false
|
||||||
|
idx := 0
|
||||||
|
for {
|
||||||
|
if escape {
|
||||||
|
escape = false
|
||||||
|
if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) {
|
||||||
|
buf.WriteRune('\\')
|
||||||
|
}
|
||||||
|
buf.WriteRune(runes[idx])
|
||||||
|
} else {
|
||||||
|
if runes[idx] == '\\' {
|
||||||
|
escape = true
|
||||||
|
} else if strings.HasPrefix(string(runes[idx:]), delim) {
|
||||||
|
idx += len(delim) - 1
|
||||||
|
vals = append(vals, strings.TrimSpace(buf.String()))
|
||||||
|
buf.Reset()
|
||||||
|
} else {
|
||||||
|
buf.WriteRune(runes[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idx += 1
|
||||||
|
if idx == len(runes) {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
vals = append(vals, strings.TrimSpace(buf.String()))
|
||||||
|
}
|
||||||
|
|
||||||
return vals
|
return vals
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
171
vendor/github.com/go-ini/ini/parser.go
generated
vendored
171
vendor/github.com/go-ini/ini/parser.go
generated
vendored
|
@ -19,11 +19,14 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var pythonMultiline = regexp.MustCompile("^(\\s+)([^\n]+)")
|
||||||
|
|
||||||
type tokenType int
|
type tokenType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -193,7 +196,10 @@ func hasSurroundedQuote(in string, quote byte) bool {
|
||||||
strings.IndexByte(in[1:], quote) == len(in)-2
|
strings.IndexByte(in[1:], quote) == len(in)-2
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) readValue(in []byte, ignoreContinuation, ignoreInlineComment bool) (string, error) {
|
func (p *parser) readValue(in []byte,
|
||||||
|
parserBufferSize int,
|
||||||
|
ignoreContinuation, ignoreInlineComment, unescapeValueDoubleQuotes, unescapeValueCommentSymbols, allowPythonMultilines, spaceBeforeInlineComment bool) (string, error) {
|
||||||
|
|
||||||
line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
|
line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
|
||||||
if len(line) == 0 {
|
if len(line) == 0 {
|
||||||
return "", nil
|
return "", nil
|
||||||
|
@ -204,6 +210,8 @@ func (p *parser) readValue(in []byte, ignoreContinuation, ignoreInlineComment bo
|
||||||
valQuote = `"""`
|
valQuote = `"""`
|
||||||
} else if line[0] == '`' {
|
} else if line[0] == '`' {
|
||||||
valQuote = "`"
|
valQuote = "`"
|
||||||
|
} else if unescapeValueDoubleQuotes && line[0] == '"' {
|
||||||
|
valQuote = `"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(valQuote) > 0 {
|
if len(valQuote) > 0 {
|
||||||
|
@ -214,31 +222,97 @@ func (p *parser) readValue(in []byte, ignoreContinuation, ignoreInlineComment bo
|
||||||
return p.readMultilines(line, line[startIdx:], valQuote)
|
return p.readMultilines(line, line[startIdx:], valQuote)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if unescapeValueDoubleQuotes && valQuote == `"` {
|
||||||
|
return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil
|
||||||
|
}
|
||||||
return line[startIdx : pos+startIdx], nil
|
return line[startIdx : pos+startIdx], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastChar := line[len(line)-1]
|
||||||
// Won't be able to reach here if value only contains whitespace
|
// Won't be able to reach here if value only contains whitespace
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
|
trimmedLastChar := line[len(line)-1]
|
||||||
|
|
||||||
// Check continuation lines when desired
|
// Check continuation lines when desired
|
||||||
if !ignoreContinuation && line[len(line)-1] == '\\' {
|
if !ignoreContinuation && trimmedLastChar == '\\' {
|
||||||
return p.readContinuationLines(line[:len(line)-1])
|
return p.readContinuationLines(line[:len(line)-1])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if ignore inline comment
|
// Check if ignore inline comment
|
||||||
if !ignoreInlineComment {
|
if !ignoreInlineComment {
|
||||||
i := strings.IndexAny(line, "#;")
|
var i int
|
||||||
|
if spaceBeforeInlineComment {
|
||||||
|
i = strings.Index(line, " #")
|
||||||
|
if i == -1 {
|
||||||
|
i = strings.Index(line, " ;")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
i = strings.IndexAny(line, "#;")
|
||||||
|
}
|
||||||
|
|
||||||
if i > -1 {
|
if i > -1 {
|
||||||
p.comment.WriteString(line[i:])
|
p.comment.WriteString(line[i:])
|
||||||
line = strings.TrimSpace(line[:i])
|
line = strings.TrimSpace(line[:i])
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trim single quotes
|
// Trim single and double quotes
|
||||||
if hasSurroundedQuote(line, '\'') ||
|
if hasSurroundedQuote(line, '\'') ||
|
||||||
hasSurroundedQuote(line, '"') {
|
hasSurroundedQuote(line, '"') {
|
||||||
line = line[1 : len(line)-1]
|
line = line[1 : len(line)-1]
|
||||||
|
} else if len(valQuote) == 0 && unescapeValueCommentSymbols {
|
||||||
|
if strings.Contains(line, `\;`) {
|
||||||
|
line = strings.Replace(line, `\;`, ";", -1)
|
||||||
|
}
|
||||||
|
if strings.Contains(line, `\#`) {
|
||||||
|
line = strings.Replace(line, `\#`, "#", -1)
|
||||||
|
}
|
||||||
|
} else if allowPythonMultilines && lastChar == '\n' {
|
||||||
|
parserBufferPeekResult, _ := p.buf.Peek(parserBufferSize)
|
||||||
|
peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
|
||||||
|
|
||||||
|
identSize := -1
|
||||||
|
val := line
|
||||||
|
|
||||||
|
for {
|
||||||
|
peekData, peekErr := peekBuffer.ReadBytes('\n')
|
||||||
|
if peekErr != nil {
|
||||||
|
if peekErr == io.EOF {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
return "", peekErr
|
||||||
|
}
|
||||||
|
|
||||||
|
peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
|
||||||
|
if len(peekMatches) != 3 {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIdentSize := len(peekMatches[1])
|
||||||
|
// NOTE: Return if not a python-ini multi-line value.
|
||||||
|
if currentIdentSize < 0 {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
identSize = currentIdentSize
|
||||||
|
|
||||||
|
// NOTE: Just advance the parser reader (buffer) in-sync with the peek buffer.
|
||||||
|
_, err := p.readUntil('\n')
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
val += fmt.Sprintf("\n%s", peekMatches[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: If it was a Python multi-line value,
|
||||||
|
// return the appended value.
|
||||||
|
if identSize > 0 {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return line, nil
|
return line, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,16 +324,54 @@ func (f *File) parse(reader io.Reader) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore error because default section name is never empty string.
|
// Ignore error because default section name is never empty string.
|
||||||
section, _ := f.NewSection(DEFAULT_SECTION)
|
name := DEFAULT_SECTION
|
||||||
|
if f.options.Insensitive {
|
||||||
|
name = strings.ToLower(DEFAULT_SECTION)
|
||||||
|
}
|
||||||
|
section, _ := f.NewSection(name)
|
||||||
|
|
||||||
|
// This "last" is not strictly equivalent to "previous one" if current key is not the first nested key
|
||||||
|
var isLastValueEmpty bool
|
||||||
|
var lastRegularKey *Key
|
||||||
|
|
||||||
var line []byte
|
var line []byte
|
||||||
var inUnparseableSection bool
|
var inUnparseableSection bool
|
||||||
|
|
||||||
|
// NOTE: Iterate and increase `currentPeekSize` until
|
||||||
|
// the size of the parser buffer is found.
|
||||||
|
// TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`.
|
||||||
|
parserBufferSize := 0
|
||||||
|
// NOTE: Peek 1kb at a time.
|
||||||
|
currentPeekSize := 1024
|
||||||
|
|
||||||
|
if f.options.AllowPythonMultilineValues {
|
||||||
|
for {
|
||||||
|
peekBytes, _ := p.buf.Peek(currentPeekSize)
|
||||||
|
peekBytesLength := len(peekBytes)
|
||||||
|
|
||||||
|
if parserBufferSize >= peekBytesLength {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPeekSize *= 2
|
||||||
|
parserBufferSize = peekBytesLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for !p.isEOF {
|
for !p.isEOF {
|
||||||
line, err = p.readUntil('\n')
|
line, err = p.readUntil('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if f.options.AllowNestedValues &&
|
||||||
|
isLastValueEmpty && len(line) > 0 {
|
||||||
|
if line[0] == ' ' || line[0] == '\t' {
|
||||||
|
lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
||||||
if len(line) == 0 {
|
if len(line) == 0 {
|
||||||
continue
|
continue
|
||||||
|
@ -277,8 +389,7 @@ func (f *File) parse(reader io.Reader) (err error) {
|
||||||
// Section
|
// Section
|
||||||
if line[0] == '[' {
|
if line[0] == '[' {
|
||||||
// Read to the next ']' (TODO: support quoted strings)
|
// Read to the next ']' (TODO: support quoted strings)
|
||||||
// TODO(unknwon): use LastIndexByte when stop supporting Go1.4
|
closeIdx := bytes.LastIndexByte(line, ']')
|
||||||
closeIdx := bytes.LastIndex(line, []byte("]"))
|
|
||||||
if closeIdx == -1 {
|
if closeIdx == -1 {
|
||||||
return fmt.Errorf("unclosed section: %s", line)
|
return fmt.Errorf("unclosed section: %s", line)
|
||||||
}
|
}
|
||||||
|
@ -320,18 +431,31 @@ func (f *File) parse(reader io.Reader) (err error) {
|
||||||
kname, offset, err := readKeyName(line)
|
kname, offset, err := readKeyName(line)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Treat as boolean key when desired, and whole line is key name.
|
// Treat as boolean key when desired, and whole line is key name.
|
||||||
if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
|
if IsErrDelimiterNotFound(err) {
|
||||||
kname, err := p.readValue(line, f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
|
switch {
|
||||||
if err != nil {
|
case f.options.AllowBooleanKeys:
|
||||||
return err
|
kname, err := p.readValue(line,
|
||||||
|
parserBufferSize,
|
||||||
|
f.options.IgnoreContinuation,
|
||||||
|
f.options.IgnoreInlineComment,
|
||||||
|
f.options.UnescapeValueDoubleQuotes,
|
||||||
|
f.options.UnescapeValueCommentSymbols,
|
||||||
|
f.options.AllowPythonMultilineValues,
|
||||||
|
f.options.SpaceBeforeInlineComment)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key, err := section.NewBooleanKey(kname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key.Comment = strings.TrimSpace(p.comment.String())
|
||||||
|
p.comment.Reset()
|
||||||
|
continue
|
||||||
|
|
||||||
|
case f.options.SkipUnrecognizableLines:
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
key, err := section.NewBooleanKey(kname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
key.Comment = strings.TrimSpace(p.comment.String())
|
|
||||||
p.comment.Reset()
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -344,10 +468,18 @@ func (f *File) parse(reader io.Reader) (err error) {
|
||||||
p.count++
|
p.count++
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err := p.readValue(line[offset:], f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
|
value, err := p.readValue(line[offset:],
|
||||||
|
parserBufferSize,
|
||||||
|
f.options.IgnoreContinuation,
|
||||||
|
f.options.IgnoreInlineComment,
|
||||||
|
f.options.UnescapeValueDoubleQuotes,
|
||||||
|
f.options.UnescapeValueCommentSymbols,
|
||||||
|
f.options.AllowPythonMultilineValues,
|
||||||
|
f.options.SpaceBeforeInlineComment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
isLastValueEmpty = len(value) == 0
|
||||||
|
|
||||||
key, err := section.NewKey(kname, value)
|
key, err := section.NewKey(kname, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -356,6 +488,7 @@ func (f *File) parse(reader io.Reader) (err error) {
|
||||||
key.isAutoIncrement = isAutoIncr
|
key.isAutoIncrement = isAutoIncr
|
||||||
key.Comment = strings.TrimSpace(p.comment.String())
|
key.Comment = strings.TrimSpace(p.comment.String())
|
||||||
p.comment.Reset()
|
p.comment.Reset()
|
||||||
|
lastRegularKey = key
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
10
vendor/github.com/go-ini/ini/section.go
generated
vendored
10
vendor/github.com/go-ini/ini/section.go
generated
vendored
|
@ -54,6 +54,14 @@ func (s *Section) Body() string {
|
||||||
return strings.TrimSpace(s.rawBody)
|
return strings.TrimSpace(s.rawBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetBody updates body content only if section is raw.
|
||||||
|
func (s *Section) SetBody(body string) {
|
||||||
|
if !s.isRawSection {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.rawBody = body
|
||||||
|
}
|
||||||
|
|
||||||
// NewKey creates a new key to given section.
|
// NewKey creates a new key to given section.
|
||||||
func (s *Section) NewKey(name, val string) (*Key, error) {
|
func (s *Section) NewKey(name, val string) (*Key, error) {
|
||||||
if len(name) == 0 {
|
if len(name) == 0 {
|
||||||
|
@ -74,6 +82,7 @@ func (s *Section) NewKey(name, val string) (*Key, error) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.keys[name].value = val
|
s.keys[name].value = val
|
||||||
|
s.keysHash[name] = val
|
||||||
}
|
}
|
||||||
return s.keys[name], nil
|
return s.keys[name], nil
|
||||||
}
|
}
|
||||||
|
@ -136,6 +145,7 @@ func (s *Section) HasKey(name string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Haskey is a backwards-compatible name for HasKey.
|
// Haskey is a backwards-compatible name for HasKey.
|
||||||
|
// TODO: delete me in v2
|
||||||
func (s *Section) Haskey(name string) bool {
|
func (s *Section) Haskey(name string) bool {
|
||||||
return s.HasKey(name)
|
return s.HasKey(name)
|
||||||
}
|
}
|
||||||
|
|
16
vendor/github.com/go-ini/ini/struct.go
generated
vendored
16
vendor/github.com/go-ini/ini/struct.go
generated
vendored
|
@ -113,7 +113,7 @@ func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowSh
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported type '[]%s'", sliceOf)
|
return fmt.Errorf("unsupported type '[]%s'", sliceOf)
|
||||||
}
|
}
|
||||||
if isStrict {
|
if err != nil && isStrict {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +166,7 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
durationVal, err := key.Duration()
|
durationVal, err := key.Duration()
|
||||||
// Skip zero value
|
// Skip zero value
|
||||||
if err == nil && int(durationVal) > 0 {
|
if err == nil && int64(durationVal) > 0 {
|
||||||
field.Set(reflect.ValueOf(durationVal))
|
field.Set(reflect.ValueOf(durationVal))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -450,6 +450,12 @@ func (s *Section) reflectFrom(val reflect.Value) error {
|
||||||
// Note: fieldName can never be empty here, ignore error.
|
// Note: fieldName can never be empty here, ignore error.
|
||||||
sec, _ = s.f.NewSection(fieldName)
|
sec, _ = s.f.NewSection(fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add comment from comment tag
|
||||||
|
if len(sec.Comment) == 0 {
|
||||||
|
sec.Comment = tpField.Tag.Get("comment")
|
||||||
|
}
|
||||||
|
|
||||||
if err = sec.reflectFrom(field); err != nil {
|
if err = sec.reflectFrom(field); err != nil {
|
||||||
return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
|
return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
|
||||||
}
|
}
|
||||||
|
@ -461,6 +467,12 @@ func (s *Section) reflectFrom(val reflect.Value) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
key, _ = s.NewKey(fieldName, "")
|
key, _ = s.NewKey(fieldName, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add comment from comment tag
|
||||||
|
if len(key.Comment) == 0 {
|
||||||
|
key.Comment = tpField.Tag.Get("comment")
|
||||||
|
}
|
||||||
|
|
||||||
if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
|
if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
|
||||||
return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
|
return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
|
||||||
}
|
}
|
||||||
|
|
10
vendor/github.com/go-xorm/core/cache.go
generated
vendored
10
vendor/github.com/go-xorm/core/cache.go
generated
vendored
|
@ -1,11 +1,12 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -55,11 +56,10 @@ func encodeIds(ids []PK) (string, error) {
|
||||||
return buf.String(), err
|
return buf.String(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func decodeIds(s string) ([]PK, error) {
|
func decodeIds(s string) ([]PK, error) {
|
||||||
pks := make([]PK, 0)
|
pks := make([]PK, 0)
|
||||||
|
|
||||||
dec := gob.NewDecoder(bytes.NewBufferString(s))
|
dec := gob.NewDecoder(strings.NewReader(s))
|
||||||
err := dec.Decode(&pks)
|
err := dec.Decode(&pks)
|
||||||
|
|
||||||
return pks, err
|
return pks, err
|
||||||
|
|
3
vendor/github.com/go-xorm/core/circle.yml
generated
vendored
3
vendor/github.com/go-xorm/core/circle.yml
generated
vendored
|
@ -11,4 +11,5 @@ database:
|
||||||
test:
|
test:
|
||||||
override:
|
override:
|
||||||
# './...' is a relative pattern which means all subdirectories
|
# './...' is a relative pattern which means all subdirectories
|
||||||
- go test -v -race
|
- go test -v -race
|
||||||
|
- go test -v -race --dbtype=sqlite3
|
||||||
|
|
16
vendor/github.com/go-xorm/core/column.go
generated
vendored
16
vendor/github.com/go-xorm/core/column.go
generated
vendored
|
@ -79,6 +79,10 @@ func (col *Column) String(d Dialect) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if col.Default != "" {
|
||||||
|
sql += "DEFAULT " + col.Default + " "
|
||||||
|
}
|
||||||
|
|
||||||
if d.ShowCreateNull() {
|
if d.ShowCreateNull() {
|
||||||
if col.Nullable {
|
if col.Nullable {
|
||||||
sql += "NULL "
|
sql += "NULL "
|
||||||
|
@ -87,10 +91,6 @@ func (col *Column) String(d Dialect) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if col.Default != "" {
|
|
||||||
sql += "DEFAULT " + col.Default + " "
|
|
||||||
}
|
|
||||||
|
|
||||||
return sql
|
return sql
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +99,10 @@ func (col *Column) StringNoPk(d Dialect) string {
|
||||||
|
|
||||||
sql += d.SqlType(col) + " "
|
sql += d.SqlType(col) + " "
|
||||||
|
|
||||||
|
if col.Default != "" {
|
||||||
|
sql += "DEFAULT " + col.Default + " "
|
||||||
|
}
|
||||||
|
|
||||||
if d.ShowCreateNull() {
|
if d.ShowCreateNull() {
|
||||||
if col.Nullable {
|
if col.Nullable {
|
||||||
sql += "NULL "
|
sql += "NULL "
|
||||||
|
@ -107,10 +111,6 @@ func (col *Column) StringNoPk(d Dialect) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if col.Default != "" {
|
|
||||||
sql += "DEFAULT " + col.Default + " "
|
|
||||||
}
|
|
||||||
|
|
||||||
return sql
|
return sql
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
57
vendor/github.com/go-xorm/core/db.go
generated
vendored
57
vendor/github.com/go-xorm/core/db.go
generated
vendored
|
@ -7,6 +7,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
DefaultCacheSize = 200
|
||||||
)
|
)
|
||||||
|
|
||||||
func MapToSlice(query string, mp interface{}) (string, []interface{}, error) {
|
func MapToSlice(query string, mp interface{}) (string, []interface{}, error) {
|
||||||
|
@ -58,9 +63,16 @@ func StructToSlice(query string, st interface{}) (string, []interface{}, error)
|
||||||
return query, args, nil
|
return query, args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type cacheStruct struct {
|
||||||
|
value reflect.Value
|
||||||
|
idx int
|
||||||
|
}
|
||||||
|
|
||||||
type DB struct {
|
type DB struct {
|
||||||
*sql.DB
|
*sql.DB
|
||||||
Mapper IMapper
|
Mapper IMapper
|
||||||
|
reflectCache map[reflect.Type]*cacheStruct
|
||||||
|
reflectCacheMutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func Open(driverName, dataSourceName string) (*DB, error) {
|
func Open(driverName, dataSourceName string) (*DB, error) {
|
||||||
|
@ -68,11 +80,32 @@ func Open(driverName, dataSourceName string) (*DB, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &DB{db, NewCacheMapper(&SnakeMapper{})}, nil
|
return &DB{
|
||||||
|
DB: db,
|
||||||
|
Mapper: NewCacheMapper(&SnakeMapper{}),
|
||||||
|
reflectCache: make(map[reflect.Type]*cacheStruct),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func FromDB(db *sql.DB) *DB {
|
func FromDB(db *sql.DB) *DB {
|
||||||
return &DB{db, NewCacheMapper(&SnakeMapper{})}
|
return &DB{
|
||||||
|
DB: db,
|
||||||
|
Mapper: NewCacheMapper(&SnakeMapper{}),
|
||||||
|
reflectCache: make(map[reflect.Type]*cacheStruct),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) reflectNew(typ reflect.Type) reflect.Value {
|
||||||
|
db.reflectCacheMutex.Lock()
|
||||||
|
defer db.reflectCacheMutex.Unlock()
|
||||||
|
cs, ok := db.reflectCache[typ]
|
||||||
|
if !ok || cs.idx+1 > DefaultCacheSize-1 {
|
||||||
|
cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), DefaultCacheSize, DefaultCacheSize), 0}
|
||||||
|
db.reflectCache[typ] = cs
|
||||||
|
} else {
|
||||||
|
cs.idx = cs.idx + 1
|
||||||
|
}
|
||||||
|
return cs.value.Index(cs.idx).Addr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
|
func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
|
||||||
|
@ -83,7 +116,7 @@ func (db *DB) Query(query string, args ...interface{}) (*Rows, error) {
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Rows{rows, db.Mapper}, nil
|
return &Rows{rows, db}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) {
|
func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) {
|
||||||
|
@ -128,8 +161,8 @@ func (db *DB) QueryRowStruct(query string, st interface{}) *Row {
|
||||||
|
|
||||||
type Stmt struct {
|
type Stmt struct {
|
||||||
*sql.Stmt
|
*sql.Stmt
|
||||||
Mapper IMapper
|
db *DB
|
||||||
names map[string]int
|
names map[string]int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) Prepare(query string) (*Stmt, error) {
|
func (db *DB) Prepare(query string) (*Stmt, error) {
|
||||||
|
@ -145,7 +178,7 @@ func (db *DB) Prepare(query string) (*Stmt, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Stmt{stmt, db.Mapper, names}, nil
|
return &Stmt{stmt, db, names}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stmt) ExecMap(mp interface{}) (sql.Result, error) {
|
func (s *Stmt) ExecMap(mp interface{}) (sql.Result, error) {
|
||||||
|
@ -179,7 +212,7 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Rows{rows, s.Mapper}, nil
|
return &Rows{rows, s.db}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stmt) QueryMap(mp interface{}) (*Rows, error) {
|
func (s *Stmt) QueryMap(mp interface{}) (*Rows, error) {
|
||||||
|
@ -274,7 +307,7 @@ func (EmptyScanner) Scan(src interface{}) error {
|
||||||
|
|
||||||
type Tx struct {
|
type Tx struct {
|
||||||
*sql.Tx
|
*sql.Tx
|
||||||
Mapper IMapper
|
db *DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) Begin() (*Tx, error) {
|
func (db *DB) Begin() (*Tx, error) {
|
||||||
|
@ -282,7 +315,7 @@ func (db *DB) Begin() (*Tx, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Tx{tx, db.Mapper}, nil
|
return &Tx{tx, db}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *Tx) Prepare(query string) (*Stmt, error) {
|
func (tx *Tx) Prepare(query string) (*Stmt, error) {
|
||||||
|
@ -298,7 +331,7 @@ func (tx *Tx) Prepare(query string) (*Stmt, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Stmt{stmt, tx.Mapper, names}, nil
|
return &Stmt{stmt, tx.db, names}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *Tx) Stmt(stmt *Stmt) *Stmt {
|
func (tx *Tx) Stmt(stmt *Stmt) *Stmt {
|
||||||
|
@ -327,7 +360,7 @@ func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Rows{rows, tx.Mapper}, nil
|
return &Rows{rows, tx.db}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) {
|
func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) {
|
||||||
|
|
7
vendor/github.com/go-xorm/core/dialect.go
generated
vendored
7
vendor/github.com/go-xorm/core/dialect.go
generated
vendored
|
@ -74,6 +74,7 @@ type Dialect interface {
|
||||||
GetIndexes(tableName string) (map[string]*Index, error)
|
GetIndexes(tableName string) (map[string]*Index, error)
|
||||||
|
|
||||||
Filters() []Filter
|
Filters() []Filter
|
||||||
|
SetParams(params map[string]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func OpenDialect(dialect Dialect) (*DB, error) {
|
func OpenDialect(dialect Dialect) (*DB, error) {
|
||||||
|
@ -148,7 +149,8 @@ func (db *Base) SupportDropIfExists() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Base) DropTableSql(tableName string) string {
|
func (db *Base) DropTableSql(tableName string) string {
|
||||||
return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName)
|
quote := db.dialect.Quote
|
||||||
|
return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) {
|
func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) {
|
||||||
|
@ -289,6 +291,9 @@ func (b *Base) LogSQL(sql string, args []interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Base) SetParams(params map[string]string) {
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dialects = map[string]func() Dialect{}
|
dialects = map[string]func() Dialect{}
|
||||||
)
|
)
|
||||||
|
|
6
vendor/github.com/go-xorm/core/filter.go
generated
vendored
6
vendor/github.com/go-xorm/core/filter.go
generated
vendored
|
@ -37,9 +37,9 @@ func (q *Quoter) Quote(content string) string {
|
||||||
func (i *IdFilter) Do(sql string, dialect Dialect, table *Table) string {
|
func (i *IdFilter) Do(sql string, dialect Dialect, table *Table) string {
|
||||||
quoter := NewQuoter(dialect)
|
quoter := NewQuoter(dialect)
|
||||||
if table != nil && len(table.PrimaryKeys) == 1 {
|
if table != nil && len(table.PrimaryKeys) == 1 {
|
||||||
sql = strings.Replace(sql, "`(id)`", quoter.Quote(table.PrimaryKeys[0]), -1)
|
sql = strings.Replace(sql, " `(id)` ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1)
|
||||||
sql = strings.Replace(sql, quoter.Quote("(id)"), quoter.Quote(table.PrimaryKeys[0]), -1)
|
sql = strings.Replace(sql, " "+quoter.Quote("(id)")+" ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1)
|
||||||
return strings.Replace(sql, "(id)", quoter.Quote(table.PrimaryKeys[0]), -1)
|
return strings.Replace(sql, " (id) ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1)
|
||||||
}
|
}
|
||||||
return sql
|
return sql
|
||||||
}
|
}
|
||||||
|
|
2
vendor/github.com/go-xorm/core/index.go
generated
vendored
2
vendor/github.com/go-xorm/core/index.go
generated
vendored
|
@ -22,6 +22,8 @@ type Index struct {
|
||||||
func (index *Index) XName(tableName string) string {
|
func (index *Index) XName(tableName string) string {
|
||||||
if !strings.HasPrefix(index.Name, "UQE_") &&
|
if !strings.HasPrefix(index.Name, "UQE_") &&
|
||||||
!strings.HasPrefix(index.Name, "IDX_") {
|
!strings.HasPrefix(index.Name, "IDX_") {
|
||||||
|
tableName = strings.Replace(tableName, `"`, "", -1)
|
||||||
|
tableName = strings.Replace(tableName, `.`, "_", -1)
|
||||||
if index.Type == UniqueType {
|
if index.Type == UniqueType {
|
||||||
return fmt.Sprintf("UQE_%v_%v", tableName, index.Name)
|
return fmt.Sprintf("UQE_%v_%v", tableName, index.Name)
|
||||||
}
|
}
|
||||||
|
|
64
vendor/github.com/go-xorm/core/rows.go
generated
vendored
64
vendor/github.com/go-xorm/core/rows.go
generated
vendored
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
type Rows struct {
|
type Rows struct {
|
||||||
*sql.Rows
|
*sql.Rows
|
||||||
Mapper IMapper
|
db *DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *Rows) ToMapString() ([]map[string]string, error) {
|
func (rs *Rows) ToMapString() ([]map[string]string, error) {
|
||||||
|
@ -105,7 +105,7 @@ func (rs *Rows) ScanStructByName(dest interface{}) error {
|
||||||
newDest := make([]interface{}, len(cols))
|
newDest := make([]interface{}, len(cols))
|
||||||
var v EmptyScanner
|
var v EmptyScanner
|
||||||
for j, name := range cols {
|
for j, name := range cols {
|
||||||
f := fieldByName(vv.Elem(), rs.Mapper.Table2Obj(name))
|
f := fieldByName(vv.Elem(), rs.db.Mapper.Table2Obj(name))
|
||||||
if f.IsValid() {
|
if f.IsValid() {
|
||||||
newDest[j] = f.Addr().Interface()
|
newDest[j] = f.Addr().Interface()
|
||||||
} else {
|
} else {
|
||||||
|
@ -116,36 +116,6 @@ func (rs *Rows) ScanStructByName(dest interface{}) error {
|
||||||
return rs.Rows.Scan(newDest...)
|
return rs.Rows.Scan(newDest...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type cacheStruct struct {
|
|
||||||
value reflect.Value
|
|
||||||
idx int
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
reflectCache = make(map[reflect.Type]*cacheStruct)
|
|
||||||
reflectCacheMutex sync.RWMutex
|
|
||||||
)
|
|
||||||
|
|
||||||
func ReflectNew(typ reflect.Type) reflect.Value {
|
|
||||||
reflectCacheMutex.RLock()
|
|
||||||
cs, ok := reflectCache[typ]
|
|
||||||
reflectCacheMutex.RUnlock()
|
|
||||||
|
|
||||||
const newSize = 200
|
|
||||||
|
|
||||||
if !ok || cs.idx+1 > newSize-1 {
|
|
||||||
cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), newSize, newSize), 0}
|
|
||||||
reflectCacheMutex.Lock()
|
|
||||||
reflectCache[typ] = cs
|
|
||||||
reflectCacheMutex.Unlock()
|
|
||||||
} else {
|
|
||||||
reflectCacheMutex.Lock()
|
|
||||||
cs.idx = cs.idx + 1
|
|
||||||
reflectCacheMutex.Unlock()
|
|
||||||
}
|
|
||||||
return cs.value.Index(cs.idx).Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
// scan data to a slice's pointer, slice's length should equal to columns' number
|
// scan data to a slice's pointer, slice's length should equal to columns' number
|
||||||
func (rs *Rows) ScanSlice(dest interface{}) error {
|
func (rs *Rows) ScanSlice(dest interface{}) error {
|
||||||
vv := reflect.ValueOf(dest)
|
vv := reflect.ValueOf(dest)
|
||||||
|
@ -197,9 +167,7 @@ func (rs *Rows) ScanMap(dest interface{}) error {
|
||||||
vvv := vv.Elem()
|
vvv := vv.Elem()
|
||||||
|
|
||||||
for i, _ := range cols {
|
for i, _ := range cols {
|
||||||
newDest[i] = ReflectNew(vvv.Type().Elem()).Interface()
|
newDest[i] = rs.db.reflectNew(vvv.Type().Elem()).Interface()
|
||||||
//v := reflect.New(vvv.Type().Elem())
|
|
||||||
//newDest[i] = v.Interface()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = rs.Rows.Scan(newDest...)
|
err = rs.Rows.Scan(newDest...)
|
||||||
|
@ -215,32 +183,6 @@ func (rs *Rows) ScanMap(dest interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*func (rs *Rows) ScanMap(dest interface{}) error {
|
|
||||||
vv := reflect.ValueOf(dest)
|
|
||||||
if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map {
|
|
||||||
return errors.New("dest should be a map's pointer")
|
|
||||||
}
|
|
||||||
|
|
||||||
cols, err := rs.Columns()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newDest := make([]interface{}, len(cols))
|
|
||||||
err = rs.ScanSlice(newDest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
vvv := vv.Elem()
|
|
||||||
|
|
||||||
for i, name := range cols {
|
|
||||||
vname := reflect.ValueOf(name)
|
|
||||||
vvv.SetMapIndex(vname, reflect.ValueOf(newDest[i]).Elem())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}*/
|
|
||||||
type Row struct {
|
type Row struct {
|
||||||
rows *Rows
|
rows *Rows
|
||||||
// One of these two will be non-nil:
|
// One of these two will be non-nil:
|
||||||
|
|
3
vendor/github.com/go-xorm/core/scan.go
generated
vendored
3
vendor/github.com/go-xorm/core/scan.go
generated
vendored
|
@ -44,6 +44,9 @@ func convertTime(dest *NullTime, src interface{}) error {
|
||||||
}
|
}
|
||||||
*dest = NullTime(t)
|
*dest = NullTime(t)
|
||||||
return nil
|
return nil
|
||||||
|
case time.Time:
|
||||||
|
*dest = NullTime(s)
|
||||||
|
return nil
|
||||||
case nil:
|
case nil:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported driver -> Scan pair: %T -> %T", src, dest)
|
return fmt.Errorf("unsupported driver -> Scan pair: %T -> %T", src, dest)
|
||||||
|
|
36
vendor/github.com/go-xorm/core/type.go
generated
vendored
36
vendor/github.com/go-xorm/core/type.go
generated
vendored
|
@ -69,15 +69,17 @@ var (
|
||||||
Enum = "ENUM"
|
Enum = "ENUM"
|
||||||
Set = "SET"
|
Set = "SET"
|
||||||
|
|
||||||
Char = "CHAR"
|
Char = "CHAR"
|
||||||
Varchar = "VARCHAR"
|
Varchar = "VARCHAR"
|
||||||
NVarchar = "NVARCHAR"
|
NVarchar = "NVARCHAR"
|
||||||
TinyText = "TINYTEXT"
|
TinyText = "TINYTEXT"
|
||||||
Text = "TEXT"
|
Text = "TEXT"
|
||||||
Clob = "CLOB"
|
Clob = "CLOB"
|
||||||
MediumText = "MEDIUMTEXT"
|
MediumText = "MEDIUMTEXT"
|
||||||
LongText = "LONGTEXT"
|
LongText = "LONGTEXT"
|
||||||
Uuid = "UUID"
|
Uuid = "UUID"
|
||||||
|
UniqueIdentifier = "UNIQUEIDENTIFIER"
|
||||||
|
SysName = "SYSNAME"
|
||||||
|
|
||||||
Date = "DATE"
|
Date = "DATE"
|
||||||
DateTime = "DATETIME"
|
DateTime = "DATETIME"
|
||||||
|
@ -132,6 +134,7 @@ var (
|
||||||
LongText: TEXT_TYPE,
|
LongText: TEXT_TYPE,
|
||||||
Uuid: TEXT_TYPE,
|
Uuid: TEXT_TYPE,
|
||||||
Clob: TEXT_TYPE,
|
Clob: TEXT_TYPE,
|
||||||
|
SysName: TEXT_TYPE,
|
||||||
|
|
||||||
Date: TIME_TYPE,
|
Date: TIME_TYPE,
|
||||||
DateTime: TIME_TYPE,
|
DateTime: TIME_TYPE,
|
||||||
|
@ -148,11 +151,12 @@ var (
|
||||||
Binary: BLOB_TYPE,
|
Binary: BLOB_TYPE,
|
||||||
VarBinary: BLOB_TYPE,
|
VarBinary: BLOB_TYPE,
|
||||||
|
|
||||||
TinyBlob: BLOB_TYPE,
|
TinyBlob: BLOB_TYPE,
|
||||||
Blob: BLOB_TYPE,
|
Blob: BLOB_TYPE,
|
||||||
MediumBlob: BLOB_TYPE,
|
MediumBlob: BLOB_TYPE,
|
||||||
LongBlob: BLOB_TYPE,
|
LongBlob: BLOB_TYPE,
|
||||||
Bytea: BLOB_TYPE,
|
Bytea: BLOB_TYPE,
|
||||||
|
UniqueIdentifier: BLOB_TYPE,
|
||||||
|
|
||||||
Bool: NUMERIC_TYPE,
|
Bool: NUMERIC_TYPE,
|
||||||
|
|
||||||
|
@ -289,9 +293,9 @@ func SQLType2Type(st SQLType) reflect.Type {
|
||||||
return reflect.TypeOf(float32(1))
|
return reflect.TypeOf(float32(1))
|
||||||
case Double:
|
case Double:
|
||||||
return reflect.TypeOf(float64(1))
|
return reflect.TypeOf(float64(1))
|
||||||
case Char, Varchar, NVarchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid, Clob:
|
case Char, Varchar, NVarchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid, Clob, SysName:
|
||||||
return reflect.TypeOf("")
|
return reflect.TypeOf("")
|
||||||
case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary:
|
case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary, UniqueIdentifier:
|
||||||
return reflect.TypeOf([]byte{})
|
return reflect.TypeOf([]byte{})
|
||||||
case Bool:
|
case Bool:
|
||||||
return reflect.TypeOf(true)
|
return reflect.TypeOf(true)
|
||||||
|
|
28
vendor/github.com/go-xorm/xorm-redis-cache/LICENSE
generated
vendored
28
vendor/github.com/go-xorm/xorm-redis-cache/LICENSE
generated
vendored
|
@ -1,28 +0,0 @@
|
||||||
Copyright (c) 2014, go-xorm
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
* Neither the name of the {organization} nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
9
vendor/github.com/go-xorm/xorm-redis-cache/README.md
generated
vendored
9
vendor/github.com/go-xorm/xorm-redis-cache/README.md
generated
vendored
|
@ -1,9 +0,0 @@
|
||||||
xorm-redis-cache
|
|
||||||
================
|
|
||||||
|
|
||||||
XORM Redis Cache
|
|
||||||
|
|
||||||
|
|
||||||
[![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/go-xorm/xorm-redis-cache)
|
|
||||||
|
|
||||||
|
|
302
vendor/github.com/go-xorm/xorm-redis-cache/redis_cacher.go
generated
vendored
302
vendor/github.com/go-xorm/xorm-redis-cache/redis_cacher.go
generated
vendored
|
@ -1,302 +0,0 @@
|
||||||
package xormrediscache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/gob"
|
|
||||||
"fmt"
|
|
||||||
"github.com/garyburd/redigo/redis"
|
|
||||||
"github.com/go-xorm/core"
|
|
||||||
"hash/crc32"
|
|
||||||
// "log"
|
|
||||||
"reflect"
|
|
||||||
// "strconv"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DEFAULT_EXPIRATION = time.Duration(0)
|
|
||||||
FOREVER_EXPIRATION = time.Duration(-1)
|
|
||||||
|
|
||||||
LOGGING_PREFIX = "[redis_cacher]"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Wraps the Redis client to meet the Cache interface.
|
|
||||||
type RedisCacher struct {
|
|
||||||
pool *redis.Pool
|
|
||||||
defaultExpiration time.Duration
|
|
||||||
|
|
||||||
Logger core.ILogger
|
|
||||||
}
|
|
||||||
|
|
||||||
// New a Redis Cacher, host as IP endpoint, i.e., localhost:6379, provide empty string or nil if Redis server doesn't
|
|
||||||
// require AUTH command, defaultExpiration sets the expire duration for a key to live. Until redigo supports
|
|
||||||
// sharding/clustering, only one host will be in hostList
|
|
||||||
//
|
|
||||||
// engine.SetDefaultCacher(xormrediscache.NewRedisCacher("localhost:6379", "", xormrediscache.DEFAULT_EXPIRATION, engine.Logger))
|
|
||||||
//
|
|
||||||
// or set MapCacher
|
|
||||||
//
|
|
||||||
// engine.MapCacher(&user, xormrediscache.NewRedisCacher("localhost:6379", "", xormrediscache.DEFAULT_EXPIRATION, engine.Logger))
|
|
||||||
//
|
|
||||||
func NewRedisCacher(host string, password string, defaultExpiration time.Duration, logger core.ILogger) *RedisCacher {
|
|
||||||
var pool = &redis.Pool{
|
|
||||||
MaxIdle: 5,
|
|
||||||
IdleTimeout: 240 * time.Second,
|
|
||||||
Dial: func() (redis.Conn, error) {
|
|
||||||
// the redis protocol should probably be made sett-able
|
|
||||||
c, err := redis.Dial("tcp", host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(password) > 0 {
|
|
||||||
if _, err := c.Do("AUTH", password); err != nil {
|
|
||||||
c.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// check with PING
|
|
||||||
if _, err := c.Do("PING"); err != nil {
|
|
||||||
c.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c, err
|
|
||||||
},
|
|
||||||
// custom connection test method
|
|
||||||
TestOnBorrow: func(c redis.Conn, t time.Time) error {
|
|
||||||
if _, err := c.Do("PING"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return &RedisCacher{pool: pool, defaultExpiration: defaultExpiration, Logger: logger}
|
|
||||||
}
|
|
||||||
|
|
||||||
func exists(conn redis.Conn, key string) bool {
|
|
||||||
existed, _ := redis.Bool(conn.Do("EXISTS", key))
|
|
||||||
return existed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) logErrf(format string, contents ...interface{}) {
|
|
||||||
if c.Logger != nil {
|
|
||||||
c.Logger.Errorf(fmt.Sprintf("%s %s", LOGGING_PREFIX, format), contents...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) logDebugf(format string, contents ...interface{}) {
|
|
||||||
if c.Logger != nil {
|
|
||||||
c.Logger.Debugf(fmt.Sprintf("%s %s", LOGGING_PREFIX, format), contents...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) getBeanKey(tableName string, id string) string {
|
|
||||||
return fmt.Sprintf("xorm:bean:%s:%s", tableName, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) getSqlKey(tableName string, sql string) string {
|
|
||||||
// hash sql to minimize key length
|
|
||||||
crc := crc32.ChecksumIEEE([]byte(sql))
|
|
||||||
return fmt.Sprintf("xorm:sql:%s:%d", tableName, crc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete all xorm cached objects
|
|
||||||
func (c *RedisCacher) Flush() error {
|
|
||||||
// conn := c.pool.Get()
|
|
||||||
// defer conn.Close()
|
|
||||||
// _, err := conn.Do("FLUSHALL")
|
|
||||||
// return err
|
|
||||||
return c.delObject("xorm:*")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) getObject(key string) interface{} {
|
|
||||||
conn := c.pool.Get()
|
|
||||||
defer conn.Close()
|
|
||||||
raw, err := conn.Do("GET", key)
|
|
||||||
if raw == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
item, err := redis.Bytes(raw, err)
|
|
||||||
if err != nil {
|
|
||||||
c.logErrf("redis.Bytes failed: %s", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := c.deserialize(item)
|
|
||||||
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) GetIds(tableName, sql string) interface{} {
|
|
||||||
sqlKey := c.getSqlKey(tableName, sql)
|
|
||||||
c.logDebugf(" GetIds|tableName:%s|sql:%s|key:%s", tableName, sql, sqlKey)
|
|
||||||
return c.getObject(sqlKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) GetBean(tableName string, id string) interface{} {
|
|
||||||
beanKey := c.getBeanKey(tableName, id)
|
|
||||||
c.logDebugf("[xorm/redis_cacher] GetBean|tableName:%s|id:%s|key:%s", tableName, id, beanKey)
|
|
||||||
return c.getObject(beanKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) putObject(key string, value interface{}) {
|
|
||||||
c.invoke(c.pool.Get().Do, key, value, c.defaultExpiration)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) PutIds(tableName, sql string, ids interface{}) {
|
|
||||||
sqlKey := c.getSqlKey(tableName, sql)
|
|
||||||
c.logDebugf("PutIds|tableName:%s|sql:%s|key:%s|obj:%s|type:%v", tableName, sql, sqlKey, ids, reflect.TypeOf(ids))
|
|
||||||
c.putObject(sqlKey, ids)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) PutBean(tableName string, id string, obj interface{}) {
|
|
||||||
beanKey := c.getBeanKey(tableName, id)
|
|
||||||
c.logDebugf("PutBean|tableName:%s|id:%s|key:%s|type:%v", tableName, id, beanKey, reflect.TypeOf(obj))
|
|
||||||
c.putObject(beanKey, obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) delObject(key string) error {
|
|
||||||
c.logDebugf("delObject key:[%s]", key)
|
|
||||||
|
|
||||||
conn := c.pool.Get()
|
|
||||||
defer conn.Close()
|
|
||||||
if !exists(conn, key) {
|
|
||||||
c.logErrf("delObject key:[%s] err: %v", key, core.ErrCacheMiss)
|
|
||||||
return core.ErrCacheMiss
|
|
||||||
}
|
|
||||||
_, err := conn.Do("DEL", key)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) delObjects(key string) error {
|
|
||||||
|
|
||||||
c.logDebugf("delObjects key:[%s]", key)
|
|
||||||
|
|
||||||
conn := c.pool.Get()
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
keys, err := conn.Do("KEYS", key)
|
|
||||||
c.logDebugf("delObjects keys: %v", keys)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, key := range keys.([]interface{}) {
|
|
||||||
conn.Do("DEL", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) DelIds(tableName, sql string) {
|
|
||||||
c.delObject(c.getSqlKey(tableName, sql))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) DelBean(tableName string, id string) {
|
|
||||||
c.delObject(c.getBeanKey(tableName, id))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) ClearIds(tableName string) {
|
|
||||||
c.delObjects(c.getSqlKey(tableName, "*"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) ClearBeans(tableName string) {
|
|
||||||
c.delObjects(c.getBeanKey(tableName, "*"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) invoke(f func(string, ...interface{}) (interface{}, error),
|
|
||||||
key string, value interface{}, expires time.Duration) error {
|
|
||||||
|
|
||||||
switch expires {
|
|
||||||
case DEFAULT_EXPIRATION:
|
|
||||||
expires = c.defaultExpiration
|
|
||||||
case FOREVER_EXPIRATION:
|
|
||||||
expires = time.Duration(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := c.serialize(value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
conn := c.pool.Get()
|
|
||||||
defer conn.Close()
|
|
||||||
if expires > 0 {
|
|
||||||
_, err := f("SETEX", key, int32(expires/time.Second), b)
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
_, err := f("SET", key, b)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) serialize(value interface{}) ([]byte, error) {
|
|
||||||
|
|
||||||
err := c.registerGobConcreteType(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if reflect.TypeOf(value).Kind() == reflect.Struct {
|
|
||||||
return nil, fmt.Errorf("serialize func only take pointer of a struct")
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
encoder := gob.NewEncoder(&b)
|
|
||||||
|
|
||||||
c.logDebugf("serialize type:%v", reflect.TypeOf(value))
|
|
||||||
err = encoder.Encode(&value)
|
|
||||||
if err != nil {
|
|
||||||
c.logErrf("gob encoding '%s' failed: %s|value:%v", value, err, value)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) deserialize(byt []byte) (ptr interface{}, err error) {
|
|
||||||
b := bytes.NewBuffer(byt)
|
|
||||||
decoder := gob.NewDecoder(b)
|
|
||||||
|
|
||||||
var p interface{}
|
|
||||||
err = decoder.Decode(&p)
|
|
||||||
if err != nil {
|
|
||||||
c.logErrf("decode failed: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
v := reflect.ValueOf(p)
|
|
||||||
c.logDebugf("deserialize type:%v", v.Type())
|
|
||||||
if v.Kind() == reflect.Struct {
|
|
||||||
|
|
||||||
var pp interface{} = &p
|
|
||||||
datas := reflect.ValueOf(pp).Elem().InterfaceData()
|
|
||||||
|
|
||||||
sp := reflect.NewAt(v.Type(),
|
|
||||||
unsafe.Pointer(datas[1])).Interface()
|
|
||||||
ptr = sp
|
|
||||||
vv := reflect.ValueOf(ptr)
|
|
||||||
c.logDebugf("deserialize convert ptr type:%v | CanAddr:%t", vv.Type(), vv.CanAddr())
|
|
||||||
} else {
|
|
||||||
ptr = p
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RedisCacher) registerGobConcreteType(value interface{}) error {
|
|
||||||
|
|
||||||
t := reflect.TypeOf(value)
|
|
||||||
|
|
||||||
c.logDebugf("registerGobConcreteType:%v", t)
|
|
||||||
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.Ptr:
|
|
||||||
v := reflect.ValueOf(value)
|
|
||||||
i := v.Elem().Interface()
|
|
||||||
gob.Register(i)
|
|
||||||
case reflect.Struct, reflect.Map, reflect.Slice:
|
|
||||||
gob.Register(value)
|
|
||||||
case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
|
||||||
// do nothing since already registered known type
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unhandled type: %v", t)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
6
vendor/github.com/go-xorm/xorm-redis-cache/run_test.sh
generated
vendored
6
vendor/github.com/go-xorm/xorm-redis-cache/run_test.sh
generated
vendored
|
@ -1,6 +0,0 @@
|
||||||
redis-cli FLUSHALL
|
|
||||||
if [ $? == "0" ];then
|
|
||||||
go test -v -run=TestMysqlWithCache
|
|
||||||
else
|
|
||||||
echo "no redis-server running on localhost"
|
|
||||||
fi
|
|
27
vendor/github.com/gorilla/context/LICENSE
generated
vendored
27
vendor/github.com/gorilla/context/LICENSE
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
10
vendor/github.com/gorilla/context/README.md
generated
vendored
10
vendor/github.com/gorilla/context/README.md
generated
vendored
|
@ -1,10 +0,0 @@
|
||||||
context
|
|
||||||
=======
|
|
||||||
[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
|
|
||||||
|
|
||||||
gorilla/context is a general purpose registry for global request variables.
|
|
||||||
|
|
||||||
> Note: gorilla/context, having been born well before `context.Context` existed, does not play well
|
|
||||||
> with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`.
|
|
||||||
|
|
||||||
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
|
|
143
vendor/github.com/gorilla/context/context.go
generated
vendored
143
vendor/github.com/gorilla/context/context.go
generated
vendored
|
@ -1,143 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
mutex sync.RWMutex
|
|
||||||
data = make(map[*http.Request]map[interface{}]interface{})
|
|
||||||
datat = make(map[*http.Request]int64)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set stores a value for a given key in a given request.
|
|
||||||
func Set(r *http.Request, key, val interface{}) {
|
|
||||||
mutex.Lock()
|
|
||||||
if data[r] == nil {
|
|
||||||
data[r] = make(map[interface{}]interface{})
|
|
||||||
datat[r] = time.Now().Unix()
|
|
||||||
}
|
|
||||||
data[r][key] = val
|
|
||||||
mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a value stored for a given key in a given request.
|
|
||||||
func Get(r *http.Request, key interface{}) interface{} {
|
|
||||||
mutex.RLock()
|
|
||||||
if ctx := data[r]; ctx != nil {
|
|
||||||
value := ctx[key]
|
|
||||||
mutex.RUnlock()
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOk returns stored value and presence state like multi-value return of map access.
|
|
||||||
func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
|
|
||||||
mutex.RLock()
|
|
||||||
if _, ok := data[r]; ok {
|
|
||||||
value, ok := data[r][key]
|
|
||||||
mutex.RUnlock()
|
|
||||||
return value, ok
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
|
|
||||||
func GetAll(r *http.Request) map[interface{}]interface{} {
|
|
||||||
mutex.RLock()
|
|
||||||
if context, ok := data[r]; ok {
|
|
||||||
result := make(map[interface{}]interface{}, len(context))
|
|
||||||
for k, v := range context {
|
|
||||||
result[k] = v
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
|
|
||||||
// the request was registered.
|
|
||||||
func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
|
|
||||||
mutex.RLock()
|
|
||||||
context, ok := data[r]
|
|
||||||
result := make(map[interface{}]interface{}, len(context))
|
|
||||||
for k, v := range context {
|
|
||||||
result[k] = v
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return result, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes a value stored for a given key in a given request.
|
|
||||||
func Delete(r *http.Request, key interface{}) {
|
|
||||||
mutex.Lock()
|
|
||||||
if data[r] != nil {
|
|
||||||
delete(data[r], key)
|
|
||||||
}
|
|
||||||
mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear removes all values stored for a given request.
|
|
||||||
//
|
|
||||||
// This is usually called by a handler wrapper to clean up request
|
|
||||||
// variables at the end of a request lifetime. See ClearHandler().
|
|
||||||
func Clear(r *http.Request) {
|
|
||||||
mutex.Lock()
|
|
||||||
clear(r)
|
|
||||||
mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear is Clear without the lock.
|
|
||||||
func clear(r *http.Request) {
|
|
||||||
delete(data, r)
|
|
||||||
delete(datat, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge removes request data stored for longer than maxAge, in seconds.
|
|
||||||
// It returns the amount of requests removed.
|
|
||||||
//
|
|
||||||
// If maxAge <= 0, all request data is removed.
|
|
||||||
//
|
|
||||||
// This is only used for sanity check: in case context cleaning was not
|
|
||||||
// properly set some request data can be kept forever, consuming an increasing
|
|
||||||
// amount of memory. In case this is detected, Purge() must be called
|
|
||||||
// periodically until the problem is fixed.
|
|
||||||
func Purge(maxAge int) int {
|
|
||||||
mutex.Lock()
|
|
||||||
count := 0
|
|
||||||
if maxAge <= 0 {
|
|
||||||
count = len(data)
|
|
||||||
data = make(map[*http.Request]map[interface{}]interface{})
|
|
||||||
datat = make(map[*http.Request]int64)
|
|
||||||
} else {
|
|
||||||
min := time.Now().Unix() - int64(maxAge)
|
|
||||||
for r := range data {
|
|
||||||
if datat[r] < min {
|
|
||||||
clear(r)
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mutex.Unlock()
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearHandler wraps an http.Handler and clears request values at the end
|
|
||||||
// of a request lifetime.
|
|
||||||
func ClearHandler(h http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer Clear(r)
|
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
88
vendor/github.com/gorilla/context/doc.go
generated
vendored
88
vendor/github.com/gorilla/context/doc.go
generated
vendored
|
@ -1,88 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package context stores values shared during a request lifetime.
|
|
||||||
|
|
||||||
Note: gorilla/context, having been born well before `context.Context` existed,
|
|
||||||
does not play well > with the shallow copying of the request that
|
|
||||||
[`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext)
|
|
||||||
(added to net/http Go 1.7 onwards) performs. You should either use *just*
|
|
||||||
gorilla/context, or moving forward, the new `http.Request.Context()`.
|
|
||||||
|
|
||||||
For example, a router can set variables extracted from the URL and later
|
|
||||||
application handlers can access those values, or it can be used to store
|
|
||||||
sessions values to be saved at the end of a request. There are several
|
|
||||||
others common uses.
|
|
||||||
|
|
||||||
The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
|
|
||||||
|
|
||||||
http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
|
|
||||||
|
|
||||||
Here's the basic usage: first define the keys that you will need. The key
|
|
||||||
type is interface{} so a key can be of any type that supports equality.
|
|
||||||
Here we define a key using a custom int type to avoid name collisions:
|
|
||||||
|
|
||||||
package foo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gorilla/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type key int
|
|
||||||
|
|
||||||
const MyKey key = 0
|
|
||||||
|
|
||||||
Then set a variable. Variables are bound to an http.Request object, so you
|
|
||||||
need a request instance to set a value:
|
|
||||||
|
|
||||||
context.Set(r, MyKey, "bar")
|
|
||||||
|
|
||||||
The application can later access the variable using the same key you provided:
|
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// val is "bar".
|
|
||||||
val := context.Get(r, foo.MyKey)
|
|
||||||
|
|
||||||
// returns ("bar", true)
|
|
||||||
val, ok := context.GetOk(r, foo.MyKey)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
And that's all about the basic usage. We discuss some other ideas below.
|
|
||||||
|
|
||||||
Any type can be stored in the context. To enforce a given type, make the key
|
|
||||||
private and wrap Get() and Set() to accept and return values of a specific
|
|
||||||
type:
|
|
||||||
|
|
||||||
type key int
|
|
||||||
|
|
||||||
const mykey key = 0
|
|
||||||
|
|
||||||
// GetMyKey returns a value for this package from the request values.
|
|
||||||
func GetMyKey(r *http.Request) SomeType {
|
|
||||||
if rv := context.Get(r, mykey); rv != nil {
|
|
||||||
return rv.(SomeType)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMyKey sets a value for this package in the request values.
|
|
||||||
func SetMyKey(r *http.Request, val SomeType) {
|
|
||||||
context.Set(r, mykey, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
Variables must be cleared at the end of a request, to remove all values
|
|
||||||
that were stored. This can be done in an http.Handler, after a request was
|
|
||||||
served. Just call Clear() passing the request:
|
|
||||||
|
|
||||||
context.Clear(r)
|
|
||||||
|
|
||||||
...or use ClearHandler(), which conveniently wraps an http.Handler to clear
|
|
||||||
variables at the end of a request lifetime.
|
|
||||||
|
|
||||||
The Routers from the packages gorilla/mux and gorilla/pat call Clear()
|
|
||||||
so if you are using either of them you don't need to clear the context manually.
|
|
||||||
*/
|
|
||||||
package context
|
|
27
vendor/github.com/gorilla/securecookie/LICENSE
generated
vendored
27
vendor/github.com/gorilla/securecookie/LICENSE
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
80
vendor/github.com/gorilla/securecookie/README.md
generated
vendored
80
vendor/github.com/gorilla/securecookie/README.md
generated
vendored
|
@ -1,80 +0,0 @@
|
||||||
securecookie
|
|
||||||
============
|
|
||||||
[![GoDoc](https://godoc.org/github.com/gorilla/securecookie?status.svg)](https://godoc.org/github.com/gorilla/securecookie) [![Build Status](https://travis-ci.org/gorilla/securecookie.png?branch=master)](https://travis-ci.org/gorilla/securecookie)
|
|
||||||
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/securecookie/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/securecookie?badge)
|
|
||||||
|
|
||||||
|
|
||||||
securecookie encodes and decodes authenticated and optionally encrypted
|
|
||||||
cookie values.
|
|
||||||
|
|
||||||
Secure cookies can't be forged, because their values are validated using HMAC.
|
|
||||||
When encrypted, the content is also inaccessible to malicious eyes. It is still
|
|
||||||
recommended that sensitive data not be stored in cookies, and that HTTPS be used
|
|
||||||
to prevent cookie [replay attacks](https://en.wikipedia.org/wiki/Replay_attack).
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
To use it, first create a new SecureCookie instance:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Hash keys should be at least 32 bytes long
|
|
||||||
var hashKey = []byte("very-secret")
|
|
||||||
// Block keys should be 16 bytes (AES-128) or 32 bytes (AES-256) long.
|
|
||||||
// Shorter keys may weaken the encryption used.
|
|
||||||
var blockKey = []byte("a-lot-secret")
|
|
||||||
var s = securecookie.New(hashKey, blockKey)
|
|
||||||
```
|
|
||||||
|
|
||||||
The hashKey is required, used to authenticate the cookie value using HMAC.
|
|
||||||
It is recommended to use a key with 32 or 64 bytes.
|
|
||||||
|
|
||||||
The blockKey is optional, used to encrypt the cookie value -- set it to nil
|
|
||||||
to not use encryption. If set, the length must correspond to the block size
|
|
||||||
of the encryption algorithm. For AES, used by default, valid lengths are
|
|
||||||
16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
|
||||||
|
|
||||||
Strong keys can be created using the convenience function GenerateRandomKey().
|
|
||||||
|
|
||||||
Once a SecureCookie instance is set, use it to encode a cookie value:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
value := map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
}
|
|
||||||
if encoded, err := s.Encode("cookie-name", value); err == nil {
|
|
||||||
cookie := &http.Cookie{
|
|
||||||
Name: "cookie-name",
|
|
||||||
Value: encoded,
|
|
||||||
Path: "/",
|
|
||||||
Secure: true,
|
|
||||||
HttpOnly: true,
|
|
||||||
}
|
|
||||||
http.SetCookie(w, cookie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Later, use the same SecureCookie instance to decode and validate a cookie
|
|
||||||
value:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if cookie, err := r.Cookie("cookie-name"); err == nil {
|
|
||||||
value := make(map[string]string)
|
|
||||||
if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
|
|
||||||
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
We stored a map[string]string, but secure cookies can hold any value that
|
|
||||||
can be encoded using `encoding/gob`. To store custom types, they must be
|
|
||||||
registered first using gob.Register(). For basic types this is not needed;
|
|
||||||
it works out of the box. An optional JSON encoder that uses `encoding/json` is
|
|
||||||
available for types compatible with JSON.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
BSD licensed. See the LICENSE file for details.
|
|
61
vendor/github.com/gorilla/securecookie/doc.go
generated
vendored
61
vendor/github.com/gorilla/securecookie/doc.go
generated
vendored
|
@ -1,61 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package securecookie encodes and decodes authenticated and optionally
|
|
||||||
encrypted cookie values.
|
|
||||||
|
|
||||||
Secure cookies can't be forged, because their values are validated using HMAC.
|
|
||||||
When encrypted, the content is also inaccessible to malicious eyes.
|
|
||||||
|
|
||||||
To use it, first create a new SecureCookie instance:
|
|
||||||
|
|
||||||
var hashKey = []byte("very-secret")
|
|
||||||
var blockKey = []byte("a-lot-secret")
|
|
||||||
var s = securecookie.New(hashKey, blockKey)
|
|
||||||
|
|
||||||
The hashKey is required, used to authenticate the cookie value using HMAC.
|
|
||||||
It is recommended to use a key with 32 or 64 bytes.
|
|
||||||
|
|
||||||
The blockKey is optional, used to encrypt the cookie value -- set it to nil
|
|
||||||
to not use encryption. If set, the length must correspond to the block size
|
|
||||||
of the encryption algorithm. For AES, used by default, valid lengths are
|
|
||||||
16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
|
||||||
|
|
||||||
Strong keys can be created using the convenience function GenerateRandomKey().
|
|
||||||
|
|
||||||
Once a SecureCookie instance is set, use it to encode a cookie value:
|
|
||||||
|
|
||||||
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
value := map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
}
|
|
||||||
if encoded, err := s.Encode("cookie-name", value); err == nil {
|
|
||||||
cookie := &http.Cookie{
|
|
||||||
Name: "cookie-name",
|
|
||||||
Value: encoded,
|
|
||||||
Path: "/",
|
|
||||||
}
|
|
||||||
http.SetCookie(w, cookie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Later, use the same SecureCookie instance to decode and validate a cookie
|
|
||||||
value:
|
|
||||||
|
|
||||||
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if cookie, err := r.Cookie("cookie-name"); err == nil {
|
|
||||||
value := make(map[string]string)
|
|
||||||
if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
|
|
||||||
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
We stored a map[string]string, but secure cookies can hold any value that
|
|
||||||
can be encoded using encoding/gob. To store custom types, they must be
|
|
||||||
registered first using gob.Register(). For basic types this is not needed;
|
|
||||||
it works out of the box.
|
|
||||||
*/
|
|
||||||
package securecookie
|
|
25
vendor/github.com/gorilla/securecookie/fuzz.go
generated
vendored
25
vendor/github.com/gorilla/securecookie/fuzz.go
generated
vendored
|
@ -1,25 +0,0 @@
|
||||||
// +build gofuzz
|
|
||||||
|
|
||||||
package securecookie
|
|
||||||
|
|
||||||
var hashKey = []byte("very-secret12345")
|
|
||||||
var blockKey = []byte("a-lot-secret1234")
|
|
||||||
var s = New(hashKey, blockKey)
|
|
||||||
|
|
||||||
type Cookie struct {
|
|
||||||
B bool
|
|
||||||
I int
|
|
||||||
S string
|
|
||||||
}
|
|
||||||
|
|
||||||
func Fuzz(data []byte) int {
|
|
||||||
datas := string(data)
|
|
||||||
var c Cookie
|
|
||||||
if err := s.Decode("fuzz", datas, &c); err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if _, err := s.Encode("fuzz", c); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
}
|
|
646
vendor/github.com/gorilla/securecookie/securecookie.go
generated
vendored
646
vendor/github.com/gorilla/securecookie/securecookie.go
generated
vendored
|
@ -1,646 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package securecookie
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/subtle"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/gob"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Error is the interface of all errors returned by functions in this library.
|
|
||||||
type Error interface {
|
|
||||||
error
|
|
||||||
|
|
||||||
// IsUsage returns true for errors indicating the client code probably
|
|
||||||
// uses this library incorrectly. For example, the client may have
|
|
||||||
// failed to provide a valid hash key, or may have failed to configure
|
|
||||||
// the Serializer adequately for encoding value.
|
|
||||||
IsUsage() bool
|
|
||||||
|
|
||||||
// IsDecode returns true for errors indicating that a cookie could not
|
|
||||||
// be decoded and validated. Since cookies are usually untrusted
|
|
||||||
// user-provided input, errors of this type should be expected.
|
|
||||||
// Usually, the proper action is simply to reject the request.
|
|
||||||
IsDecode() bool
|
|
||||||
|
|
||||||
// IsInternal returns true for unexpected errors occurring in the
|
|
||||||
// securecookie implementation.
|
|
||||||
IsInternal() bool
|
|
||||||
|
|
||||||
// Cause, if it returns a non-nil value, indicates that this error was
|
|
||||||
// propagated from some underlying library. If this method returns nil,
|
|
||||||
// this error was raised directly by this library.
|
|
||||||
//
|
|
||||||
// Cause is provided principally for debugging/logging purposes; it is
|
|
||||||
// rare that application logic should perform meaningfully different
|
|
||||||
// logic based on Cause. See, for example, the caveats described on
|
|
||||||
// (MultiError).Cause().
|
|
||||||
Cause() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorType is a bitmask giving the error type(s) of an cookieError value.
|
|
||||||
type errorType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
usageError = errorType(1 << iota)
|
|
||||||
decodeError
|
|
||||||
internalError
|
|
||||||
)
|
|
||||||
|
|
||||||
type cookieError struct {
|
|
||||||
typ errorType
|
|
||||||
msg string
|
|
||||||
cause error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e cookieError) IsUsage() bool { return (e.typ & usageError) != 0 }
|
|
||||||
func (e cookieError) IsDecode() bool { return (e.typ & decodeError) != 0 }
|
|
||||||
func (e cookieError) IsInternal() bool { return (e.typ & internalError) != 0 }
|
|
||||||
|
|
||||||
func (e cookieError) Cause() error { return e.cause }
|
|
||||||
|
|
||||||
func (e cookieError) Error() string {
|
|
||||||
parts := []string{"securecookie: "}
|
|
||||||
if e.msg == "" {
|
|
||||||
parts = append(parts, "error")
|
|
||||||
} else {
|
|
||||||
parts = append(parts, e.msg)
|
|
||||||
}
|
|
||||||
if c := e.Cause(); c != nil {
|
|
||||||
parts = append(parts, " - caused by: ", c.Error())
|
|
||||||
}
|
|
||||||
return strings.Join(parts, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errGeneratingIV = cookieError{typ: internalError, msg: "failed to generate random iv"}
|
|
||||||
|
|
||||||
errNoCodecs = cookieError{typ: usageError, msg: "no codecs provided"}
|
|
||||||
errHashKeyNotSet = cookieError{typ: usageError, msg: "hash key is not set"}
|
|
||||||
errBlockKeyNotSet = cookieError{typ: usageError, msg: "block key is not set"}
|
|
||||||
errEncodedValueTooLong = cookieError{typ: usageError, msg: "the value is too long"}
|
|
||||||
|
|
||||||
errValueToDecodeTooLong = cookieError{typ: decodeError, msg: "the value is too long"}
|
|
||||||
errTimestampInvalid = cookieError{typ: decodeError, msg: "invalid timestamp"}
|
|
||||||
errTimestampTooNew = cookieError{typ: decodeError, msg: "timestamp is too new"}
|
|
||||||
errTimestampExpired = cookieError{typ: decodeError, msg: "expired timestamp"}
|
|
||||||
errDecryptionFailed = cookieError{typ: decodeError, msg: "the value could not be decrypted"}
|
|
||||||
errValueNotByte = cookieError{typ: decodeError, msg: "value not a []byte."}
|
|
||||||
errValueNotBytePtr = cookieError{typ: decodeError, msg: "value not a pointer to []byte."}
|
|
||||||
|
|
||||||
// ErrMacInvalid indicates that cookie decoding failed because the HMAC
|
|
||||||
// could not be extracted and verified. Direct use of this error
|
|
||||||
// variable is deprecated; it is public only for legacy compatibility,
|
|
||||||
// and may be privatized in the future, as it is rarely useful to
|
|
||||||
// distinguish between this error and other Error implementations.
|
|
||||||
ErrMacInvalid = cookieError{typ: decodeError, msg: "the value is not valid"}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Codec defines an interface to encode and decode cookie values.
|
|
||||||
type Codec interface {
|
|
||||||
Encode(name string, value interface{}) (string, error)
|
|
||||||
Decode(name, value string, dst interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new SecureCookie.
|
|
||||||
//
|
|
||||||
// hashKey is required, used to authenticate values using HMAC. Create it using
|
|
||||||
// GenerateRandomKey(). It is recommended to use a key with 32 or 64 bytes.
|
|
||||||
//
|
|
||||||
// blockKey is optional, used to encrypt values. Create it using
|
|
||||||
// GenerateRandomKey(). The key length must correspond to the block size
|
|
||||||
// of the encryption algorithm. For AES, used by default, valid lengths are
|
|
||||||
// 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
|
||||||
// The default encoder used for cookie serialization is encoding/gob.
|
|
||||||
//
|
|
||||||
// Note that keys created using GenerateRandomKey() are not automatically
|
|
||||||
// persisted. New keys will be created when the application is restarted, and
|
|
||||||
// previously issued cookies will not be able to be decoded.
|
|
||||||
func New(hashKey, blockKey []byte) *SecureCookie {
|
|
||||||
s := &SecureCookie{
|
|
||||||
hashKey: hashKey,
|
|
||||||
blockKey: blockKey,
|
|
||||||
hashFunc: sha256.New,
|
|
||||||
maxAge: 86400 * 30,
|
|
||||||
maxLength: 4096,
|
|
||||||
sz: GobEncoder{},
|
|
||||||
}
|
|
||||||
if hashKey == nil {
|
|
||||||
s.err = errHashKeyNotSet
|
|
||||||
}
|
|
||||||
if blockKey != nil {
|
|
||||||
s.BlockFunc(aes.NewCipher)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecureCookie encodes and decodes authenticated and optionally encrypted
|
|
||||||
// cookie values.
|
|
||||||
type SecureCookie struct {
|
|
||||||
hashKey []byte
|
|
||||||
hashFunc func() hash.Hash
|
|
||||||
blockKey []byte
|
|
||||||
block cipher.Block
|
|
||||||
maxLength int
|
|
||||||
maxAge int64
|
|
||||||
minAge int64
|
|
||||||
err error
|
|
||||||
sz Serializer
|
|
||||||
// For testing purposes, the function that returns the current timestamp.
|
|
||||||
// If not set, it will use time.Now().UTC().Unix().
|
|
||||||
timeFunc func() int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serializer provides an interface for providing custom serializers for cookie
|
|
||||||
// values.
|
|
||||||
type Serializer interface {
|
|
||||||
Serialize(src interface{}) ([]byte, error)
|
|
||||||
Deserialize(src []byte, dst interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// GobEncoder encodes cookie values using encoding/gob. This is the simplest
|
|
||||||
// encoder and can handle complex types via gob.Register.
|
|
||||||
type GobEncoder struct{}
|
|
||||||
|
|
||||||
// JSONEncoder encodes cookie values using encoding/json. Users who wish to
|
|
||||||
// encode complex types need to satisfy the json.Marshaller and
|
|
||||||
// json.Unmarshaller interfaces.
|
|
||||||
type JSONEncoder struct{}
|
|
||||||
|
|
||||||
// NopEncoder does not encode cookie values, and instead simply accepts a []byte
|
|
||||||
// (as an interface{}) and returns a []byte. This is particularly useful when
|
|
||||||
// you encoding an object upstream and do not wish to re-encode it.
|
|
||||||
type NopEncoder struct{}
|
|
||||||
|
|
||||||
// MaxLength restricts the maximum length, in bytes, for the cookie value.
|
|
||||||
//
|
|
||||||
// Default is 4096, which is the maximum value accepted by Internet Explorer.
|
|
||||||
func (s *SecureCookie) MaxLength(value int) *SecureCookie {
|
|
||||||
s.maxLength = value
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxAge restricts the maximum age, in seconds, for the cookie value.
|
|
||||||
//
|
|
||||||
// Default is 86400 * 30. Set it to 0 for no restriction.
|
|
||||||
func (s *SecureCookie) MaxAge(value int) *SecureCookie {
|
|
||||||
s.maxAge = int64(value)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// MinAge restricts the minimum age, in seconds, for the cookie value.
|
|
||||||
//
|
|
||||||
// Default is 0 (no restriction).
|
|
||||||
func (s *SecureCookie) MinAge(value int) *SecureCookie {
|
|
||||||
s.minAge = int64(value)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashFunc sets the hash function used to create HMAC.
|
|
||||||
//
|
|
||||||
// Default is crypto/sha256.New.
|
|
||||||
func (s *SecureCookie) HashFunc(f func() hash.Hash) *SecureCookie {
|
|
||||||
s.hashFunc = f
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockFunc sets the encryption function used to create a cipher.Block.
|
|
||||||
//
|
|
||||||
// Default is crypto/aes.New.
|
|
||||||
func (s *SecureCookie) BlockFunc(f func([]byte) (cipher.Block, error)) *SecureCookie {
|
|
||||||
if s.blockKey == nil {
|
|
||||||
s.err = errBlockKeyNotSet
|
|
||||||
} else if block, err := f(s.blockKey); err == nil {
|
|
||||||
s.block = block
|
|
||||||
} else {
|
|
||||||
s.err = cookieError{cause: err, typ: usageError}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encoding sets the encoding/serialization method for cookies.
|
|
||||||
//
|
|
||||||
// Default is encoding/gob. To encode special structures using encoding/gob,
|
|
||||||
// they must be registered first using gob.Register().
|
|
||||||
func (s *SecureCookie) SetSerializer(sz Serializer) *SecureCookie {
|
|
||||||
s.sz = sz
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode encodes a cookie value.
|
|
||||||
//
|
|
||||||
// It serializes, optionally encrypts, signs with a message authentication code,
|
|
||||||
// and finally encodes the value.
|
|
||||||
//
|
|
||||||
// The name argument is the cookie name. It is stored with the encoded value.
|
|
||||||
// The value argument is the value to be encoded. It can be any value that can
|
|
||||||
// be encoded using the currently selected serializer; see SetSerializer().
|
|
||||||
//
|
|
||||||
// It is the client's responsibility to ensure that value, when encoded using
|
|
||||||
// the current serialization/encryption settings on s and then base64-encoded,
|
|
||||||
// is shorter than the maximum permissible length.
|
|
||||||
func (s *SecureCookie) Encode(name string, value interface{}) (string, error) {
|
|
||||||
if s.err != nil {
|
|
||||||
return "", s.err
|
|
||||||
}
|
|
||||||
if s.hashKey == nil {
|
|
||||||
s.err = errHashKeyNotSet
|
|
||||||
return "", s.err
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
var b []byte
|
|
||||||
// 1. Serialize.
|
|
||||||
if b, err = s.sz.Serialize(value); err != nil {
|
|
||||||
return "", cookieError{cause: err, typ: usageError}
|
|
||||||
}
|
|
||||||
// 2. Encrypt (optional).
|
|
||||||
if s.block != nil {
|
|
||||||
if b, err = encrypt(s.block, b); err != nil {
|
|
||||||
return "", cookieError{cause: err, typ: usageError}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b = encode(b)
|
|
||||||
// 3. Create MAC for "name|date|value". Extra pipe to be used later.
|
|
||||||
b = []byte(fmt.Sprintf("%s|%d|%s|", name, s.timestamp(), b))
|
|
||||||
mac := createMac(hmac.New(s.hashFunc, s.hashKey), b[:len(b)-1])
|
|
||||||
// Append mac, remove name.
|
|
||||||
b = append(b, mac...)[len(name)+1:]
|
|
||||||
// 4. Encode to base64.
|
|
||||||
b = encode(b)
|
|
||||||
// 5. Check length.
|
|
||||||
if s.maxLength != 0 && len(b) > s.maxLength {
|
|
||||||
return "", errEncodedValueTooLong
|
|
||||||
}
|
|
||||||
// Done.
|
|
||||||
return string(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode decodes a cookie value.
|
|
||||||
//
|
|
||||||
// It decodes, verifies a message authentication code, optionally decrypts and
|
|
||||||
// finally deserializes the value.
|
|
||||||
//
|
|
||||||
// The name argument is the cookie name. It must be the same name used when
|
|
||||||
// it was stored. The value argument is the encoded cookie value. The dst
|
|
||||||
// argument is where the cookie will be decoded. It must be a pointer.
|
|
||||||
func (s *SecureCookie) Decode(name, value string, dst interface{}) error {
|
|
||||||
if s.err != nil {
|
|
||||||
return s.err
|
|
||||||
}
|
|
||||||
if s.hashKey == nil {
|
|
||||||
s.err = errHashKeyNotSet
|
|
||||||
return s.err
|
|
||||||
}
|
|
||||||
// 1. Check length.
|
|
||||||
if s.maxLength != 0 && len(value) > s.maxLength {
|
|
||||||
return errValueToDecodeTooLong
|
|
||||||
}
|
|
||||||
// 2. Decode from base64.
|
|
||||||
b, err := decode([]byte(value))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// 3. Verify MAC. Value is "date|value|mac".
|
|
||||||
parts := bytes.SplitN(b, []byte("|"), 3)
|
|
||||||
if len(parts) != 3 {
|
|
||||||
return ErrMacInvalid
|
|
||||||
}
|
|
||||||
h := hmac.New(s.hashFunc, s.hashKey)
|
|
||||||
b = append([]byte(name+"|"), b[:len(b)-len(parts[2])-1]...)
|
|
||||||
if err = verifyMac(h, b, parts[2]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// 4. Verify date ranges.
|
|
||||||
var t1 int64
|
|
||||||
if t1, err = strconv.ParseInt(string(parts[0]), 10, 64); err != nil {
|
|
||||||
return errTimestampInvalid
|
|
||||||
}
|
|
||||||
t2 := s.timestamp()
|
|
||||||
if s.minAge != 0 && t1 > t2-s.minAge {
|
|
||||||
return errTimestampTooNew
|
|
||||||
}
|
|
||||||
if s.maxAge != 0 && t1 < t2-s.maxAge {
|
|
||||||
return errTimestampExpired
|
|
||||||
}
|
|
||||||
// 5. Decrypt (optional).
|
|
||||||
b, err = decode(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if s.block != nil {
|
|
||||||
if b, err = decrypt(s.block, b); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 6. Deserialize.
|
|
||||||
if err = s.sz.Deserialize(b, dst); err != nil {
|
|
||||||
return cookieError{cause: err, typ: decodeError}
|
|
||||||
}
|
|
||||||
// Done.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// timestamp returns the current timestamp, in seconds.
|
|
||||||
//
|
|
||||||
// For testing purposes, the function that generates the timestamp can be
|
|
||||||
// overridden. If not set, it will return time.Now().UTC().Unix().
|
|
||||||
func (s *SecureCookie) timestamp() int64 {
|
|
||||||
if s.timeFunc == nil {
|
|
||||||
return time.Now().UTC().Unix()
|
|
||||||
}
|
|
||||||
return s.timeFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authentication -------------------------------------------------------------
|
|
||||||
|
|
||||||
// createMac creates a message authentication code (MAC).
|
|
||||||
func createMac(h hash.Hash, value []byte) []byte {
|
|
||||||
h.Write(value)
|
|
||||||
return h.Sum(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyMac verifies that a message authentication code (MAC) is valid.
|
|
||||||
func verifyMac(h hash.Hash, value []byte, mac []byte) error {
|
|
||||||
mac2 := createMac(h, value)
|
|
||||||
// Check that both MACs are of equal length, as subtle.ConstantTimeCompare
|
|
||||||
// does not do this prior to Go 1.4.
|
|
||||||
if len(mac) == len(mac2) && subtle.ConstantTimeCompare(mac, mac2) == 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ErrMacInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encryption -----------------------------------------------------------------
|
|
||||||
|
|
||||||
// encrypt encrypts a value using the given block in counter mode.
|
|
||||||
//
|
|
||||||
// A random initialization vector (http://goo.gl/zF67k) with the length of the
|
|
||||||
// block size is prepended to the resulting ciphertext.
|
|
||||||
func encrypt(block cipher.Block, value []byte) ([]byte, error) {
|
|
||||||
iv := GenerateRandomKey(block.BlockSize())
|
|
||||||
if iv == nil {
|
|
||||||
return nil, errGeneratingIV
|
|
||||||
}
|
|
||||||
// Encrypt it.
|
|
||||||
stream := cipher.NewCTR(block, iv)
|
|
||||||
stream.XORKeyStream(value, value)
|
|
||||||
// Return iv + ciphertext.
|
|
||||||
return append(iv, value...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrypt decrypts a value using the given block in counter mode.
|
|
||||||
//
|
|
||||||
// The value to be decrypted must be prepended by a initialization vector
|
|
||||||
// (http://goo.gl/zF67k) with the length of the block size.
|
|
||||||
func decrypt(block cipher.Block, value []byte) ([]byte, error) {
|
|
||||||
size := block.BlockSize()
|
|
||||||
if len(value) > size {
|
|
||||||
// Extract iv.
|
|
||||||
iv := value[:size]
|
|
||||||
// Extract ciphertext.
|
|
||||||
value = value[size:]
|
|
||||||
// Decrypt it.
|
|
||||||
stream := cipher.NewCTR(block, iv)
|
|
||||||
stream.XORKeyStream(value, value)
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
return nil, errDecryptionFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialization --------------------------------------------------------------
|
|
||||||
|
|
||||||
// Serialize encodes a value using gob.
|
|
||||||
func (e GobEncoder) Serialize(src interface{}) ([]byte, error) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
enc := gob.NewEncoder(buf)
|
|
||||||
if err := enc.Encode(src); err != nil {
|
|
||||||
return nil, cookieError{cause: err, typ: usageError}
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deserialize decodes a value using gob.
|
|
||||||
func (e GobEncoder) Deserialize(src []byte, dst interface{}) error {
|
|
||||||
dec := gob.NewDecoder(bytes.NewBuffer(src))
|
|
||||||
if err := dec.Decode(dst); err != nil {
|
|
||||||
return cookieError{cause: err, typ: decodeError}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize encodes a value using encoding/json.
|
|
||||||
func (e JSONEncoder) Serialize(src interface{}) ([]byte, error) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
enc := json.NewEncoder(buf)
|
|
||||||
if err := enc.Encode(src); err != nil {
|
|
||||||
return nil, cookieError{cause: err, typ: usageError}
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deserialize decodes a value using encoding/json.
|
|
||||||
func (e JSONEncoder) Deserialize(src []byte, dst interface{}) error {
|
|
||||||
dec := json.NewDecoder(bytes.NewReader(src))
|
|
||||||
if err := dec.Decode(dst); err != nil {
|
|
||||||
return cookieError{cause: err, typ: decodeError}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize passes a []byte through as-is.
|
|
||||||
func (e NopEncoder) Serialize(src interface{}) ([]byte, error) {
|
|
||||||
if b, ok := src.([]byte); ok {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errValueNotByte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deserialize passes a []byte through as-is.
|
|
||||||
func (e NopEncoder) Deserialize(src []byte, dst interface{}) error {
|
|
||||||
if dat, ok := dst.(*[]byte); ok {
|
|
||||||
*dat = src
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errValueNotBytePtr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encoding -------------------------------------------------------------------
|
|
||||||
|
|
||||||
// encode encodes a value using base64.
|
|
||||||
func encode(value []byte) []byte {
|
|
||||||
encoded := make([]byte, base64.URLEncoding.EncodedLen(len(value)))
|
|
||||||
base64.URLEncoding.Encode(encoded, value)
|
|
||||||
return encoded
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode decodes a cookie using base64.
|
|
||||||
func decode(value []byte) ([]byte, error) {
|
|
||||||
decoded := make([]byte, base64.URLEncoding.DecodedLen(len(value)))
|
|
||||||
b, err := base64.URLEncoding.Decode(decoded, value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, cookieError{cause: err, typ: decodeError, msg: "base64 decode failed"}
|
|
||||||
}
|
|
||||||
return decoded[:b], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// GenerateRandomKey creates a random key with the given length in bytes.
|
|
||||||
// On failure, returns nil.
|
|
||||||
//
|
|
||||||
// Callers should explicitly check for the possibility of a nil return, treat
|
|
||||||
// it as a failure of the system random number generator, and not continue.
|
|
||||||
func GenerateRandomKey(length int) []byte {
|
|
||||||
k := make([]byte, length)
|
|
||||||
if _, err := io.ReadFull(rand.Reader, k); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
|
|
||||||
// CodecsFromPairs returns a slice of SecureCookie instances.
|
|
||||||
//
|
|
||||||
// It is a convenience function to create a list of codecs for key rotation. Note
|
|
||||||
// that the generated Codecs will have the default options applied: callers
|
|
||||||
// should iterate over each Codec and type-assert the underlying *SecureCookie to
|
|
||||||
// change these.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// codecs := securecookie.CodecsFromPairs(
|
|
||||||
// []byte("new-hash-key"),
|
|
||||||
// []byte("new-block-key"),
|
|
||||||
// []byte("old-hash-key"),
|
|
||||||
// []byte("old-block-key"),
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// // Modify each instance.
|
|
||||||
// for _, s := range codecs {
|
|
||||||
// if cookie, ok := s.(*securecookie.SecureCookie); ok {
|
|
||||||
// cookie.MaxAge(86400 * 7)
|
|
||||||
// cookie.SetSerializer(securecookie.JSONEncoder{})
|
|
||||||
// cookie.HashFunc(sha512.New512_256)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
func CodecsFromPairs(keyPairs ...[]byte) []Codec {
|
|
||||||
codecs := make([]Codec, len(keyPairs)/2+len(keyPairs)%2)
|
|
||||||
for i := 0; i < len(keyPairs); i += 2 {
|
|
||||||
var blockKey []byte
|
|
||||||
if i+1 < len(keyPairs) {
|
|
||||||
blockKey = keyPairs[i+1]
|
|
||||||
}
|
|
||||||
codecs[i/2] = New(keyPairs[i], blockKey)
|
|
||||||
}
|
|
||||||
return codecs
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeMulti encodes a cookie value using a group of codecs.
|
|
||||||
//
|
|
||||||
// The codecs are tried in order. Multiple codecs are accepted to allow
|
|
||||||
// key rotation.
|
|
||||||
//
|
|
||||||
// On error, may return a MultiError.
|
|
||||||
func EncodeMulti(name string, value interface{}, codecs ...Codec) (string, error) {
|
|
||||||
if len(codecs) == 0 {
|
|
||||||
return "", errNoCodecs
|
|
||||||
}
|
|
||||||
|
|
||||||
var errors MultiError
|
|
||||||
for _, codec := range codecs {
|
|
||||||
encoded, err := codec.Encode(name, value)
|
|
||||||
if err == nil {
|
|
||||||
return encoded, nil
|
|
||||||
}
|
|
||||||
errors = append(errors, err)
|
|
||||||
}
|
|
||||||
return "", errors
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeMulti decodes a cookie value using a group of codecs.
|
|
||||||
//
|
|
||||||
// The codecs are tried in order. Multiple codecs are accepted to allow
|
|
||||||
// key rotation.
|
|
||||||
//
|
|
||||||
// On error, may return a MultiError.
|
|
||||||
func DecodeMulti(name string, value string, dst interface{}, codecs ...Codec) error {
|
|
||||||
if len(codecs) == 0 {
|
|
||||||
return errNoCodecs
|
|
||||||
}
|
|
||||||
|
|
||||||
var errors MultiError
|
|
||||||
for _, codec := range codecs {
|
|
||||||
err := codec.Decode(name, value, dst)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
errors = append(errors, err)
|
|
||||||
}
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiError groups multiple errors.
|
|
||||||
type MultiError []error
|
|
||||||
|
|
||||||
func (m MultiError) IsUsage() bool { return m.any(func(e Error) bool { return e.IsUsage() }) }
|
|
||||||
func (m MultiError) IsDecode() bool { return m.any(func(e Error) bool { return e.IsDecode() }) }
|
|
||||||
func (m MultiError) IsInternal() bool { return m.any(func(e Error) bool { return e.IsInternal() }) }
|
|
||||||
|
|
||||||
// Cause returns nil for MultiError; there is no unique underlying cause in the
|
|
||||||
// general case.
|
|
||||||
//
|
|
||||||
// Note: we could conceivably return a non-nil Cause only when there is exactly
|
|
||||||
// one child error with a Cause. However, it would be brittle for client code
|
|
||||||
// to rely on the arity of causes inside a MultiError, so we have opted not to
|
|
||||||
// provide this functionality. Clients which really wish to access the Causes
|
|
||||||
// of the underlying errors are free to iterate through the errors themselves.
|
|
||||||
func (m MultiError) Cause() error { return nil }
|
|
||||||
|
|
||||||
func (m MultiError) Error() string {
|
|
||||||
s, n := "", 0
|
|
||||||
for _, e := range m {
|
|
||||||
if e != nil {
|
|
||||||
if n == 0 {
|
|
||||||
s = e.Error()
|
|
||||||
}
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch n {
|
|
||||||
case 0:
|
|
||||||
return "(0 errors)"
|
|
||||||
case 1:
|
|
||||||
return s
|
|
||||||
case 2:
|
|
||||||
return s + " (and 1 other error)"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// any returns true if any element of m is an Error for which pred returns true.
|
|
||||||
func (m MultiError) any(pred func(Error) bool) bool {
|
|
||||||
for _, e := range m {
|
|
||||||
if ourErr, ok := e.(Error); ok && pred(ourErr) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
27
vendor/github.com/gorilla/sessions/LICENSE
generated
vendored
27
vendor/github.com/gorilla/sessions/LICENSE
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
90
vendor/github.com/gorilla/sessions/README.md
generated
vendored
90
vendor/github.com/gorilla/sessions/README.md
generated
vendored
|
@ -1,90 +0,0 @@
|
||||||
sessions
|
|
||||||
========
|
|
||||||
[![GoDoc](https://godoc.org/github.com/gorilla/sessions?status.svg)](https://godoc.org/github.com/gorilla/sessions) [![Build Status](https://travis-ci.org/gorilla/sessions.png?branch=master)](https://travis-ci.org/gorilla/sessions)
|
|
||||||
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/sessions/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/sessions?badge)
|
|
||||||
|
|
||||||
|
|
||||||
gorilla/sessions provides cookie and filesystem sessions and infrastructure for
|
|
||||||
custom session backends.
|
|
||||||
|
|
||||||
The key features are:
|
|
||||||
|
|
||||||
* Simple API: use it as an easy way to set signed (and optionally
|
|
||||||
encrypted) cookies.
|
|
||||||
* Built-in backends to store sessions in cookies or the filesystem.
|
|
||||||
* Flash messages: session values that last until read.
|
|
||||||
* Convenient way to switch session persistency (aka "remember me") and set
|
|
||||||
other attributes.
|
|
||||||
* Mechanism to rotate authentication and encryption keys.
|
|
||||||
* Multiple sessions per request, even using different backends.
|
|
||||||
* Interfaces and infrastructure for custom session backends: sessions from
|
|
||||||
different stores can be retrieved and batch-saved using a common API.
|
|
||||||
|
|
||||||
Let's start with an example that shows the sessions API in a nutshell:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"github.com/gorilla/sessions"
|
|
||||||
)
|
|
||||||
|
|
||||||
var store = sessions.NewCookieStore([]byte("something-very-secret"))
|
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Get a session. We're ignoring the error resulted from decoding an
|
|
||||||
// existing session: Get() always returns a session, even if empty.
|
|
||||||
session, _ := store.Get(r, "session-name")
|
|
||||||
// Set some session values.
|
|
||||||
session.Values["foo"] = "bar"
|
|
||||||
session.Values[42] = 43
|
|
||||||
// Save it before we write to the response/return from the handler.
|
|
||||||
session.Save(r, w)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
First we initialize a session store calling `NewCookieStore()` and passing a
|
|
||||||
secret key used to authenticate the session. Inside the handler, we call
|
|
||||||
`store.Get()` to retrieve an existing session or create a new one. Then we set
|
|
||||||
some session values in session.Values, which is a `map[interface{}]interface{}`.
|
|
||||||
And finally we call `session.Save()` to save the session in the response.
|
|
||||||
|
|
||||||
Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
|
|
||||||
with
|
|
||||||
[`context.ClearHandler`](http://www.gorillatoolkit.org/pkg/context#ClearHandler)
|
|
||||||
or else you will leak memory! An easy way to do this is to wrap the top-level
|
|
||||||
mux when calling http.ListenAndServe:
|
|
||||||
|
|
||||||
```go
|
|
||||||
http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))
|
|
||||||
```
|
|
||||||
|
|
||||||
The ClearHandler function is provided by the gorilla/context package.
|
|
||||||
|
|
||||||
More examples are available [on the Gorilla
|
|
||||||
website](http://www.gorillatoolkit.org/pkg/sessions).
|
|
||||||
|
|
||||||
## Store Implementations
|
|
||||||
|
|
||||||
Other implementations of the `sessions.Store` interface:
|
|
||||||
|
|
||||||
* [github.com/starJammer/gorilla-sessions-arangodb](https://github.com/starJammer/gorilla-sessions-arangodb) - ArangoDB
|
|
||||||
* [github.com/yosssi/boltstore](https://github.com/yosssi/boltstore) - Bolt
|
|
||||||
* [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase
|
|
||||||
* [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb on AWS
|
|
||||||
* [github.com/savaki/dynastore](https://github.com/savaki/dynastore) - DynamoDB on AWS (Official AWS library)
|
|
||||||
* [github.com/bradleypeabody/gorilla-sessions-memcache](https://github.com/bradleypeabody/gorilla-sessions-memcache) - Memcache
|
|
||||||
* [github.com/dsoprea/go-appengine-sessioncascade](https://github.com/dsoprea/go-appengine-sessioncascade) - Memcache/Datastore/Context in AppEngine
|
|
||||||
* [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore) - MongoDB
|
|
||||||
* [github.com/srinathgs/mysqlstore](https://github.com/srinathgs/mysqlstore) - MySQL
|
|
||||||
* [github.com/EnumApps/clustersqlstore](https://github.com/EnumApps/clustersqlstore) - MySQL Cluster
|
|
||||||
* [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore) - PostgreSQL
|
|
||||||
* [github.com/boj/redistore](https://github.com/boj/redistore) - Redis
|
|
||||||
* [github.com/boj/rethinkstore](https://github.com/boj/rethinkstore) - RethinkDB
|
|
||||||
* [github.com/boj/riakstore](https://github.com/boj/riakstore) - Riak
|
|
||||||
* [github.com/michaeljs1990/sqlitestore](https://github.com/michaeljs1990/sqlitestore) - SQLite
|
|
||||||
* [github.com/wader/gormstore](https://github.com/wader/gormstore) - GORM (MySQL, PostgreSQL, SQLite)
|
|
||||||
* [github.com/gernest/qlstore](https://github.com/gernest/qlstore) - ql
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
BSD licensed. See the LICENSE file for details.
|
|
198
vendor/github.com/gorilla/sessions/doc.go
generated
vendored
198
vendor/github.com/gorilla/sessions/doc.go
generated
vendored
|
@ -1,198 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package sessions provides cookie and filesystem sessions and
|
|
||||||
infrastructure for custom session backends.
|
|
||||||
|
|
||||||
The key features are:
|
|
||||||
|
|
||||||
* Simple API: use it as an easy way to set signed (and optionally
|
|
||||||
encrypted) cookies.
|
|
||||||
* Built-in backends to store sessions in cookies or the filesystem.
|
|
||||||
* Flash messages: session values that last until read.
|
|
||||||
* Convenient way to switch session persistency (aka "remember me") and set
|
|
||||||
other attributes.
|
|
||||||
* Mechanism to rotate authentication and encryption keys.
|
|
||||||
* Multiple sessions per request, even using different backends.
|
|
||||||
* Interfaces and infrastructure for custom session backends: sessions from
|
|
||||||
different stores can be retrieved and batch-saved using a common API.
|
|
||||||
|
|
||||||
Let's start with an example that shows the sessions API in a nutshell:
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"github.com/gorilla/sessions"
|
|
||||||
)
|
|
||||||
|
|
||||||
var store = sessions.NewCookieStore([]byte("something-very-secret"))
|
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Get a session. Get() always returns a session, even if empty.
|
|
||||||
session, err := store.Get(r, "session-name")
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set some session values.
|
|
||||||
session.Values["foo"] = "bar"
|
|
||||||
session.Values[42] = 43
|
|
||||||
// Save it before we write to the response/return from the handler.
|
|
||||||
session.Save(r, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
First we initialize a session store calling NewCookieStore() and passing a
|
|
||||||
secret key used to authenticate the session. Inside the handler, we call
|
|
||||||
store.Get() to retrieve an existing session or a new one. Then we set some
|
|
||||||
session values in session.Values, which is a map[interface{}]interface{}.
|
|
||||||
And finally we call session.Save() to save the session in the response.
|
|
||||||
|
|
||||||
Note that in production code, we should check for errors when calling
|
|
||||||
session.Save(r, w), and either display an error message or otherwise handle it.
|
|
||||||
|
|
||||||
Save must be called before writing to the response, otherwise the session
|
|
||||||
cookie will not be sent to the client.
|
|
||||||
|
|
||||||
Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
|
|
||||||
with context.ClearHandler as or else you will leak memory! An easy way to do this
|
|
||||||
is to wrap the top-level mux when calling http.ListenAndServe:
|
|
||||||
|
|
||||||
http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))
|
|
||||||
|
|
||||||
The ClearHandler function is provided by the gorilla/context package.
|
|
||||||
|
|
||||||
That's all you need to know for the basic usage. Let's take a look at other
|
|
||||||
options, starting with flash messages.
|
|
||||||
|
|
||||||
Flash messages are session values that last until read. The term appeared with
|
|
||||||
Ruby On Rails a few years back. When we request a flash message, it is removed
|
|
||||||
from the session. To add a flash, call session.AddFlash(), and to get all
|
|
||||||
flashes, call session.Flashes(). Here is an example:
|
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Get a session.
|
|
||||||
session, err := store.Get(r, "session-name")
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the previously flashes, if any.
|
|
||||||
if flashes := session.Flashes(); len(flashes) > 0 {
|
|
||||||
// Use the flash values.
|
|
||||||
} else {
|
|
||||||
// Set a new flash.
|
|
||||||
session.AddFlash("Hello, flash messages world!")
|
|
||||||
}
|
|
||||||
session.Save(r, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
Flash messages are useful to set information to be read after a redirection,
|
|
||||||
like after form submissions.
|
|
||||||
|
|
||||||
There may also be cases where you want to store a complex datatype within a
|
|
||||||
session, such as a struct. Sessions are serialised using the encoding/gob package,
|
|
||||||
so it is easy to register new datatypes for storage in sessions:
|
|
||||||
|
|
||||||
import(
|
|
||||||
"encoding/gob"
|
|
||||||
"github.com/gorilla/sessions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Person struct {
|
|
||||||
FirstName string
|
|
||||||
LastName string
|
|
||||||
Email string
|
|
||||||
Age int
|
|
||||||
}
|
|
||||||
|
|
||||||
type M map[string]interface{}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
gob.Register(&Person{})
|
|
||||||
gob.Register(&M{})
|
|
||||||
}
|
|
||||||
|
|
||||||
As it's not possible to pass a raw type as a parameter to a function, gob.Register()
|
|
||||||
relies on us passing it a value of the desired type. In the example above we've passed
|
|
||||||
it a pointer to a struct and a pointer to a custom type representing a
|
|
||||||
map[string]interface. (We could have passed non-pointer values if we wished.) This will
|
|
||||||
then allow us to serialise/deserialise values of those types to and from our sessions.
|
|
||||||
|
|
||||||
Note that because session values are stored in a map[string]interface{}, there's
|
|
||||||
a need to type-assert data when retrieving it. We'll use the Person struct we registered above:
|
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
session, err := store.Get(r, "session-name")
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve our struct and type-assert it
|
|
||||||
val := session.Values["person"]
|
|
||||||
var person = &Person{}
|
|
||||||
if person, ok := val.(*Person); !ok {
|
|
||||||
// Handle the case that it's not an expected type
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we can use our person object
|
|
||||||
}
|
|
||||||
|
|
||||||
By default, session cookies last for a month. This is probably too long for
|
|
||||||
some cases, but it is easy to change this and other attributes during
|
|
||||||
runtime. Sessions can be configured individually or the store can be
|
|
||||||
configured and then all sessions saved using it will use that configuration.
|
|
||||||
We access session.Options or store.Options to set a new configuration. The
|
|
||||||
fields are basically a subset of http.Cookie fields. Let's change the
|
|
||||||
maximum age of a session to one week:
|
|
||||||
|
|
||||||
session.Options = &sessions.Options{
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: 86400 * 7,
|
|
||||||
HttpOnly: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
Sometimes we may want to change authentication and/or encryption keys without
|
|
||||||
breaking existing sessions. The CookieStore supports key rotation, and to use
|
|
||||||
it you just need to set multiple authentication and encryption keys, in pairs,
|
|
||||||
to be tested in order:
|
|
||||||
|
|
||||||
var store = sessions.NewCookieStore(
|
|
||||||
[]byte("new-authentication-key"),
|
|
||||||
[]byte("new-encryption-key"),
|
|
||||||
[]byte("old-authentication-key"),
|
|
||||||
[]byte("old-encryption-key"),
|
|
||||||
)
|
|
||||||
|
|
||||||
New sessions will be saved using the first pair. Old sessions can still be
|
|
||||||
read because the first pair will fail, and the second will be tested. This
|
|
||||||
makes it easy to "rotate" secret keys and still be able to validate existing
|
|
||||||
sessions. Note: for all pairs the encryption key is optional; set it to nil
|
|
||||||
or omit it and and encryption won't be used.
|
|
||||||
|
|
||||||
Multiple sessions can be used in the same request, even with different
|
|
||||||
session backends. When this happens, calling Save() on each session
|
|
||||||
individually would be cumbersome, so we have a way to save all sessions
|
|
||||||
at once: it's sessions.Save(). Here's an example:
|
|
||||||
|
|
||||||
var store = sessions.NewCookieStore([]byte("something-very-secret"))
|
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Get a session and set a value.
|
|
||||||
session1, _ := store.Get(r, "session-one")
|
|
||||||
session1.Values["foo"] = "bar"
|
|
||||||
// Get another session and set another value.
|
|
||||||
session2, _ := store.Get(r, "session-two")
|
|
||||||
session2.Values[42] = 43
|
|
||||||
// Save all sessions.
|
|
||||||
sessions.Save(r, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
This is possible because when we call Get() from a session store, it adds the
|
|
||||||
session to a common registry. Save() uses it to save all registered sessions.
|
|
||||||
*/
|
|
||||||
package sessions
|
|
102
vendor/github.com/gorilla/sessions/lex.go
generated
vendored
102
vendor/github.com/gorilla/sessions/lex.go
generated
vendored
|
@ -1,102 +0,0 @@
|
||||||
// This file contains code adapted from the Go standard library
|
|
||||||
// https://github.com/golang/go/blob/39ad0fd0789872f9469167be7fe9578625ff246e/src/net/http/lex.go
|
|
||||||
|
|
||||||
package sessions
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
var isTokenTable = [127]bool{
|
|
||||||
'!': true,
|
|
||||||
'#': true,
|
|
||||||
'$': true,
|
|
||||||
'%': true,
|
|
||||||
'&': true,
|
|
||||||
'\'': true,
|
|
||||||
'*': true,
|
|
||||||
'+': true,
|
|
||||||
'-': true,
|
|
||||||
'.': true,
|
|
||||||
'0': true,
|
|
||||||
'1': true,
|
|
||||||
'2': true,
|
|
||||||
'3': true,
|
|
||||||
'4': true,
|
|
||||||
'5': true,
|
|
||||||
'6': true,
|
|
||||||
'7': true,
|
|
||||||
'8': true,
|
|
||||||
'9': true,
|
|
||||||
'A': true,
|
|
||||||
'B': true,
|
|
||||||
'C': true,
|
|
||||||
'D': true,
|
|
||||||
'E': true,
|
|
||||||
'F': true,
|
|
||||||
'G': true,
|
|
||||||
'H': true,
|
|
||||||
'I': true,
|
|
||||||
'J': true,
|
|
||||||
'K': true,
|
|
||||||
'L': true,
|
|
||||||
'M': true,
|
|
||||||
'N': true,
|
|
||||||
'O': true,
|
|
||||||
'P': true,
|
|
||||||
'Q': true,
|
|
||||||
'R': true,
|
|
||||||
'S': true,
|
|
||||||
'T': true,
|
|
||||||
'U': true,
|
|
||||||
'W': true,
|
|
||||||
'V': true,
|
|
||||||
'X': true,
|
|
||||||
'Y': true,
|
|
||||||
'Z': true,
|
|
||||||
'^': true,
|
|
||||||
'_': true,
|
|
||||||
'`': true,
|
|
||||||
'a': true,
|
|
||||||
'b': true,
|
|
||||||
'c': true,
|
|
||||||
'd': true,
|
|
||||||
'e': true,
|
|
||||||
'f': true,
|
|
||||||
'g': true,
|
|
||||||
'h': true,
|
|
||||||
'i': true,
|
|
||||||
'j': true,
|
|
||||||
'k': true,
|
|
||||||
'l': true,
|
|
||||||
'm': true,
|
|
||||||
'n': true,
|
|
||||||
'o': true,
|
|
||||||
'p': true,
|
|
||||||
'q': true,
|
|
||||||
'r': true,
|
|
||||||
's': true,
|
|
||||||
't': true,
|
|
||||||
'u': true,
|
|
||||||
'v': true,
|
|
||||||
'w': true,
|
|
||||||
'x': true,
|
|
||||||
'y': true,
|
|
||||||
'z': true,
|
|
||||||
'|': true,
|
|
||||||
'~': true,
|
|
||||||
}
|
|
||||||
|
|
||||||
func isToken(r rune) bool {
|
|
||||||
i := int(r)
|
|
||||||
return i < len(isTokenTable) && isTokenTable[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNotToken(r rune) bool {
|
|
||||||
return !isToken(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isCookieNameValid(raw string) bool {
|
|
||||||
if raw == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return strings.IndexFunc(raw, isNotToken) < 0
|
|
||||||
}
|
|
241
vendor/github.com/gorilla/sessions/sessions.go
generated
vendored
241
vendor/github.com/gorilla/sessions/sessions.go
generated
vendored
|
@ -1,241 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sessions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/gob"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Default flashes key.
|
|
||||||
const flashesKey = "_flash"
|
|
||||||
|
|
||||||
// Options --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Options stores configuration for a session or session store.
|
|
||||||
//
|
|
||||||
// Fields are a subset of http.Cookie fields.
|
|
||||||
type Options struct {
|
|
||||||
Path string
|
|
||||||
Domain string
|
|
||||||
// MaxAge=0 means no 'Max-Age' attribute specified.
|
|
||||||
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'.
|
|
||||||
// MaxAge>0 means Max-Age attribute present and given in seconds.
|
|
||||||
MaxAge int
|
|
||||||
Secure bool
|
|
||||||
HttpOnly bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// NewSession is called by session stores to create a new session instance.
|
|
||||||
func NewSession(store Store, name string) *Session {
|
|
||||||
return &Session{
|
|
||||||
Values: make(map[interface{}]interface{}),
|
|
||||||
store: store,
|
|
||||||
name: name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session stores the values and optional configuration for a session.
|
|
||||||
type Session struct {
|
|
||||||
// The ID of the session, generated by stores. It should not be used for
|
|
||||||
// user data.
|
|
||||||
ID string
|
|
||||||
// Values contains the user-data for the session.
|
|
||||||
Values map[interface{}]interface{}
|
|
||||||
Options *Options
|
|
||||||
IsNew bool
|
|
||||||
store Store
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flashes returns a slice of flash messages from the session.
|
|
||||||
//
|
|
||||||
// A single variadic argument is accepted, and it is optional: it defines
|
|
||||||
// the flash key. If not defined "_flash" is used by default.
|
|
||||||
func (s *Session) Flashes(vars ...string) []interface{} {
|
|
||||||
var flashes []interface{}
|
|
||||||
key := flashesKey
|
|
||||||
if len(vars) > 0 {
|
|
||||||
key = vars[0]
|
|
||||||
}
|
|
||||||
if v, ok := s.Values[key]; ok {
|
|
||||||
// Drop the flashes and return it.
|
|
||||||
delete(s.Values, key)
|
|
||||||
flashes = v.([]interface{})
|
|
||||||
}
|
|
||||||
return flashes
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddFlash adds a flash message to the session.
|
|
||||||
//
|
|
||||||
// A single variadic argument is accepted, and it is optional: it defines
|
|
||||||
// the flash key. If not defined "_flash" is used by default.
|
|
||||||
func (s *Session) AddFlash(value interface{}, vars ...string) {
|
|
||||||
key := flashesKey
|
|
||||||
if len(vars) > 0 {
|
|
||||||
key = vars[0]
|
|
||||||
}
|
|
||||||
var flashes []interface{}
|
|
||||||
if v, ok := s.Values[key]; ok {
|
|
||||||
flashes = v.([]interface{})
|
|
||||||
}
|
|
||||||
s.Values[key] = append(flashes, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save is a convenience method to save this session. It is the same as calling
|
|
||||||
// store.Save(request, response, session). You should call Save before writing to
|
|
||||||
// the response or returning from the handler.
|
|
||||||
func (s *Session) Save(r *http.Request, w http.ResponseWriter) error {
|
|
||||||
return s.store.Save(r, w, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name used to register the session.
|
|
||||||
func (s *Session) Name() string {
|
|
||||||
return s.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store returns the session store used to register the session.
|
|
||||||
func (s *Session) Store() Store {
|
|
||||||
return s.store
|
|
||||||
}
|
|
||||||
|
|
||||||
// Registry -------------------------------------------------------------------
|
|
||||||
|
|
||||||
// sessionInfo stores a session tracked by the registry.
|
|
||||||
type sessionInfo struct {
|
|
||||||
s *Session
|
|
||||||
e error
|
|
||||||
}
|
|
||||||
|
|
||||||
// contextKey is the type used to store the registry in the context.
|
|
||||||
type contextKey int
|
|
||||||
|
|
||||||
// registryKey is the key used to store the registry in the context.
|
|
||||||
const registryKey contextKey = 0
|
|
||||||
|
|
||||||
// GetRegistry returns a registry instance for the current request.
|
|
||||||
func GetRegistry(r *http.Request) *Registry {
|
|
||||||
registry := context.Get(r, registryKey)
|
|
||||||
if registry != nil {
|
|
||||||
return registry.(*Registry)
|
|
||||||
}
|
|
||||||
newRegistry := &Registry{
|
|
||||||
request: r,
|
|
||||||
sessions: make(map[string]sessionInfo),
|
|
||||||
}
|
|
||||||
context.Set(r, registryKey, newRegistry)
|
|
||||||
return newRegistry
|
|
||||||
}
|
|
||||||
|
|
||||||
// Registry stores sessions used during a request.
|
|
||||||
type Registry struct {
|
|
||||||
request *http.Request
|
|
||||||
sessions map[string]sessionInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get registers and returns a session for the given name and session store.
|
|
||||||
//
|
|
||||||
// It returns a new session if there are no sessions registered for the name.
|
|
||||||
func (s *Registry) Get(store Store, name string) (session *Session, err error) {
|
|
||||||
if !isCookieNameValid(name) {
|
|
||||||
return nil, fmt.Errorf("sessions: invalid character in cookie name: %s", name)
|
|
||||||
}
|
|
||||||
if info, ok := s.sessions[name]; ok {
|
|
||||||
session, err = info.s, info.e
|
|
||||||
} else {
|
|
||||||
session, err = store.New(s.request, name)
|
|
||||||
session.name = name
|
|
||||||
s.sessions[name] = sessionInfo{s: session, e: err}
|
|
||||||
}
|
|
||||||
session.store = store
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save saves all sessions registered for the current request.
|
|
||||||
func (s *Registry) Save(w http.ResponseWriter) error {
|
|
||||||
var errMulti MultiError
|
|
||||||
for name, info := range s.sessions {
|
|
||||||
session := info.s
|
|
||||||
if session.store == nil {
|
|
||||||
errMulti = append(errMulti, fmt.Errorf(
|
|
||||||
"sessions: missing store for session %q", name))
|
|
||||||
} else if err := session.store.Save(s.request, w, session); err != nil {
|
|
||||||
errMulti = append(errMulti, fmt.Errorf(
|
|
||||||
"sessions: error saving session %q -- %v", name, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if errMulti != nil {
|
|
||||||
return errMulti
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers --------------------------------------------------------------------
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
gob.Register([]interface{}{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save saves all sessions used during the current request.
|
|
||||||
func Save(r *http.Request, w http.ResponseWriter) error {
|
|
||||||
return GetRegistry(r).Save(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCookie returns an http.Cookie with the options set. It also sets
|
|
||||||
// the Expires field calculated based on the MaxAge value, for Internet
|
|
||||||
// Explorer compatibility.
|
|
||||||
func NewCookie(name, value string, options *Options) *http.Cookie {
|
|
||||||
cookie := &http.Cookie{
|
|
||||||
Name: name,
|
|
||||||
Value: value,
|
|
||||||
Path: options.Path,
|
|
||||||
Domain: options.Domain,
|
|
||||||
MaxAge: options.MaxAge,
|
|
||||||
Secure: options.Secure,
|
|
||||||
HttpOnly: options.HttpOnly,
|
|
||||||
}
|
|
||||||
if options.MaxAge > 0 {
|
|
||||||
d := time.Duration(options.MaxAge) * time.Second
|
|
||||||
cookie.Expires = time.Now().Add(d)
|
|
||||||
} else if options.MaxAge < 0 {
|
|
||||||
// Set it to the past to expire now.
|
|
||||||
cookie.Expires = time.Unix(1, 0)
|
|
||||||
}
|
|
||||||
return cookie
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
// MultiError stores multiple errors.
|
|
||||||
//
|
|
||||||
// Borrowed from the App Engine SDK.
|
|
||||||
type MultiError []error
|
|
||||||
|
|
||||||
func (m MultiError) Error() string {
|
|
||||||
s, n := "", 0
|
|
||||||
for _, e := range m {
|
|
||||||
if e != nil {
|
|
||||||
if n == 0 {
|
|
||||||
s = e.Error()
|
|
||||||
}
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch n {
|
|
||||||
case 0:
|
|
||||||
return "(0 errors)"
|
|
||||||
case 1:
|
|
||||||
return s
|
|
||||||
case 2:
|
|
||||||
return s + " (and 1 other error)"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
|
|
||||||
}
|
|
295
vendor/github.com/gorilla/sessions/store.go
generated
vendored
295
vendor/github.com/gorilla/sessions/store.go
generated
vendored
|
@ -1,295 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sessions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base32"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/gorilla/securecookie"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Store is an interface for custom session stores.
|
|
||||||
//
|
|
||||||
// See CookieStore and FilesystemStore for examples.
|
|
||||||
type Store interface {
|
|
||||||
// Get should return a cached session.
|
|
||||||
Get(r *http.Request, name string) (*Session, error)
|
|
||||||
|
|
||||||
// New should create and return a new session.
|
|
||||||
//
|
|
||||||
// Note that New should never return a nil session, even in the case of
|
|
||||||
// an error if using the Registry infrastructure to cache the session.
|
|
||||||
New(r *http.Request, name string) (*Session, error)
|
|
||||||
|
|
||||||
// Save should persist session to the underlying store implementation.
|
|
||||||
Save(r *http.Request, w http.ResponseWriter, s *Session) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// CookieStore ----------------------------------------------------------------
|
|
||||||
|
|
||||||
// NewCookieStore returns a new CookieStore.
|
|
||||||
//
|
|
||||||
// Keys are defined in pairs to allow key rotation, but the common case is
|
|
||||||
// to set a single authentication key and optionally an encryption key.
|
|
||||||
//
|
|
||||||
// The first key in a pair is used for authentication and the second for
|
|
||||||
// encryption. The encryption key can be set to nil or omitted in the last
|
|
||||||
// pair, but the authentication key is required in all pairs.
|
|
||||||
//
|
|
||||||
// It is recommended to use an authentication key with 32 or 64 bytes.
|
|
||||||
// The encryption key, if set, must be either 16, 24, or 32 bytes to select
|
|
||||||
// AES-128, AES-192, or AES-256 modes.
|
|
||||||
//
|
|
||||||
// Use the convenience function securecookie.GenerateRandomKey() to create
|
|
||||||
// strong keys.
|
|
||||||
func NewCookieStore(keyPairs ...[]byte) *CookieStore {
|
|
||||||
cs := &CookieStore{
|
|
||||||
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
|
||||||
Options: &Options{
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: 86400 * 30,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cs.MaxAge(cs.Options.MaxAge)
|
|
||||||
return cs
|
|
||||||
}
|
|
||||||
|
|
||||||
// CookieStore stores sessions using secure cookies.
|
|
||||||
type CookieStore struct {
|
|
||||||
Codecs []securecookie.Codec
|
|
||||||
Options *Options // default configuration
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a session for the given name after adding it to the registry.
|
|
||||||
//
|
|
||||||
// It returns a new session if the sessions doesn't exist. Access IsNew on
|
|
||||||
// the session to check if it is an existing session or a new one.
|
|
||||||
//
|
|
||||||
// It returns a new session and an error if the session exists but could
|
|
||||||
// not be decoded.
|
|
||||||
func (s *CookieStore) Get(r *http.Request, name string) (*Session, error) {
|
|
||||||
return GetRegistry(r).Get(s, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a session for the given name without adding it to the registry.
|
|
||||||
//
|
|
||||||
// The difference between New() and Get() is that calling New() twice will
|
|
||||||
// decode the session data twice, while Get() registers and reuses the same
|
|
||||||
// decoded session after the first call.
|
|
||||||
func (s *CookieStore) New(r *http.Request, name string) (*Session, error) {
|
|
||||||
session := NewSession(s, name)
|
|
||||||
opts := *s.Options
|
|
||||||
session.Options = &opts
|
|
||||||
session.IsNew = true
|
|
||||||
var err error
|
|
||||||
if c, errCookie := r.Cookie(name); errCookie == nil {
|
|
||||||
err = securecookie.DecodeMulti(name, c.Value, &session.Values,
|
|
||||||
s.Codecs...)
|
|
||||||
if err == nil {
|
|
||||||
session.IsNew = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return session, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save adds a single session to the response.
|
|
||||||
func (s *CookieStore) Save(r *http.Request, w http.ResponseWriter,
|
|
||||||
session *Session) error {
|
|
||||||
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
|
|
||||||
s.Codecs...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxAge sets the maximum age for the store and the underlying cookie
|
|
||||||
// implementation. Individual sessions can be deleted by setting Options.MaxAge
|
|
||||||
// = -1 for that session.
|
|
||||||
func (s *CookieStore) MaxAge(age int) {
|
|
||||||
s.Options.MaxAge = age
|
|
||||||
|
|
||||||
// Set the maxAge for each securecookie instance.
|
|
||||||
for _, codec := range s.Codecs {
|
|
||||||
if sc, ok := codec.(*securecookie.SecureCookie); ok {
|
|
||||||
sc.MaxAge(age)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilesystemStore ------------------------------------------------------------
|
|
||||||
|
|
||||||
var fileMutex sync.RWMutex
|
|
||||||
|
|
||||||
// NewFilesystemStore returns a new FilesystemStore.
|
|
||||||
//
|
|
||||||
// The path argument is the directory where sessions will be saved. If empty
|
|
||||||
// it will use os.TempDir().
|
|
||||||
//
|
|
||||||
// See NewCookieStore() for a description of the other parameters.
|
|
||||||
func NewFilesystemStore(path string, keyPairs ...[]byte) *FilesystemStore {
|
|
||||||
if path == "" {
|
|
||||||
path = os.TempDir()
|
|
||||||
}
|
|
||||||
fs := &FilesystemStore{
|
|
||||||
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
|
||||||
Options: &Options{
|
|
||||||
Path: "/",
|
|
||||||
MaxAge: 86400 * 30,
|
|
||||||
},
|
|
||||||
path: path,
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.MaxAge(fs.Options.MaxAge)
|
|
||||||
return fs
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilesystemStore stores sessions in the filesystem.
|
|
||||||
//
|
|
||||||
// It also serves as a reference for custom stores.
|
|
||||||
//
|
|
||||||
// This store is still experimental and not well tested. Feedback is welcome.
|
|
||||||
type FilesystemStore struct {
|
|
||||||
Codecs []securecookie.Codec
|
|
||||||
Options *Options // default configuration
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxLength restricts the maximum length of new sessions to l.
|
|
||||||
// If l is 0 there is no limit to the size of a session, use with caution.
|
|
||||||
// The default for a new FilesystemStore is 4096.
|
|
||||||
func (s *FilesystemStore) MaxLength(l int) {
|
|
||||||
for _, c := range s.Codecs {
|
|
||||||
if codec, ok := c.(*securecookie.SecureCookie); ok {
|
|
||||||
codec.MaxLength(l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a session for the given name after adding it to the registry.
|
|
||||||
//
|
|
||||||
// See CookieStore.Get().
|
|
||||||
func (s *FilesystemStore) Get(r *http.Request, name string) (*Session, error) {
|
|
||||||
return GetRegistry(r).Get(s, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a session for the given name without adding it to the registry.
|
|
||||||
//
|
|
||||||
// See CookieStore.New().
|
|
||||||
func (s *FilesystemStore) New(r *http.Request, name string) (*Session, error) {
|
|
||||||
session := NewSession(s, name)
|
|
||||||
opts := *s.Options
|
|
||||||
session.Options = &opts
|
|
||||||
session.IsNew = true
|
|
||||||
var err error
|
|
||||||
if c, errCookie := r.Cookie(name); errCookie == nil {
|
|
||||||
err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
|
|
||||||
if err == nil {
|
|
||||||
err = s.load(session)
|
|
||||||
if err == nil {
|
|
||||||
session.IsNew = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return session, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save adds a single session to the response.
|
|
||||||
//
|
|
||||||
// If the Options.MaxAge of the session is <= 0 then the session file will be
|
|
||||||
// deleted from the store path. With this process it enforces the properly
|
|
||||||
// session cookie handling so no need to trust in the cookie management in the
|
|
||||||
// web browser.
|
|
||||||
func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter,
|
|
||||||
session *Session) error {
|
|
||||||
// Delete if max-age is <= 0
|
|
||||||
if session.Options.MaxAge <= 0 {
|
|
||||||
if err := s.erase(session); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
http.SetCookie(w, NewCookie(session.Name(), "", session.Options))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if session.ID == "" {
|
|
||||||
// Because the ID is used in the filename, encode it to
|
|
||||||
// use alphanumeric characters only.
|
|
||||||
session.ID = strings.TrimRight(
|
|
||||||
base32.StdEncoding.EncodeToString(
|
|
||||||
securecookie.GenerateRandomKey(32)), "=")
|
|
||||||
}
|
|
||||||
if err := s.save(session); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
|
|
||||||
s.Codecs...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxAge sets the maximum age for the store and the underlying cookie
|
|
||||||
// implementation. Individual sessions can be deleted by setting Options.MaxAge
|
|
||||||
// = -1 for that session.
|
|
||||||
func (s *FilesystemStore) MaxAge(age int) {
|
|
||||||
s.Options.MaxAge = age
|
|
||||||
|
|
||||||
// Set the maxAge for each securecookie instance.
|
|
||||||
for _, codec := range s.Codecs {
|
|
||||||
if sc, ok := codec.(*securecookie.SecureCookie); ok {
|
|
||||||
sc.MaxAge(age)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// save writes encoded session.Values to a file.
|
|
||||||
func (s *FilesystemStore) save(session *Session) error {
|
|
||||||
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
|
|
||||||
s.Codecs...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
filename := filepath.Join(s.path, "session_"+session.ID)
|
|
||||||
fileMutex.Lock()
|
|
||||||
defer fileMutex.Unlock()
|
|
||||||
return ioutil.WriteFile(filename, []byte(encoded), 0600)
|
|
||||||
}
|
|
||||||
|
|
||||||
// load reads a file and decodes its content into session.Values.
|
|
||||||
func (s *FilesystemStore) load(session *Session) error {
|
|
||||||
filename := filepath.Join(s.path, "session_"+session.ID)
|
|
||||||
fileMutex.RLock()
|
|
||||||
defer fileMutex.RUnlock()
|
|
||||||
fdata, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = securecookie.DecodeMulti(session.Name(), string(fdata),
|
|
||||||
&session.Values, s.Codecs...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete session file
|
|
||||||
func (s *FilesystemStore) erase(session *Session) error {
|
|
||||||
filename := filepath.Join(s.path, "session_"+session.ID)
|
|
||||||
|
|
||||||
fileMutex.RLock()
|
|
||||||
defer fileMutex.RUnlock()
|
|
||||||
|
|
||||||
err := os.Remove(filename)
|
|
||||||
return err
|
|
||||||
}
|
|
21
vendor/github.com/labstack/echo-contrib/LICENSE
generated
vendored
21
vendor/github.com/labstack/echo-contrib/LICENSE
generated
vendored
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2017 LabStack
|
|
||||||
|
|
||||||
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.
|
|
65
vendor/github.com/labstack/echo-contrib/session/session.go
generated
vendored
65
vendor/github.com/labstack/echo-contrib/session/session.go
generated
vendored
|
@ -1,65 +0,0 @@
|
||||||
package session
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gorilla/sessions"
|
|
||||||
"github.com/labstack/echo"
|
|
||||||
"github.com/labstack/echo/middleware"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// Config defines the config for CasbinAuth middleware.
|
|
||||||
Config struct {
|
|
||||||
// Skipper defines a function to skip middleware.
|
|
||||||
Skipper middleware.Skipper
|
|
||||||
|
|
||||||
// Session store.
|
|
||||||
// Required.
|
|
||||||
Store sessions.Store
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
key = "_session_store"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DefaultConfig is the default Session middleware config.
|
|
||||||
DefaultConfig = Config{
|
|
||||||
Skipper: middleware.DefaultSkipper,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get returns a named session.
|
|
||||||
func Get(name string, c echo.Context) (*sessions.Session, error) {
|
|
||||||
store := c.Get(key).(sessions.Store)
|
|
||||||
return store.Get(c.Request(), name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middleware returns a Session middleware.
|
|
||||||
func Middleware(store sessions.Store) echo.MiddlewareFunc {
|
|
||||||
c := DefaultConfig
|
|
||||||
c.Store = store
|
|
||||||
return MiddlewareWithConfig(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MiddlewareWithConfig returns a Sessions middleware with config.
|
|
||||||
// See `Middleware()`.
|
|
||||||
func MiddlewareWithConfig(config Config) echo.MiddlewareFunc {
|
|
||||||
// Defaults
|
|
||||||
if config.Skipper == nil {
|
|
||||||
config.Skipper = DefaultConfig.Skipper
|
|
||||||
}
|
|
||||||
if config.Store == nil {
|
|
||||||
panic("echo: session middleware requires store")
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
if config.Skipper(c) {
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
c.Set(key, config.Store)
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
485
vendor/github.com/mattn/go-sqlite3/README.md
generated
vendored
485
vendor/github.com/mattn/go-sqlite3/README.md
generated
vendored
|
@ -6,13 +6,43 @@ go-sqlite3
|
||||||
[![Coverage Status](https://coveralls.io/repos/mattn/go-sqlite3/badge.svg?branch=master)](https://coveralls.io/r/mattn/go-sqlite3?branch=master)
|
[![Coverage Status](https://coveralls.io/repos/mattn/go-sqlite3/badge.svg?branch=master)](https://coveralls.io/r/mattn/go-sqlite3?branch=master)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-sqlite3)](https://goreportcard.com/report/github.com/mattn/go-sqlite3)
|
[![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-sqlite3)](https://goreportcard.com/report/github.com/mattn/go-sqlite3)
|
||||||
|
|
||||||
Description
|
# Description
|
||||||
-----------
|
|
||||||
|
|
||||||
sqlite3 driver conforming to the built-in database/sql interface
|
sqlite3 driver conforming to the built-in database/sql interface
|
||||||
|
|
||||||
Installation
|
Supported Golang version:
|
||||||
------------
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
|
||||||
|
[This package follows the official Golang Release Policy.](https://golang.org/doc/devel/release.html#policy)
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [API Reference](#api-reference)
|
||||||
|
- [Connection String](#connection-string)
|
||||||
|
- [Features](#features)
|
||||||
|
- [Compilation](#compilation)
|
||||||
|
- [Android](#android)
|
||||||
|
- [ARM](#arm)
|
||||||
|
- [Cross Compile](#cross-compile)
|
||||||
|
- [Google Cloud Platform](#google-cloud-platform)
|
||||||
|
- [Linux](#linux)
|
||||||
|
- [Alpine](#alpine)
|
||||||
|
- [Fedora](#fedora)
|
||||||
|
- [Ubuntu](#ubuntu)
|
||||||
|
- [Mac OSX](#mac-osx)
|
||||||
|
- [Windows](#windows)
|
||||||
|
- [Errors](#errors)
|
||||||
|
- [User Authentication](#user-authentication)
|
||||||
|
- [Compile](#compile)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Extensions](#extensions)
|
||||||
|
- [Spatialite](#spatialite)
|
||||||
|
- [FAQ](#faq)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
This package can be installed with the go get command:
|
This package can be installed with the go get command:
|
||||||
|
|
||||||
|
@ -20,70 +50,460 @@ This package can be installed with the go get command:
|
||||||
|
|
||||||
_go-sqlite3_ is *cgo* package.
|
_go-sqlite3_ is *cgo* package.
|
||||||
If you want to build your app using go-sqlite3, you need gcc.
|
If you want to build your app using go-sqlite3, you need gcc.
|
||||||
However, if you install _go-sqlite3_ with `go install github.com/mattn/go-sqlite3`, you don't need gcc to build your app anymore.
|
However, after you have built and installed _go-sqlite3_ with `go install github.com/mattn/go-sqlite3` (which requires gcc), you can build your app without relying on gcc in future.
|
||||||
|
|
||||||
Documentation
|
***Important: because this is a `CGO` enabled package you are required to set the environment variable `CGO_ENABLED=1` and have a `gcc` compile present within your path.***
|
||||||
-------------
|
|
||||||
|
# API Reference
|
||||||
|
|
||||||
API documentation can be found here: http://godoc.org/github.com/mattn/go-sqlite3
|
API documentation can be found here: http://godoc.org/github.com/mattn/go-sqlite3
|
||||||
|
|
||||||
Examples can be found under the `./_example` directory
|
Examples can be found under the [examples](./_example) directory
|
||||||
|
|
||||||
FAQ
|
# Connection String
|
||||||
---
|
|
||||||
|
|
||||||
* Want to build go-sqlite3 with libsqlite3 on my linux.
|
When creating a new SQLite database or connection to an existing one, with the file name additional options can be given.
|
||||||
|
This is also known as a DSN string. (Data Source Name).
|
||||||
|
|
||||||
Use `go build --tags "libsqlite3 linux"`
|
Options are append after the filename of the SQLite database.
|
||||||
|
The database filename and options are seperated by an `?` (Question Mark).
|
||||||
|
|
||||||
* Want to build go-sqlite3 with libsqlite3 on OS X.
|
This also applies when using an in-memory database instead of a file.
|
||||||
|
|
||||||
Install sqlite3 from homebrew: `brew install sqlite3`
|
Options can be given using the following format: `KEYWORD=VALUE` and multiple options can be combined with the `&` ampersand.
|
||||||
|
|
||||||
Use `go build --tags "libsqlite3 darwin"`
|
This library supports dsn options of SQLite itself and provides additional options.
|
||||||
|
|
||||||
* Want to build go-sqlite3 with icu extension.
|
Boolean values can be one of:
|
||||||
|
* `0` `no` `false` `off`
|
||||||
|
* `1` `yes` `true` `on`
|
||||||
|
|
||||||
Use `go build --tags "icu"`
|
| Name | Key | Value(s) | Description |
|
||||||
|
|------|-----|----------|-------------|
|
||||||
|
| UA - Create | `_auth` | - | Create User Authentication, for more information see [User Authentication](#user-authentication) |
|
||||||
|
| UA - Username | `_auth_user` | `string` | Username for User Authentication, for more information see [User Authentication](#user-authentication) |
|
||||||
|
| UA - Password | `_auth_pass` | `string` | Password for User Authentication, for more information see [User Authentication](#user-authentication) |
|
||||||
|
| UA - Crypt | `_auth_crypt` | <ul><li>SHA1</li><li>SSHA1</li><li>SHA256</li><li>SSHA256</li><li>SHA384</li><li>SSHA384</li><li>SHA512</li><li>SSHA512</li></ul> | Password encoder to use for User Authentication, for more information see [User Authentication](#user-authentication) |
|
||||||
|
| UA - Salt | `_auth_salt` | `string` | Salt to use if the configure password encoder requires a salt, for User Authentication, for more information see [User Authentication](#user-authentication) |
|
||||||
|
| Auto Vacuum | `_auto_vacuum` \| `_vacuum` | <ul><li>`0` \| `none`</li><li>`1` \| `full`</li><li>`2` \| `incremental`</li></ul> | For more information see [PRAGMA auto_vacuum](https://www.sqlite.org/pragma.html#pragma_auto_vacuum) |
|
||||||
|
| Busy Timeout | `_busy_timeout` \| `_timeout` | `int` | Specify value for sqlite3_busy_timeout. For more information see [PRAGMA busy_timeout](https://www.sqlite.org/pragma.html#pragma_busy_timeout) |
|
||||||
|
| Case Sensitive LIKE | `_case_sensitive_like` \| `_cslike` | `boolean` | For more information see [PRAGMA case_sensitive_like](https://www.sqlite.org/pragma.html#pragma_case_sensitive_like) |
|
||||||
|
| Defer Foreign Keys | `_defer_foreign_keys` \| `_defer_fk` | `boolean` | For more information see [PRAGMA defer_foreign_keys](https://www.sqlite.org/pragma.html#pragma_defer_foreign_keys) |
|
||||||
|
| Foreign Keys | `_foreign_keys` \| `_fk` | `boolean` | For more information see [PRAGMA foreign_keys](https://www.sqlite.org/pragma.html#pragma_foreign_keys) |
|
||||||
|
| Ignore CHECK Constraints | `_ignore_check_constraints` | `boolean` | For more information see [PRAGMA ignore_check_constraints](https://www.sqlite.org/pragma.html#pragma_ignore_check_constraints) |
|
||||||
|
| Immutable | `immutable` | `boolean` | For more information see [Immutable](https://www.sqlite.org/c3ref/open.html) |
|
||||||
|
| Journal Mode | `_journal_mode` \| `_journal` | <ul><li>DELETE</li><li>TRUNCATE</li><li>PERSIST</li><li>MEMORY</li><li>WAL</li><li>OFF</li></ul> | For more information see [PRAGMA journal_mode](https://www.sqlite.org/pragma.html#pragma_journal_mode) |
|
||||||
|
| Locking Mode | `_locking_mode` \| `_locking` | <ul><li>NORMAL</li><li>EXCLUSIVE</li></ul> | For more information see [PRAGMA locking_mode](https://www.sqlite.org/pragma.html#pragma_locking_mode) |
|
||||||
|
| Mode | `mode` | <ul><li>ro</li><li>rw</li><li>rwc</li><li>memory</li></ul> | Access Mode of the database. For more information see [SQLite Open](https://www.sqlite.org/c3ref/open.html) |
|
||||||
|
| Mutex Locking | `_mutex` | <ul><li>no</li><li>full</li></ul> | Specify mutex mode. |
|
||||||
|
| Query Only | `_query_only` | `boolean` | For more information see [PRAGMA query_only](https://www.sqlite.org/pragma.html#pragma_query_only) |
|
||||||
|
| Recursive Triggers | `_recursive_triggers` \| `_rt` | `boolean` | For more information see [PRAGMA recursive_triggers](https://www.sqlite.org/pragma.html#pragma_recursive_triggers) |
|
||||||
|
| Secure Delete | `_secure_delete` | `boolean` \| `FAST` | For more information see [PRAGMA secure_delete](https://www.sqlite.org/pragma.html#pragma_secure_delete) |
|
||||||
|
| Shared-Cache Mode | `cache` | <ul><li>shared</li><li>private</li></ul> | Set cache mode for more information see [sqlite.org](https://www.sqlite.org/sharedcache.html) |
|
||||||
|
| Synchronous | `_synchronous` \| `_sync` | <ul><li>0 \| OFF</li><li>1 \| NORMAL</li><li>2 \| FULL</li><li>3 \| EXTRA</li></ul> | For more information see [PRAGMA synchronous](https://www.sqlite.org/pragma.html#pragma_synchronous) |
|
||||||
|
| Time Zone Location | `_loc` | auto | Specify location of time format. |
|
||||||
|
| Transaction Lock | `_txlock` | <ul><li>immediate</li><li>deferred</li><li>exclusive</li></ul> | Specify locking behavior for transactions. |
|
||||||
|
| Writable Schema | `_writable_schema` | `Boolean` | When this pragma is on, the SQLITE_MASTER tables in which database can be changed using ordinary UPDATE, INSERT, and DELETE statements. Warning: misuse of this pragma can easily result in a corrupt database file. |
|
||||||
|
|
||||||
Available extensions: `json1`, `fts5`, `icu`
|
## DSN Examples
|
||||||
|
|
||||||
* Can't build go-sqlite3 on windows 64bit.
|
```
|
||||||
|
file:test.db?cache=shared&mode=memory
|
||||||
|
```
|
||||||
|
|
||||||
|
# Features
|
||||||
|
|
||||||
|
This package allows additional configuration of features available within SQLite3 to be enabled or disabled by golang build constraints also known as build `tags`.
|
||||||
|
|
||||||
|
[Click here for more information about build tags / constraints.](https://golang.org/pkg/go/build/#hdr-Build_Constraints)
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
If you wish to build this library with additional extensions / features.
|
||||||
|
Use the following command.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build --tags "<FEATURE>"
|
||||||
|
```
|
||||||
|
|
||||||
|
For available features see the extension list.
|
||||||
|
When using multiple build tags, all the different tags should be space delimted.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build --tags "icu json1 fts5 secure_delete"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Feature / Extension List
|
||||||
|
|
||||||
|
| Extension | Build Tag | Description |
|
||||||
|
|-----------|-----------|-------------|
|
||||||
|
| Additional Statistics | sqlite_stat4 | This option adds additional logic to the ANALYZE command and to the query planner that can help SQLite to chose a better query plan under certain situations. The ANALYZE command is enhanced to collect histogram data from all columns of every index and store that data in the sqlite_stat4 table.<br><br>The query planner will then use the histogram data to help it make better index choices. The downside of this compile-time option is that it violates the query planner stability guarantee making it more difficult to ensure consistent performance in mass-produced applications.<br><br>SQLITE_ENABLE_STAT4 is an enhancement of SQLITE_ENABLE_STAT3. STAT3 only recorded histogram data for the left-most column of each index whereas the STAT4 enhancement records histogram data from all columns of each index.<br><br>The SQLITE_ENABLE_STAT3 compile-time option is a no-op and is ignored if the SQLITE_ENABLE_STAT4 compile-time option is used |
|
||||||
|
| Allow URI Authority | sqlite_allow_uri_authority | URI filenames normally throws an error if the authority section is not either empty or "localhost".<br><br>However, if SQLite is compiled with the SQLITE_ALLOW_URI_AUTHORITY compile-time option, then the URI is converted into a Uniform Naming Convention (UNC) filename and passed down to the underlying operating system that way |
|
||||||
|
| App Armor | sqlite_app_armor | When defined, this C-preprocessor macro activates extra code that attempts to detect misuse of the SQLite API, such as passing in NULL pointers to required parameters or using objects after they have been destroyed. <br><br>App Armor is not available under `Windows`. |
|
||||||
|
| Disable Load Extensions | sqlite_omit_load_extension | Loading of external extensions is enabled by default.<br><br>To disable extension loading add the build tag `sqlite_omit_load_extension`. |
|
||||||
|
| Foreign Keys | sqlite_foreign_keys | This macro determines whether enforcement of foreign key constraints is enabled or disabled by default for new database connections.<br><br>Each database connection can always turn enforcement of foreign key constraints on and off and run-time using the foreign_keys pragma.<br><br>Enforcement of foreign key constraints is normally off by default, but if this compile-time parameter is set to 1, enforcement of foreign key constraints will be on by default |
|
||||||
|
| Full Auto Vacuum | sqlite_vacuum_full | Set the default auto vacuum to full |
|
||||||
|
| Incremental Auto Vacuum | sqlite_vacuum_incr | Set the default auto vacuum to incremental |
|
||||||
|
| Full Text Search Engine | sqlite_fts5 | When this option is defined in the amalgamation, versions 5 of the full-text search engine (fts5) is added to the build automatically |
|
||||||
|
| International Components for Unicode | sqlite_icu | This option causes the International Components for Unicode or "ICU" extension to SQLite to be added to the build |
|
||||||
|
| Introspect PRAGMAS | sqlite_introspect | This option adds some extra PRAGMA statements. <ul><li>PRAGMA function_list</li><li>PRAGMA module_list</li><li>PRAGMA pragma_list</li></ul> |
|
||||||
|
| JSON SQL Functions | sqlite_json | When this option is defined in the amalgamation, the JSON SQL functions are added to the build automatically |
|
||||||
|
| Secure Delete | sqlite_secure_delete | This compile-time option changes the default setting of the secure_delete pragma.<br><br>When this option is not used, secure_delete defaults to off. When this option is present, secure_delete defaults to on.<br><br>The secure_delete setting causes deleted content to be overwritten with zeros. There is a small performance penalty since additional I/O must occur.<br><br>On the other hand, secure_delete can prevent fragments of sensitive information from lingering in unused parts of the database file after it has been deleted. See the documentation on the secure_delete pragma for additional information |
|
||||||
|
| Secure Delete (FAST) | sqlite_secure_delete_fast | For more information see [PRAGMA secure_delete](https://www.sqlite.org/pragma.html#pragma_secure_delete) |
|
||||||
|
| Tracing / Debug | sqlite_trace | Activate trace functions |
|
||||||
|
| User Authentication | sqlite_userauth | SQLite User Authentication see [User Authentication](#user-authentication) for more information. |
|
||||||
|
|
||||||
|
# Compilation
|
||||||
|
|
||||||
|
This package requires `CGO_ENABLED=1` ennvironment variable if not set by default, and the presence of the `gcc` compiler.
|
||||||
|
|
||||||
|
If you need to add additional CFLAGS or LDFLAGS to the build command, and do not want to modify this package. Then this can be achieved by using the `CGO_CFLAGS` and `CGO_LDFLAGS` environment variables.
|
||||||
|
|
||||||
|
## Android
|
||||||
|
|
||||||
|
This package can be compiled for android.
|
||||||
|
Compile with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build --tags "android"
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information see [#201](https://github.com/mattn/go-sqlite3/issues/201)
|
||||||
|
|
||||||
|
# ARM
|
||||||
|
|
||||||
|
To compile for `ARM` use the following environment.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
env CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ \
|
||||||
|
CGO_ENABLED=1 GOOS=linux GOARCH=arm GOARM=7 \
|
||||||
|
go build -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional information:
|
||||||
|
- [#242](https://github.com/mattn/go-sqlite3/issues/242)
|
||||||
|
- [#504](https://github.com/mattn/go-sqlite3/issues/504)
|
||||||
|
|
||||||
|
# Cross Compile
|
||||||
|
|
||||||
|
This library can be cross-compiled.
|
||||||
|
|
||||||
|
In some cases you are required to the `CC` environment variable with the cross compiler.
|
||||||
|
|
||||||
|
Additional information:
|
||||||
|
- [#491](https://github.com/mattn/go-sqlite3/issues/491)
|
||||||
|
- [#560](https://github.com/mattn/go-sqlite3/issues/560)
|
||||||
|
|
||||||
|
# Google Cloud Platform
|
||||||
|
|
||||||
|
Building on GCP is not possible because `Google Cloud Platform does not allow `gcc` to be executed.
|
||||||
|
|
||||||
|
Please work only with compiled final binaries.
|
||||||
|
|
||||||
|
## Linux
|
||||||
|
|
||||||
|
To compile this package on Linux you must install the development tools for your linux distribution.
|
||||||
|
|
||||||
|
To compile under linux use the build tag `linux`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build --tags "linux"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you wish to link directly to libsqlite3 then you can use the `libsqlite3` build tag.
|
||||||
|
|
||||||
|
```
|
||||||
|
go build --tags "libsqlite3 linux"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Alpine
|
||||||
|
|
||||||
|
When building in an `alpine` container run the following command before building.
|
||||||
|
|
||||||
|
```
|
||||||
|
apk add --update gcc musl-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fedora
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo yum groupinstall "Development Tools" "Development Libraries"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ubuntu
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install build-essential
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mac OSX
|
||||||
|
|
||||||
|
OSX should have all the tools present to compile this package, if not install XCode this will add all the developers tools.
|
||||||
|
|
||||||
|
Required dependency
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install sqlite3
|
||||||
|
```
|
||||||
|
|
||||||
|
For OSX there is an additional package install which is required if you whish to build the `icu` extension.
|
||||||
|
|
||||||
|
This additional package can be installed with `homebrew`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew upgrade icu4c
|
||||||
|
```
|
||||||
|
|
||||||
|
To compile for Mac OSX.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build --tags "darwin"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you wish to link directly to libsqlite3 then you can use the `libsqlite3` build tag.
|
||||||
|
|
||||||
|
```
|
||||||
|
go build --tags "libsqlite3 darwin"
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional information:
|
||||||
|
- [#206](https://github.com/mattn/go-sqlite3/issues/206)
|
||||||
|
- [#404](https://github.com/mattn/go-sqlite3/issues/404)
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
To compile this package on Windows OS you must have the `gcc` compiler installed.
|
||||||
|
|
||||||
|
1) Install a Windows `gcc` toolchain.
|
||||||
|
2) Add the `bin` folders to the Windows path if the installer did not do this by default.
|
||||||
|
3) Open a terminal for the TDM-GCC toolchain, can be found in the Windows Start menu.
|
||||||
|
4) Navigate to your project folder and run the `go build ...` command for this package.
|
||||||
|
|
||||||
|
For example the TDM-GCC Toolchain can be found [here](ttps://sourceforge.net/projects/tdm-gcc/).
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
- Compile error: `can not be used when making a shared object; recompile with -fPIC`
|
||||||
|
|
||||||
|
When receiving a compile time error referencing recompile with `-FPIC` then you
|
||||||
|
are probably using a hardend system.
|
||||||
|
|
||||||
|
You can copile the library on a hardend system with the following command.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build -ldflags '-extldflags=-fno-PIC'
|
||||||
|
```
|
||||||
|
|
||||||
|
More details see [#120](https://github.com/mattn/go-sqlite3/issues/120)
|
||||||
|
|
||||||
|
- Can't build go-sqlite3 on windows 64bit.
|
||||||
|
|
||||||
> Probably, you are using go 1.0, go1.0 has a problem when it comes to compiling/linking on windows 64bit.
|
> Probably, you are using go 1.0, go1.0 has a problem when it comes to compiling/linking on windows 64bit.
|
||||||
> See: [#27](https://github.com/mattn/go-sqlite3/issues/27)
|
> See: [#27](https://github.com/mattn/go-sqlite3/issues/27)
|
||||||
|
|
||||||
* Getting insert error while query is opened.
|
- `go get github.com/mattn/go-sqlite3` throws compilation error.
|
||||||
|
|
||||||
|
`gcc` throws: `internal compiler error`
|
||||||
|
|
||||||
|
Remove the download repository from your disk and try re-install with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install github.com/mattn/go-sqlite3
|
||||||
|
```
|
||||||
|
|
||||||
|
# User Authentication
|
||||||
|
|
||||||
|
This package supports the SQLite User Authentication module.
|
||||||
|
|
||||||
|
## Compile
|
||||||
|
|
||||||
|
To use the User authentication module the package has to be compiled with the tag `sqlite_userauth`. See [Features](#features).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Create protected database
|
||||||
|
|
||||||
|
To create a database protected by user authentication provide the following argument to the connection string `_auth`.
|
||||||
|
This will enable user authentication within the database. This option however requires two additional arguments:
|
||||||
|
|
||||||
|
- `_auth_user`
|
||||||
|
- `_auth_pass`
|
||||||
|
|
||||||
|
When `_auth` is present on the connection string user authentication will be enabled and the provided user will be created
|
||||||
|
as an `admin` user. After initial creation, the parameter `_auth` has no effect anymore and can be omitted from the connection string.
|
||||||
|
|
||||||
|
Example connection string:
|
||||||
|
|
||||||
|
Create an user authentication database with user `admin` and password `admin`.
|
||||||
|
|
||||||
|
`file:test.s3db?_auth&_auth_user=admin&_auth_pass=admin`
|
||||||
|
|
||||||
|
Create an user authentication database with user `admin` and password `admin` and use `SHA1` for the password encoding.
|
||||||
|
|
||||||
|
`file:test.s3db?_auth&_auth_user=admin&_auth_pass=admin&_auth_crypt=sha1`
|
||||||
|
|
||||||
|
### Password Encoding
|
||||||
|
|
||||||
|
The passwords within the user authentication module of SQLite are encoded with the SQLite function `sqlite_cryp`.
|
||||||
|
This function uses a ceasar-cypher which is quite insecure.
|
||||||
|
This library provides several additional password encoders which can be configured through the connection string.
|
||||||
|
|
||||||
|
The password cypher can be configured with the key `_auth_crypt`. And if the configured password encoder also requires an
|
||||||
|
salt this can be configured with `_auth_salt`.
|
||||||
|
|
||||||
|
#### Available Encoders
|
||||||
|
|
||||||
|
- SHA1
|
||||||
|
- SSHA1 (Salted SHA1)
|
||||||
|
- SHA256
|
||||||
|
- SSHA256 (salted SHA256)
|
||||||
|
- SHA384
|
||||||
|
- SSHA384 (salted SHA384)
|
||||||
|
- SHA512
|
||||||
|
- SSHA512 (salted SHA512)
|
||||||
|
|
||||||
|
### Restrictions
|
||||||
|
|
||||||
|
Operations on the database regarding to user management can only be preformed by an administrator user.
|
||||||
|
|
||||||
|
### Support
|
||||||
|
|
||||||
|
The user authentication supports two kinds of users
|
||||||
|
|
||||||
|
- administrators
|
||||||
|
- regular users
|
||||||
|
|
||||||
|
### User Management
|
||||||
|
|
||||||
|
User management can be done by directly using the `*SQLiteConn` or by SQL.
|
||||||
|
|
||||||
|
#### SQL
|
||||||
|
|
||||||
|
The following sql functions are available for user management.
|
||||||
|
|
||||||
|
| Function | Arguments | Description |
|
||||||
|
|----------|-----------|-------------|
|
||||||
|
| `authenticate` | username `string`, password `string` | Will authenticate an user, this is done by the connection; and should not be used manually. |
|
||||||
|
| `auth_user_add` | username `string`, password `string`, admin `int` | This function will add an user to the database.<br>if the database is not protected by user authentication it will enable it. Argument `admin` is an integer identifying if the added user should be an administrator. Only Administrators can add administrators. |
|
||||||
|
| `auth_user_change` | username `string`, password `string`, admin `int` | Function to modify an user. Users can change their own password, but only an administrator can change the administrator flag. |
|
||||||
|
| `authUserDelete` | username `string` | Delete an user from the database. Can only be used by an administrator. The current logged in administrator cannot be deleted. This is to make sure their is always an administrator remaining. |
|
||||||
|
|
||||||
|
These functions will return an integer.
|
||||||
|
|
||||||
|
- 0 (SQLITE_OK)
|
||||||
|
- 23 (SQLITE_AUTH) Failed to perform due to authentication or insufficient privileges
|
||||||
|
|
||||||
|
##### Examples
|
||||||
|
|
||||||
|
```sql
|
||||||
|
// Autheticate user
|
||||||
|
// Create Admin User
|
||||||
|
SELECT auth_user_add('admin2', 'admin2', 1);
|
||||||
|
|
||||||
|
// Change password for user
|
||||||
|
SELECT auth_user_change('user', 'userpassword', 0);
|
||||||
|
|
||||||
|
// Delete user
|
||||||
|
SELECT user_delete('user');
|
||||||
|
```
|
||||||
|
|
||||||
|
#### *SQLiteConn
|
||||||
|
|
||||||
|
The following functions are available for User authentication from the `*SQLiteConn`.
|
||||||
|
|
||||||
|
| Function | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `Authenticate(username, password string) error` | Authenticate user |
|
||||||
|
| `AuthUserAdd(username, password string, admin bool) error` | Add user |
|
||||||
|
| `AuthUserChange(username, password string, admin bool) error` | Modify user |
|
||||||
|
| `AuthUserDelete(username string) error` | Delete user |
|
||||||
|
|
||||||
|
### Attached database
|
||||||
|
|
||||||
|
When using attached databases. SQLite will use the authentication from the `main` database for the attached database(s).
|
||||||
|
|
||||||
|
# Extensions
|
||||||
|
|
||||||
|
If you want your own extension to be listed here or you want to add a reference to an extension; please submit an Issue for this.
|
||||||
|
|
||||||
|
## Spatialite
|
||||||
|
|
||||||
|
Spatialite is available as an extension to SQLite, and can be used in combination with this repository.
|
||||||
|
For an example see [shaxbee/go-spatialite](https://github.com/shaxbee/go-spatialite).
|
||||||
|
|
||||||
|
# FAQ
|
||||||
|
|
||||||
|
- Getting insert error while query is opened.
|
||||||
|
|
||||||
> You can pass some arguments into the connection string, for example, a URI.
|
> You can pass some arguments into the connection string, for example, a URI.
|
||||||
> See: [#39](https://github.com/mattn/go-sqlite3/issues/39)
|
> See: [#39](https://github.com/mattn/go-sqlite3/issues/39)
|
||||||
|
|
||||||
* Do you want to cross compile? mingw on Linux or Mac?
|
- Do you want to cross compile? mingw on Linux or Mac?
|
||||||
|
|
||||||
> See: [#106](https://github.com/mattn/go-sqlite3/issues/106)
|
> See: [#106](https://github.com/mattn/go-sqlite3/issues/106)
|
||||||
> See also: http://www.limitlessfx.com/cross-compile-golang-app-for-windows-from-linux.html
|
> See also: http://www.limitlessfx.com/cross-compile-golang-app-for-windows-from-linux.html
|
||||||
|
|
||||||
* Want to get time.Time with current locale
|
- Want to get time.Time with current locale
|
||||||
|
|
||||||
Use `_loc=auto` in SQLite3 filename schema like `file:foo.db?_loc=auto`.
|
Use `_loc=auto` in SQLite3 filename schema like `file:foo.db?_loc=auto`.
|
||||||
|
|
||||||
* Can I use this in multiple routines concurrently?
|
- Can I use this in multiple routines concurrently?
|
||||||
|
|
||||||
Yes for readonly. But, No for writable. See [#50](https://github.com/mattn/go-sqlite3/issues/50), [#51](https://github.com/mattn/go-sqlite3/issues/51), [#209](https://github.com/mattn/go-sqlite3/issues/209).
|
Yes for readonly. But, No for writable. See [#50](https://github.com/mattn/go-sqlite3/issues/50), [#51](https://github.com/mattn/go-sqlite3/issues/51), [#209](https://github.com/mattn/go-sqlite3/issues/209), [#274](https://github.com/mattn/go-sqlite3/issues/274).
|
||||||
|
|
||||||
* Why is it racy if I use a `sql.Open("sqlite3", ":memory:")` database?
|
- Why I'm getting `no such table` error?
|
||||||
|
|
||||||
|
Why is it racy if I use a `sql.Open("sqlite3", ":memory:")` database?
|
||||||
|
|
||||||
Each connection to :memory: opens a brand new in-memory sql database, so if
|
Each connection to :memory: opens a brand new in-memory sql database, so if
|
||||||
the stdlib's sql engine happens to open another connection and you've only
|
the stdlib's sql engine happens to open another connection and you've only
|
||||||
specified ":memory:", that connection will see a brand new database. A
|
specified ":memory:", that connection will see a brand new database. A
|
||||||
workaround is to use "file::memory:?mode=memory&cache=shared". Every
|
workaround is to use "file::memory:?mode=memory&cache=shared". Every
|
||||||
connection to this string will point to the same in-memory database. See
|
connection to this string will point to the same in-memory database.
|
||||||
[#204](https://github.com/mattn/go-sqlite3/issues/204) for more info.
|
|
||||||
|
For more information see
|
||||||
|
* [#204](https://github.com/mattn/go-sqlite3/issues/204)
|
||||||
|
* [#511](https://github.com/mattn/go-sqlite3/issues/511)
|
||||||
|
|
||||||
License
|
- Reading from database with large amount of goroutines fails on OSX.
|
||||||
-------
|
|
||||||
|
|
||||||
MIT: http://mattn.mit-license.org/2012
|
OS X limits OS-wide to not have more than 1000 files open simultaneously by default.
|
||||||
|
|
||||||
|
For more information see [#289](https://github.com/mattn/go-sqlite3/issues/289)
|
||||||
|
|
||||||
|
- Trying to execure a `.` (dot) command throws an error.
|
||||||
|
|
||||||
|
Error: `Error: near ".": syntax error`
|
||||||
|
Dot command are part of SQLite3 CLI not of this library.
|
||||||
|
|
||||||
|
You need to implement the feature or call the sqlite3 cli.
|
||||||
|
|
||||||
|
More infomation see [#305](https://github.com/mattn/go-sqlite3/issues/305)
|
||||||
|
|
||||||
|
- Error: `database is locked`
|
||||||
|
|
||||||
|
When you get an database is locked. Please use the following options.
|
||||||
|
|
||||||
|
Add to DSN: `cache=shared`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```go
|
||||||
|
db, err := sql.Open("sqlite3", "file:locked.sqlite?cache=shared")
|
||||||
|
```
|
||||||
|
|
||||||
|
Second please set the database connections of the SQL package to 1.
|
||||||
|
|
||||||
|
```go
|
||||||
|
db.SetMaxOpenConn(1)
|
||||||
|
```
|
||||||
|
|
||||||
|
More information see [#209](https://github.com/mattn/go-sqlite3/issues/209)
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
MIT: http://mattn.mit-license.org/2018
|
||||||
|
|
||||||
sqlite3-binding.c, sqlite3-binding.h, sqlite3ext.h
|
sqlite3-binding.c, sqlite3-binding.h, sqlite3ext.h
|
||||||
|
|
||||||
|
@ -91,7 +511,8 @@ The -binding suffix was added to avoid build failures under gccgo.
|
||||||
|
|
||||||
In this repository, those files are an amalgamation of code that was copied from SQLite3. The license of that code is the same as the license of SQLite3.
|
In this repository, those files are an amalgamation of code that was copied from SQLite3. The license of that code is the same as the license of SQLite3.
|
||||||
|
|
||||||
Author
|
# Author
|
||||||
------
|
|
||||||
|
|
||||||
Yasuhiro Matsumoto (a.k.a mattn)
|
Yasuhiro Matsumoto (a.k.a mattn)
|
||||||
|
|
||||||
|
G.J.R. Timmer
|
||||||
|
|
10
vendor/github.com/mattn/go-sqlite3/callback.go
generated
vendored
10
vendor/github.com/mattn/go-sqlite3/callback.go
generated
vendored
|
@ -331,8 +331,18 @@ func callbackRetText(ctx *C.sqlite3_context, v reflect.Value) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func callbackRetNil(ctx *C.sqlite3_context, v reflect.Value) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func callbackRet(typ reflect.Type) (callbackRetConverter, error) {
|
func callbackRet(typ reflect.Type) (callbackRetConverter, error) {
|
||||||
switch typ.Kind() {
|
switch typ.Kind() {
|
||||||
|
case reflect.Interface:
|
||||||
|
errorInterface := reflect.TypeOf((*error)(nil)).Elem()
|
||||||
|
if typ.Implements(errorInterface) {
|
||||||
|
return callbackRetNil, nil
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
if typ.Elem().Kind() != reflect.Uint8 {
|
if typ.Elem().Kind() != reflect.Uint8 {
|
||||||
return nil, errors.New("the only supported slice type is []byte")
|
return nil, errors.New("the only supported slice type is []byte")
|
||||||
|
|
17357
vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c
generated
vendored
17357
vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c
generated
vendored
File diff suppressed because it is too large
Load diff
1015
vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h
generated
vendored
1015
vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h
generated
vendored
File diff suppressed because it is too large
Load diff
808
vendor/github.com/mattn/go-sqlite3/sqlite3.go
generated
vendored
808
vendor/github.com/mattn/go-sqlite3/sqlite3.go
generated
vendored
|
@ -1,17 +1,28 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
||||||
|
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
||||||
//
|
//
|
||||||
// Use of this source code is governed by an MIT-style
|
// Use of this source code is governed by an MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build cgo
|
||||||
|
|
||||||
package sqlite3
|
package sqlite3
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo CFLAGS: -std=gnu99
|
#cgo CFLAGS: -std=gnu99
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_RTREE -DSQLITE_THREADSAFE=1
|
#cgo CFLAGS: -DSQLITE_ENABLE_RTREE
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS4_UNICODE61
|
#cgo CFLAGS: -DSQLITE_THREADSAFE=1
|
||||||
|
#cgo CFLAGS: -DHAVE_USLEEP=1
|
||||||
|
#cgo CFLAGS: -DSQLITE_ENABLE_FTS3
|
||||||
|
#cgo CFLAGS: -DSQLITE_ENABLE_FTS3_PARENTHESIS
|
||||||
|
#cgo CFLAGS: -DSQLITE_ENABLE_FTS4_UNICODE61
|
||||||
#cgo CFLAGS: -DSQLITE_TRACE_SIZE_LIMIT=15
|
#cgo CFLAGS: -DSQLITE_TRACE_SIZE_LIMIT=15
|
||||||
|
#cgo CFLAGS: -DSQLITE_OMIT_DEPRECATED
|
||||||
#cgo CFLAGS: -DSQLITE_DISABLE_INTRINSIC
|
#cgo CFLAGS: -DSQLITE_DISABLE_INTRINSIC
|
||||||
|
#cgo CFLAGS: -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1
|
||||||
|
#cgo CFLAGS: -DSQLITE_ENABLE_UPDATE_DELETE_LIMIT
|
||||||
#cgo CFLAGS: -Wno-deprecated-declarations
|
#cgo CFLAGS: -Wno-deprecated-declarations
|
||||||
|
#cgo linux,!android CFLAGS: -DHAVE_PREAD64=1 -DHAVE_PWRITE64=1
|
||||||
#ifndef USE_LIBSQLITE3
|
#ifndef USE_LIBSQLITE3
|
||||||
#include <sqlite3-binding.h>
|
#include <sqlite3-binding.h>
|
||||||
#else
|
#else
|
||||||
|
@ -136,6 +147,7 @@ static int _sqlite3_limit(sqlite3* db, int limitId, int newLimit) {
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -149,8 +161,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SQLiteTimestampFormats is timestamp formats understood by both this module
|
// SQLiteTimestampFormats is timestamp formats understood by both this module
|
||||||
|
@ -171,6 +181,12 @@ var SQLiteTimestampFormats = []string{
|
||||||
"2006-01-02",
|
"2006-01-02",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
columnDate string = "date"
|
||||||
|
columnDatetime string = "datetime"
|
||||||
|
columnTimestamp string = "timestamp"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
sql.Register("sqlite3", &SQLiteDriver{})
|
sql.Register("sqlite3", &SQLiteDriver{})
|
||||||
}
|
}
|
||||||
|
@ -390,7 +406,7 @@ func (c *SQLiteConn) RegisterCommitHook(callback func() int) {
|
||||||
if callback == nil {
|
if callback == nil {
|
||||||
C.sqlite3_commit_hook(c.db, nil, nil)
|
C.sqlite3_commit_hook(c.db, nil, nil)
|
||||||
} else {
|
} else {
|
||||||
C.sqlite3_commit_hook(c.db, (*[0]byte)(unsafe.Pointer(C.commitHookTrampoline)), unsafe.Pointer(newHandle(c, callback)))
|
C.sqlite3_commit_hook(c.db, (*[0]byte)(C.commitHookTrampoline), unsafe.Pointer(newHandle(c, callback)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,7 +419,7 @@ func (c *SQLiteConn) RegisterRollbackHook(callback func()) {
|
||||||
if callback == nil {
|
if callback == nil {
|
||||||
C.sqlite3_rollback_hook(c.db, nil, nil)
|
C.sqlite3_rollback_hook(c.db, nil, nil)
|
||||||
} else {
|
} else {
|
||||||
C.sqlite3_rollback_hook(c.db, (*[0]byte)(unsafe.Pointer(C.rollbackHookTrampoline)), unsafe.Pointer(newHandle(c, callback)))
|
C.sqlite3_rollback_hook(c.db, (*[0]byte)(C.rollbackHookTrampoline), unsafe.Pointer(newHandle(c, callback)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,7 +436,7 @@ func (c *SQLiteConn) RegisterUpdateHook(callback func(int, string, string, int64
|
||||||
if callback == nil {
|
if callback == nil {
|
||||||
C.sqlite3_update_hook(c.db, nil, nil)
|
C.sqlite3_update_hook(c.db, nil, nil)
|
||||||
} else {
|
} else {
|
||||||
C.sqlite3_update_hook(c.db, (*[0]byte)(unsafe.Pointer(C.updateHookTrampoline)), unsafe.Pointer(newHandle(c, callback)))
|
C.sqlite3_update_hook(c.db, (*[0]byte)(C.updateHookTrampoline), unsafe.Pointer(newHandle(c, callback)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -502,7 +518,7 @@ func (c *SQLiteConn) RegisterFunc(name string, impl interface{}, pure bool) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
func sqlite3CreateFunction(db *C.sqlite3, zFunctionName *C.char, nArg C.int, eTextRep C.int, pApp uintptr, xFunc unsafe.Pointer, xStep unsafe.Pointer, xFinal unsafe.Pointer) C.int {
|
func sqlite3CreateFunction(db *C.sqlite3, zFunctionName *C.char, nArg C.int, eTextRep C.int, pApp uintptr, xFunc unsafe.Pointer, xStep unsafe.Pointer, xFinal unsafe.Pointer) C.int {
|
||||||
return C._sqlite3_create_function(db, zFunctionName, nArg, eTextRep, C.uintptr_t(pApp), (*[0]byte)(unsafe.Pointer(xFunc)), (*[0]byte)(unsafe.Pointer(xStep)), (*[0]byte)(unsafe.Pointer(xFinal)))
|
return C._sqlite3_create_function(db, zFunctionName, nArg, eTextRep, C.uintptr_t(pApp), (*[0]byte)(xFunc), (*[0]byte)(xStep), (*[0]byte)(xFinal))
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterAggregator makes a Go type available as a SQLite aggregation function.
|
// RegisterAggregator makes a Go type available as a SQLite aggregation function.
|
||||||
|
@ -764,33 +780,140 @@ func errorString(err Error) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open database and return a new connection.
|
// Open database and return a new connection.
|
||||||
|
//
|
||||||
|
// A pragma can take either zero or one argument.
|
||||||
|
// The argument is may be either in parentheses or it may be separated from
|
||||||
|
// the pragma name by an equal sign. The two syntaxes yield identical results.
|
||||||
|
// In many pragmas, the argument is a boolean. The boolean can be one of:
|
||||||
|
// 1 yes true on
|
||||||
|
// 0 no false off
|
||||||
|
//
|
||||||
// You can specify a DSN string using a URI as the filename.
|
// You can specify a DSN string using a URI as the filename.
|
||||||
// test.db
|
// test.db
|
||||||
// file:test.db?cache=shared&mode=memory
|
// file:test.db?cache=shared&mode=memory
|
||||||
// :memory:
|
// :memory:
|
||||||
// file::memory:
|
// file::memory:
|
||||||
|
//
|
||||||
|
// mode
|
||||||
|
// Access mode of the database.
|
||||||
|
// https://www.sqlite.org/c3ref/open.html
|
||||||
|
// Values:
|
||||||
|
// - ro
|
||||||
|
// - rw
|
||||||
|
// - rwc
|
||||||
|
// - memory
|
||||||
|
//
|
||||||
|
// shared
|
||||||
|
// SQLite Shared-Cache Mode
|
||||||
|
// https://www.sqlite.org/sharedcache.html
|
||||||
|
// Values:
|
||||||
|
// - shared
|
||||||
|
// - private
|
||||||
|
//
|
||||||
|
// immutable=Boolean
|
||||||
|
// The immutable parameter is a boolean query parameter that indicates
|
||||||
|
// that the database file is stored on read-only media. When immutable is set,
|
||||||
|
// SQLite assumes that the database file cannot be changed,
|
||||||
|
// even by a process with higher privilege,
|
||||||
|
// and so the database is opened read-only and all locking and change detection is disabled.
|
||||||
|
// Caution: Setting the immutable property on a database file that
|
||||||
|
// does in fact change can result in incorrect query results and/or SQLITE_CORRUPT errors.
|
||||||
|
//
|
||||||
// go-sqlite3 adds the following query parameters to those used by SQLite:
|
// go-sqlite3 adds the following query parameters to those used by SQLite:
|
||||||
// _loc=XXX
|
// _loc=XXX
|
||||||
// Specify location of time format. It's possible to specify "auto".
|
// Specify location of time format. It's possible to specify "auto".
|
||||||
// _busy_timeout=XXX
|
//
|
||||||
// Specify value for sqlite3_busy_timeout.
|
// _mutex=XXX
|
||||||
|
// Specify mutex mode. XXX can be "no", "full".
|
||||||
|
//
|
||||||
// _txlock=XXX
|
// _txlock=XXX
|
||||||
// Specify locking behavior for transactions. XXX can be "immediate",
|
// Specify locking behavior for transactions. XXX can be "immediate",
|
||||||
// "deferred", "exclusive".
|
// "deferred", "exclusive".
|
||||||
// _foreign_keys=X
|
//
|
||||||
// Enable or disable enforcement of foreign keys. X can be 1 or 0.
|
// _auto_vacuum=X | _vacuum=X
|
||||||
// _recursive_triggers=X
|
// 0 | none - Auto Vacuum disabled
|
||||||
// Enable or disable recursive triggers. X can be 1 or 0.
|
// 1 | full - Auto Vacuum FULL
|
||||||
|
// 2 | incremental - Auto Vacuum Incremental
|
||||||
|
//
|
||||||
|
// _busy_timeout=XXX"| _timeout=XXX
|
||||||
|
// Specify value for sqlite3_busy_timeout.
|
||||||
|
//
|
||||||
|
// _case_sensitive_like=Boolean | _cslike=Boolean
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_case_sensitive_like
|
||||||
|
// Default or disabled the LIKE operation is case-insensitive.
|
||||||
|
// When enabling this options behaviour of LIKE will become case-sensitive.
|
||||||
|
//
|
||||||
|
// _defer_foreign_keys=Boolean | _defer_fk=Boolean
|
||||||
|
// Defer Foreign Keys until outermost transaction is committed.
|
||||||
|
//
|
||||||
|
// _foreign_keys=Boolean | _fk=Boolean
|
||||||
|
// Enable or disable enforcement of foreign keys.
|
||||||
|
//
|
||||||
|
// _ignore_check_constraints=Boolean
|
||||||
|
// This pragma enables or disables the enforcement of CHECK constraints.
|
||||||
|
// The default setting is off, meaning that CHECK constraints are enforced by default.
|
||||||
|
//
|
||||||
|
// _journal_mode=MODE | _journal=MODE
|
||||||
|
// Set journal mode for the databases associated with the current connection.
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_journal_mode
|
||||||
|
//
|
||||||
|
// _locking_mode=X | _locking=X
|
||||||
|
// Sets the database connection locking-mode.
|
||||||
|
// The locking-mode is either NORMAL or EXCLUSIVE.
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_locking_mode
|
||||||
|
//
|
||||||
|
// _query_only=Boolean
|
||||||
|
// The query_only pragma prevents all changes to database files when enabled.
|
||||||
|
//
|
||||||
|
// _recursive_triggers=Boolean | _rt=Boolean
|
||||||
|
// Enable or disable recursive triggers.
|
||||||
|
//
|
||||||
|
// _secure_delete=Boolean|FAST
|
||||||
|
// When secure_delete is on, SQLite overwrites deleted content with zeros.
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_secure_delete
|
||||||
|
//
|
||||||
|
// _synchronous=X | _sync=X
|
||||||
|
// Change the setting of the "synchronous" flag.
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_synchronous
|
||||||
|
//
|
||||||
|
// _writable_schema=Boolean
|
||||||
|
// When this pragma is on, the SQLITE_MASTER tables in which database
|
||||||
|
// can be changed using ordinary UPDATE, INSERT, and DELETE statements.
|
||||||
|
// Warning: misuse of this pragma can easily result in a corrupt database file.
|
||||||
|
//
|
||||||
|
//
|
||||||
func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
if C.sqlite3_threadsafe() == 0 {
|
if C.sqlite3_threadsafe() == 0 {
|
||||||
return nil, errors.New("sqlite library was not compiled for thread-safe operation")
|
return nil, errors.New("sqlite library was not compiled for thread-safe operation")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pkey string
|
||||||
|
|
||||||
|
// Options
|
||||||
var loc *time.Location
|
var loc *time.Location
|
||||||
|
authCreate := false
|
||||||
|
authUser := ""
|
||||||
|
authPass := ""
|
||||||
|
authCrypt := ""
|
||||||
|
authSalt := ""
|
||||||
|
mutex := C.int(C.SQLITE_OPEN_FULLMUTEX)
|
||||||
txlock := "BEGIN"
|
txlock := "BEGIN"
|
||||||
|
|
||||||
|
// PRAGMA's
|
||||||
|
autoVacuum := -1
|
||||||
busyTimeout := 5000
|
busyTimeout := 5000
|
||||||
|
caseSensitiveLike := -1
|
||||||
|
deferForeignKeys := -1
|
||||||
foreignKeys := -1
|
foreignKeys := -1
|
||||||
|
ignoreCheckConstraints := -1
|
||||||
|
journalMode := "DELETE"
|
||||||
|
lockingMode := "NORMAL"
|
||||||
|
queryOnly := -1
|
||||||
recursiveTriggers := -1
|
recursiveTriggers := -1
|
||||||
|
secureDelete := "DEFAULT"
|
||||||
|
synchronousMode := "NORMAL"
|
||||||
|
writableSchema := -1
|
||||||
|
|
||||||
pos := strings.IndexRune(dsn, '?')
|
pos := strings.IndexRune(dsn, '?')
|
||||||
if pos >= 1 {
|
if pos >= 1 {
|
||||||
params, err := url.ParseQuery(dsn[pos+1:])
|
params, err := url.ParseQuery(dsn[pos+1:])
|
||||||
|
@ -798,11 +921,29 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
if _, ok := params["_auth"]; ok {
|
||||||
|
authCreate = true
|
||||||
|
}
|
||||||
|
if val := params.Get("_auth_user"); val != "" {
|
||||||
|
authUser = val
|
||||||
|
}
|
||||||
|
if val := params.Get("_auth_pass"); val != "" {
|
||||||
|
authPass = val
|
||||||
|
}
|
||||||
|
if val := params.Get("_auth_crypt"); val != "" {
|
||||||
|
authCrypt = val
|
||||||
|
}
|
||||||
|
if val := params.Get("_auth_salt"); val != "" {
|
||||||
|
authSalt = val
|
||||||
|
}
|
||||||
|
|
||||||
// _loc
|
// _loc
|
||||||
if val := params.Get("_loc"); val != "" {
|
if val := params.Get("_loc"); val != "" {
|
||||||
if val == "auto" {
|
switch strings.ToLower(val) {
|
||||||
|
case "auto":
|
||||||
loc = time.Local
|
loc = time.Local
|
||||||
} else {
|
default:
|
||||||
loc, err = time.LoadLocation(val)
|
loc, err = time.LoadLocation(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Invalid _loc: %v: %v", val, err)
|
return nil, fmt.Errorf("Invalid _loc: %v: %v", val, err)
|
||||||
|
@ -810,18 +951,21 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// _busy_timeout
|
// _mutex
|
||||||
if val := params.Get("_busy_timeout"); val != "" {
|
if val := params.Get("_mutex"); val != "" {
|
||||||
iv, err := strconv.ParseInt(val, 10, 64)
|
switch strings.ToLower(val) {
|
||||||
if err != nil {
|
case "no":
|
||||||
return nil, fmt.Errorf("Invalid _busy_timeout: %v: %v", val, err)
|
mutex = C.SQLITE_OPEN_NOMUTEX
|
||||||
|
case "full":
|
||||||
|
mutex = C.SQLITE_OPEN_FULLMUTEX
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid _mutex: %v", val)
|
||||||
}
|
}
|
||||||
busyTimeout = int(iv)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// _txlock
|
// _txlock
|
||||||
if val := params.Get("_txlock"); val != "" {
|
if val := params.Get("_txlock"); val != "" {
|
||||||
switch val {
|
switch strings.ToLower(val) {
|
||||||
case "immediate":
|
case "immediate":
|
||||||
txlock = "BEGIN IMMEDIATE"
|
txlock = "BEGIN IMMEDIATE"
|
||||||
case "exclusive":
|
case "exclusive":
|
||||||
|
@ -833,27 +977,262 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// _foreign_keys
|
// Auto Vacuum (_vacuum)
|
||||||
if val := params.Get("_foreign_keys"); val != "" {
|
//
|
||||||
switch val {
|
// https://www.sqlite.org/pragma.html#pragma_auto_vacuum
|
||||||
case "1":
|
//
|
||||||
foreignKeys = 1
|
pkey = "" // Reset pkey
|
||||||
case "0":
|
if _, ok := params["_auto_vacuum"]; ok {
|
||||||
foreignKeys = 0
|
pkey = "_auto_vacuum"
|
||||||
|
}
|
||||||
|
if _, ok := params["_vacuum"]; ok {
|
||||||
|
pkey = "_vacuum"
|
||||||
|
}
|
||||||
|
if val := params.Get(pkey); val != "" {
|
||||||
|
switch strings.ToLower(val) {
|
||||||
|
case "0", "none":
|
||||||
|
autoVacuum = 0
|
||||||
|
case "1", "full":
|
||||||
|
autoVacuum = 1
|
||||||
|
case "2", "incremental":
|
||||||
|
autoVacuum = 2
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Invalid _foreign_keys: %v", val)
|
return nil, fmt.Errorf("Invalid _auto_vacuum: %v, expecting value of '0 NONE 1 FULL 2 INCREMENTAL'", val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// _recursive_triggers
|
// Busy Timeout (_busy_timeout)
|
||||||
if val := params.Get("_recursive_triggers"); val != "" {
|
//
|
||||||
switch val {
|
// https://www.sqlite.org/pragma.html#pragma_busy_timeout
|
||||||
case "1":
|
//
|
||||||
recursiveTriggers = 1
|
pkey = "" // Reset pkey
|
||||||
case "0":
|
if _, ok := params["_busy_timeout"]; ok {
|
||||||
recursiveTriggers = 0
|
pkey = "_busy_timeout"
|
||||||
|
}
|
||||||
|
if _, ok := params["_timeout"]; ok {
|
||||||
|
pkey = "_timeout"
|
||||||
|
}
|
||||||
|
if val := params.Get(pkey); val != "" {
|
||||||
|
iv, err := strconv.ParseInt(val, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Invalid _busy_timeout: %v: %v", val, err)
|
||||||
|
}
|
||||||
|
busyTimeout = int(iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case Sensitive Like (_cslike)
|
||||||
|
//
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_case_sensitive_like
|
||||||
|
//
|
||||||
|
pkey = "" // Reset pkey
|
||||||
|
if _, ok := params["_case_sensitive_like"]; ok {
|
||||||
|
pkey = "_case_sensitive_like"
|
||||||
|
}
|
||||||
|
if _, ok := params["_cslike"]; ok {
|
||||||
|
pkey = "_cslike"
|
||||||
|
}
|
||||||
|
if val := params.Get(pkey); val != "" {
|
||||||
|
switch strings.ToLower(val) {
|
||||||
|
case "0", "no", "false", "off":
|
||||||
|
caseSensitiveLike = 0
|
||||||
|
case "1", "yes", "true", "on":
|
||||||
|
caseSensitiveLike = 1
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Invalid _recursive_triggers: %v", val)
|
return nil, fmt.Errorf("Invalid _case_sensitive_like: %v, expecting boolean value of '0 1 false true no yes off on'", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer Foreign Keys (_defer_foreign_keys | _defer_fk)
|
||||||
|
//
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_defer_foreign_keys
|
||||||
|
//
|
||||||
|
pkey = "" // Reset pkey
|
||||||
|
if _, ok := params["_defer_foreign_keys"]; ok {
|
||||||
|
pkey = "_defer_foreign_keys"
|
||||||
|
}
|
||||||
|
if _, ok := params["_defer_fk"]; ok {
|
||||||
|
pkey = "_defer_fk"
|
||||||
|
}
|
||||||
|
if val := params.Get(pkey); val != "" {
|
||||||
|
switch strings.ToLower(val) {
|
||||||
|
case "0", "no", "false", "off":
|
||||||
|
deferForeignKeys = 0
|
||||||
|
case "1", "yes", "true", "on":
|
||||||
|
deferForeignKeys = 1
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid _defer_foreign_keys: %v, expecting boolean value of '0 1 false true no yes off on'", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foreign Keys (_foreign_keys | _fk)
|
||||||
|
//
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_foreign_keys
|
||||||
|
//
|
||||||
|
pkey = "" // Reset pkey
|
||||||
|
if _, ok := params["_foreign_keys"]; ok {
|
||||||
|
pkey = "_foreign_keys"
|
||||||
|
}
|
||||||
|
if _, ok := params["_fk"]; ok {
|
||||||
|
pkey = "_fk"
|
||||||
|
}
|
||||||
|
if val := params.Get(pkey); val != "" {
|
||||||
|
switch strings.ToLower(val) {
|
||||||
|
case "0", "no", "false", "off":
|
||||||
|
foreignKeys = 0
|
||||||
|
case "1", "yes", "true", "on":
|
||||||
|
foreignKeys = 1
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid _foreign_keys: %v, expecting boolean value of '0 1 false true no yes off on'", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore CHECK Constrains (_ignore_check_constraints)
|
||||||
|
//
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_ignore_check_constraints
|
||||||
|
//
|
||||||
|
if val := params.Get("_ignore_check_constraints"); val != "" {
|
||||||
|
switch strings.ToLower(val) {
|
||||||
|
case "0", "no", "false", "off":
|
||||||
|
ignoreCheckConstraints = 0
|
||||||
|
case "1", "yes", "true", "on":
|
||||||
|
ignoreCheckConstraints = 1
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid _ignore_check_constraints: %v, expecting boolean value of '0 1 false true no yes off on'", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Journal Mode (_journal_mode | _journal)
|
||||||
|
//
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_journal_mode
|
||||||
|
//
|
||||||
|
pkey = "" // Reset pkey
|
||||||
|
if _, ok := params["_journal_mode"]; ok {
|
||||||
|
pkey = "_journal_mode"
|
||||||
|
}
|
||||||
|
if _, ok := params["_journal"]; ok {
|
||||||
|
pkey = "_journal"
|
||||||
|
}
|
||||||
|
if val := params.Get(pkey); val != "" {
|
||||||
|
switch strings.ToUpper(val) {
|
||||||
|
case "DELETE", "TRUNCATE", "PERSIST", "MEMORY", "OFF":
|
||||||
|
journalMode = strings.ToUpper(val)
|
||||||
|
case "WAL":
|
||||||
|
journalMode = strings.ToUpper(val)
|
||||||
|
|
||||||
|
// For WAL Mode set Synchronous Mode to 'NORMAL'
|
||||||
|
// See https://www.sqlite.org/pragma.html#pragma_synchronous
|
||||||
|
synchronousMode = "NORMAL"
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid _journal: %v, expecting value of 'DELETE TRUNCATE PERSIST MEMORY WAL OFF'", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locking Mode (_locking)
|
||||||
|
//
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_locking_mode
|
||||||
|
//
|
||||||
|
pkey = "" // Reset pkey
|
||||||
|
if _, ok := params["_locking_mode"]; ok {
|
||||||
|
pkey = "_locking_mode"
|
||||||
|
}
|
||||||
|
if _, ok := params["_locking"]; ok {
|
||||||
|
pkey = "_locking"
|
||||||
|
}
|
||||||
|
if val := params.Get("_locking"); val != "" {
|
||||||
|
switch strings.ToUpper(val) {
|
||||||
|
case "NORMAL", "EXCLUSIVE":
|
||||||
|
lockingMode = strings.ToUpper(val)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid _locking_mode: %v, expecting value of 'NORMAL EXCLUSIVE", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query Only (_query_only)
|
||||||
|
//
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_query_only
|
||||||
|
//
|
||||||
|
if val := params.Get("_query_only"); val != "" {
|
||||||
|
switch strings.ToLower(val) {
|
||||||
|
case "0", "no", "false", "off":
|
||||||
|
queryOnly = 0
|
||||||
|
case "1", "yes", "true", "on":
|
||||||
|
queryOnly = 1
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid _query_only: %v, expecting boolean value of '0 1 false true no yes off on'", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursive Triggers (_recursive_triggers)
|
||||||
|
//
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_recursive_triggers
|
||||||
|
//
|
||||||
|
pkey = "" // Reset pkey
|
||||||
|
if _, ok := params["_recursive_triggers"]; ok {
|
||||||
|
pkey = "_recursive_triggers"
|
||||||
|
}
|
||||||
|
if _, ok := params["_rt"]; ok {
|
||||||
|
pkey = "_rt"
|
||||||
|
}
|
||||||
|
if val := params.Get(pkey); val != "" {
|
||||||
|
switch strings.ToLower(val) {
|
||||||
|
case "0", "no", "false", "off":
|
||||||
|
recursiveTriggers = 0
|
||||||
|
case "1", "yes", "true", "on":
|
||||||
|
recursiveTriggers = 1
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid _recursive_triggers: %v, expecting boolean value of '0 1 false true no yes off on'", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secure Delete (_secure_delete)
|
||||||
|
//
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_secure_delete
|
||||||
|
//
|
||||||
|
if val := params.Get("_secure_delete"); val != "" {
|
||||||
|
switch strings.ToLower(val) {
|
||||||
|
case "0", "no", "false", "off":
|
||||||
|
secureDelete = "OFF"
|
||||||
|
case "1", "yes", "true", "on":
|
||||||
|
secureDelete = "ON"
|
||||||
|
case "fast":
|
||||||
|
secureDelete = "FAST"
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid _secure_delete: %v, expecting boolean value of '0 1 false true no yes off on fast'", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronous Mode (_synchronous | _sync)
|
||||||
|
//
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_synchronous
|
||||||
|
//
|
||||||
|
pkey = "" // Reset pkey
|
||||||
|
if _, ok := params["_synchronous"]; ok {
|
||||||
|
pkey = "_synchronous"
|
||||||
|
}
|
||||||
|
if _, ok := params["_sync"]; ok {
|
||||||
|
pkey = "_sync"
|
||||||
|
}
|
||||||
|
if val := params.Get(pkey); val != "" {
|
||||||
|
switch strings.ToUpper(val) {
|
||||||
|
case "0", "OFF", "1", "NORMAL", "2", "FULL", "3", "EXTRA":
|
||||||
|
synchronousMode = strings.ToUpper(val)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid _synchronous: %v, expecting value of '0 OFF 1 NORMAL 2 FULL 3 EXTRA'", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writable Schema (_writeable_schema)
|
||||||
|
//
|
||||||
|
// https://www.sqlite.org/pragma.html#pragma_writeable_schema
|
||||||
|
//
|
||||||
|
if val := params.Get("_writable_schema"); val != "" {
|
||||||
|
switch strings.ToLower(val) {
|
||||||
|
case "0", "no", "false", "off":
|
||||||
|
writableSchema = 0
|
||||||
|
case "1", "yes", "true", "on":
|
||||||
|
writableSchema = 1
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid _writable_schema: %v, expecting boolean value of '0 1 false true no yes off on'", val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -866,9 +1245,7 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
name := C.CString(dsn)
|
name := C.CString(dsn)
|
||||||
defer C.free(unsafe.Pointer(name))
|
defer C.free(unsafe.Pointer(name))
|
||||||
rv := C._sqlite3_open_v2(name, &db,
|
rv := C._sqlite3_open_v2(name, &db,
|
||||||
C.SQLITE_OPEN_FULLMUTEX|
|
mutex|C.SQLITE_OPEN_READWRITE|C.SQLITE_OPEN_CREATE,
|
||||||
C.SQLITE_OPEN_READWRITE|
|
|
||||||
C.SQLITE_OPEN_CREATE,
|
|
||||||
nil)
|
nil)
|
||||||
if rv != 0 {
|
if rv != 0 {
|
||||||
return nil, Error{Code: ErrNo(rv)}
|
return nil, Error{Code: ErrNo(rv)}
|
||||||
|
@ -892,30 +1269,268 @@ func (d *SQLiteDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if foreignKeys == 0 {
|
|
||||||
if err := exec("PRAGMA foreign_keys = OFF;"); err != nil {
|
// USER AUTHENTICATION
|
||||||
C.sqlite3_close_v2(db)
|
//
|
||||||
return nil, err
|
// User Authentication is always performed even when
|
||||||
}
|
// sqlite_userauth is not compiled in, because without user authentication
|
||||||
} else if foreignKeys == 1 {
|
// the authentication is a no-op.
|
||||||
if err := exec("PRAGMA foreign_keys = ON;"); err != nil {
|
//
|
||||||
C.sqlite3_close_v2(db)
|
// Workflow
|
||||||
return nil, err
|
// - Authenticate
|
||||||
|
// ON::SUCCESS => Continue
|
||||||
|
// ON::SQLITE_AUTH => Return error and exit Open(...)
|
||||||
|
//
|
||||||
|
// - Activate User Authentication
|
||||||
|
// Check if the user wants to activate User Authentication.
|
||||||
|
// If so then first create a temporary AuthConn to the database
|
||||||
|
// This is possible because we are already succesfully authenticated.
|
||||||
|
//
|
||||||
|
// - Check if `sqlite_user`` table exists
|
||||||
|
// YES => Add the provided user from DSN as Admin User and
|
||||||
|
// activate user authentication.
|
||||||
|
// NO => Continue
|
||||||
|
//
|
||||||
|
|
||||||
|
// Create connection to SQLite
|
||||||
|
conn := &SQLiteConn{db: db, loc: loc, txlock: txlock}
|
||||||
|
|
||||||
|
// Password Cipher has to be registerd before authentication
|
||||||
|
if len(authCrypt) > 0 {
|
||||||
|
switch strings.ToUpper(authCrypt) {
|
||||||
|
case "SHA1":
|
||||||
|
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA1, true); err != nil {
|
||||||
|
return nil, fmt.Errorf("CryptEncoderSHA1: %s", err)
|
||||||
|
}
|
||||||
|
case "SSHA1":
|
||||||
|
if len(authSalt) == 0 {
|
||||||
|
return nil, fmt.Errorf("_auth_crypt=ssha1, requires _auth_salt")
|
||||||
|
}
|
||||||
|
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA1(authSalt), true); err != nil {
|
||||||
|
return nil, fmt.Errorf("CryptEncoderSSHA1: %s", err)
|
||||||
|
}
|
||||||
|
case "SHA256":
|
||||||
|
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA256, true); err != nil {
|
||||||
|
return nil, fmt.Errorf("CryptEncoderSHA256: %s", err)
|
||||||
|
}
|
||||||
|
case "SSHA256":
|
||||||
|
if len(authSalt) == 0 {
|
||||||
|
return nil, fmt.Errorf("_auth_crypt=ssha256, requires _auth_salt")
|
||||||
|
}
|
||||||
|
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA256(authSalt), true); err != nil {
|
||||||
|
return nil, fmt.Errorf("CryptEncoderSSHA256: %s", err)
|
||||||
|
}
|
||||||
|
case "SHA384":
|
||||||
|
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA384, true); err != nil {
|
||||||
|
return nil, fmt.Errorf("CryptEncoderSHA384: %s", err)
|
||||||
|
}
|
||||||
|
case "SSHA384":
|
||||||
|
if len(authSalt) == 0 {
|
||||||
|
return nil, fmt.Errorf("_auth_crypt=ssha384, requires _auth_salt")
|
||||||
|
}
|
||||||
|
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA384(authSalt), true); err != nil {
|
||||||
|
return nil, fmt.Errorf("CryptEncoderSSHA384: %s", err)
|
||||||
|
}
|
||||||
|
case "SHA512":
|
||||||
|
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSHA512, true); err != nil {
|
||||||
|
return nil, fmt.Errorf("CryptEncoderSHA512: %s", err)
|
||||||
|
}
|
||||||
|
case "SSHA512":
|
||||||
|
if len(authSalt) == 0 {
|
||||||
|
return nil, fmt.Errorf("_auth_crypt=ssha512, requires _auth_salt")
|
||||||
|
}
|
||||||
|
if err := conn.RegisterFunc("sqlite_crypt", CryptEncoderSSHA512(authSalt), true); err != nil {
|
||||||
|
return nil, fmt.Errorf("CryptEncoderSSHA512: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if recursiveTriggers == 0 {
|
|
||||||
if err := exec("PRAGMA recursive_triggers = OFF;"); err != nil {
|
// Preform Authentication
|
||||||
C.sqlite3_close_v2(db)
|
if err := conn.Authenticate(authUser, authPass); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else if recursiveTriggers == 1 {
|
|
||||||
if err := exec("PRAGMA recursive_triggers = ON;"); err != nil {
|
// Register: authenticate
|
||||||
|
// Authenticate will perform an authentication of the provided username
|
||||||
|
// and password against the database.
|
||||||
|
//
|
||||||
|
// If a database contains the SQLITE_USER table, then the
|
||||||
|
// call to Authenticate must be invoked with an
|
||||||
|
// appropriate username and password prior to enable read and write
|
||||||
|
//access to the database.
|
||||||
|
//
|
||||||
|
// Return SQLITE_OK on success or SQLITE_ERROR if the username/password
|
||||||
|
// combination is incorrect or unknown.
|
||||||
|
//
|
||||||
|
// If the SQLITE_USER table is not present in the database file, then
|
||||||
|
// this interface is a harmless no-op returnning SQLITE_OK.
|
||||||
|
if err := conn.RegisterFunc("authenticate", conn.authenticate, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// Register: auth_user_add
|
||||||
|
// auth_user_add can be used (by an admin user only)
|
||||||
|
// to create a new user. When called on a no-authentication-required
|
||||||
|
// database, this routine converts the database into an authentication-
|
||||||
|
// required database, automatically makes the added user an
|
||||||
|
// administrator, and logs in the current connection as that user.
|
||||||
|
// The AuthUserAdd only works for the "main" database, not
|
||||||
|
// for any ATTACH-ed databases. Any call to AuthUserAdd by a
|
||||||
|
// non-admin user results in an error.
|
||||||
|
if err := conn.RegisterFunc("auth_user_add", conn.authUserAdd, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// Register: auth_user_change
|
||||||
|
// auth_user_change can be used to change a users
|
||||||
|
// login credentials or admin privilege. Any user can change their own
|
||||||
|
// login credentials. Only an admin user can change another users login
|
||||||
|
// credentials or admin privilege setting. No user may change their own
|
||||||
|
// admin privilege setting.
|
||||||
|
if err := conn.RegisterFunc("auth_user_change", conn.authUserChange, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// Register: auth_user_delete
|
||||||
|
// auth_user_delete can be used (by an admin user only)
|
||||||
|
// to delete a user. The currently logged-in user cannot be deleted,
|
||||||
|
// which guarantees that there is always an admin user and hence that
|
||||||
|
// the database cannot be converted into a no-authentication-required
|
||||||
|
// database.
|
||||||
|
if err := conn.RegisterFunc("auth_user_delete", conn.authUserDelete, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register: auth_enabled
|
||||||
|
// auth_enabled can be used to check if user authentication is enabled
|
||||||
|
if err := conn.RegisterFunc("auth_enabled", conn.authEnabled, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto Vacuum
|
||||||
|
// Moved auto_vacuum command, the user preference for auto_vacuum needs to be implemented directly after
|
||||||
|
// the authentication and before the sqlite_user table gets created if the user
|
||||||
|
// decides to activate User Authentication because
|
||||||
|
// auto_vacuum needs to be set before any tables are created
|
||||||
|
// and activating user authentication creates the internal table `sqlite_user`.
|
||||||
|
if autoVacuum > -1 {
|
||||||
|
if err := exec(fmt.Sprintf("PRAGMA auto_vacuum = %d;", autoVacuum)); err != nil {
|
||||||
C.sqlite3_close_v2(db)
|
C.sqlite3_close_v2(db)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := &SQLiteConn{db: db, loc: loc, txlock: txlock}
|
// Check if user wants to activate User Authentication
|
||||||
|
if authCreate {
|
||||||
|
// Before going any further, we need to check that the user
|
||||||
|
// has provided an username and password within the DSN.
|
||||||
|
// We are not allowed to continue.
|
||||||
|
if len(authUser) < 0 {
|
||||||
|
return nil, fmt.Errorf("Missing '_auth_user' while user authentication was requested with '_auth'")
|
||||||
|
}
|
||||||
|
if len(authPass) < 0 {
|
||||||
|
return nil, fmt.Errorf("Missing '_auth_pass' while user authentication was requested with '_auth'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if User Authentication is Enabled
|
||||||
|
authExists := conn.AuthEnabled()
|
||||||
|
if !authExists {
|
||||||
|
if err := conn.AuthUserAdd(authUser, authPass, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case Sensitive LIKE
|
||||||
|
if caseSensitiveLike > -1 {
|
||||||
|
if err := exec(fmt.Sprintf("PRAGMA case_sensitive_like = %d;", caseSensitiveLike)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer Foreign Keys
|
||||||
|
if deferForeignKeys > -1 {
|
||||||
|
if err := exec(fmt.Sprintf("PRAGMA defer_foreign_keys = %d;", deferForeignKeys)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forgein Keys
|
||||||
|
if foreignKeys > -1 {
|
||||||
|
if err := exec(fmt.Sprintf("PRAGMA foreign_keys = %d;", foreignKeys)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore CHECK Constraints
|
||||||
|
if ignoreCheckConstraints > -1 {
|
||||||
|
if err := exec(fmt.Sprintf("PRAGMA ignore_check_constraints = %d;", ignoreCheckConstraints)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Journal Mode
|
||||||
|
// Because default Journal Mode is DELETE this PRAGMA can always be executed.
|
||||||
|
if err := exec(fmt.Sprintf("PRAGMA journal_mode = %s;", journalMode)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locking Mode
|
||||||
|
// Because the default is NORMAL and this is not changed in this package
|
||||||
|
// by using the compile time SQLITE_DEFAULT_LOCKING_MODE this PRAGMA can always be executed
|
||||||
|
if err := exec(fmt.Sprintf("PRAGMA locking_mode = %s;", lockingMode)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query Only
|
||||||
|
if queryOnly > -1 {
|
||||||
|
if err := exec(fmt.Sprintf("PRAGMA query_only = %d;", queryOnly)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursive Triggers
|
||||||
|
if recursiveTriggers > -1 {
|
||||||
|
if err := exec(fmt.Sprintf("PRAGMA recursive_triggers = %d;", recursiveTriggers)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secure Delete
|
||||||
|
//
|
||||||
|
// Because this package can set the compile time flag SQLITE_SECURE_DELETE with a build tag
|
||||||
|
// the default value for secureDelete var is 'DEFAULT' this way
|
||||||
|
// you can compile with secure_delete 'ON' and disable it for a specific database connection.
|
||||||
|
if secureDelete != "DEFAULT" {
|
||||||
|
if err := exec(fmt.Sprintf("PRAGMA secure_delete = %s;", secureDelete)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronous Mode
|
||||||
|
//
|
||||||
|
// Because default is NORMAL this statement is always executed
|
||||||
|
if err := exec(fmt.Sprintf("PRAGMA synchronous = %s;", synchronousMode)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writable Schema
|
||||||
|
if writableSchema > -1 {
|
||||||
|
if err := exec(fmt.Sprintf("PRAGMA writable_schema = %d;", writableSchema)); err != nil {
|
||||||
|
C.sqlite3_close_v2(db)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(d.Extensions) > 0 {
|
if len(d.Extensions) > 0 {
|
||||||
if err := conn.loadExtensions(d.Extensions); err != nil {
|
if err := conn.loadExtensions(d.Extensions); err != nil {
|
||||||
|
@ -997,6 +1612,17 @@ const (
|
||||||
SQLITE_LIMIT_WORKER_THREADS = C.SQLITE_LIMIT_WORKER_THREADS
|
SQLITE_LIMIT_WORKER_THREADS = C.SQLITE_LIMIT_WORKER_THREADS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GetFilename returns the absolute path to the file containing
|
||||||
|
// the requested schema. When passed an empty string, it will
|
||||||
|
// instead use the database's default schema: "main".
|
||||||
|
// See: sqlite3_db_filename, https://www.sqlite.org/c3ref/db_filename.html
|
||||||
|
func (c *SQLiteConn) GetFilename(schemaName string) string {
|
||||||
|
if schemaName == "" {
|
||||||
|
schemaName = "main"
|
||||||
|
}
|
||||||
|
return C.GoString(C.sqlite3_db_filename(c.db, C.CString(schemaName)))
|
||||||
|
}
|
||||||
|
|
||||||
// GetLimit returns the current value of a run-time limit.
|
// GetLimit returns the current value of a run-time limit.
|
||||||
// See: sqlite3_limit, http://www.sqlite.org/c3ref/limit.html
|
// See: sqlite3_limit, http://www.sqlite.org/c3ref/limit.html
|
||||||
func (c *SQLiteConn) GetLimit(id int) int {
|
func (c *SQLiteConn) GetLimit(id int) int {
|
||||||
|
@ -1071,7 +1697,7 @@ func (s *SQLiteStmt) bind(args []namedValue) error {
|
||||||
case int64:
|
case int64:
|
||||||
rv = C.sqlite3_bind_int64(s.s, n, C.sqlite3_int64(v))
|
rv = C.sqlite3_bind_int64(s.s, n, C.sqlite3_int64(v))
|
||||||
case bool:
|
case bool:
|
||||||
if bool(v) {
|
if v {
|
||||||
rv = C.sqlite3_bind_int(s.s, n, 1)
|
rv = C.sqlite3_bind_int(s.s, n, 1)
|
||||||
} else {
|
} else {
|
||||||
rv = C.sqlite3_bind_int(s.s, n, 0)
|
rv = C.sqlite3_bind_int(s.s, n, 0)
|
||||||
|
@ -1079,11 +1705,15 @@ func (s *SQLiteStmt) bind(args []namedValue) error {
|
||||||
case float64:
|
case float64:
|
||||||
rv = C.sqlite3_bind_double(s.s, n, C.double(v))
|
rv = C.sqlite3_bind_double(s.s, n, C.double(v))
|
||||||
case []byte:
|
case []byte:
|
||||||
ln := len(v)
|
if v == nil {
|
||||||
if ln == 0 {
|
rv = C.sqlite3_bind_null(s.s, n)
|
||||||
v = placeHolder
|
} else {
|
||||||
|
ln := len(v)
|
||||||
|
if ln == 0 {
|
||||||
|
v = placeHolder
|
||||||
|
}
|
||||||
|
rv = C._sqlite3_bind_blob(s.s, n, unsafe.Pointer(&v[0]), C.int(ln))
|
||||||
}
|
}
|
||||||
rv = C._sqlite3_bind_blob(s.s, n, unsafe.Pointer(&v[0]), C.int(ln))
|
|
||||||
case time.Time:
|
case time.Time:
|
||||||
b := []byte(v.Format(SQLiteTimestampFormats[0]))
|
b := []byte(v.Format(SQLiteTimestampFormats[0]))
|
||||||
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)))
|
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)))
|
||||||
|
@ -1122,18 +1752,20 @@ func (s *SQLiteStmt) query(ctx context.Context, args []namedValue) (driver.Rows,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
go func(db *C.sqlite3) {
|
if ctxdone := ctx.Done(); ctxdone != nil {
|
||||||
select {
|
go func(db *C.sqlite3) {
|
||||||
case <-ctx.Done():
|
|
||||||
select {
|
select {
|
||||||
|
case <-ctxdone:
|
||||||
|
select {
|
||||||
|
case <-rows.done:
|
||||||
|
default:
|
||||||
|
C.sqlite3_interrupt(db)
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
case <-rows.done:
|
case <-rows.done:
|
||||||
default:
|
|
||||||
C.sqlite3_interrupt(db)
|
|
||||||
rows.Close()
|
|
||||||
}
|
}
|
||||||
case <-rows.done:
|
}(s.c.db)
|
||||||
}
|
}
|
||||||
}(s.c.db)
|
|
||||||
|
|
||||||
return rows, nil
|
return rows, nil
|
||||||
}
|
}
|
||||||
|
@ -1167,19 +1799,21 @@ func (s *SQLiteStmt) exec(ctx context.Context, args []namedValue) (driver.Result
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
done := make(chan struct{})
|
if ctxdone := ctx.Done(); ctxdone != nil {
|
||||||
defer close(done)
|
done := make(chan struct{})
|
||||||
go func(db *C.sqlite3) {
|
defer close(done)
|
||||||
select {
|
go func(db *C.sqlite3) {
|
||||||
case <-done:
|
|
||||||
case <-ctx.Done():
|
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
default:
|
case <-ctxdone:
|
||||||
C.sqlite3_interrupt(db)
|
select {
|
||||||
|
case <-done:
|
||||||
|
default:
|
||||||
|
C.sqlite3_interrupt(db)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}(s.c.db)
|
||||||
}(s.c.db)
|
}
|
||||||
|
|
||||||
var rowid, changes C.longlong
|
var rowid, changes C.longlong
|
||||||
rv := C._sqlite3_step(s.s, &rowid, &changes)
|
rv := C._sqlite3_step(s.s, &rowid, &changes)
|
||||||
|
@ -1273,7 +1907,7 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error {
|
||||||
case C.SQLITE_INTEGER:
|
case C.SQLITE_INTEGER:
|
||||||
val := int64(C.sqlite3_column_int64(rc.s.s, C.int(i)))
|
val := int64(C.sqlite3_column_int64(rc.s.s, C.int(i)))
|
||||||
switch rc.decltype[i] {
|
switch rc.decltype[i] {
|
||||||
case "timestamp", "datetime", "date":
|
case columnTimestamp, columnDatetime, columnDate:
|
||||||
var t time.Time
|
var t time.Time
|
||||||
// Assume a millisecond unix timestamp if it's 13 digits -- too
|
// Assume a millisecond unix timestamp if it's 13 digits -- too
|
||||||
// large to be a reasonable timestamp in seconds.
|
// large to be a reasonable timestamp in seconds.
|
||||||
|
@ -1303,11 +1937,9 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error {
|
||||||
}
|
}
|
||||||
n := int(C.sqlite3_column_bytes(rc.s.s, C.int(i)))
|
n := int(C.sqlite3_column_bytes(rc.s.s, C.int(i)))
|
||||||
switch dest[i].(type) {
|
switch dest[i].(type) {
|
||||||
case sql.RawBytes:
|
|
||||||
dest[i] = (*[1 << 30]byte)(unsafe.Pointer(p))[0:n]
|
|
||||||
default:
|
default:
|
||||||
slice := make([]byte, n)
|
slice := make([]byte, n)
|
||||||
copy(slice[:], (*[1 << 30]byte)(unsafe.Pointer(p))[0:n])
|
copy(slice[:], (*[1 << 30]byte)(p)[0:n])
|
||||||
dest[i] = slice
|
dest[i] = slice
|
||||||
}
|
}
|
||||||
case C.SQLITE_NULL:
|
case C.SQLITE_NULL:
|
||||||
|
@ -1320,7 +1952,7 @@ func (rc *SQLiteRows) Next(dest []driver.Value) error {
|
||||||
s := C.GoStringN((*C.char)(unsafe.Pointer(C.sqlite3_column_text(rc.s.s, C.int(i)))), C.int(n))
|
s := C.GoStringN((*C.char)(unsafe.Pointer(C.sqlite3_column_text(rc.s.s, C.int(i)))), C.int(n))
|
||||||
|
|
||||||
switch rc.decltype[i] {
|
switch rc.decltype[i] {
|
||||||
case "timestamp", "datetime", "date":
|
case columnTimestamp, columnDatetime, columnDate:
|
||||||
var t time.Time
|
var t time.Time
|
||||||
s = strings.TrimSuffix(s, "Z")
|
s = strings.TrimSuffix(s, "Z")
|
||||||
for _, format := range SQLiteTimestampFormats {
|
for _, format := range SQLiteTimestampFormats {
|
||||||
|
|
13
vendor/github.com/mattn/go-sqlite3/sqlite3_fts5.go
generated
vendored
13
vendor/github.com/mattn/go-sqlite3/sqlite3_fts5.go
generated
vendored
|
@ -1,13 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// +build fts5
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_FTS5
|
|
||||||
#cgo LDFLAGS: -lm
|
|
||||||
*/
|
|
||||||
import "C"
|
|
1
vendor/github.com/mattn/go-sqlite3/sqlite3_go18.go
generated
vendored
1
vendor/github.com/mattn/go-sqlite3/sqlite3_go18.go
generated
vendored
|
@ -3,6 +3,7 @@
|
||||||
// Use of this source code is governed by an MIT-style
|
// Use of this source code is governed by an MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build cgo
|
||||||
// +build go1.8
|
// +build go1.8
|
||||||
|
|
||||||
package sqlite3
|
package sqlite3
|
||||||
|
|
13
vendor/github.com/mattn/go-sqlite3/sqlite3_icu.go
generated
vendored
13
vendor/github.com/mattn/go-sqlite3/sqlite3_icu.go
generated
vendored
|
@ -1,13 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// +build icu
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo LDFLAGS: -licuuc -licui18n
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_ICU
|
|
||||||
*/
|
|
||||||
import "C"
|
|
12
vendor/github.com/mattn/go-sqlite3/sqlite3_json1.go
generated
vendored
12
vendor/github.com/mattn/go-sqlite3/sqlite3_json1.go
generated
vendored
|
@ -1,12 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// +build json1
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_JSON1
|
|
||||||
*/
|
|
||||||
import "C"
|
|
2
vendor/github.com/mattn/go-sqlite3/sqlite3_libsqlite3.go
generated
vendored
2
vendor/github.com/mattn/go-sqlite3/sqlite3_libsqlite3.go
generated
vendored
|
@ -2,6 +2,7 @@
|
||||||
//
|
//
|
||||||
// Use of this source code is governed by an MIT-style
|
// Use of this source code is governed by an MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build libsqlite3
|
// +build libsqlite3
|
||||||
|
|
||||||
package sqlite3
|
package sqlite3
|
||||||
|
@ -10,6 +11,7 @@ package sqlite3
|
||||||
#cgo CFLAGS: -DUSE_LIBSQLITE3
|
#cgo CFLAGS: -DUSE_LIBSQLITE3
|
||||||
#cgo linux LDFLAGS: -lsqlite3
|
#cgo linux LDFLAGS: -lsqlite3
|
||||||
#cgo darwin LDFLAGS: -L/usr/local/opt/sqlite/lib -lsqlite3
|
#cgo darwin LDFLAGS: -L/usr/local/opt/sqlite/lib -lsqlite3
|
||||||
|
#cgo openbsd LDFLAGS: -lsqlite3
|
||||||
#cgo solaris LDFLAGS: -lsqlite3
|
#cgo solaris LDFLAGS: -lsqlite3
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
1
vendor/github.com/mattn/go-sqlite3/sqlite3_load_extension.go
generated
vendored
1
vendor/github.com/mattn/go-sqlite3/sqlite3_load_extension.go
generated
vendored
|
@ -2,6 +2,7 @@
|
||||||
//
|
//
|
||||||
// Use of this source code is governed by an MIT-style
|
// Use of this source code is governed by an MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build !sqlite_omit_load_extension
|
// +build !sqlite_omit_load_extension
|
||||||
|
|
||||||
package sqlite3
|
package sqlite3
|
||||||
|
|
23
vendor/github.com/mattn/go-sqlite3/sqlite3_omit_load_extension.go
generated
vendored
23
vendor/github.com/mattn/go-sqlite3/sqlite3_omit_load_extension.go
generated
vendored
|
@ -1,23 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// +build sqlite_omit_load_extension
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_OMIT_LOAD_EXTENSION
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *SQLiteConn) loadExtensions(extensions []string) error {
|
|
||||||
return errors.New("Extensions have been disabled for static builds")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SQLiteConn) LoadExtension(lib string, entry string) error {
|
|
||||||
return errors.New("Extensions have been disabled for static builds")
|
|
||||||
}
|
|
2
vendor/github.com/mattn/go-sqlite3/sqlite3_other.go
generated
vendored
2
vendor/github.com/mattn/go-sqlite3/sqlite3_other.go
generated
vendored
|
@ -2,6 +2,7 @@
|
||||||
//
|
//
|
||||||
// Use of this source code is governed by an MIT-style
|
// Use of this source code is governed by an MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package sqlite3
|
package sqlite3
|
||||||
|
@ -9,6 +10,5 @@ package sqlite3
|
||||||
/*
|
/*
|
||||||
#cgo CFLAGS: -I.
|
#cgo CFLAGS: -I.
|
||||||
#cgo linux LDFLAGS: -ldl
|
#cgo linux LDFLAGS: -ldl
|
||||||
#cgo solaris LDFLAGS: -lc
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
25
vendor/github.com/mattn/go-sqlite3/sqlite3_trace.go
generated
vendored
25
vendor/github.com/mattn/go-sqlite3/sqlite3_trace.go
generated
vendored
|
@ -2,7 +2,8 @@
|
||||||
//
|
//
|
||||||
// Use of this source code is governed by an MIT-style
|
// Use of this source code is governed by an MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
// +build trace
|
|
||||||
|
// +build sqlite_trace trace
|
||||||
|
|
||||||
package sqlite3
|
package sqlite3
|
||||||
|
|
||||||
|
@ -28,10 +29,10 @@ import (
|
||||||
// Trace... constants identify the possible events causing callback invocation.
|
// Trace... constants identify the possible events causing callback invocation.
|
||||||
// Values are same as the corresponding SQLite Trace Event Codes.
|
// Values are same as the corresponding SQLite Trace Event Codes.
|
||||||
const (
|
const (
|
||||||
TraceStmt = C.SQLITE_TRACE_STMT
|
TraceStmt = uint32(C.SQLITE_TRACE_STMT)
|
||||||
TraceProfile = C.SQLITE_TRACE_PROFILE
|
TraceProfile = uint32(C.SQLITE_TRACE_PROFILE)
|
||||||
TraceRow = C.SQLITE_TRACE_ROW
|
TraceRow = uint32(C.SQLITE_TRACE_ROW)
|
||||||
TraceClose = C.SQLITE_TRACE_CLOSE
|
TraceClose = uint32(C.SQLITE_TRACE_CLOSE)
|
||||||
)
|
)
|
||||||
|
|
||||||
type TraceInfo struct {
|
type TraceInfo struct {
|
||||||
|
@ -71,7 +72,7 @@ type TraceUserCallback func(TraceInfo) int
|
||||||
|
|
||||||
type TraceConfig struct {
|
type TraceConfig struct {
|
||||||
Callback TraceUserCallback
|
Callback TraceUserCallback
|
||||||
EventMask C.uint
|
EventMask uint32
|
||||||
WantExpandedSQL bool
|
WantExpandedSQL bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +106,8 @@ func traceCallbackTrampoline(
|
||||||
// Parameter named 'X' in SQLite docs (eXtra event data?):
|
// Parameter named 'X' in SQLite docs (eXtra event data?):
|
||||||
xValue unsafe.Pointer) C.int {
|
xValue unsafe.Pointer) C.int {
|
||||||
|
|
||||||
|
eventCode := uint32(traceEventCode)
|
||||||
|
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
panic(fmt.Sprintf("No context (ev 0x%x)", traceEventCode))
|
panic(fmt.Sprintf("No context (ev 0x%x)", traceEventCode))
|
||||||
}
|
}
|
||||||
|
@ -114,7 +117,7 @@ func traceCallbackTrampoline(
|
||||||
|
|
||||||
var traceConf TraceConfig
|
var traceConf TraceConfig
|
||||||
var found bool
|
var found bool
|
||||||
if traceEventCode == TraceClose {
|
if eventCode == TraceClose {
|
||||||
// clean up traceMap: 'pop' means get and delete
|
// clean up traceMap: 'pop' means get and delete
|
||||||
traceConf, found = popTraceMapping(connHandle)
|
traceConf, found = popTraceMapping(connHandle)
|
||||||
} else {
|
} else {
|
||||||
|
@ -123,16 +126,16 @@ func traceCallbackTrampoline(
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
panic(fmt.Sprintf("Mapping not found for handle 0x%x (ev 0x%x)",
|
panic(fmt.Sprintf("Mapping not found for handle 0x%x (ev 0x%x)",
|
||||||
connHandle, traceEventCode))
|
connHandle, eventCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
var info TraceInfo
|
var info TraceInfo
|
||||||
|
|
||||||
info.EventCode = uint32(traceEventCode)
|
info.EventCode = eventCode
|
||||||
info.AutoCommit = (int(C.sqlite3_get_autocommit(contextDB)) != 0)
|
info.AutoCommit = (int(C.sqlite3_get_autocommit(contextDB)) != 0)
|
||||||
info.ConnHandle = connHandle
|
info.ConnHandle = connHandle
|
||||||
|
|
||||||
switch traceEventCode {
|
switch eventCode {
|
||||||
case TraceStmt:
|
case TraceStmt:
|
||||||
info.StmtHandle = uintptr(p)
|
info.StmtHandle = uintptr(p)
|
||||||
|
|
||||||
|
@ -183,7 +186,7 @@ func traceCallbackTrampoline(
|
||||||
// registering this callback trampoline with SQLite --- for cleanup.
|
// registering this callback trampoline with SQLite --- for cleanup.
|
||||||
// In the future there may be more events forced to "selected" in SQLite
|
// In the future there may be more events forced to "selected" in SQLite
|
||||||
// for the driver's needs.
|
// for the driver's needs.
|
||||||
if traceConf.EventMask&traceEventCode == 0 {
|
if traceConf.EventMask&eventCode == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
646
vendor/github.com/mattn/go-sqlite3/sqlite3_vtable.go
generated
vendored
646
vendor/github.com/mattn/go-sqlite3/sqlite3_vtable.go
generated
vendored
|
@ -1,646 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// +build vtable
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -std=gnu99
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_RTREE -DSQLITE_THREADSAFE
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS4_UNICODE61
|
|
||||||
#cgo CFLAGS: -DSQLITE_TRACE_SIZE_LIMIT=15
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_COLUMN_METADATA=1
|
|
||||||
#cgo CFLAGS: -Wno-deprecated-declarations
|
|
||||||
|
|
||||||
#ifndef USE_LIBSQLITE3
|
|
||||||
#include <sqlite3-binding.h>
|
|
||||||
#else
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#endif
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <memory.h>
|
|
||||||
|
|
||||||
static inline char *_sqlite3_mprintf(char *zFormat, char *arg) {
|
|
||||||
return sqlite3_mprintf(zFormat, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct goVTab goVTab;
|
|
||||||
|
|
||||||
struct goVTab {
|
|
||||||
sqlite3_vtab base;
|
|
||||||
void *vTab;
|
|
||||||
};
|
|
||||||
|
|
||||||
uintptr_t goMInit(void *db, void *pAux, int argc, char **argv, char **pzErr, int isCreate);
|
|
||||||
|
|
||||||
static int cXInit(sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char **pzErr, int isCreate) {
|
|
||||||
void *vTab = (void *)goMInit(db, pAux, argc, (char**)argv, pzErr, isCreate);
|
|
||||||
if (!vTab || *pzErr) {
|
|
||||||
return SQLITE_ERROR;
|
|
||||||
}
|
|
||||||
goVTab *pvTab = (goVTab *)sqlite3_malloc(sizeof(goVTab));
|
|
||||||
if (!pvTab) {
|
|
||||||
*pzErr = sqlite3_mprintf("%s", "Out of memory");
|
|
||||||
return SQLITE_NOMEM;
|
|
||||||
}
|
|
||||||
memset(pvTab, 0, sizeof(goVTab));
|
|
||||||
pvTab->vTab = vTab;
|
|
||||||
|
|
||||||
*ppVTab = (sqlite3_vtab *)pvTab;
|
|
||||||
*pzErr = 0;
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cXCreate(sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char **pzErr) {
|
|
||||||
return cXInit(db, pAux, argc, argv, ppVTab, pzErr, 1);
|
|
||||||
}
|
|
||||||
static inline int cXConnect(sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char **pzErr) {
|
|
||||||
return cXInit(db, pAux, argc, argv, ppVTab, pzErr, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVBestIndex(void *pVTab, void *icp);
|
|
||||||
|
|
||||||
static inline int cXBestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *info) {
|
|
||||||
char *pzErr = goVBestIndex(((goVTab*)pVTab)->vTab, info);
|
|
||||||
if (pzErr) {
|
|
||||||
if (pVTab->zErrMsg)
|
|
||||||
sqlite3_free(pVTab->zErrMsg);
|
|
||||||
pVTab->zErrMsg = pzErr;
|
|
||||||
return SQLITE_ERROR;
|
|
||||||
}
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVRelease(void *pVTab, int isDestroy);
|
|
||||||
|
|
||||||
static int cXRelease(sqlite3_vtab *pVTab, int isDestroy) {
|
|
||||||
char *pzErr = goVRelease(((goVTab*)pVTab)->vTab, isDestroy);
|
|
||||||
if (pzErr) {
|
|
||||||
if (pVTab->zErrMsg)
|
|
||||||
sqlite3_free(pVTab->zErrMsg);
|
|
||||||
pVTab->zErrMsg = pzErr;
|
|
||||||
return SQLITE_ERROR;
|
|
||||||
}
|
|
||||||
if (pVTab->zErrMsg)
|
|
||||||
sqlite3_free(pVTab->zErrMsg);
|
|
||||||
sqlite3_free(pVTab);
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cXDisconnect(sqlite3_vtab *pVTab) {
|
|
||||||
return cXRelease(pVTab, 0);
|
|
||||||
}
|
|
||||||
static inline int cXDestroy(sqlite3_vtab *pVTab) {
|
|
||||||
return cXRelease(pVTab, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct goVTabCursor goVTabCursor;
|
|
||||||
|
|
||||||
struct goVTabCursor {
|
|
||||||
sqlite3_vtab_cursor base;
|
|
||||||
void *vTabCursor;
|
|
||||||
};
|
|
||||||
|
|
||||||
uintptr_t goVOpen(void *pVTab, char **pzErr);
|
|
||||||
|
|
||||||
static int cXOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) {
|
|
||||||
void *vTabCursor = (void *)goVOpen(((goVTab*)pVTab)->vTab, &(pVTab->zErrMsg));
|
|
||||||
goVTabCursor *pCursor = (goVTabCursor *)sqlite3_malloc(sizeof(goVTabCursor));
|
|
||||||
if (!pCursor) {
|
|
||||||
return SQLITE_NOMEM;
|
|
||||||
}
|
|
||||||
memset(pCursor, 0, sizeof(goVTabCursor));
|
|
||||||
pCursor->vTabCursor = vTabCursor;
|
|
||||||
*ppCursor = (sqlite3_vtab_cursor *)pCursor;
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int setErrMsg(sqlite3_vtab_cursor *pCursor, char *pzErr) {
|
|
||||||
if (pCursor->pVtab->zErrMsg)
|
|
||||||
sqlite3_free(pCursor->pVtab->zErrMsg);
|
|
||||||
pCursor->pVtab->zErrMsg = pzErr;
|
|
||||||
return SQLITE_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVClose(void *pCursor);
|
|
||||||
|
|
||||||
static int cXClose(sqlite3_vtab_cursor *pCursor) {
|
|
||||||
char *pzErr = goVClose(((goVTabCursor*)pCursor)->vTabCursor);
|
|
||||||
if (pzErr) {
|
|
||||||
return setErrMsg(pCursor, pzErr);
|
|
||||||
}
|
|
||||||
sqlite3_free(pCursor);
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVFilter(void *pCursor, int idxNum, char* idxName, int argc, sqlite3_value **argv);
|
|
||||||
|
|
||||||
static int cXFilter(sqlite3_vtab_cursor *pCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) {
|
|
||||||
char *pzErr = goVFilter(((goVTabCursor*)pCursor)->vTabCursor, idxNum, (char*)idxStr, argc, argv);
|
|
||||||
if (pzErr) {
|
|
||||||
return setErrMsg(pCursor, pzErr);
|
|
||||||
}
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVNext(void *pCursor);
|
|
||||||
|
|
||||||
static int cXNext(sqlite3_vtab_cursor *pCursor) {
|
|
||||||
char *pzErr = goVNext(((goVTabCursor*)pCursor)->vTabCursor);
|
|
||||||
if (pzErr) {
|
|
||||||
return setErrMsg(pCursor, pzErr);
|
|
||||||
}
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
int goVEof(void *pCursor);
|
|
||||||
|
|
||||||
static inline int cXEof(sqlite3_vtab_cursor *pCursor) {
|
|
||||||
return goVEof(((goVTabCursor*)pCursor)->vTabCursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVColumn(void *pCursor, void *cp, int col);
|
|
||||||
|
|
||||||
static int cXColumn(sqlite3_vtab_cursor *pCursor, sqlite3_context *ctx, int i) {
|
|
||||||
char *pzErr = goVColumn(((goVTabCursor*)pCursor)->vTabCursor, ctx, i);
|
|
||||||
if (pzErr) {
|
|
||||||
return setErrMsg(pCursor, pzErr);
|
|
||||||
}
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVRowid(void *pCursor, sqlite3_int64 *pRowid);
|
|
||||||
|
|
||||||
static int cXRowid(sqlite3_vtab_cursor *pCursor, sqlite3_int64 *pRowid) {
|
|
||||||
char *pzErr = goVRowid(((goVTabCursor*)pCursor)->vTabCursor, pRowid);
|
|
||||||
if (pzErr) {
|
|
||||||
return setErrMsg(pCursor, pzErr);
|
|
||||||
}
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVUpdate(void *pVTab, int argc, sqlite3_value **argv, sqlite3_int64 *pRowid);
|
|
||||||
|
|
||||||
static int cXUpdate(sqlite3_vtab *pVTab, int argc, sqlite3_value **argv, sqlite3_int64 *pRowid) {
|
|
||||||
char *pzErr = goVUpdate(((goVTab*)pVTab)->vTab, argc, argv, pRowid);
|
|
||||||
if (pzErr) {
|
|
||||||
if (pVTab->zErrMsg)
|
|
||||||
sqlite3_free(pVTab->zErrMsg);
|
|
||||||
pVTab->zErrMsg = pzErr;
|
|
||||||
return SQLITE_ERROR;
|
|
||||||
}
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static sqlite3_module goModule = {
|
|
||||||
0, // iVersion
|
|
||||||
cXCreate, // xCreate - create a table
|
|
||||||
cXConnect, // xConnect - connect to an existing table
|
|
||||||
cXBestIndex, // xBestIndex - Determine search strategy
|
|
||||||
cXDisconnect, // xDisconnect - Disconnect from a table
|
|
||||||
cXDestroy, // xDestroy - Drop a table
|
|
||||||
cXOpen, // xOpen - open a cursor
|
|
||||||
cXClose, // xClose - close a cursor
|
|
||||||
cXFilter, // xFilter - configure scan constraints
|
|
||||||
cXNext, // xNext - advance a cursor
|
|
||||||
cXEof, // xEof
|
|
||||||
cXColumn, // xColumn - read data
|
|
||||||
cXRowid, // xRowid - read data
|
|
||||||
cXUpdate, // xUpdate - write data
|
|
||||||
// Not implemented
|
|
||||||
0, // xBegin - begin transaction
|
|
||||||
0, // xSync - sync transaction
|
|
||||||
0, // xCommit - commit transaction
|
|
||||||
0, // xRollback - rollback transaction
|
|
||||||
0, // xFindFunction - function overloading
|
|
||||||
0, // xRename - rename the table
|
|
||||||
0, // xSavepoint
|
|
||||||
0, // xRelease
|
|
||||||
0 // xRollbackTo
|
|
||||||
};
|
|
||||||
|
|
||||||
void goMDestroy(void*);
|
|
||||||
|
|
||||||
static int _sqlite3_create_module(sqlite3 *db, const char *zName, uintptr_t pClientData) {
|
|
||||||
return sqlite3_create_module_v2(db, zName, &goModule, (void*) pClientData, goMDestroy);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sqliteModule struct {
|
|
||||||
c *SQLiteConn
|
|
||||||
name string
|
|
||||||
module Module
|
|
||||||
}
|
|
||||||
|
|
||||||
type sqliteVTab struct {
|
|
||||||
module *sqliteModule
|
|
||||||
vTab VTab
|
|
||||||
}
|
|
||||||
|
|
||||||
type sqliteVTabCursor struct {
|
|
||||||
vTab *sqliteVTab
|
|
||||||
vTabCursor VTabCursor
|
|
||||||
}
|
|
||||||
|
|
||||||
// Op is type of operations.
|
|
||||||
type Op uint8
|
|
||||||
|
|
||||||
// Op mean identity of operations.
|
|
||||||
const (
|
|
||||||
OpEQ Op = 2
|
|
||||||
OpGT = 4
|
|
||||||
OpLE = 8
|
|
||||||
OpLT = 16
|
|
||||||
OpGE = 32
|
|
||||||
OpMATCH = 64
|
|
||||||
OpLIKE = 65 /* 3.10.0 and later only */
|
|
||||||
OpGLOB = 66 /* 3.10.0 and later only */
|
|
||||||
OpREGEXP = 67 /* 3.10.0 and later only */
|
|
||||||
OpScanUnique = 1 /* Scan visits at most 1 row */
|
|
||||||
)
|
|
||||||
|
|
||||||
// InfoConstraint give information of constraint.
|
|
||||||
type InfoConstraint struct {
|
|
||||||
Column int
|
|
||||||
Op Op
|
|
||||||
Usable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfoOrderBy give information of order-by.
|
|
||||||
type InfoOrderBy struct {
|
|
||||||
Column int
|
|
||||||
Desc bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func constraints(info *C.sqlite3_index_info) []InfoConstraint {
|
|
||||||
l := info.nConstraint
|
|
||||||
slice := (*[1 << 30]C.struct_sqlite3_index_constraint)(unsafe.Pointer(info.aConstraint))[:l:l]
|
|
||||||
|
|
||||||
cst := make([]InfoConstraint, 0, l)
|
|
||||||
for _, c := range slice {
|
|
||||||
var usable bool
|
|
||||||
if c.usable > 0 {
|
|
||||||
usable = true
|
|
||||||
}
|
|
||||||
cst = append(cst, InfoConstraint{
|
|
||||||
Column: int(c.iColumn),
|
|
||||||
Op: Op(c.op),
|
|
||||||
Usable: usable,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return cst
|
|
||||||
}
|
|
||||||
|
|
||||||
func orderBys(info *C.sqlite3_index_info) []InfoOrderBy {
|
|
||||||
l := info.nOrderBy
|
|
||||||
slice := (*[1 << 30]C.struct_sqlite3_index_orderby)(unsafe.Pointer(info.aOrderBy))[:l:l]
|
|
||||||
|
|
||||||
ob := make([]InfoOrderBy, 0, l)
|
|
||||||
for _, c := range slice {
|
|
||||||
var desc bool
|
|
||||||
if c.desc > 0 {
|
|
||||||
desc = true
|
|
||||||
}
|
|
||||||
ob = append(ob, InfoOrderBy{
|
|
||||||
Column: int(c.iColumn),
|
|
||||||
Desc: desc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return ob
|
|
||||||
}
|
|
||||||
|
|
||||||
// IndexResult is a Go struct representation of what eventually ends up in the
|
|
||||||
// output fields for `sqlite3_index_info`
|
|
||||||
// See: https://www.sqlite.org/c3ref/index_info.html
|
|
||||||
type IndexResult struct {
|
|
||||||
Used []bool // aConstraintUsage
|
|
||||||
IdxNum int
|
|
||||||
IdxStr string
|
|
||||||
AlreadyOrdered bool // orderByConsumed
|
|
||||||
EstimatedCost float64
|
|
||||||
EstimatedRows float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// mPrintf is a utility wrapper around sqlite3_mprintf
|
|
||||||
func mPrintf(format, arg string) *C.char {
|
|
||||||
cf := C.CString(format)
|
|
||||||
defer C.free(unsafe.Pointer(cf))
|
|
||||||
ca := C.CString(arg)
|
|
||||||
defer C.free(unsafe.Pointer(ca))
|
|
||||||
return C._sqlite3_mprintf(cf, ca)
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goMInit
|
|
||||||
func goMInit(db, pClientData unsafe.Pointer, argc C.int, argv **C.char, pzErr **C.char, isCreate C.int) C.uintptr_t {
|
|
||||||
m := lookupHandle(uintptr(pClientData)).(*sqliteModule)
|
|
||||||
if m.c.db != (*C.sqlite3)(db) {
|
|
||||||
*pzErr = mPrintf("%s", "Inconsistent db handles")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
args := make([]string, argc)
|
|
||||||
var A []*C.char
|
|
||||||
slice := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(argv)), Len: int(argc), Cap: int(argc)}
|
|
||||||
a := reflect.NewAt(reflect.TypeOf(A), unsafe.Pointer(&slice)).Elem().Interface()
|
|
||||||
for i, s := range a.([]*C.char) {
|
|
||||||
args[i] = C.GoString(s)
|
|
||||||
}
|
|
||||||
var vTab VTab
|
|
||||||
var err error
|
|
||||||
if isCreate == 1 {
|
|
||||||
vTab, err = m.module.Create(m.c, args)
|
|
||||||
} else {
|
|
||||||
vTab, err = m.module.Connect(m.c, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
*pzErr = mPrintf("%s", err.Error())
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
vt := sqliteVTab{m, vTab}
|
|
||||||
*pzErr = nil
|
|
||||||
return C.uintptr_t(newHandle(m.c, &vt))
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVRelease
|
|
||||||
func goVRelease(pVTab unsafe.Pointer, isDestroy C.int) *C.char {
|
|
||||||
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
|
|
||||||
var err error
|
|
||||||
if isDestroy == 1 {
|
|
||||||
err = vt.vTab.Destroy()
|
|
||||||
} else {
|
|
||||||
err = vt.vTab.Disconnect()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVOpen
|
|
||||||
func goVOpen(pVTab unsafe.Pointer, pzErr **C.char) C.uintptr_t {
|
|
||||||
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
|
|
||||||
vTabCursor, err := vt.vTab.Open()
|
|
||||||
if err != nil {
|
|
||||||
*pzErr = mPrintf("%s", err.Error())
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
vtc := sqliteVTabCursor{vt, vTabCursor}
|
|
||||||
*pzErr = nil
|
|
||||||
return C.uintptr_t(newHandle(vt.module.c, &vtc))
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVBestIndex
|
|
||||||
func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char {
|
|
||||||
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
|
|
||||||
info := (*C.sqlite3_index_info)(icp)
|
|
||||||
csts := constraints(info)
|
|
||||||
res, err := vt.vTab.BestIndex(csts, orderBys(info))
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
if len(res.Used) != len(csts) {
|
|
||||||
return mPrintf("Result.Used != expected value", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a pointer to constraint_usage struct so we can update in place.
|
|
||||||
l := info.nConstraint
|
|
||||||
s := (*[1 << 30]C.struct_sqlite3_index_constraint_usage)(unsafe.Pointer(info.aConstraintUsage))[:l:l]
|
|
||||||
index := 1
|
|
||||||
for i := C.int(0); i < info.nConstraint; i++ {
|
|
||||||
if res.Used[i] {
|
|
||||||
s[i].argvIndex = C.int(index)
|
|
||||||
s[i].omit = C.uchar(1)
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info.idxNum = C.int(res.IdxNum)
|
|
||||||
idxStr := C.CString(res.IdxStr)
|
|
||||||
defer C.free(unsafe.Pointer(idxStr))
|
|
||||||
info.idxStr = idxStr
|
|
||||||
info.needToFreeIdxStr = C.int(0)
|
|
||||||
if res.AlreadyOrdered {
|
|
||||||
info.orderByConsumed = C.int(1)
|
|
||||||
}
|
|
||||||
info.estimatedCost = C.double(res.EstimatedCost)
|
|
||||||
info.estimatedRows = C.sqlite3_int64(res.EstimatedRows)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVClose
|
|
||||||
func goVClose(pCursor unsafe.Pointer) *C.char {
|
|
||||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
|
||||||
err := vtc.vTabCursor.Close()
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goMDestroy
|
|
||||||
func goMDestroy(pClientData unsafe.Pointer) {
|
|
||||||
m := lookupHandle(uintptr(pClientData)).(*sqliteModule)
|
|
||||||
m.module.DestroyModule()
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVFilter
|
|
||||||
func goVFilter(pCursor unsafe.Pointer, idxNum C.int, idxName *C.char, argc C.int, argv **C.sqlite3_value) *C.char {
|
|
||||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
|
||||||
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc]
|
|
||||||
vals := make([]interface{}, 0, argc)
|
|
||||||
for _, v := range args {
|
|
||||||
conv, err := callbackArgGeneric(v)
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
vals = append(vals, conv.Interface())
|
|
||||||
}
|
|
||||||
err := vtc.vTabCursor.Filter(int(idxNum), C.GoString(idxName), vals)
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVNext
|
|
||||||
func goVNext(pCursor unsafe.Pointer) *C.char {
|
|
||||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
|
||||||
err := vtc.vTabCursor.Next()
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVEof
|
|
||||||
func goVEof(pCursor unsafe.Pointer) C.int {
|
|
||||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
|
||||||
err := vtc.vTabCursor.EOF()
|
|
||||||
if err {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVColumn
|
|
||||||
func goVColumn(pCursor, cp unsafe.Pointer, col C.int) *C.char {
|
|
||||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
|
||||||
c := (*SQLiteContext)(cp)
|
|
||||||
err := vtc.vTabCursor.Column(c, int(col))
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVRowid
|
|
||||||
func goVRowid(pCursor unsafe.Pointer, pRowid *C.sqlite3_int64) *C.char {
|
|
||||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
|
||||||
rowid, err := vtc.vTabCursor.Rowid()
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
*pRowid = C.sqlite3_int64(rowid)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVUpdate
|
|
||||||
func goVUpdate(pVTab unsafe.Pointer, argc C.int, argv **C.sqlite3_value, pRowid *C.sqlite3_int64) *C.char {
|
|
||||||
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
|
|
||||||
|
|
||||||
var tname string
|
|
||||||
if n, ok := vt.vTab.(interface {
|
|
||||||
TableName() string
|
|
||||||
}); ok {
|
|
||||||
tname = n.TableName() + " "
|
|
||||||
}
|
|
||||||
|
|
||||||
err := fmt.Errorf("virtual %s table %sis read-only", vt.module.name, tname)
|
|
||||||
if v, ok := vt.vTab.(VTabUpdater); ok {
|
|
||||||
// convert argv
|
|
||||||
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc]
|
|
||||||
vals := make([]interface{}, 0, argc)
|
|
||||||
for _, v := range args {
|
|
||||||
conv, err := callbackArgGeneric(v)
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// work around for SQLITE_NULL
|
|
||||||
x := conv.Interface()
|
|
||||||
if z, ok := x.([]byte); ok && z == nil {
|
|
||||||
x = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
vals = append(vals, x)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case argc == 1:
|
|
||||||
err = v.Delete(vals[0])
|
|
||||||
|
|
||||||
case argc > 1 && vals[0] == nil:
|
|
||||||
var id int64
|
|
||||||
id, err = v.Insert(vals[1], vals[2:])
|
|
||||||
if err == nil {
|
|
||||||
*pRowid = C.sqlite3_int64(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
case argc > 1:
|
|
||||||
err = v.Update(vals[1], vals[2:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Module is a "virtual table module", it defines the implementation of a
|
|
||||||
// virtual tables. See: http://sqlite.org/c3ref/module.html
|
|
||||||
type Module interface {
|
|
||||||
// http://sqlite.org/vtab.html#xcreate
|
|
||||||
Create(c *SQLiteConn, args []string) (VTab, error)
|
|
||||||
// http://sqlite.org/vtab.html#xconnect
|
|
||||||
Connect(c *SQLiteConn, args []string) (VTab, error)
|
|
||||||
// http://sqlite.org/c3ref/create_module.html
|
|
||||||
DestroyModule()
|
|
||||||
}
|
|
||||||
|
|
||||||
// VTab describes a particular instance of the virtual table.
|
|
||||||
// See: http://sqlite.org/c3ref/vtab.html
|
|
||||||
type VTab interface {
|
|
||||||
// http://sqlite.org/vtab.html#xbestindex
|
|
||||||
BestIndex([]InfoConstraint, []InfoOrderBy) (*IndexResult, error)
|
|
||||||
// http://sqlite.org/vtab.html#xdisconnect
|
|
||||||
Disconnect() error
|
|
||||||
// http://sqlite.org/vtab.html#sqlite3_module.xDestroy
|
|
||||||
Destroy() error
|
|
||||||
// http://sqlite.org/vtab.html#xopen
|
|
||||||
Open() (VTabCursor, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VTabUpdater is a type that allows a VTab to be inserted, updated, or
|
|
||||||
// deleted.
|
|
||||||
// See: https://sqlite.org/vtab.html#xupdate
|
|
||||||
type VTabUpdater interface {
|
|
||||||
Delete(interface{}) error
|
|
||||||
Insert(interface{}, []interface{}) (int64, error)
|
|
||||||
Update(interface{}, []interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// VTabCursor describes cursors that point into the virtual table and are used
|
|
||||||
// to loop through the virtual table. See: http://sqlite.org/c3ref/vtab_cursor.html
|
|
||||||
type VTabCursor interface {
|
|
||||||
// http://sqlite.org/vtab.html#xclose
|
|
||||||
Close() error
|
|
||||||
// http://sqlite.org/vtab.html#xfilter
|
|
||||||
Filter(idxNum int, idxStr string, vals []interface{}) error
|
|
||||||
// http://sqlite.org/vtab.html#xnext
|
|
||||||
Next() error
|
|
||||||
// http://sqlite.org/vtab.html#xeof
|
|
||||||
EOF() bool
|
|
||||||
// http://sqlite.org/vtab.html#xcolumn
|
|
||||||
Column(c *SQLiteContext, col int) error
|
|
||||||
// http://sqlite.org/vtab.html#xrowid
|
|
||||||
Rowid() (int64, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeclareVTab declares the Schema of a virtual table.
|
|
||||||
// See: http://sqlite.org/c3ref/declare_vtab.html
|
|
||||||
func (c *SQLiteConn) DeclareVTab(sql string) error {
|
|
||||||
zSQL := C.CString(sql)
|
|
||||||
defer C.free(unsafe.Pointer(zSQL))
|
|
||||||
rv := C.sqlite3_declare_vtab(c.db, zSQL)
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return c.lastError()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateModule registers a virtual table implementation.
|
|
||||||
// See: http://sqlite.org/c3ref/create_module.html
|
|
||||||
func (c *SQLiteConn) CreateModule(moduleName string, module Module) error {
|
|
||||||
mname := C.CString(moduleName)
|
|
||||||
defer C.free(unsafe.Pointer(mname))
|
|
||||||
udm := sqliteModule{c, moduleName, module}
|
|
||||||
rv := C._sqlite3_create_module(c.db, mname, C.uintptr_t(newHandle(c, &udm)))
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return c.lastError()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
8
vendor/github.com/mattn/go-sqlite3/sqlite3_windows.go
generated
vendored
8
vendor/github.com/mattn/go-sqlite3/sqlite3_windows.go
generated
vendored
|
@ -2,13 +2,17 @@
|
||||||
//
|
//
|
||||||
// Use of this source code is governed by an MIT-style
|
// Use of this source code is governed by an MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package sqlite3
|
package sqlite3
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo CFLAGS: -I. -fno-stack-check -fno-stack-protector -mno-stack-arg-probe
|
#cgo CFLAGS: -I.
|
||||||
#cgo windows,386 CFLAGS: -D_USE_32BIT_TIME_T
|
#cgo CFLAGS: -fno-stack-check
|
||||||
|
#cgo CFLAGS: -fno-stack-protector
|
||||||
|
#cgo CFLAGS: -mno-stack-arg-probe
|
||||||
#cgo LDFLAGS: -lmingwex -lmingw32
|
#cgo LDFLAGS: -lmingwex -lmingw32
|
||||||
|
#cgo windows,386 CFLAGS: -D_USE_32BIT_TIME_T
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
37
vendor/github.com/mattn/go-sqlite3/sqlite3ext.h
generated
vendored
37
vendor/github.com/mattn/go-sqlite3/sqlite3ext.h
generated
vendored
|
@ -293,6 +293,24 @@ struct sqlite3_api_routines {
|
||||||
int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*));
|
int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*));
|
||||||
void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*));
|
void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*));
|
||||||
void *(*value_pointer)(sqlite3_value*,const char*);
|
void *(*value_pointer)(sqlite3_value*,const char*);
|
||||||
|
int (*vtab_nochange)(sqlite3_context*);
|
||||||
|
int (*value_nochange)(sqlite3_value*);
|
||||||
|
const char *(*vtab_collation)(sqlite3_index_info*,int);
|
||||||
|
/* Version 3.24.0 and later */
|
||||||
|
int (*keyword_count)(void);
|
||||||
|
int (*keyword_name)(int,const char**,int*);
|
||||||
|
int (*keyword_check)(const char*,int);
|
||||||
|
sqlite3_str *(*str_new)(sqlite3*);
|
||||||
|
char *(*str_finish)(sqlite3_str*);
|
||||||
|
void (*str_appendf)(sqlite3_str*, const char *zFormat, ...);
|
||||||
|
void (*str_vappendf)(sqlite3_str*, const char *zFormat, va_list);
|
||||||
|
void (*str_append)(sqlite3_str*, const char *zIn, int N);
|
||||||
|
void (*str_appendall)(sqlite3_str*, const char *zIn);
|
||||||
|
void (*str_appendchar)(sqlite3_str*, int N, char C);
|
||||||
|
void (*str_reset)(sqlite3_str*);
|
||||||
|
int (*str_errcode)(sqlite3_str*);
|
||||||
|
int (*str_length)(sqlite3_str*);
|
||||||
|
char *(*str_value)(sqlite3_str*);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -559,6 +577,25 @@ typedef int (*sqlite3_loadext_entry)(
|
||||||
#define sqlite3_bind_pointer sqlite3_api->bind_pointer
|
#define sqlite3_bind_pointer sqlite3_api->bind_pointer
|
||||||
#define sqlite3_result_pointer sqlite3_api->result_pointer
|
#define sqlite3_result_pointer sqlite3_api->result_pointer
|
||||||
#define sqlite3_value_pointer sqlite3_api->value_pointer
|
#define sqlite3_value_pointer sqlite3_api->value_pointer
|
||||||
|
/* Version 3.22.0 and later */
|
||||||
|
#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange
|
||||||
|
#define sqlite3_value_nochange sqlite3_api->value_nochange
|
||||||
|
#define sqlite3_vtab_collation sqlite3_api->vtab_collation
|
||||||
|
/* Version 3.24.0 and later */
|
||||||
|
#define sqlite3_keyword_count sqlite3_api->keyword_count
|
||||||
|
#define sqlite3_keyword_name sqlite3_api->keyword_name
|
||||||
|
#define sqlite3_keyword_check sqlite3_api->keyword_check
|
||||||
|
#define sqlite3_str_new sqlite3_api->str_new
|
||||||
|
#define sqlite3_str_finish sqlite3_api->str_finish
|
||||||
|
#define sqlite3_str_appendf sqlite3_api->str_appendf
|
||||||
|
#define sqlite3_str_vappendf sqlite3_api->str_vappendf
|
||||||
|
#define sqlite3_str_append sqlite3_api->str_append
|
||||||
|
#define sqlite3_str_appendall sqlite3_api->str_appendall
|
||||||
|
#define sqlite3_str_appendchar sqlite3_api->str_appendchar
|
||||||
|
#define sqlite3_str_reset sqlite3_api->str_reset
|
||||||
|
#define sqlite3_str_errcode sqlite3_api->str_errcode
|
||||||
|
#define sqlite3_str_length sqlite3_api->str_length
|
||||||
|
#define sqlite3_str_value sqlite3_api->str_value
|
||||||
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
|
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
|
||||||
|
|
||||||
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
||||||
|
|
27
vendor/golang.org/x/net/LICENSE
generated
vendored
27
vendor/golang.org/x/net/LICENSE
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
22
vendor/golang.org/x/net/PATENTS
generated
vendored
22
vendor/golang.org/x/net/PATENTS
generated
vendored
|
@ -1,22 +0,0 @@
|
||||||
Additional IP Rights Grant (Patents)
|
|
||||||
|
|
||||||
"This implementation" means the copyrightable works distributed by
|
|
||||||
Google as part of the Go project.
|
|
||||||
|
|
||||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
|
||||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
|
||||||
patent license to make, have made, use, offer to sell, sell, import,
|
|
||||||
transfer and otherwise run, modify and propagate the contents of this
|
|
||||||
implementation of Go, where such license applies only to those patent
|
|
||||||
claims, both currently owned or controlled by Google and acquired in
|
|
||||||
the future, licensable by Google that are necessarily infringed by this
|
|
||||||
implementation of Go. This grant does not include claims that would be
|
|
||||||
infringed only as a consequence of further modification of this
|
|
||||||
implementation. If you or your agent or exclusive licensee institute or
|
|
||||||
order or agree to the institution of patent litigation against any
|
|
||||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
|
||||||
that this implementation of Go or any code incorporated within this
|
|
||||||
implementation of Go constitutes direct or contributory patent
|
|
||||||
infringement, or inducement of patent infringement, then any patent
|
|
||||||
rights granted to you under this License for this implementation of Go
|
|
||||||
shall terminate as of the date such litigation is filed.
|
|
56
vendor/golang.org/x/net/context/context.go
generated
vendored
56
vendor/golang.org/x/net/context/context.go
generated
vendored
|
@ -1,56 +0,0 @@
|
||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package context defines the Context type, which carries deadlines,
|
|
||||||
// cancelation signals, and other request-scoped values across API boundaries
|
|
||||||
// and between processes.
|
|
||||||
// As of Go 1.7 this package is available in the standard library under the
|
|
||||||
// name context. https://golang.org/pkg/context.
|
|
||||||
//
|
|
||||||
// Incoming requests to a server should create a Context, and outgoing calls to
|
|
||||||
// servers should accept a Context. The chain of function calls between must
|
|
||||||
// propagate the Context, optionally replacing it with a modified copy created
|
|
||||||
// using WithDeadline, WithTimeout, WithCancel, or WithValue.
|
|
||||||
//
|
|
||||||
// Programs that use Contexts should follow these rules to keep interfaces
|
|
||||||
// consistent across packages and enable static analysis tools to check context
|
|
||||||
// propagation:
|
|
||||||
//
|
|
||||||
// Do not store Contexts inside a struct type; instead, pass a Context
|
|
||||||
// explicitly to each function that needs it. The Context should be the first
|
|
||||||
// parameter, typically named ctx:
|
|
||||||
//
|
|
||||||
// func DoSomething(ctx context.Context, arg Arg) error {
|
|
||||||
// // ... use ctx ...
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
|
|
||||||
// if you are unsure about which Context to use.
|
|
||||||
//
|
|
||||||
// Use context Values only for request-scoped data that transits processes and
|
|
||||||
// APIs, not for passing optional parameters to functions.
|
|
||||||
//
|
|
||||||
// The same Context may be passed to functions running in different goroutines;
|
|
||||||
// Contexts are safe for simultaneous use by multiple goroutines.
|
|
||||||
//
|
|
||||||
// See http://blog.golang.org/context for example code for a server that uses
|
|
||||||
// Contexts.
|
|
||||||
package context // import "golang.org/x/net/context"
|
|
||||||
|
|
||||||
// Background returns a non-nil, empty Context. It is never canceled, has no
|
|
||||||
// values, and has no deadline. It is typically used by the main function,
|
|
||||||
// initialization, and tests, and as the top-level Context for incoming
|
|
||||||
// requests.
|
|
||||||
func Background() Context {
|
|
||||||
return background
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
|
||||||
// it's unclear which Context to use or it is not yet available (because the
|
|
||||||
// surrounding function has not yet been extended to accept a Context
|
|
||||||
// parameter). TODO is recognized by static analysis tools that determine
|
|
||||||
// whether Contexts are propagated correctly in a program.
|
|
||||||
func TODO() Context {
|
|
||||||
return todo
|
|
||||||
}
|
|
72
vendor/golang.org/x/net/context/go17.go
generated
vendored
72
vendor/golang.org/x/net/context/go17.go
generated
vendored
|
@ -1,72 +0,0 @@
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context" // standard library's context, as of Go 1.7
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
todo = context.TODO()
|
|
||||||
background = context.Background()
|
|
||||||
)
|
|
||||||
|
|
||||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
|
||||||
var Canceled = context.Canceled
|
|
||||||
|
|
||||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
|
||||||
// deadline passes.
|
|
||||||
var DeadlineExceeded = context.DeadlineExceeded
|
|
||||||
|
|
||||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
|
||||||
// context's Done channel is closed when the returned cancel function is called
|
|
||||||
// or when the parent context's Done channel is closed, whichever happens first.
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete.
|
|
||||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
|
||||||
ctx, f := context.WithCancel(parent)
|
|
||||||
return ctx, CancelFunc(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
|
||||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
|
||||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
|
||||||
// context's Done channel is closed when the deadline expires, when the returned
|
|
||||||
// cancel function is called, or when the parent context's Done channel is
|
|
||||||
// closed, whichever happens first.
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete.
|
|
||||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
|
||||||
ctx, f := context.WithDeadline(parent, deadline)
|
|
||||||
return ctx, CancelFunc(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete:
|
|
||||||
//
|
|
||||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
|
||||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
|
||||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
|
||||||
// return slowOperation(ctx)
|
|
||||||
// }
|
|
||||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
|
||||||
return WithDeadline(parent, time.Now().Add(timeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithValue returns a copy of parent in which the value associated with key is
|
|
||||||
// val.
|
|
||||||
//
|
|
||||||
// Use context Values only for request-scoped data that transits processes and
|
|
||||||
// APIs, not for passing optional parameters to functions.
|
|
||||||
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
|
||||||
return context.WithValue(parent, key, val)
|
|
||||||
}
|
|
20
vendor/golang.org/x/net/context/go19.go
generated
vendored
20
vendor/golang.org/x/net/context/go19.go
generated
vendored
|
@ -1,20 +0,0 @@
|
||||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build go1.9
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import "context" // standard library's context, as of Go 1.7
|
|
||||||
|
|
||||||
// A Context carries a deadline, a cancelation signal, and other values across
|
|
||||||
// API boundaries.
|
|
||||||
//
|
|
||||||
// Context's methods may be called by multiple goroutines simultaneously.
|
|
||||||
type Context = context.Context
|
|
||||||
|
|
||||||
// A CancelFunc tells an operation to abandon its work.
|
|
||||||
// A CancelFunc does not wait for the work to stop.
|
|
||||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
|
||||||
type CancelFunc = context.CancelFunc
|
|
300
vendor/golang.org/x/net/context/pre_go17.go
generated
vendored
300
vendor/golang.org/x/net/context/pre_go17.go
generated
vendored
|
@ -1,300 +0,0 @@
|
||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !go1.7
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
|
|
||||||
// struct{}, since vars of this type must have distinct addresses.
|
|
||||||
type emptyCtx int
|
|
||||||
|
|
||||||
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*emptyCtx) Done() <-chan struct{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*emptyCtx) Err() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*emptyCtx) Value(key interface{}) interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *emptyCtx) String() string {
|
|
||||||
switch e {
|
|
||||||
case background:
|
|
||||||
return "context.Background"
|
|
||||||
case todo:
|
|
||||||
return "context.TODO"
|
|
||||||
}
|
|
||||||
return "unknown empty Context"
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
background = new(emptyCtx)
|
|
||||||
todo = new(emptyCtx)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
|
||||||
var Canceled = errors.New("context canceled")
|
|
||||||
|
|
||||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
|
||||||
// deadline passes.
|
|
||||||
var DeadlineExceeded = errors.New("context deadline exceeded")
|
|
||||||
|
|
||||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
|
||||||
// context's Done channel is closed when the returned cancel function is called
|
|
||||||
// or when the parent context's Done channel is closed, whichever happens first.
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete.
|
|
||||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
|
||||||
c := newCancelCtx(parent)
|
|
||||||
propagateCancel(parent, c)
|
|
||||||
return c, func() { c.cancel(true, Canceled) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// newCancelCtx returns an initialized cancelCtx.
|
|
||||||
func newCancelCtx(parent Context) *cancelCtx {
|
|
||||||
return &cancelCtx{
|
|
||||||
Context: parent,
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// propagateCancel arranges for child to be canceled when parent is.
|
|
||||||
func propagateCancel(parent Context, child canceler) {
|
|
||||||
if parent.Done() == nil {
|
|
||||||
return // parent is never canceled
|
|
||||||
}
|
|
||||||
if p, ok := parentCancelCtx(parent); ok {
|
|
||||||
p.mu.Lock()
|
|
||||||
if p.err != nil {
|
|
||||||
// parent has already been canceled
|
|
||||||
child.cancel(false, p.err)
|
|
||||||
} else {
|
|
||||||
if p.children == nil {
|
|
||||||
p.children = make(map[canceler]bool)
|
|
||||||
}
|
|
||||||
p.children[child] = true
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
|
||||||
} else {
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-parent.Done():
|
|
||||||
child.cancel(false, parent.Err())
|
|
||||||
case <-child.Done():
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parentCancelCtx follows a chain of parent references until it finds a
|
|
||||||
// *cancelCtx. This function understands how each of the concrete types in this
|
|
||||||
// package represents its parent.
|
|
||||||
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
|
|
||||||
for {
|
|
||||||
switch c := parent.(type) {
|
|
||||||
case *cancelCtx:
|
|
||||||
return c, true
|
|
||||||
case *timerCtx:
|
|
||||||
return c.cancelCtx, true
|
|
||||||
case *valueCtx:
|
|
||||||
parent = c.Context
|
|
||||||
default:
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeChild removes a context from its parent.
|
|
||||||
func removeChild(parent Context, child canceler) {
|
|
||||||
p, ok := parentCancelCtx(parent)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.mu.Lock()
|
|
||||||
if p.children != nil {
|
|
||||||
delete(p.children, child)
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// A canceler is a context type that can be canceled directly. The
|
|
||||||
// implementations are *cancelCtx and *timerCtx.
|
|
||||||
type canceler interface {
|
|
||||||
cancel(removeFromParent bool, err error)
|
|
||||||
Done() <-chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A cancelCtx can be canceled. When canceled, it also cancels any children
|
|
||||||
// that implement canceler.
|
|
||||||
type cancelCtx struct {
|
|
||||||
Context
|
|
||||||
|
|
||||||
done chan struct{} // closed by the first cancel call.
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
children map[canceler]bool // set to nil by the first cancel call
|
|
||||||
err error // set to non-nil by the first cancel call
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cancelCtx) Done() <-chan struct{} {
|
|
||||||
return c.done
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cancelCtx) Err() error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return c.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cancelCtx) String() string {
|
|
||||||
return fmt.Sprintf("%v.WithCancel", c.Context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// cancel closes c.done, cancels each of c's children, and, if
|
|
||||||
// removeFromParent is true, removes c from its parent's children.
|
|
||||||
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
|
|
||||||
if err == nil {
|
|
||||||
panic("context: internal error: missing cancel error")
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
|
||||||
if c.err != nil {
|
|
||||||
c.mu.Unlock()
|
|
||||||
return // already canceled
|
|
||||||
}
|
|
||||||
c.err = err
|
|
||||||
close(c.done)
|
|
||||||
for child := range c.children {
|
|
||||||
// NOTE: acquiring the child's lock while holding parent's lock.
|
|
||||||
child.cancel(false, err)
|
|
||||||
}
|
|
||||||
c.children = nil
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
if removeFromParent {
|
|
||||||
removeChild(c.Context, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
|
||||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
|
||||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
|
||||||
// context's Done channel is closed when the deadline expires, when the returned
|
|
||||||
// cancel function is called, or when the parent context's Done channel is
|
|
||||||
// closed, whichever happens first.
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete.
|
|
||||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
|
||||||
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
|
|
||||||
// The current deadline is already sooner than the new one.
|
|
||||||
return WithCancel(parent)
|
|
||||||
}
|
|
||||||
c := &timerCtx{
|
|
||||||
cancelCtx: newCancelCtx(parent),
|
|
||||||
deadline: deadline,
|
|
||||||
}
|
|
||||||
propagateCancel(parent, c)
|
|
||||||
d := deadline.Sub(time.Now())
|
|
||||||
if d <= 0 {
|
|
||||||
c.cancel(true, DeadlineExceeded) // deadline has already passed
|
|
||||||
return c, func() { c.cancel(true, Canceled) }
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
if c.err == nil {
|
|
||||||
c.timer = time.AfterFunc(d, func() {
|
|
||||||
c.cancel(true, DeadlineExceeded)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return c, func() { c.cancel(true, Canceled) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
|
|
||||||
// implement Done and Err. It implements cancel by stopping its timer then
|
|
||||||
// delegating to cancelCtx.cancel.
|
|
||||||
type timerCtx struct {
|
|
||||||
*cancelCtx
|
|
||||||
timer *time.Timer // Under cancelCtx.mu.
|
|
||||||
|
|
||||||
deadline time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
|
|
||||||
return c.deadline, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *timerCtx) String() string {
|
|
||||||
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *timerCtx) cancel(removeFromParent bool, err error) {
|
|
||||||
c.cancelCtx.cancel(false, err)
|
|
||||||
if removeFromParent {
|
|
||||||
// Remove this timerCtx from its parent cancelCtx's children.
|
|
||||||
removeChild(c.cancelCtx.Context, c)
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
|
||||||
if c.timer != nil {
|
|
||||||
c.timer.Stop()
|
|
||||||
c.timer = nil
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
|
||||||
//
|
|
||||||
// Canceling this context releases resources associated with it, so code should
|
|
||||||
// call cancel as soon as the operations running in this Context complete:
|
|
||||||
//
|
|
||||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
|
||||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
|
||||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
|
||||||
// return slowOperation(ctx)
|
|
||||||
// }
|
|
||||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
|
||||||
return WithDeadline(parent, time.Now().Add(timeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithValue returns a copy of parent in which the value associated with key is
|
|
||||||
// val.
|
|
||||||
//
|
|
||||||
// Use context Values only for request-scoped data that transits processes and
|
|
||||||
// APIs, not for passing optional parameters to functions.
|
|
||||||
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
|
||||||
return &valueCtx{parent, key, val}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A valueCtx carries a key-value pair. It implements Value for that key and
|
|
||||||
// delegates all other calls to the embedded Context.
|
|
||||||
type valueCtx struct {
|
|
||||||
Context
|
|
||||||
key, val interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *valueCtx) String() string {
|
|
||||||
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *valueCtx) Value(key interface{}) interface{} {
|
|
||||||
if c.key == key {
|
|
||||||
return c.val
|
|
||||||
}
|
|
||||||
return c.Context.Value(key)
|
|
||||||
}
|
|
109
vendor/golang.org/x/net/context/pre_go19.go
generated
vendored
109
vendor/golang.org/x/net/context/pre_go19.go
generated
vendored
|
@ -1,109 +0,0 @@
|
||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !go1.9
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// A Context carries a deadline, a cancelation signal, and other values across
|
|
||||||
// API boundaries.
|
|
||||||
//
|
|
||||||
// Context's methods may be called by multiple goroutines simultaneously.
|
|
||||||
type Context interface {
|
|
||||||
// Deadline returns the time when work done on behalf of this context
|
|
||||||
// should be canceled. Deadline returns ok==false when no deadline is
|
|
||||||
// set. Successive calls to Deadline return the same results.
|
|
||||||
Deadline() (deadline time.Time, ok bool)
|
|
||||||
|
|
||||||
// Done returns a channel that's closed when work done on behalf of this
|
|
||||||
// context should be canceled. Done may return nil if this context can
|
|
||||||
// never be canceled. Successive calls to Done return the same value.
|
|
||||||
//
|
|
||||||
// WithCancel arranges for Done to be closed when cancel is called;
|
|
||||||
// WithDeadline arranges for Done to be closed when the deadline
|
|
||||||
// expires; WithTimeout arranges for Done to be closed when the timeout
|
|
||||||
// elapses.
|
|
||||||
//
|
|
||||||
// Done is provided for use in select statements:
|
|
||||||
//
|
|
||||||
// // Stream generates values with DoSomething and sends them to out
|
|
||||||
// // until DoSomething returns an error or ctx.Done is closed.
|
|
||||||
// func Stream(ctx context.Context, out chan<- Value) error {
|
|
||||||
// for {
|
|
||||||
// v, err := DoSomething(ctx)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// select {
|
|
||||||
// case <-ctx.Done():
|
|
||||||
// return ctx.Err()
|
|
||||||
// case out <- v:
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// See http://blog.golang.org/pipelines for more examples of how to use
|
|
||||||
// a Done channel for cancelation.
|
|
||||||
Done() <-chan struct{}
|
|
||||||
|
|
||||||
// Err returns a non-nil error value after Done is closed. Err returns
|
|
||||||
// Canceled if the context was canceled or DeadlineExceeded if the
|
|
||||||
// context's deadline passed. No other values for Err are defined.
|
|
||||||
// After Done is closed, successive calls to Err return the same value.
|
|
||||||
Err() error
|
|
||||||
|
|
||||||
// Value returns the value associated with this context for key, or nil
|
|
||||||
// if no value is associated with key. Successive calls to Value with
|
|
||||||
// the same key returns the same result.
|
|
||||||
//
|
|
||||||
// Use context values only for request-scoped data that transits
|
|
||||||
// processes and API boundaries, not for passing optional parameters to
|
|
||||||
// functions.
|
|
||||||
//
|
|
||||||
// A key identifies a specific value in a Context. Functions that wish
|
|
||||||
// to store values in Context typically allocate a key in a global
|
|
||||||
// variable then use that key as the argument to context.WithValue and
|
|
||||||
// Context.Value. A key can be any type that supports equality;
|
|
||||||
// packages should define keys as an unexported type to avoid
|
|
||||||
// collisions.
|
|
||||||
//
|
|
||||||
// Packages that define a Context key should provide type-safe accessors
|
|
||||||
// for the values stores using that key:
|
|
||||||
//
|
|
||||||
// // Package user defines a User type that's stored in Contexts.
|
|
||||||
// package user
|
|
||||||
//
|
|
||||||
// import "golang.org/x/net/context"
|
|
||||||
//
|
|
||||||
// // User is the type of value stored in the Contexts.
|
|
||||||
// type User struct {...}
|
|
||||||
//
|
|
||||||
// // key is an unexported type for keys defined in this package.
|
|
||||||
// // This prevents collisions with keys defined in other packages.
|
|
||||||
// type key int
|
|
||||||
//
|
|
||||||
// // userKey is the key for user.User values in Contexts. It is
|
|
||||||
// // unexported; clients use user.NewContext and user.FromContext
|
|
||||||
// // instead of using this key directly.
|
|
||||||
// var userKey key = 0
|
|
||||||
//
|
|
||||||
// // NewContext returns a new Context that carries value u.
|
|
||||||
// func NewContext(ctx context.Context, u *User) context.Context {
|
|
||||||
// return context.WithValue(ctx, userKey, u)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // FromContext returns the User value stored in ctx, if any.
|
|
||||||
// func FromContext(ctx context.Context) (*User, bool) {
|
|
||||||
// u, ok := ctx.Value(userKey).(*User)
|
|
||||||
// return u, ok
|
|
||||||
// }
|
|
||||||
Value(key interface{}) interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A CancelFunc tells an operation to abandon its work.
|
|
||||||
// A CancelFunc does not wait for the work to stop.
|
|
||||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
|
||||||
type CancelFunc func()
|
|
84
vendor/gopkg.in/testfixtures.v2/README.md
generated
vendored
84
vendor/gopkg.in/testfixtures.v2/README.md
generated
vendored
|
@ -1,10 +1,9 @@
|
||||||
# Go Test Fixtures
|
# Go Test Fixtures
|
||||||
|
|
||||||
[![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](https://github.com/go-testfixtures/testfixtures/blob/master/LICENSE)
|
|
||||||
[![Join the chat at https://gitter.im/go-testfixtures/testfixtures](https://badges.gitter.im/go-testfixtures/testfixtures.svg)](https://gitter.im/go-testfixtures/testfixtures?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
||||||
[![GoDoc](https://godoc.org/gopkg.in/testfixtures.v2?status.svg)](https://godoc.org/gopkg.in/testfixtures.v2)
|
[![GoDoc](https://godoc.org/gopkg.in/testfixtures.v2?status.svg)](https://godoc.org/gopkg.in/testfixtures.v2)
|
||||||
[![Build Status](https://travis-ci.org/go-testfixtures/testfixtures.svg?branch=master)](https://travis-ci.org/go-testfixtures/testfixtures)
|
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/go-testfixtures/testfixtures)](https://goreportcard.com/report/github.com/go-testfixtures/testfixtures)
|
[![Go Report Card](https://goreportcard.com/badge/github.com/go-testfixtures/testfixtures)](https://goreportcard.com/report/github.com/go-testfixtures/testfixtures)
|
||||||
|
[![Build Status](https://travis-ci.org/go-testfixtures/testfixtures.svg?branch=master)](https://travis-ci.org/go-testfixtures/testfixtures)
|
||||||
|
[![Build status](https://ci.appveyor.com/api/projects/status/d2h6gq37wxbus1x7?svg=true)](https://ci.appveyor.com/project/andreynering/testfixtures)
|
||||||
|
|
||||||
> ***Warning***: this package will wipe the database data before loading the
|
> ***Warning***: this package will wipe the database data before loading the
|
||||||
fixtures! It is supposed to be used on a test database. Please, double check
|
fixtures! It is supposed to be used on a test database. Please, double check
|
||||||
|
@ -28,25 +27,25 @@ the tests.
|
||||||
First, get it:
|
First, get it:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go get -u gopkg.in/testfixtures.v2
|
go get -u -v gopkg.in/testfixtures.v2
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Create a folder for the fixture files. Each file should contain data for a
|
Create a folder for the fixture files. Each file should contain data for a
|
||||||
single table and have the name `<table-name>.yml`:
|
single table and have the name `<table_name>.yml`:
|
||||||
|
|
||||||
```yml
|
```
|
||||||
myapp
|
myapp/
|
||||||
- myapp.go
|
myapp.go
|
||||||
- myapp_test.go
|
myapp_test.go
|
||||||
- ...
|
...
|
||||||
- fixtures:
|
fixtures/
|
||||||
- posts.yml
|
posts.yml
|
||||||
- comments.yml
|
comments.yml
|
||||||
- tags.yml
|
tags.yml
|
||||||
- posts_tags.yml
|
posts_tags.yml
|
||||||
- ...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
The file would look like this (it can have as many record you want):
|
The file would look like this (it can have as many record you want):
|
||||||
|
@ -270,7 +269,7 @@ Oracle is supported as well. Use:
|
||||||
&testfixtures.Oracle{}
|
&testfixtures.Oracle{}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Generation fixtures for a existent database (experimental)
|
## Generating fixtures for a existing database (experimental)
|
||||||
|
|
||||||
The following code will generate a YAML file for each table of the database in
|
The following code will generate a YAML file for each table of the database in
|
||||||
the given folder. It may be useful to boostrap a test scenario from a sample
|
the given folder. It may be useful to boostrap a test scenario from a sample
|
||||||
|
@ -283,6 +282,23 @@ if err != nil {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := testfixtures.GenerateFixturesForTables(
|
||||||
|
db,
|
||||||
|
[]*TableInfo{
|
||||||
|
&TableInfo{Name: "table_name", Where: "foo = 'bar'"},
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
&testfixtures.PostgreSQL{},
|
||||||
|
"testdata/fixtures",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error generating fixtures: %v", err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
> This was thought to run in small sample databases. It will likely break
|
> This was thought to run in small sample databases. It will likely break
|
||||||
if run in a production/big database.
|
if run in a production/big database.
|
||||||
|
|
||||||
|
@ -314,28 +330,11 @@ go test -tags 'sqlite postgresql mysql'
|
||||||
go test -v -bench=. -tags postgresql
|
go test -v -bench=. -tags postgresql
|
||||||
```
|
```
|
||||||
|
|
||||||
Travis runs tests for PostgreSQL, MySQL and SQLite.
|
Travis runs tests for PostgreSQL, MySQL and SQLite. AppVeyor run for all
|
||||||
|
these and also Microsoft SQL Server.
|
||||||
|
|
||||||
To set the connection string of tests for each database, edit the `.env`
|
To set the connection string of tests for each database, copy the `.sample.env`
|
||||||
file, but do not include the changes a in pull request.
|
file as `.env` and edit it according to your environment.
|
||||||
|
|
||||||
## Changes in v2
|
|
||||||
|
|
||||||
A context was created to allow cache of some SQL statements. See in the
|
|
||||||
documentation above how to use it.
|
|
||||||
|
|
||||||
The helpers were renamed to have a smaller name:
|
|
||||||
|
|
||||||
```go
|
|
||||||
PostgreSQLHelper{} -> PostgreSQL{}
|
|
||||||
MySQLHelper{} -> MySQL{}
|
|
||||||
SQLiteHelper{} -> SQLite{}
|
|
||||||
SQLServerHelper{} -> SQLServer{}
|
|
||||||
OracleHelper{} -> Oracle{}
|
|
||||||
```
|
|
||||||
|
|
||||||
The old functions and helpers are still available for backward compatibility.
|
|
||||||
See the file [deprecated.go](https://github.com/go-testfixtures/testfixtures/blob/master/deprecated.go)
|
|
||||||
|
|
||||||
## Alternatives
|
## Alternatives
|
||||||
|
|
||||||
|
@ -352,17 +351,8 @@ unit test database code without having to connect to a real database
|
||||||
- [dbcleaner][dbcleaner] - Clean database for testing, inspired by
|
- [dbcleaner][dbcleaner] - Clean database for testing, inspired by
|
||||||
database_cleaner for Ruby
|
database_cleaner for Ruby
|
||||||
|
|
||||||
There's also these other implementations of test fixtures for Go:
|
|
||||||
|
|
||||||
- [go-fixtures][gofixtures]: Django style fixtures for Go
|
|
||||||
- [mongofixtures][mongofixtures]: Fixtures for MongoDB
|
|
||||||
- [fixturer][fixturer]: Another fixture loader supporting MySQL
|
|
||||||
|
|
||||||
[railstests]: http://guides.rubyonrails.org/testing.html#the-test-database
|
[railstests]: http://guides.rubyonrails.org/testing.html#the-test-database
|
||||||
[gotxdb]: https://github.com/DATA-DOG/go-txdb
|
[gotxdb]: https://github.com/DATA-DOG/go-txdb
|
||||||
[gosqlmock]: https://github.com/DATA-DOG/go-sqlmock
|
[gosqlmock]: https://github.com/DATA-DOG/go-sqlmock
|
||||||
[gofixtures]: https://github.com/AreaHQ/go-fixtures
|
|
||||||
[mongofixtures]: https://github.com/OwlyCode/mongofixtures
|
|
||||||
[fixturer]: https://github.com/44hapa/fixturer
|
|
||||||
[factorygo]: https://github.com/bluele/factory-go
|
[factorygo]: https://github.com/bluele/factory-go
|
||||||
[dbcleaner]: https://github.com/khaiql/dbcleaner
|
[dbcleaner]: https://github.com/khaiql/dbcleaner
|
||||||
|
|
104
vendor/gopkg.in/testfixtures.v2/Taskfile.yml
generated
vendored
104
vendor/gopkg.in/testfixtures.v2/Taskfile.yml
generated
vendored
|
@ -1,62 +1,64 @@
|
||||||
# github.com/go-task/task
|
# github.com/go-task/task
|
||||||
|
|
||||||
dl-deps:
|
version: '2'
|
||||||
desc: Download cli deps
|
|
||||||
cmds:
|
|
||||||
- go get -u github.com/golang/lint/golint
|
|
||||||
- go get -u github.com/go-task/task/cmd/task
|
|
||||||
|
|
||||||
lint:
|
tasks:
|
||||||
desc: Runs golint
|
dl-deps:
|
||||||
cmds:
|
desc: Download cli deps
|
||||||
- golint .
|
cmds:
|
||||||
|
- go get -u github.com/golang/lint/golint
|
||||||
|
|
||||||
test-free:
|
lint:
|
||||||
desc: Test free databases (PG, MySQL and SQLite)
|
desc: Runs golint
|
||||||
cmds:
|
cmds:
|
||||||
- task: test-pg
|
- golint .
|
||||||
- task: test-mysql
|
|
||||||
- task: test-sqlite
|
|
||||||
|
|
||||||
test-all:
|
test-free:
|
||||||
desc: Test all databases (PG, MySQL, SQLite, SQLServer and Oracle)
|
desc: Test free databases (PG, MySQL and SQLite)
|
||||||
cmds:
|
cmds:
|
||||||
- task: test-pg
|
- task: test-pg
|
||||||
- task: test-mysql
|
- task: test-mysql
|
||||||
- task: test-sqlite
|
- task: test-sqlite
|
||||||
- task: test-sqlserver
|
|
||||||
- task: test-oracle
|
|
||||||
|
|
||||||
test-pg:
|
test-all:
|
||||||
desc: Test PostgreSQL
|
desc: Test all databases (PG, MySQL, SQLite, SQLServer and Oracle)
|
||||||
cmds:
|
cmds:
|
||||||
- task: test-db
|
- task: test-pg
|
||||||
vars: {DATABASE: postgresql}
|
- task: test-mysql
|
||||||
|
- task: test-sqlite
|
||||||
|
- task: test-sqlserver
|
||||||
|
- task: test-oracle
|
||||||
|
|
||||||
test-mysql:
|
test-pg:
|
||||||
desc: Test MySQL
|
desc: Test PostgreSQL
|
||||||
cmds:
|
cmds:
|
||||||
- task: test-db
|
- task: test-db
|
||||||
vars: {DATABASE: mysql}
|
vars: {DATABASE: postgresql}
|
||||||
|
|
||||||
test-sqlite:
|
test-mysql:
|
||||||
desc: Test SQLite
|
desc: Test MySQL
|
||||||
cmds:
|
cmds:
|
||||||
- task: test-db
|
- task: test-db
|
||||||
vars: {DATABASE: sqlite}
|
vars: {DATABASE: mysql}
|
||||||
|
|
||||||
test-sqlserver:
|
test-sqlite:
|
||||||
desc: Test SQLServer
|
desc: Test SQLite
|
||||||
cmds:
|
cmds:
|
||||||
- task: test-db
|
- task: test-db
|
||||||
vars: {DATABASE: sqlserver}
|
vars: {DATABASE: sqlite}
|
||||||
|
|
||||||
test-oracle:
|
test-sqlserver:
|
||||||
desc: Test Oracle
|
desc: Test SQLServer
|
||||||
cmds:
|
cmds:
|
||||||
- task: test-db
|
- task: test-db
|
||||||
vars: {DATABASE: oracle}
|
vars: {DATABASE: sqlserver}
|
||||||
|
|
||||||
test-db:
|
test-oracle:
|
||||||
cmds:
|
desc: Test Oracle
|
||||||
- go test -v -tags {{.DATABASE}}
|
cmds:
|
||||||
|
- task: test-db
|
||||||
|
vars: {DATABASE: oracle}
|
||||||
|
|
||||||
|
test-db:
|
||||||
|
cmds:
|
||||||
|
- go test -v -tags {{.DATABASE}}
|
||||||
|
|
10
vendor/gopkg.in/testfixtures.v2/helper.go
generated
vendored
10
vendor/gopkg.in/testfixtures.v2/helper.go
generated
vendored
|
@ -32,6 +32,16 @@ type queryable interface {
|
||||||
QueryRow(string, ...interface{}) *sql.Row
|
QueryRow(string, ...interface{}) *sql.Row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// batchSplitter is an interface with method which returns byte slice for
|
||||||
|
// splitting SQL batches. This need to split sql statements and run its
|
||||||
|
// separately.
|
||||||
|
//
|
||||||
|
// For Microsoft SQL Server batch splitter is "GO". For details see
|
||||||
|
// https://docs.microsoft.com/en-us/sql/t-sql/language-elements/sql-server-utilities-statements-go
|
||||||
|
type batchSplitter interface {
|
||||||
|
splitter() []byte
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ Helper = &MySQL{}
|
_ Helper = &MySQL{}
|
||||||
_ Helper = &PostgreSQL{}
|
_ Helper = &PostgreSQL{}
|
||||||
|
|
3
vendor/gopkg.in/testfixtures.v2/mysql.go
generated
vendored
3
vendor/gopkg.in/testfixtures.v2/mysql.go
generated
vendored
|
@ -40,7 +40,8 @@ func (h *MySQL) tableNames(q queryable) ([]string, error) {
|
||||||
query := `
|
query := `
|
||||||
SELECT table_name
|
SELECT table_name
|
||||||
FROM information_schema.tables
|
FROM information_schema.tables
|
||||||
WHERE table_schema=?;
|
WHERE table_schema = ?
|
||||||
|
AND table_type = 'BASE TABLE';
|
||||||
`
|
`
|
||||||
dbName, err := h.databaseName(q)
|
dbName, err := h.databaseName(q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
22
vendor/gopkg.in/testfixtures.v2/postgresql.go
generated
vendored
22
vendor/gopkg.in/testfixtures.v2/postgresql.go
generated
vendored
|
@ -62,13 +62,12 @@ func (h *PostgreSQL) tableNames(q queryable) ([]string, error) {
|
||||||
var tables []string
|
var tables []string
|
||||||
|
|
||||||
sql := `
|
sql := `
|
||||||
SELECT pg_namespace.nspname || '.' || pg_class.relname
|
SELECT pg_namespace.nspname || '.' || pg_class.relname
|
||||||
FROM pg_class
|
FROM pg_class
|
||||||
INNER JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
|
INNER JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
|
||||||
WHERE pg_class.relkind = 'r'
|
WHERE pg_class.relkind = 'r'
|
||||||
AND
|
AND pg_namespace.nspname NOT IN ('pg_catalog', 'information_schema')
|
||||||
pg_namespace.nspname NOT IN ('pg_catalog', 'information_schema')
|
AND pg_namespace.nspname NOT LIKE 'pg_toast%';
|
||||||
AND pg_namespace.nspname NOT LIKE 'pg_toast%';
|
|
||||||
`
|
`
|
||||||
rows, err := q.Query(sql)
|
rows, err := q.Query(sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -121,11 +120,10 @@ func (*PostgreSQL) getNonDeferrableConstraints(q queryable) ([]pgConstraint, err
|
||||||
var constraints []pgConstraint
|
var constraints []pgConstraint
|
||||||
|
|
||||||
sql := `
|
sql := `
|
||||||
SELECT table_schema || '.' || table_name,
|
SELECT table_schema || '.' || table_name, constraint_name
|
||||||
constraint_name
|
|
||||||
FROM information_schema.table_constraints
|
FROM information_schema.table_constraints
|
||||||
WHERE constraint_type = 'FOREIGN KEY'
|
WHERE constraint_type = 'FOREIGN KEY'
|
||||||
AND is_deferrable = 'NO'
|
AND is_deferrable = 'NO'
|
||||||
`
|
`
|
||||||
rows, err := q.Query(sql)
|
rows, err := q.Query(sql)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -269,8 +267,8 @@ func (h *PostgreSQL) afterLoad(q queryable) error {
|
||||||
|
|
||||||
func (h *PostgreSQL) getChecksum(q queryable, tableName string) (string, error) {
|
func (h *PostgreSQL) getChecksum(q queryable, tableName string) (string, error) {
|
||||||
sqlStr := fmt.Sprintf(`
|
sqlStr := fmt.Sprintf(`
|
||||||
SELECT md5(CAST((array_agg(t.*)) AS TEXT))
|
SELECT md5(CAST((array_agg(t.*)) AS TEXT))
|
||||||
FROM %s AS t
|
FROM %s AS t
|
||||||
`,
|
`,
|
||||||
h.quoteKeyword(tableName),
|
h.quoteKeyword(tableName),
|
||||||
)
|
)
|
||||||
|
|
2
vendor/gopkg.in/testfixtures.v2/sqlite.go
generated
vendored
2
vendor/gopkg.in/testfixtures.v2/sqlite.go
generated
vendored
|
@ -29,7 +29,7 @@ func (*SQLite) tableNames(q queryable) ([]string, error) {
|
||||||
query := `
|
query := `
|
||||||
SELECT name
|
SELECT name
|
||||||
FROM sqlite_master
|
FROM sqlite_master
|
||||||
WHERE type='table';
|
WHERE type = 'table';
|
||||||
`
|
`
|
||||||
rows, err := q.Query(query)
|
rows, err := q.Query(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
25
vendor/gopkg.in/testfixtures.v2/sqlserver.go
generated
vendored
25
vendor/gopkg.in/testfixtures.v2/sqlserver.go
generated
vendored
|
@ -3,6 +3,7 @@ package testfixtures
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SQLServer is the helper for SQL Server for this package.
|
// SQLServer is the helper for SQL Server for this package.
|
||||||
|
@ -28,8 +29,12 @@ func (*SQLServer) paramType() int {
|
||||||
return paramTypeQuestion
|
return paramTypeQuestion
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*SQLServer) quoteKeyword(str string) string {
|
func (*SQLServer) quoteKeyword(s string) string {
|
||||||
return fmt.Sprintf("[%s]", str)
|
parts := strings.Split(s, ".")
|
||||||
|
for i, p := range parts {
|
||||||
|
parts[i] = fmt.Sprintf(`[%s]`, p)
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*SQLServer) databaseName(q queryable) (string, error) {
|
func (*SQLServer) databaseName(q queryable) (string, error) {
|
||||||
|
@ -39,7 +44,7 @@ func (*SQLServer) databaseName(q queryable) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*SQLServer) tableNames(q queryable) ([]string, error) {
|
func (*SQLServer) tableNames(q queryable) ([]string, error) {
|
||||||
rows, err := q.Query("SELECT table_name FROM information_schema.tables")
|
rows, err := q.Query("SELECT table_schema + '.' + table_name FROM information_schema.tables")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -59,14 +64,14 @@ func (*SQLServer) tableNames(q queryable) ([]string, error) {
|
||||||
return tables, nil
|
return tables, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*SQLServer) tableHasIdentityColumn(q queryable, tableName string) bool {
|
func (h *SQLServer) tableHasIdentityColumn(q queryable, tableName string) bool {
|
||||||
sql := `
|
sql := `
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM SYS.IDENTITY_COLUMNS
|
FROM SYS.IDENTITY_COLUMNS
|
||||||
WHERE OBJECT_NAME(OBJECT_ID) = ?
|
WHERE OBJECT_ID = OBJECT_ID(?)
|
||||||
`
|
`
|
||||||
var count int
|
var count int
|
||||||
q.QueryRow(sql, tableName).Scan(&count)
|
q.QueryRow(sql, h.quoteKeyword(tableName)).Scan(&count)
|
||||||
return count > 0
|
return count > 0
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -120,3 +125,11 @@ func (h *SQLServer) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction)
|
||||||
|
|
||||||
return tx.Commit()
|
return tx.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// splitter is a batchSplitter interface implementation. We need it for
|
||||||
|
// SQL Server because commands like a `CREATE SCHEMA...` and a `CREATE TABLE...`
|
||||||
|
// could not be executed in the same batch.
|
||||||
|
// See https://docs.microsoft.com/en-us/previous-versions/sql/sql-server-2008-r2/ms175502(v=sql.105)#rules-for-using-batches
|
||||||
|
func (*SQLServer) splitter() []byte {
|
||||||
|
return []byte("GO\n")
|
||||||
|
}
|
||||||
|
|
235
vendor/vendor.json
vendored
235
vendor/vendor.json
vendored
|
@ -1,235 +0,0 @@
|
||||||
{
|
|
||||||
"comment": "",
|
|
||||||
"ignore": "test",
|
|
||||||
"package": [
|
|
||||||
{
|
|
||||||
"path": "appengine/cloudsql",
|
|
||||||
"revision": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "mrz/kicZiUaHxkyfvC/DyQcr8Do=",
|
|
||||||
"path": "github.com/davecgh/go-spew/spew",
|
|
||||||
"revision": "ecdeabc65495df2dec95d7c4a4c3e021903035e5",
|
|
||||||
"revisionTime": "2017-10-02T20:02:53Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "GXOurDGgsLmJs0wounpdWZZRSGw=",
|
|
||||||
"path": "github.com/dgrijalva/jwt-go",
|
|
||||||
"revision": "a539ee1a749a2b895533f979515ac7e6e0f5b650",
|
|
||||||
"revisionTime": "2017-06-08T00:51:49Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "2UmMbNHc8FBr98mJFN1k8ISOIHk=",
|
|
||||||
"path": "github.com/garyburd/redigo/internal",
|
|
||||||
"revision": "b389d5392e340092642eec4e36754e0280ad1f8e",
|
|
||||||
"revisionTime": "2017-11-27T19:16:32Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "YdFePCOhDZ/jM44yDKwRaOKJM0g=",
|
|
||||||
"path": "github.com/garyburd/redigo/redis",
|
|
||||||
"revision": "b389d5392e340092642eec4e36754e0280ad1f8e",
|
|
||||||
"revisionTime": "2017-11-27T19:16:32Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "87LEfpY9cOk9CP7pWyIbmQ/6enU=",
|
|
||||||
"path": "github.com/go-ini/ini",
|
|
||||||
"revision": "20b96f641a5ea98f2f8619ff4f3e061cff4833bd",
|
|
||||||
"revisionTime": "2017-08-13T05:15:16Z",
|
|
||||||
"version": "v1.28.2",
|
|
||||||
"versionExact": "v1.28.2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "github.com/go-ini/iniv1.28.2",
|
|
||||||
"revision": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "fSlTr0rwLji0DfsUOKYLkGUkiWo=",
|
|
||||||
"path": "github.com/go-sql-driver/mysql",
|
|
||||||
"revision": "ee359f95877bdef36cbb602711e49b6f0becfca9",
|
|
||||||
"revisionTime": "2017-10-07T15:01:58Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "9SXbj96wb1PgppBZzxMIN0axbFQ=",
|
|
||||||
"path": "github.com/go-xorm/builder",
|
|
||||||
"revision": "c8871c857d2555fbfbd8524f895be5386d3d8836",
|
|
||||||
"revisionTime": "2017-05-19T03:21:30Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "HMavuxvDhKOwmbbFnYt9hfT6jE0=",
|
|
||||||
"path": "github.com/go-xorm/core",
|
|
||||||
"revision": "da1adaf7a28ca792961721a34e6e04945200c890",
|
|
||||||
"revisionTime": "2017-09-09T08:56:53Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "+KmPfckyKvrUZPIHBYHylg/7V8o=",
|
|
||||||
"path": "github.com/go-xorm/xorm",
|
|
||||||
"revision": "29d4a0330a00b9be468b70e3fb0f74109348c358",
|
|
||||||
"revisionTime": "2017-09-30T01:26:13Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "hrEOZ15JNp6e+iSiJSfDsSmpIk8=",
|
|
||||||
"path": "github.com/go-xorm/xorm-redis-cache",
|
|
||||||
"revision": "e1df07666d87ee04d84730241ccbf30cfe0eb9ca",
|
|
||||||
"revisionTime": "2014-08-20T09:53:34Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "g/V4qrXjUGG9B+e3hB+4NAYJ5Gs=",
|
|
||||||
"path": "github.com/gorilla/context",
|
|
||||||
"revision": "08b5f424b9271eedf6f9f0ce86cb9396ed337a42",
|
|
||||||
"revisionTime": "2016-08-17T18:46:32Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "ucTBCc7dDRKLGPsYfAzu/Gq63qA=",
|
|
||||||
"path": "github.com/gorilla/securecookie",
|
|
||||||
"revision": "e59506cc896acb7f7bf732d4fdf5e25f7ccd8983",
|
|
||||||
"revisionTime": "2017-02-24T19:38:04Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "dxwmtflRNX8Q+gtB5EchmMwFnpQ=",
|
|
||||||
"path": "github.com/gorilla/sessions",
|
|
||||||
"revision": "a3acf13e802c358d65f249324d14ed24aac11370",
|
|
||||||
"revisionTime": "2017-10-08T21:47:40Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "5Dl+trUFDD93Sn8T51jBdUcqqJ4=",
|
|
||||||
"path": "github.com/labstack/echo",
|
|
||||||
"revision": "1049c9613cd371b7ea8f219404c9a821734781ed",
|
|
||||||
"revisionTime": "2017-04-26T17:09:29Z",
|
|
||||||
"version": "v3.1",
|
|
||||||
"versionExact": "v3.1.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "pbCEi6MgZ+4VHg1r1eDLUJL4CoA=",
|
|
||||||
"path": "github.com/labstack/echo-contrib/session",
|
|
||||||
"revision": "525b19f5bad83f2c917421eedf23ef1749fb7298",
|
|
||||||
"revisionTime": "2017-09-25T15:53:04Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "9eyG7H5z0h7Bz3u+BqnxXXebLvY=",
|
|
||||||
"path": "github.com/labstack/echo/middleware",
|
|
||||||
"revision": "1049c9613cd371b7ea8f219404c9a821734781ed",
|
|
||||||
"revisionTime": "2017-04-26T17:09:29Z",
|
|
||||||
"version": "v3.1",
|
|
||||||
"versionExact": "v3.1.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "NvfQq6Y6kh9cMw+y1m00ivEd5b4=",
|
|
||||||
"path": "github.com/labstack/gommon/bytes",
|
|
||||||
"revision": "57409ada9da0f2afad6664c49502f8c50fbd8476",
|
|
||||||
"revisionTime": "2017-09-25T05:28:17Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "R6DzcBLEP0BONPpsyr+11N7xh5w=",
|
|
||||||
"path": "github.com/labstack/gommon/color",
|
|
||||||
"revision": "57409ada9da0f2afad6664c49502f8c50fbd8476",
|
|
||||||
"revisionTime": "2017-09-25T05:28:17Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "uHA6zbtxzJce4r+6uk8zu11qkuk=",
|
|
||||||
"path": "github.com/labstack/gommon/log",
|
|
||||||
"revision": "57409ada9da0f2afad6664c49502f8c50fbd8476",
|
|
||||||
"revisionTime": "2017-09-25T05:28:17Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "lW02E/TGpV59u9q49TsK0Cmva0c=",
|
|
||||||
"path": "github.com/labstack/gommon/random",
|
|
||||||
"revision": "57409ada9da0f2afad6664c49502f8c50fbd8476",
|
|
||||||
"revisionTime": "2017-09-25T05:28:17Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "cTDA66oZUy18cIzJsU1diKq+9CE=",
|
|
||||||
"path": "github.com/mattn/go-colorable",
|
|
||||||
"revision": "ad5389df28cdac544c99bd7b9161a0b5b6ca9d1b",
|
|
||||||
"revisionTime": "2017-08-16T03:18:13Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "y/A5iuvwjytQE2CqVuphQRXR2nI=",
|
|
||||||
"path": "github.com/mattn/go-isatty",
|
|
||||||
"revision": "a5cdd64afdee435007ee3e9f6ed4684af949d568",
|
|
||||||
"revisionTime": "2017-09-25T05:49:04Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "zKDmGyNRpAeeTGXwzKEvy+UY/QQ=",
|
|
||||||
"path": "github.com/mattn/go-sqlite3",
|
|
||||||
"revision": "d5ffb5c0cca8778699a929b236766f4a7af674e8",
|
|
||||||
"revisionTime": "2017-11-22T00:24:37Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "LuFv4/jlrmFNnDb/5SCSEPAM9vU=",
|
|
||||||
"path": "github.com/pmezard/go-difflib/difflib",
|
|
||||||
"revision": "792786c7400a136282c1664665ae0a8db921c6c2",
|
|
||||||
"revisionTime": "2016-01-10T10:55:54Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "uyVU5fRfEK17QZuODRjeZX8zDqg=",
|
|
||||||
"path": "github.com/stretchr/testify/assert",
|
|
||||||
"revision": "87b1dfb5b2fa649f52695dd9eae19abe404a4308",
|
|
||||||
"revisionTime": "2017-12-31T12:27:32Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "LTOa3BADhwvT0wFCknPueQALm8I=",
|
|
||||||
"path": "github.com/valyala/bytebufferpool",
|
|
||||||
"revision": "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7",
|
|
||||||
"revisionTime": "2016-08-17T18:16:52Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "gtG8jeB0fcTmKZf+6Vbcoa6nfec=",
|
|
||||||
"path": "github.com/valyala/fasttemplate",
|
|
||||||
"revision": "dcecefd839c4193db0d35b88ec65b4c12d360ab0",
|
|
||||||
"revisionTime": "2017-02-24T21:24:29Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "9TH05dyfdsA0EJvxly42ms2ykeM=",
|
|
||||||
"path": "golang.org/x/crypto/acme",
|
|
||||||
"revision": "9419663f5a44be8b34ca85f08abc5fe1be11f8a3",
|
|
||||||
"revisionTime": "2017-09-30T17:45:11Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "4z75fxISOgKOmbS/X+qcTCXdyCs=",
|
|
||||||
"path": "golang.org/x/crypto/acme/autocert",
|
|
||||||
"revision": "9419663f5a44be8b34ca85f08abc5fe1be11f8a3",
|
|
||||||
"revisionTime": "2017-09-30T17:45:11Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "UWjVYmoHlIfHzVIskELHiJQtMOI=",
|
|
||||||
"path": "golang.org/x/crypto/bcrypt",
|
|
||||||
"revision": "9419663f5a44be8b34ca85f08abc5fe1be11f8a3",
|
|
||||||
"revisionTime": "2017-09-30T17:45:11Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "oVPHWesOmZ02vLq2fglGvf+AMgk=",
|
|
||||||
"path": "golang.org/x/crypto/blowfish",
|
|
||||||
"revision": "9419663f5a44be8b34ca85f08abc5fe1be11f8a3",
|
|
||||||
"revisionTime": "2017-09-30T17:45:11Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=",
|
|
||||||
"path": "golang.org/x/net/context",
|
|
||||||
"revision": "a8b9294777976932365dabb6640cf1468d95c70f",
|
|
||||||
"revisionTime": "2017-11-29T19:21:16Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "tY+5thYxjKDUQyQXYcBqogmMS5U=",
|
|
||||||
"path": "golang.org/x/sys/unix",
|
|
||||||
"revision": "314a259e304ff91bd6985da2a7149bbf91237993",
|
|
||||||
"revisionTime": "2017-07-19T03:44:26Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "gopkg.in/ini.v1.28.2",
|
|
||||||
"revision": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "ob4ZeUbmT7SgWIStHaqq0+vjLdI=",
|
|
||||||
"path": "gopkg.in/testfixtures.v2",
|
|
||||||
"revision": "f79bf941e2785516a66bcf4de9a21b0b827ed716",
|
|
||||||
"revisionTime": "2017-10-30T10:15:45Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"checksumSHA1": "qOmvuDm+F+2nQQecUZBVkZrTn6Y=",
|
|
||||||
"path": "gopkg.in/yaml.v2",
|
|
||||||
"revision": "d670f9405373e636a5a2765eea47fac0c9bc91a4",
|
|
||||||
"revisionTime": "2018-01-09T11:43:31Z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rootPath": "git.mowie.cc/konrad/Library"
|
|
||||||
}
|
|
Loading…
Reference in a new issue