FoundationDB backend TLS support and housekeeping (#5800)

* Fix typo in documentation

* Update fdb-go-install.sh for new release tags

* Exclude FoundationDB bindings from vendoring, delete vendored copy

FoundationDB bindings are tightly coupled to the server version and
client library version used in a specific deployment. Bindings need
to be installed using the fdb-go-install.sh script, as documented in
the foundationdb backend documentation.

* Add TLS support to FoundationDB backend

TLS support appeared in FoundationDB 5.2.4, raising the minimum API version
for TLS-aware FoundationDB code to 520.

* Update documentation for FoundationDB TLS support
This commit is contained in:
Julien Blache 2019-01-08 09:01:44 -08:00 committed by Jim Kalafut
parent 46cbfb0e4b
commit 91d432fc85
26 changed files with 118 additions and 4942 deletions

View file

@ -6,15 +6,28 @@ this procedure will fail with a descriptive error message at runtime.
## Installing the Go bindings ## Installing the Go bindings
You will need to install the FoundationDB Go bindings to build the FoundationDB ### Picking a version
backend. Make sure you have the FoundationDB client library installed on your
system, along with Mono (core is enough), then install the Go bindings using The version of the Go bindings and the FoundationDB client library used to
the `fdb-go-install.sh` script: build them must match.
This version will determine the minimum API version that can be used, hence
it should be no higher than the version of FoundationDB used in your cluster,
and must also satisfy the requirements of the backend code.
The minimum required API version for the FoundationDB backend is 520.
### Installation
Make sure you have Mono installed (core is enough), then install the
Go bindings using the `fdb-go-install.sh` script:
``` ```
$ physical/foundationdb/fdb-go-install.sh $ physical/foundationdb/fdb-go-install.sh install --fdbver x.y.z
``` ```
By default, if `--fdbver x.y.z` is not specified, version 5.2.4 will be used.
## Building Vault ## Building Vault
To build Vault the FoundationDB backend, add FDB_ENABLED=1 when invoking To build Vault the FoundationDB backend, add FDB_ENABLED=1 when invoking

View file

@ -12,7 +12,7 @@
# #
DESTDIR="${DESTDIR:-}" DESTDIR="${DESTDIR:-}"
FDBVER="${FDBVER:-5.1.0}" FDBVER="${FDBVER:-5.2.4}"
REMOTE="${REMOTE:-github.com}" REMOTE="${REMOTE:-github.com}"
FDBREPO="${FDBREPO:-apple/foundationdb}" FDBREPO="${FDBREPO:-apple/foundationdb}"
@ -210,18 +210,18 @@ else
if [[ -d "${fdbdir}" ]] ; then if [[ -d "${fdbdir}" ]] ; then
echo "Directory ${fdbdir} already exists ; checking out appropriate tag" echo "Directory ${fdbdir} already exists ; checking out appropriate tag"
cmd1=( 'git' '-C' "${fdbdir}" 'fetch' 'origin' ) cmd1=( 'git' '-C' "${fdbdir}" 'fetch' 'origin' )
cmd2=( 'git' '-C' "${fdbdir}" 'checkout' "release-${FDBVER}" ) cmd2=( 'git' '-C' "${fdbdir}" 'checkout' "${FDBVER}" )
if ! echo "${cmd1[*]}" || ! "${cmd1[@]}" ; then if ! echo "${cmd1[*]}" || ! "${cmd1[@]}" ; then
let status="${status} + 1" let status="${status} + 1"
echo "Could not pull latest changes from origin" echo "Could not pull latest changes from origin"
elif ! echo "${cmd2[*]}" || ! "${cmd2[@]}" ; then elif ! echo "${cmd2[*]}" || ! "${cmd2[@]}" ; then
let status="${status} + 1" let status="${status} + 1"
echo "Could not checkout tag release-${FDBVER}." echo "Could not checkout tag ${FDBVER}."
fi fi
else else
echo "Downloading foundation repository into ${destdir}:" echo "Downloading foundation repository into ${destdir}:"
cmd=( 'git' '-C' "${destdir}" 'clone' '--branch' "release-${FDBVER}" "https://${REMOTE}/${FDBREPO}.git" ) cmd=( 'git' '-C' "${destdir}" 'clone' '--branch' "${FDBVER}" "https://${REMOTE}/${FDBREPO}.git" )
echo "${cmd[*]}" echo "${cmd[*]}"
if ! "${cmd[@]}" ; then if ! "${cmd[@]}" ; then
@ -238,7 +238,7 @@ else
: :
elif [[ "${status}" -eq 0 ]] ; then elif [[ "${status}" -eq 0 ]] ; then
echo "Building generated files." echo "Building generated files."
# FoundationDB starting with 5.2 can figure that out on its own # FoundationDB starting with 6.0 can figure that out on its own
if [ -e '/usr/bin/mcs' ]; then if [ -e '/usr/bin/mcs' ]; then
MCS_BIN=/usr/bin/mcs MCS_BIN=/usr/bin/mcs
else else

View file

@ -27,6 +27,9 @@ import (
) )
const ( const (
// The minimum acceptable API version
minAPIVersion = 520
// The namespace under our top directory containing keys only for list operations // The namespace under our top directory containing keys only for list operations
metaKeysNamespace = "_meta-keys" metaKeysNamespace = "_meta-keys"
@ -137,6 +140,22 @@ func NewFDBBackend(conf map[string]string, logger log.Logger) (physical.Backend,
dirPath := strings.Split(strings.Trim(path, "/"), "/") dirPath := strings.Split(strings.Trim(path, "/"), "/")
// TLS support
tlsCertFile, hasCertFile := conf["tls_cert_file"]
tlsKeyFile, hasKeyFile := conf["tls_key_file"]
tlsCAFile, hasCAFile := conf["tls_ca_file"]
tlsEnabled := hasCertFile && hasKeyFile && hasCAFile
if (hasCertFile || hasKeyFile || hasCAFile) && !tlsEnabled {
return nil, fmt.Errorf("FoundationDB TLS requires all 3 of tls_cert_file, tls_key_file, and tls_ca_file")
}
tlsVerifyPeers, ok := conf["tls_verify_peers"]
if !ok && tlsEnabled {
return nil, fmt.Errorf("Required option tls_verify_peers not set in configuration")
}
// FoundationDB API version // FoundationDB API version
fdbApiVersionStr, ok := conf["api_version"] fdbApiVersionStr, ok := conf["api_version"]
if !ok { if !ok {
@ -147,6 +166,12 @@ func NewFDBBackend(conf map[string]string, logger log.Logger) (physical.Backend,
if err != nil { if err != nil {
return nil, errwrap.Wrapf("failed to parse fdb_api_version parameter: {{err}}", err) return nil, errwrap.Wrapf("failed to parse fdb_api_version parameter: {{err}}", err)
} }
// Check requested FDB API version against minimum required API version
if fdbApiVersionInt < minAPIVersion {
return nil, fmt.Errorf("Configured FoundationDB API version lower than minimum required version: %d < %d", fdbApiVersionInt, minAPIVersion)
}
logger.Debug("FoundationDB API version set", "fdb_api_version", fdbApiVersionInt) logger.Debug("FoundationDB API version set", "fdb_api_version", fdbApiVersionInt)
// FoundationDB cluster file // FoundationDB cluster file
@ -174,6 +199,38 @@ func NewFDBBackend(conf map[string]string, logger log.Logger) (physical.Backend,
return nil, errwrap.Wrapf("failed to set FDB API version: {{err}}", err) return nil, errwrap.Wrapf("failed to set FDB API version: {{err}}", err)
} }
if tlsEnabled {
opts := fdb.Options()
tlsPassword, ok := conf["tls_password"]
if ok {
err := opts.SetTLSPassword(tlsPassword)
if err != nil {
return nil, errwrap.Wrapf("failed to set TLS password: {{err}}", err)
}
}
err := opts.SetTLSCaPath(tlsCAFile)
if err != nil {
return nil, errwrap.Wrapf("failed to set TLS CA bundle path: {{err}}", err)
}
err = opts.SetTLSCertPath(tlsCertFile)
if err != nil {
return nil, errwrap.Wrapf("failed to set TLS certificate path: {{err}}", err)
}
err = opts.SetTLSKeyPath(tlsKeyFile)
if err != nil {
return nil, errwrap.Wrapf("failed to set TLS key path: {{err}}", err)
}
err = opts.SetTLSVerifyPeers([]byte(tlsVerifyPeers))
if err != nil {
return nil, errwrap.Wrapf("failed to set TLS peer verification criteria: {{err}}", err)
}
}
db, err := fdb.Open(fdbClusterFile, []byte("DB")) db, err := fdb.Open(fdbClusterFile, []byte("DB"))
if err != nil { if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("failed to open database with cluster file '%s': {{err}}", fdbClusterFile), err) return nil, errwrap.Wrapf(fmt.Sprintf("failed to open database with cluster file '%s': {{err}}", fdbClusterFile), err)

View file

@ -23,7 +23,7 @@ import (
) )
func connectToFoundationDB(clusterFile string) (*fdb.Database, error) { func connectToFoundationDB(clusterFile string) (*fdb.Database, error) {
if err := fdb.APIVersion(510); err != nil { if err := fdb.APIVersion(520); err != nil {
return nil, errwrap.Wrapf("failed to set FDB API version: {{err}}", err) return nil, errwrap.Wrapf("failed to set FDB API version: {{err}}", err)
} }
@ -112,7 +112,7 @@ func TestFoundationDBBackend(t *testing.T) {
logger := logging.NewVaultLogger(log.Debug) logger := logging.NewVaultLogger(log.Debug)
config := map[string]string{ config := map[string]string{
"path": topDir, "path": topDir,
"api_version": "510", "api_version": "520",
"cluster_file": clusterFile, "cluster_file": clusterFile,
} }

View file

@ -1,207 +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.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also 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 third-party archives.
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.
-------------------------------------------------------------------------------
SOFTWARE DISTRIBUTED WITH FOUNDATIONDB:
The FoundationDB software includes a number of subcomponents with separate
copyright notices and license terms - please see the file ACKNOWLEDGEMENTS.
-------------------------------------------------------------------------------

View file

@ -1,74 +0,0 @@
/*
* cluster.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go API
package fdb
/*
#define FDB_API_VERSION 610
#include <foundationdb/fdb_c.h>
*/
import "C"
import (
"runtime"
)
// Cluster is a handle to a FoundationDB cluster. Cluster is a lightweight
// object that may be efficiently copied, and is safe for concurrent use by
// multiple goroutines.
//
// It is generally preferable to use Open or OpenDefault to obtain a database
// handle directly.
type Cluster struct {
*cluster
}
type cluster struct {
ptr *C.FDBCluster
}
func (c *cluster) destroy() {
C.fdb_cluster_destroy(c.ptr)
}
// OpenDatabase returns a database handle from the FoundationDB cluster. It is
// generally preferable to use Open or OpenDefault to obtain a database handle
// directly.
//
// In the current release, the database name must be []byte("DB").
func (c Cluster) OpenDatabase(dbName []byte) (Database, error) {
f := C.fdb_cluster_create_database(c.ptr, byteSliceToPtr(dbName), C.int(len(dbName)))
fdb_future_block_until_ready(f)
var outd *C.FDBDatabase
if err := C.fdb_future_get_database(f, &outd); err != 0 {
return Database{}, Error{int(err)}
}
C.fdb_future_destroy(f)
d := &database{outd}
runtime.SetFinalizer(d, (*database).destroy)
return Database{d}, nil
}

View file

@ -1,238 +0,0 @@
/*
* database.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go API
package fdb
/*
#define FDB_API_VERSION 610
#include <foundationdb/fdb_c.h>
*/
import "C"
import (
"runtime"
)
// Database is a handle to a FoundationDB database. Database is a lightweight
// object that may be efficiently copied, and is safe for concurrent use by
// multiple goroutines.
//
// Although Database provides convenience methods for reading and writing data,
// modifications to a database are usually made via transactions, which are
// usually created and committed automatically by the (Database).Transact
// method.
type Database struct {
*database
}
type database struct {
ptr *C.FDBDatabase
}
// DatabaseOptions is a handle with which to set options that affect a Database
// object. A DatabaseOptions instance should be obtained with the
// (Database).Options method.
type DatabaseOptions struct {
d *database
}
func (opt DatabaseOptions) setOpt(code int, param []byte) error {
return setOpt(func(p *C.uint8_t, pl C.int) C.fdb_error_t {
return C.fdb_database_set_option(opt.d.ptr, C.FDBDatabaseOption(code), p, pl)
}, param)
}
func (d *database) destroy() {
C.fdb_database_destroy(d.ptr)
}
// CreateTransaction returns a new FoundationDB transaction. It is generally
// preferable to use the (Database).Transact method, which handles
// automatically creating and committing a transaction with appropriate retry
// behavior.
func (d Database) CreateTransaction() (Transaction, error) {
var outt *C.FDBTransaction
if err := C.fdb_database_create_transaction(d.ptr, &outt); err != 0 {
return Transaction{}, Error{int(err)}
}
t := &transaction{outt, d}
runtime.SetFinalizer(t, (*transaction).destroy)
return Transaction{t}, nil
}
func retryable(wrapped func() (interface{}, error), onError func(Error) FutureNil) (ret interface{}, e error) {
for {
ret, e = wrapped()
/* No error means success! */
if e == nil {
return
}
ep, ok := e.(Error)
if ok {
e = onError(ep).Get()
}
/* If OnError returns an error, then it's not
/* retryable; otherwise take another pass at things */
if e != nil {
return
}
}
}
// Transact runs a caller-provided function inside a retry loop, providing it
// with a newly created Transaction. After the function returns, the Transaction
// will be committed automatically. Any error during execution of the function
// (by panic or return) or the commit will cause the function and commit to be
// retried or, if fatal, return the error to the caller.
//
// When working with Future objects in a transactional function, you may either
// explicitly check and return error values using Get, or call MustGet. Transact
// will recover a panicked Error and either retry the transaction or return the
// error.
//
// Do not return Future objects from the function provided to Transact. The
// Transaction created by Transact may be finalized at any point after Transact
// returns, resulting in the cancellation of any outstanding
// reads. Additionally, any errors returned or panicked by the Future will no
// longer be able to trigger a retry of the caller-provided function.
//
// See the Transactor interface for an example of using Transact with
// Transaction and Database objects.
func (d Database) Transact(f func(Transaction) (interface{}, error)) (interface{}, error) {
tr, e := d.CreateTransaction()
/* Any error here is non-retryable */
if e != nil {
return nil, e
}
wrapped := func() (ret interface{}, e error) {
defer panicToError(&e)
ret, e = f(tr)
if e == nil {
e = tr.Commit().Get()
}
return
}
return retryable(wrapped, tr.OnError)
}
// ReadTransact runs a caller-provided function inside a retry loop, providing
// it with a newly created Transaction (as a ReadTransaction). Any error during
// execution of the function (by panic or return) will cause the function to be
// retried or, if fatal, return the error to the caller.
//
// When working with Future objects in a read-only transactional function, you
// may either explicitly check and return error values using Get, or call
// MustGet. ReadTransact will recover a panicked Error and either retry the
// transaction or return the error.
//
// Do not return Future objects from the function provided to ReadTransact. The
// Transaction created by ReadTransact may be finalized at any point after
// ReadTransact returns, resulting in the cancellation of any outstanding
// reads. Additionally, any errors returned or panicked by the Future will no
// longer be able to trigger a retry of the caller-provided function.
//
// See the ReadTransactor interface for an example of using ReadTransact with
// Transaction, Snapshot and Database objects.
func (d Database) ReadTransact(f func(ReadTransaction) (interface{}, error)) (interface{}, error) {
tr, e := d.CreateTransaction()
/* Any error here is non-retryable */
if e != nil {
return nil, e
}
wrapped := func() (ret interface{}, e error) {
defer panicToError(&e)
ret, e = f(tr)
if e == nil {
e = tr.Commit().Get()
}
return
}
return retryable(wrapped, tr.OnError)
}
// Options returns a DatabaseOptions instance suitable for setting options
// specific to this database.
func (d Database) Options() DatabaseOptions {
return DatabaseOptions{d.database}
}
// LocalityGetBoundaryKeys returns a slice of keys that fall within the provided
// range. Each key is located at the start of a contiguous range stored on a
// single server.
//
// If limit is non-zero, only the first limit keys will be returned. In large
// databases, the number of boundary keys may be large. In these cases, a
// non-zero limit should be used, along with multiple calls to
// LocalityGetBoundaryKeys.
//
// If readVersion is non-zero, the boundary keys as of readVersion will be
// returned.
func (d Database) LocalityGetBoundaryKeys(er ExactRange, limit int, readVersion int64) ([]Key, error) {
tr, e := d.CreateTransaction()
if e != nil {
return nil, e
}
if readVersion != 0 {
tr.SetReadVersion(readVersion)
}
tr.Options().SetReadSystemKeys()
tr.Options().SetLockAware()
bk, ek := er.FDBRangeKeys()
ffer := KeyRange{append(Key("\xFF/keyServers/"), bk.FDBKey()...), append(Key("\xFF/keyServers/"), ek.FDBKey()...)}
kvs, e := tr.Snapshot().GetRange(ffer, RangeOptions{Limit: limit}).GetSliceWithError()
if e != nil {
return nil, e
}
size := len(kvs)
if limit != 0 && limit < size {
size = limit
}
boundaries := make([]Key, size)
for i := 0; i < size; i++ {
boundaries[i] = kvs[i].Key[13:]
}
return boundaries, nil
}

View file

@ -1,166 +0,0 @@
/*
* allocator.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go Directory Layer
package directory
import (
"bytes"
"encoding/binary"
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/subspace"
"math/rand"
"sync"
)
var oneBytes = []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
var allocatorMutex = sync.Mutex{}
type highContentionAllocator struct {
counters, recent subspace.Subspace
}
func newHCA(s subspace.Subspace) highContentionAllocator {
var hca highContentionAllocator
hca.counters = s.Sub(0)
hca.recent = s.Sub(1)
return hca
}
func windowSize(start int64) int64 {
// Larger window sizes are better for high contention, smaller sizes for
// keeping the keys small. But if there are many allocations, the keys
// can't be too small. So start small and scale up. We don't want this to
// ever get *too* big because we have to store about window_size/2 recent
// items.
if start < 255 {
return 64
}
if start < 65535 {
return 1024
}
return 8192
}
func (hca highContentionAllocator) allocate(tr fdb.Transaction, s subspace.Subspace) (subspace.Subspace, error) {
for {
rr := tr.Snapshot().GetRange(hca.counters, fdb.RangeOptions{Limit: 1, Reverse: true})
kvs, e := rr.GetSliceWithError()
if e != nil {
return nil, e
}
var start int64
var window int64
if len(kvs) == 1 {
t, e := hca.counters.Unpack(kvs[0].Key)
if e != nil {
return nil, e
}
start = t[0].(int64)
}
windowAdvanced := false
for {
allocatorMutex.Lock()
if windowAdvanced {
tr.ClearRange(fdb.KeyRange{hca.counters, hca.counters.Sub(start)})
tr.Options().SetNextWriteNoWriteConflictRange()
tr.ClearRange(fdb.KeyRange{hca.recent, hca.recent.Sub(start)})
}
// Increment the allocation count for the current window
tr.Add(hca.counters.Sub(start), oneBytes)
countFuture := tr.Snapshot().Get(hca.counters.Sub(start))
allocatorMutex.Unlock()
countStr, e := countFuture.Get()
if e != nil {
return nil, e
}
var count int64
if countStr == nil {
count = 0
} else {
e = binary.Read(bytes.NewBuffer(countStr), binary.LittleEndian, &count)
if e != nil {
return nil, e
}
}
window = windowSize(start)
if count*2 < window {
break
}
start += window
windowAdvanced = true
}
for {
// As of the snapshot being read from, the window is less than half
// full, so this should be expected to take 2 tries. Under high
// contention (and when the window advances), there is an additional
// subsequent risk of conflict for this transaction.
candidate := rand.Int63n(window) + start
key := hca.recent.Sub(candidate)
allocatorMutex.Lock()
latestCounter := tr.Snapshot().GetRange(hca.counters, fdb.RangeOptions{Limit: 1, Reverse: true})
candidateValue := tr.Get(key)
tr.Options().SetNextWriteNoWriteConflictRange()
tr.Set(key, []byte(""))
allocatorMutex.Unlock()
kvs, e = latestCounter.GetSliceWithError()
if e != nil {
return nil, e
}
if len(kvs) > 0 {
t, e := hca.counters.Unpack(kvs[0].Key)
if e != nil {
return nil, e
}
currentStart := t[0].(int64)
if currentStart > start {
break
}
}
v, e := candidateValue.Get()
if e != nil {
return nil, e
}
if v == nil {
tr.AddWriteConflictKey(key)
return s.Sub(candidate), nil
}
}
}
}

View file

@ -1,241 +0,0 @@
/*
* directory.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go Directory Layer
// Package directory provides a tool for managing related subspaces. Directories
// are a recommended approach for administering applications. Each application
// should create or open at least one directory to manage its subspaces.
//
// For general guidance on directory usage, see the Directories section of the
// Developer Guide
// (https://apple.github.io/foundationdb/developer-guide.html#directories).
//
// Directories are identified by hierarchical paths analogous to the paths in a
// Unix-like file system. A path is represented as a slice of strings. Each
// directory has an associated subspace used to store its content. The directory
// layer maps each path to a short prefix used for the corresponding
// subspace. In effect, directories provide a level of indirection for access to
// subspaces.
//
// Directory operations are transactional. A byte slice layer option is used as
// a metadata identifier when opening a directory.
package directory
import (
"errors"
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/subspace"
)
const (
_SUBDIRS int = 0
// []int32{1,0,0} by any other name
_MAJORVERSION int32 = 1
_MINORVERSION int32 = 0
_MICROVERSION int32 = 0
)
// Directory represents a subspace of keys in a FoundationDB database,
// identified by a hierarchical path.
type Directory interface {
// CreateOrOpen opens the directory specified by path (relative to this
// Directory), and returns the directory and its contents as a
// DirectorySubspace. If the directory does not exist, it is created
// (creating parent directories if necessary).
//
// If the byte slice layer is specified and the directory is new, it is
// recorded as the layer; if layer is specified and the directory already
// exists, it is compared against the layer specified when the directory was
// created, and an error is returned if they differ.
CreateOrOpen(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error)
// Open opens the directory specified by path (relative to this Directory),
// and returns the directory and its contents as a DirectorySubspace (or an
// error if the directory does not exist).
//
// If the byte slice layer is specified, it is compared against the layer
// specified when the directory was created, and an error is returned if
// they differ.
Open(rt fdb.ReadTransactor, path []string, layer []byte) (DirectorySubspace, error)
// Create creates a directory specified by path (relative to this
// Directory), and returns the directory and its contents as a
// DirectorySubspace (or an error if the directory already exists).
//
// If the byte slice layer is specified, it is recorded as the layer and
// will be checked when opening the directory in the future.
Create(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error)
// CreatePrefix behaves like Create, but uses a manually specified byte
// slice prefix to physically store the contents of this directory, rather
// than an automatically allocated prefix.
//
// If this Directory was created in a root directory that does not allow
// manual prefixes, CreatePrefix will return an error. The default root
// directory does not allow manual prefixes.
CreatePrefix(t fdb.Transactor, path []string, layer []byte, prefix []byte) (DirectorySubspace, error)
// Move moves the directory at oldPath to newPath (both relative to this
// Directory), and returns the directory (at its new location) and its
// contents as a DirectorySubspace. Move will return an error if a directory
// does not exist at oldPath, a directory already exists at newPath, or the
// parent directory of newPath does not exist.
//
// There is no effect on the physical prefix of the given directory or on
// clients that already have the directory open.
Move(t fdb.Transactor, oldPath []string, newPath []string) (DirectorySubspace, error)
// MoveTo moves this directory to newAbsolutePath (relative to the root
// directory of this Directory), and returns the directory (at its new
// location) and its contents as a DirectorySubspace. MoveTo will return an
// error if a directory already exists at newAbsolutePath or the parent
// directory of newAbsolutePath does not exist.
//
// There is no effect on the physical prefix of the given directory or on
// clients that already have the directory open.
MoveTo(t fdb.Transactor, newAbsolutePath []string) (DirectorySubspace, error)
// Remove removes the directory at path (relative to this Directory), its
// content, and all subdirectories. Remove returns true if a directory
// existed at path and was removed, and false if no directory exists at
// path.
//
// Note that clients that have already opened this directory might still
// insert data into its contents after removal.
Remove(t fdb.Transactor, path []string) (bool, error)
// Exists returns true if the directory at path (relative to this Directory)
// exists, and false otherwise.
Exists(rt fdb.ReadTransactor, path []string) (bool, error)
// List returns the names of the immediate subdirectories of the directory
// at path (relative to this Directory) as a slice of strings. Each string
// is the name of the last component of a subdirectory's path.
List(rt fdb.ReadTransactor, path []string) ([]string, error)
// GetLayer returns the layer specified when this Directory was created.
GetLayer() []byte
// GetPath returns the path with which this Directory was opened.
GetPath() []string
}
func stringsEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
func moveTo(t fdb.Transactor, dl directoryLayer, path, newAbsolutePath []string) (DirectorySubspace, error) {
partition_len := len(dl.path)
if !stringsEqual(newAbsolutePath[:partition_len], dl.path) {
return nil, errors.New("cannot move between partitions")
}
return dl.Move(t, path[partition_len:], newAbsolutePath[partition_len:])
}
var root = NewDirectoryLayer(subspace.FromBytes([]byte{0xFE}), subspace.AllKeys(), false)
// CreateOrOpen opens the directory specified by path (resolved relative to the
// default root directory), and returns the directory and its contents as a
// DirectorySubspace. If the directory does not exist, it is created (creating
// parent directories if necessary).
//
// If the byte slice layer is specified and the directory is new, it is recorded
// as the layer; if layer is specified and the directory already exists, it is
// compared against the layer specified when the directory was created, and an
// error is returned if they differ.
func CreateOrOpen(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error) {
return root.CreateOrOpen(t, path, layer)
}
// Open opens the directory specified by path (resolved relative to the default
// root directory), and returns the directory and its contents as a
// DirectorySubspace (or an error if the directory does not exist).
//
// If the byte slice layer is specified, it is compared against the layer
// specified when the directory was created, and an error is returned if they
// differ.
func Open(rt fdb.ReadTransactor, path []string, layer []byte) (DirectorySubspace, error) {
return root.Open(rt, path, layer)
}
// Create creates a directory specified by path (resolved relative to the
// default root directory), and returns the directory and its contents as a
// DirectorySubspace (or an error if the directory already exists).
//
// If the byte slice layer is specified, it is recorded as the layer and will be
// checked when opening the directory in the future.
func Create(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error) {
return root.Create(t, path, layer)
}
// Move moves the directory at oldPath to newPath (both resolved relative to the
// default root directory), and returns the directory (at its new location) and
// its contents as a DirectorySubspace. Move will return an error if a directory
// does not exist at oldPath, a directory already exists at newPath, or the
// parent directory of newPath does not exit.
//
// There is no effect on the physical prefix of the given directory or on
// clients that already have the directory open.
func Move(t fdb.Transactor, oldPath []string, newPath []string) (DirectorySubspace, error) {
return root.Move(t, oldPath, newPath)
}
// Exists returns true if the directory at path (relative to the default root
// directory) exists, and false otherwise.
func Exists(rt fdb.ReadTransactor, path []string) (bool, error) {
return root.Exists(rt, path)
}
// List returns the names of the immediate subdirectories of the default root
// directory as a slice of strings. Each string is the name of the last
// component of a subdirectory's path.
func List(rt fdb.ReadTransactor, path []string) ([]string, error) {
return root.List(rt, path)
}
// Root returns the default root directory. Any attempt to move or remove the
// root directory will return an error.
//
// The default root directory stores directory layer metadata in keys beginning
// with 0xFE, and allocates newly created directories in (unused) prefixes
// starting with 0x00 through 0xFD. This is appropriate for otherwise empty
// databases, but may conflict with other formal or informal partitionings of
// keyspace. If you already have other content in your database, you may wish to
// use NewDirectoryLayer to construct a non-standard root directory to control
// where metadata and keys are stored.
//
// As an alternative to Root, you may use the package-level functions
// CreateOrOpen, Open, Create, CreatePrefix, Move, Exists and List to operate
// directly on the default DirectoryLayer.
func Root() Directory {
return root
}

View file

@ -1,613 +0,0 @@
/*
* directoryLayer.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go Directory Layer
package directory
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/subspace"
"github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
)
type directoryLayer struct {
nodeSS subspace.Subspace
contentSS subspace.Subspace
allowManualPrefixes bool
allocator highContentionAllocator
rootNode subspace.Subspace
path []string
}
// NewDirectoryLayer returns a new root directory (as a Directory). The
// subspaces nodeSS and contentSS control where the directory metadata and
// contents are stored. The default root directory has a nodeSS of
// subspace.FromBytes([]byte{0xFE}) and a contentSS of
// subspace.AllKeys(). Specifying more restrictive values for nodeSS and
// contentSS will allow using the directory layer alongside other content in a
// database.
//
// If allowManualPrefixes is false, all calls to CreatePrefix on the returned
// Directory (or any subdirectories) will fail, and all directory prefixes will
// be automatically allocated. The default root directory does not allow manual
// prefixes.
func NewDirectoryLayer(nodeSS, contentSS subspace.Subspace, allowManualPrefixes bool) Directory {
var dl directoryLayer
dl.nodeSS = subspace.FromBytes(nodeSS.Bytes())
dl.contentSS = subspace.FromBytes(contentSS.Bytes())
dl.allowManualPrefixes = allowManualPrefixes
dl.rootNode = dl.nodeSS.Sub(dl.nodeSS.Bytes())
dl.allocator = newHCA(dl.rootNode.Sub([]byte("hca")))
return dl
}
func (dl directoryLayer) createOrOpen(rtr fdb.ReadTransaction, tr *fdb.Transaction, path []string, layer []byte, prefix []byte, allowCreate, allowOpen bool) (DirectorySubspace, error) {
if e := dl.checkVersion(rtr, nil); e != nil {
return nil, e
}
if prefix != nil && !dl.allowManualPrefixes {
if len(dl.path) == 0 {
return nil, errors.New("cannot specify a prefix unless manual prefixes are enabled")
}
return nil, errors.New("cannot specify a prefix in a partition")
}
if len(path) == 0 {
return nil, errors.New("the root directory cannot be opened")
}
existingNode := dl.find(rtr, path).prefetchMetadata(rtr)
if existingNode.exists() {
if existingNode.isInPartition(nil, false) {
subpath := existingNode.getPartitionSubpath()
enc, e := existingNode.getContents(dl, nil)
if e != nil {
return nil, e
}
return enc.(directoryPartition).createOrOpen(rtr, tr, subpath, layer, prefix, allowCreate, allowOpen)
}
if !allowOpen {
return nil, errors.New("the directory already exists")
}
if layer != nil {
if l, e := existingNode._layer.Get(); e != nil || bytes.Compare(l, layer) != 0 {
return nil, errors.New("the directory was created with an incompatible layer")
}
}
return existingNode.getContents(dl, nil)
}
if !allowCreate {
return nil, errors.New("the directory does not exist")
}
if e := dl.checkVersion(rtr, tr); e != nil {
return nil, e
}
if prefix == nil {
newss, e := dl.allocator.allocate(*tr, dl.contentSS)
if e != nil {
return nil, fmt.Errorf("unable to allocate new directory prefix (%s)", e.Error())
}
if !isRangeEmpty(rtr, newss) {
return nil, fmt.Errorf("the database has keys stored at the prefix chosen by the automatic prefix allocator: %v", prefix)
}
prefix = newss.Bytes()
pf, e := dl.isPrefixFree(rtr.Snapshot(), prefix)
if e != nil {
return nil, e
}
if !pf {
return nil, errors.New("the directory layer has manually allocated prefixes that conflict with the automatic prefix allocator")
}
} else {
pf, e := dl.isPrefixFree(rtr, prefix)
if e != nil {
return nil, e
}
if !pf {
return nil, errors.New("the given prefix is already in use")
}
}
var parentNode subspace.Subspace
if len(path) > 1 {
pd, e := dl.createOrOpen(rtr, tr, path[:len(path)-1], nil, nil, true, true)
if e != nil {
return nil, e
}
parentNode = dl.nodeWithPrefix(pd.Bytes())
} else {
parentNode = dl.rootNode
}
if parentNode == nil {
return nil, errors.New("the parent directory does not exist")
}
node := dl.nodeWithPrefix(prefix)
tr.Set(parentNode.Sub(_SUBDIRS, path[len(path)-1]), prefix)
if layer == nil {
layer = []byte{}
}
tr.Set(node.Sub([]byte("layer")), layer)
return dl.contentsOfNode(node, path, layer)
}
func (dl directoryLayer) CreateOrOpen(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error) {
r, e := t.Transact(func(tr fdb.Transaction) (interface{}, error) {
return dl.createOrOpen(tr, &tr, path, layer, nil, true, true)
})
if e != nil {
return nil, e
}
return r.(DirectorySubspace), nil
}
func (dl directoryLayer) Create(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error) {
r, e := t.Transact(func(tr fdb.Transaction) (interface{}, error) {
return dl.createOrOpen(tr, &tr, path, layer, nil, true, false)
})
if e != nil {
return nil, e
}
return r.(DirectorySubspace), nil
}
func (dl directoryLayer) CreatePrefix(t fdb.Transactor, path []string, layer []byte, prefix []byte) (DirectorySubspace, error) {
if prefix == nil {
prefix = []byte{}
}
r, e := t.Transact(func(tr fdb.Transaction) (interface{}, error) {
return dl.createOrOpen(tr, &tr, path, layer, prefix, true, false)
})
if e != nil {
return nil, e
}
return r.(DirectorySubspace), nil
}
func (dl directoryLayer) Open(rt fdb.ReadTransactor, path []string, layer []byte) (DirectorySubspace, error) {
r, e := rt.ReadTransact(func(rtr fdb.ReadTransaction) (interface{}, error) {
return dl.createOrOpen(rtr, nil, path, layer, nil, false, true)
})
if e != nil {
return nil, e
}
return r.(DirectorySubspace), nil
}
func (dl directoryLayer) Exists(rt fdb.ReadTransactor, path []string) (bool, error) {
r, e := rt.ReadTransact(func(rtr fdb.ReadTransaction) (interface{}, error) {
if e := dl.checkVersion(rtr, nil); e != nil {
return false, e
}
node := dl.find(rtr, path).prefetchMetadata(rtr)
if !node.exists() {
return false, nil
}
if node.isInPartition(nil, false) {
nc, e := node.getContents(dl, nil)
if e != nil {
return false, e
}
return nc.Exists(rtr, node.getPartitionSubpath())
}
return true, nil
})
if e != nil {
return false, e
}
return r.(bool), nil
}
func (dl directoryLayer) List(rt fdb.ReadTransactor, path []string) ([]string, error) {
r, e := rt.ReadTransact(func(rtr fdb.ReadTransaction) (interface{}, error) {
if e := dl.checkVersion(rtr, nil); e != nil {
return nil, e
}
node := dl.find(rtr, path).prefetchMetadata(rtr)
if !node.exists() {
return nil, errors.New("the directory does not exist")
}
if node.isInPartition(nil, true) {
nc, e := node.getContents(dl, nil)
if e != nil {
return nil, e
}
return nc.List(rtr, node.getPartitionSubpath())
}
return dl.subdirNames(rtr, node.subspace)
})
if e != nil {
return nil, e
}
return r.([]string), nil
}
func (dl directoryLayer) MoveTo(t fdb.Transactor, newAbsolutePath []string) (DirectorySubspace, error) {
return nil, errors.New("the root directory cannot be moved")
}
func (dl directoryLayer) Move(t fdb.Transactor, oldPath []string, newPath []string) (DirectorySubspace, error) {
r, e := t.Transact(func(tr fdb.Transaction) (interface{}, error) {
if e := dl.checkVersion(tr, &tr); e != nil {
return nil, e
}
sliceEnd := len(oldPath)
if sliceEnd > len(newPath) {
sliceEnd = len(newPath)
}
if stringsEqual(oldPath, newPath[:sliceEnd]) {
return nil, errors.New("the destination directory cannot be a subdirectory of the source directory")
}
oldNode := dl.find(tr, oldPath).prefetchMetadata(tr)
newNode := dl.find(tr, newPath).prefetchMetadata(tr)
if !oldNode.exists() {
return nil, errors.New("the source directory does not exist")
}
if oldNode.isInPartition(nil, false) || newNode.isInPartition(nil, false) {
if !oldNode.isInPartition(nil, false) || !newNode.isInPartition(nil, false) || !stringsEqual(oldNode.path, newNode.path) {
return nil, errors.New("cannot move between partitions")
}
nnc, e := newNode.getContents(dl, nil)
if e != nil {
return nil, e
}
return nnc.Move(tr, oldNode.getPartitionSubpath(), newNode.getPartitionSubpath())
}
if newNode.exists() {
return nil, errors.New("the destination directory already exists. Remove it first")
}
parentNode := dl.find(tr, newPath[:len(newPath)-1])
if !parentNode.exists() {
return nil, errors.New("the parent of the destination directory does not exist. Create it first")
}
p, e := dl.nodeSS.Unpack(oldNode.subspace)
if e != nil {
return nil, e
}
tr.Set(parentNode.subspace.Sub(_SUBDIRS, newPath[len(newPath)-1]), p[0].([]byte))
dl.removeFromParent(tr, oldPath)
l, e := oldNode._layer.Get()
if e != nil {
return nil, e
}
return dl.contentsOfNode(oldNode.subspace, newPath, l)
})
if e != nil {
return nil, e
}
return r.(DirectorySubspace), nil
}
func (dl directoryLayer) Remove(t fdb.Transactor, path []string) (bool, error) {
r, e := t.Transact(func(tr fdb.Transaction) (interface{}, error) {
if e := dl.checkVersion(tr, &tr); e != nil {
return false, e
}
if len(path) == 0 {
return false, errors.New("the root directory cannot be removed")
}
node := dl.find(tr, path).prefetchMetadata(tr)
if !node.exists() {
return false, nil
}
if node.isInPartition(nil, false) {
nc, e := node.getContents(dl, nil)
if e != nil {
return false, e
}
return nc.(directoryPartition).Remove(tr, node.getPartitionSubpath())
}
if e := dl.removeRecursive(tr, node.subspace); e != nil {
return false, e
}
dl.removeFromParent(tr, path)
return true, nil
})
if e != nil {
return false, e
}
return r.(bool), nil
}
func (dl directoryLayer) removeRecursive(tr fdb.Transaction, node subspace.Subspace) error {
nodes := dl.subdirNodes(tr, node)
for i := range nodes {
if e := dl.removeRecursive(tr, nodes[i]); e != nil {
return e
}
}
p, e := dl.nodeSS.Unpack(node)
if e != nil {
return e
}
kr, e := fdb.PrefixRange(p[0].([]byte))
if e != nil {
return e
}
tr.ClearRange(kr)
tr.ClearRange(node)
return nil
}
func (dl directoryLayer) removeFromParent(tr fdb.Transaction, path []string) {
parent := dl.find(tr, path[:len(path)-1])
tr.Clear(parent.subspace.Sub(_SUBDIRS, path[len(path)-1]))
}
func (dl directoryLayer) GetLayer() []byte {
return []byte{}
}
func (dl directoryLayer) GetPath() []string {
return dl.path
}
func (dl directoryLayer) subdirNames(rtr fdb.ReadTransaction, node subspace.Subspace) ([]string, error) {
sd := node.Sub(_SUBDIRS)
rr := rtr.GetRange(sd, fdb.RangeOptions{})
ri := rr.Iterator()
var ret []string
for ri.Advance() {
kv, e := ri.Get()
if e != nil {
return nil, e
}
p, e := sd.Unpack(kv.Key)
if e != nil {
return nil, e
}
ret = append(ret, p[0].(string))
}
return ret, nil
}
func (dl directoryLayer) subdirNodes(tr fdb.Transaction, node subspace.Subspace) []subspace.Subspace {
sd := node.Sub(_SUBDIRS)
rr := tr.GetRange(sd, fdb.RangeOptions{})
ri := rr.Iterator()
var ret []subspace.Subspace
for ri.Advance() {
kv := ri.MustGet()
ret = append(ret, dl.nodeWithPrefix(kv.Value))
}
return ret
}
func (dl directoryLayer) nodeContainingKey(rtr fdb.ReadTransaction, key []byte) (subspace.Subspace, error) {
if bytes.HasPrefix(key, dl.nodeSS.Bytes()) {
return dl.rootNode, nil
}
bk, _ := dl.nodeSS.FDBRangeKeys()
kr := fdb.KeyRange{bk, fdb.Key(append(dl.nodeSS.Pack(tuple.Tuple{key}), 0x00))}
kvs, e := rtr.GetRange(kr, fdb.RangeOptions{Reverse: true, Limit: 1}).GetSliceWithError()
if e != nil {
return nil, e
}
if len(kvs) == 1 {
pp, e := dl.nodeSS.Unpack(kvs[0].Key)
if e != nil {
return nil, e
}
prevPrefix := pp[0].([]byte)
if bytes.HasPrefix(key, prevPrefix) {
return dl.nodeWithPrefix(prevPrefix), nil
}
}
return nil, nil
}
func (dl directoryLayer) isPrefixFree(rtr fdb.ReadTransaction, prefix []byte) (bool, error) {
if len(prefix) == 0 {
return false, nil
}
nck, e := dl.nodeContainingKey(rtr, prefix)
if e != nil {
return false, e
}
if nck != nil {
return false, nil
}
kr, e := fdb.PrefixRange(prefix)
if e != nil {
return false, e
}
bk, ek := kr.FDBRangeKeys()
if !isRangeEmpty(rtr, fdb.KeyRange{dl.nodeSS.Pack(tuple.Tuple{bk}), dl.nodeSS.Pack(tuple.Tuple{ek})}) {
return false, nil
}
return true, nil
}
func (dl directoryLayer) checkVersion(rtr fdb.ReadTransaction, tr *fdb.Transaction) error {
version, err := rtr.Get(dl.rootNode.Sub([]byte("version"))).Get()
if err != nil {
return err
}
if version == nil {
if tr != nil {
dl.initializeDirectory(*tr)
}
return nil
}
var versions []int32
buf := bytes.NewBuffer(version)
for i := 0; i < 3; i++ {
var v int32
err := binary.Read(buf, binary.LittleEndian, &v)
if err != nil {
return errors.New("cannot determine directory version present in database")
}
versions = append(versions, v)
}
if versions[0] > _MAJORVERSION {
return fmt.Errorf("cannot load directory with version %d.%d.%d using directory layer %d.%d.%d", versions[0], versions[1], versions[2], _MAJORVERSION, _MINORVERSION, _MICROVERSION)
}
if versions[1] > _MINORVERSION && tr != nil /* aka write access allowed */ {
return fmt.Errorf("directory with version %d.%d.%d is read-only when opened using directory layer %d.%d.%d", versions[0], versions[1], versions[2], _MAJORVERSION, _MINORVERSION, _MICROVERSION)
}
return nil
}
func (dl directoryLayer) initializeDirectory(tr fdb.Transaction) {
buf := new(bytes.Buffer)
// bytes.Buffer claims that Write will always return a nil error, which
// means the error return here can only be an encoding issue. So long as we
// don't set our own versions to something completely invalid, we should be
// OK to ignore error returns.
binary.Write(buf, binary.LittleEndian, _MAJORVERSION)
binary.Write(buf, binary.LittleEndian, _MINORVERSION)
binary.Write(buf, binary.LittleEndian, _MICROVERSION)
tr.Set(dl.rootNode.Sub([]byte("version")), buf.Bytes())
}
func (dl directoryLayer) contentsOfNode(node subspace.Subspace, path []string, layer []byte) (DirectorySubspace, error) {
p, e := dl.nodeSS.Unpack(node)
if e != nil {
return nil, e
}
prefix := p[0]
newPath := make([]string, len(dl.path)+len(path))
copy(newPath, dl.path)
copy(newPath[len(dl.path):], path)
pb := prefix.([]byte)
ss := subspace.FromBytes(pb)
if bytes.Compare(layer, []byte("partition")) == 0 {
nssb := make([]byte, len(pb)+1)
copy(nssb, pb)
nssb[len(pb)] = 0xFE
ndl := NewDirectoryLayer(subspace.FromBytes(nssb), ss, false).(directoryLayer)
ndl.path = newPath
return directoryPartition{ndl, dl}, nil
}
return directorySubspace{ss, dl, newPath, layer}, nil
}
func (dl directoryLayer) nodeWithPrefix(prefix []byte) subspace.Subspace {
if prefix == nil {
return nil
}
return dl.nodeSS.Sub(prefix)
}
func (dl directoryLayer) find(rtr fdb.ReadTransaction, path []string) *node {
n := &node{dl.rootNode, []string{}, path, nil}
for i := range path {
n = &node{dl.nodeWithPrefix(rtr.Get(n.subspace.Sub(_SUBDIRS, path[i])).MustGet()), path[:i+1], path, nil}
if !n.exists() || bytes.Compare(n.layer(rtr).MustGet(), []byte("partition")) == 0 {
return n
}
}
return n
}
func (dl directoryLayer) partitionSubpath(lpath, rpath []string) []string {
r := make([]string, len(lpath)-len(dl.path)+len(rpath))
copy(r, lpath[len(dl.path):])
copy(r[len(lpath)-len(dl.path):], rpath)
return r
}
func isRangeEmpty(rtr fdb.ReadTransaction, r fdb.Range) bool {
kvs := rtr.GetRange(r, fdb.RangeOptions{Limit: 1}).GetSliceOrPanic()
return len(kvs) == 0
}

View file

@ -1,91 +0,0 @@
/*
* directoryPartition.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go Directory Layer
package directory
import (
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/subspace"
"github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
)
type directoryPartition struct {
directoryLayer
parentDirectoryLayer directoryLayer
}
func (dp directoryPartition) Sub(el ...tuple.TupleElement) subspace.Subspace {
panic("cannot open subspace in the root of a directory partition")
}
func (dp directoryPartition) Bytes() []byte {
panic("cannot get key for the root of a directory partition")
}
func (dp directoryPartition) Pack(t tuple.Tuple) fdb.Key {
panic("cannot pack keys using the root of a directory partition")
}
func (dp directoryPartition) Unpack(k fdb.KeyConvertible) (tuple.Tuple, error) {
panic("cannot unpack keys using the root of a directory partition")
}
func (dp directoryPartition) Contains(k fdb.KeyConvertible) bool {
panic("cannot check whether a key belongs to the root of a directory partition")
}
func (dp directoryPartition) FDBKey() fdb.Key {
panic("cannot use the root of a directory partition as a key")
}
func (dp directoryPartition) FDBRangeKeys() (fdb.KeyConvertible, fdb.KeyConvertible) {
panic("cannot get range for the root of a directory partition")
}
func (dp directoryPartition) FDBRangeKeySelectors() (fdb.Selectable, fdb.Selectable) {
panic("cannot get range for the root of a directory partition")
}
func (dp directoryPartition) GetLayer() []byte {
return []byte("partition")
}
func (dp directoryPartition) getLayerForPath(path []string) directoryLayer {
if len(path) == 0 {
return dp.parentDirectoryLayer
}
return dp.directoryLayer
}
func (dp directoryPartition) MoveTo(t fdb.Transactor, newAbsolutePath []string) (DirectorySubspace, error) {
return moveTo(t, dp.parentDirectoryLayer, dp.path, newAbsolutePath)
}
func (dp directoryPartition) Remove(t fdb.Transactor, path []string) (bool, error) {
dl := dp.getLayerForPath(path)
return dl.Remove(t, dl.partitionSubpath(dp.path, path))
}
func (dp directoryPartition) Exists(rt fdb.ReadTransactor, path []string) (bool, error) {
dl := dp.getLayerForPath(path)
return dl.Exists(rt, dl.partitionSubpath(dp.path, path))
}

View file

@ -1,88 +0,0 @@
/*
* directorySubspace.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go Directory Layer
package directory
import (
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/subspace"
)
// DirectorySubspace represents a Directory that may also be used as a Subspace
// to store key/value pairs. Subdirectories of a root directory (as returned by
// Root or NewDirectoryLayer) are DirectorySubspaces, and provide all methods of
// the Directory and subspace.Subspace interfaces.
type DirectorySubspace interface {
subspace.Subspace
Directory
}
type directorySubspace struct {
subspace.Subspace
dl directoryLayer
path []string
layer []byte
}
func (d directorySubspace) CreateOrOpen(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error) {
return d.dl.CreateOrOpen(t, d.dl.partitionSubpath(d.path, path), layer)
}
func (d directorySubspace) Create(t fdb.Transactor, path []string, layer []byte) (DirectorySubspace, error) {
return d.dl.Create(t, d.dl.partitionSubpath(d.path, path), layer)
}
func (d directorySubspace) CreatePrefix(t fdb.Transactor, path []string, layer []byte, prefix []byte) (DirectorySubspace, error) {
return d.dl.CreatePrefix(t, d.dl.partitionSubpath(d.path, path), layer, prefix)
}
func (d directorySubspace) Open(rt fdb.ReadTransactor, path []string, layer []byte) (DirectorySubspace, error) {
return d.dl.Open(rt, d.dl.partitionSubpath(d.path, path), layer)
}
func (d directorySubspace) MoveTo(t fdb.Transactor, newAbsolutePath []string) (DirectorySubspace, error) {
return moveTo(t, d.dl, d.path, newAbsolutePath)
}
func (d directorySubspace) Move(t fdb.Transactor, oldPath []string, newPath []string) (DirectorySubspace, error) {
return d.dl.Move(t, d.dl.partitionSubpath(d.path, oldPath), d.dl.partitionSubpath(d.path, newPath))
}
func (d directorySubspace) Remove(t fdb.Transactor, path []string) (bool, error) {
return d.dl.Remove(t, d.dl.partitionSubpath(d.path, path))
}
func (d directorySubspace) Exists(rt fdb.ReadTransactor, path []string) (bool, error) {
return d.dl.Exists(rt, d.dl.partitionSubpath(d.path, path))
}
func (d directorySubspace) List(rt fdb.ReadTransactor, path []string) (subdirs []string, e error) {
return d.dl.List(rt, d.dl.partitionSubpath(d.path, path))
}
func (d directorySubspace) GetLayer() []byte {
return d.layer
}
func (d directorySubspace) GetPath() []string {
return d.path
}

View file

@ -1,75 +0,0 @@
/*
* node.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go Directory Layer
package directory
import (
"bytes"
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/subspace"
)
type node struct {
subspace subspace.Subspace
path []string
targetPath []string
_layer fdb.FutureByteSlice
}
func (n *node) exists() bool {
if n.subspace == nil {
return false
}
return true
}
func (n *node) prefetchMetadata(rtr fdb.ReadTransaction) *node {
if n.exists() {
n.layer(rtr)
}
return n
}
func (n *node) layer(rtr fdb.ReadTransaction) fdb.FutureByteSlice {
if n._layer == nil {
fv := rtr.Get(n.subspace.Sub([]byte("layer")))
n._layer = fv
}
return n._layer
}
func (n *node) isInPartition(tr *fdb.Transaction, includeEmptySubpath bool) bool {
return n.exists() && bytes.Compare(n._layer.MustGet(), []byte("partition")) == 0 && (includeEmptySubpath || len(n.targetPath) > len(n.path))
}
func (n *node) getPartitionSubpath() []string {
return n.targetPath[len(n.path):]
}
func (n *node) getContents(dl directoryLayer, tr *fdb.Transaction) (DirectorySubspace, error) {
l, err := n._layer.Get()
if err != nil {
return nil, err
}
return dl.contentsOfNode(n.subspace, n.path, l)
}

View file

@ -1,209 +0,0 @@
/*
* doc.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go API
/*
Package fdb provides an interface to FoundationDB databases (version 2.0 or higher).
To build and run programs using this package, you must have an installed copy of
the FoundationDB client libraries (version 2.0.0 or later), available for Linux,
Windows and OS X at https://www.foundationdb.org/download/.
This documentation specifically applies to the FoundationDB Go binding. For more
extensive guidance to programming with FoundationDB, as well as API
documentation for the other FoundationDB interfaces, please see
https://apple.github.io/foundationdb/index.html.
Basic Usage
A basic interaction with the FoundationDB API is demonstrated below:
package main
import (
"github.com/apple/foundationdb/bindings/go/src/fdb"
"log"
"fmt"
)
func main() {
// Different API versions may expose different runtime behaviors.
fdb.MustAPIVersion(610)
// Open the default database from the system cluster
db := fdb.MustOpenDefault()
// Database reads and writes happen inside transactions
ret, e := db.Transact(func(tr fdb.Transaction) (interface{}, error) {
tr.Set(fdb.Key("hello"), []byte("world"))
return tr.Get(fdb.Key("foo")).MustGet(), nil
// db.Transact automatically commits (and if necessary,
// retries) the transaction
})
if e != nil {
log.Fatalf("Unable to perform FDB transaction (%v)", e)
}
fmt.Printf("hello is now world, foo was: %s\n", string(ret.([]byte)))
}
Futures
Many functions in this package are asynchronous and return Future objects. A
Future represents a value (or error) to be available at some later
time. Functions documented as blocking on a Future will block the calling
goroutine until the Future is ready (although if the Future is already ready,
the call will not block at all). While a goroutine is blocked on a Future, other
goroutines are free to execute and interact with the FoundationDB API.
It is possible (and often recommended) to call several asynchronous operations
and have multiple Future objects outstanding inside a single goroutine. All
operations will execute in parallel, and the calling goroutine will not block
until a blocking method on any one of the Futures is called.
On Panics
Idiomatic Go code strongly frowns at panics that escape library/package
boundaries, in favor of explicitly returned errors. Idiomatic FoundationDB
client programs, however, are built around the idea of retryable
programmer-provided transactional functions. Retryable transactions can be
implemented using only error values:
ret, e := db.Transact(func (tr Transaction) (interface{}, error) {
// FoundationDB futures represent a value that will become available
futureValueOne := tr.Get(fdb.Key("foo"))
futureValueTwo := tr.Get(fdb.Key("bar"))
// Both reads are being carried out in parallel
// Get the first value (or any error)
valueOne, e := futureValueOne.Get()
if e != nil {
return nil, e
}
// Get the second value (or any error)
valueTwo, e := futureValueTwo.Get()
if e != nil {
return nil, e
}
// Return the two values
return []string{valueOne, valueTwo}, nil
})
If either read encounters an error, it will be returned to Transact, which will
determine if the error is retryable or not (using (Transaction).OnError). If the
error is an FDB Error and retryable (such as a conflict with with another
transaction), then the programmer-provided function will be run again. If the
error is fatal (or not an FDB Error), then the error will be returned to the
caller of Transact.
In practice, checking for an error from every asynchronous future type in the
FoundationDB API quickly becomes frustrating. As a convenience, every Future
type also has a MustGet method, which returns the same type and value as Get,
but exposes FoundationDB Errors via a panic rather than an explicitly returned
error. The above example may be rewritten as:
ret, e := db.Transact(func (tr Transaction) (interface{}, error) {
// FoundationDB futures represent a value that will become available
futureValueOne := tr.Get(fdb.Key("foo"))
futureValueTwo := tr.Get(fdb.Key("bar"))
// Both reads are being carried out in parallel
// Get the first value
valueOne := futureValueOne.MustGet()
// Get the second value
valueTwo := futureValueTwo.MustGet()
// Return the two values
return []string{valueOne, valueTwo}, nil
})
Any panic that occurs during execution of the caller-provided function will be
recovered by the (Database).Transact method. If the error is an FDB Error, it
will either result in a retry of the function or be returned by Transact. If the
error is any other type (panics from code other than MustGet), Transact will
re-panic the original value.
Note that (Transaction).Transact also recovers panics, but does not itself
retry. If the recovered value is an FDB Error, it will be returned to the caller
of (Transaction).Transact; all other values will be re-panicked.
Transactions and Goroutines
When using a Transactor in the fdb package, particular care must be taken if
goroutines are created inside of the function passed to the Transact method. Any
panic from the goroutine will not be recovered by Transact, and (unless
otherwise recovered) will result in the termination of that goroutine.
Furthermore, any errors returned or panicked by fdb methods called in the
goroutine must be safely returned to the function passed to Transact, and either
returned or panicked, to allow Transact to appropriately retry or terminate the
transactional function.
Lastly, a transactional function may be retried indefinitely. It is advisable to
make sure any goroutines created during the transactional function have
completed before returning from the transactional function, or a potentially
unbounded number of goroutines may be created.
Given these complexities, it is generally best practice to use a single
goroutine for each logical thread of interaction with FoundationDB, and allow
each goroutine to block when necessary to wait for Futures to become ready.
Streaming Modes
When using GetRange methods in the FoundationDB API, clients can request large
ranges of the database to iterate over. Making such a request doesn't
necessarily mean that the client will consume all of the data in the range --
sometimes the client doesn't know how far it intends to iterate in
advance. FoundationDB tries to balance latency and bandwidth by requesting data
for iteration in batches.
The Mode field of the RangeOptions struct allows a client to customize this
performance tradeoff by providing extra information about how the iterator will
be used.
The default value of Mode is StreamingModeIterator, which tries to provide a
reasonable default balance. Other streaming modes that prioritize throughput or
latency are available -- see the documented StreamingMode values for specific
options.
Atomic Operations
The FDB package provides a number of atomic operations on the Database and
Transaction objects. An atomic operation is a single database command that
carries out several logical steps: reading the value of a key, performing a
transformation on that value, and writing the result. Different atomic
operations perform different transformations. Like other database operations, an
atomic operation is used within a transaction.
For more information on atomic operations in FoundationDB, please see
https://apple.github.io/foundationdb/developer-guide.html#atomic-operations. The
operands to atomic operations in this API must be provided as appropriately
encoded byte slices. To convert a Go type to a byte slice, see the binary
package.
The current atomic operations in this API are Add, BitAnd, BitOr, BitXor, Max, Min,
SetVersionstampedKey, SetVersionstampedValue (all methods on Transaction).
*/
package fdb

View file

@ -1,59 +0,0 @@
/*
* errors.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go API
package fdb
/*
#define FDB_API_VERSION 610
#include <foundationdb/fdb_c.h>
*/
import "C"
import (
"fmt"
)
// Error represents a low-level error returned by the FoundationDB C library. An
// Error may be returned by any FoundationDB API function that returns error, or
// as a panic from any FoundationDB API function whose name ends with OrPanic.
//
// You may compare the Code field of an Error against the list of FoundationDB
// error codes at https://apple.github.io/foundationdb/api-error-codes.html,
// but generally an Error should be passed to (Transaction).OnError. When using
// (Database).Transact, non-fatal errors will be retried automatically.
type Error struct {
Code int
}
func (e Error) Error() string {
return fmt.Sprintf("FoundationDB error code %d (%s)", e.Code, C.GoString(C.fdb_get_error(C.fdb_error_t(e.Code))))
}
// SOMEDAY: these (along with others) should be coming from fdb.options?
var (
errNetworkNotSetup = Error{2008}
errAPIVersionUnset = Error{2200}
errAPIVersionAlreadySet = Error{2201}
errAPIVersionNotSupported = Error{2203}
)

View file

@ -1,389 +0,0 @@
/*
* fdb.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go API
package fdb
/*
#define FDB_API_VERSION 610
#include <foundationdb/fdb_c.h>
#include <stdlib.h>
*/
import "C"
import (
"fmt"
"log"
"runtime"
"sync"
"unsafe"
)
/* Would put this in futures.go but for the documented issue with
/* exports and functions in preamble
/* (https://code.google.com/p/go-wiki/wiki/cgo#Global_functions) */
//export unlockMutex
func unlockMutex(p unsafe.Pointer) {
m := (*sync.Mutex)(p)
m.Unlock()
}
// A Transactor can execute a function that requires a Transaction. Functions
// written to accept a Transactor are called transactional functions, and may be
// called with either a Database or a Transaction.
type Transactor interface {
// Transact executes the caller-provided function, providing it with a
// Transaction (itself a Transactor, allowing composition of transactional
// functions).
Transact(func(Transaction) (interface{}, error)) (interface{}, error)
// All Transactors are also ReadTransactors, allowing them to be used with
// read-only transactional functions.
ReadTransactor
}
// A ReadTransactor can execute a function that requires a
// ReadTransaction. Functions written to accept a ReadTransactor are called
// read-only transactional functions, and may be called with a Database,
// Transaction or Snapshot.
type ReadTransactor interface {
// ReadTransact executes the caller-provided function, providing it with a
// ReadTransaction (itself a ReadTransactor, allowing composition of
// read-only transactional functions).
ReadTransact(func(ReadTransaction) (interface{}, error)) (interface{}, error)
}
func setOpt(setter func(*C.uint8_t, C.int) C.fdb_error_t, param []byte) error {
if err := setter(byteSliceToPtr(param), C.int(len(param))); err != 0 {
return Error{int(err)}
}
return nil
}
// NetworkOptions is a handle with which to set options that affect the entire
// FoundationDB client. A NetworkOptions instance should be obtained with the
// fdb.Options function.
type NetworkOptions struct {
}
// Options returns a NetworkOptions instance suitable for setting options that
// affect the entire FoundationDB client.
func Options() NetworkOptions {
return NetworkOptions{}
}
func (opt NetworkOptions) setOpt(code int, param []byte) error {
networkMutex.Lock()
defer networkMutex.Unlock()
if apiVersion == 0 {
return errAPIVersionUnset
}
return setOpt(func(p *C.uint8_t, pl C.int) C.fdb_error_t {
return C.fdb_network_set_option(C.FDBNetworkOption(code), p, pl)
}, param)
}
// APIVersion determines the runtime behavior the fdb package. If the requested
// version is not supported by both the fdb package and the FoundationDB C
// library, an error will be returned. APIVersion must be called prior to any
// other functions in the fdb package.
//
// Currently, this package supports API versions 200 through 610.
//
// Warning: When using the multi-version client API, setting an API version that
// is not supported by a particular client library will prevent that client from
// being used to connect to the cluster. In particular, you should not advance
// the API version of your application after upgrading your client until the
// cluster has also been upgraded.
func APIVersion(version int) error {
headerVersion := 610
networkMutex.Lock()
defer networkMutex.Unlock()
if apiVersion != 0 {
if apiVersion == version {
return nil
}
return errAPIVersionAlreadySet
}
if version < 200 || version > 610 {
return errAPIVersionNotSupported
}
if e := C.fdb_select_api_version_impl(C.int(version), C.int(headerVersion)); e != 0 {
if e != 0 {
if e == 2203 {
maxSupportedVersion := C.fdb_get_max_api_version()
if headerVersion > int(maxSupportedVersion) {
return fmt.Errorf("This version of the FoundationDB Go binding is not supported by the installed FoundationDB C library. The binding requires a library that supports API version %d, but the installed library supports a maximum version of %d.", version, maxSupportedVersion)
}
return fmt.Errorf("API version %d is not supported by the installed FoundationDB C library.", version)
}
return Error{int(e)}
}
}
apiVersion = version
return nil
}
// Determines if an API version has already been selected, i.e., if
// APIVersion or MustAPIVersion have already been called.
func IsAPIVersionSelected() bool {
return apiVersion != 0
}
// Returns the API version that has been selected through APIVersion
// or MustAPIVersion. If the version has already been selected, then
// the first value returned is the API version and the error is
// nil. If the API version has not yet been set, then the error
// will be non-nil.
func GetAPIVersion() (int, error) {
if IsAPIVersionSelected() {
return apiVersion, nil
}
return 0, errAPIVersionUnset
}
// MustAPIVersion is like APIVersion but panics if the API version is not
// supported.
func MustAPIVersion(version int) {
err := APIVersion(version)
if err != nil {
panic(err)
}
}
// MustGetAPIVersion is like GetAPIVersion but panics if the API version
// has not yet been set.
func MustGetAPIVersion() int {
apiVersion, err := GetAPIVersion()
if err != nil {
panic(err)
}
return apiVersion
}
var apiVersion int
var networkStarted bool
var networkMutex sync.Mutex
type DatabaseId struct {
clusterFile string
dbName string
}
var openClusters map[string]Cluster
var openDatabases map[DatabaseId]Database
func init() {
openClusters = make(map[string]Cluster)
openDatabases = make(map[DatabaseId]Database)
}
func startNetwork() error {
if e := C.fdb_setup_network(); e != 0 {
return Error{int(e)}
}
go func() {
e := C.fdb_run_network()
if e != 0 {
log.Printf("Unhandled error in FoundationDB network thread: %v (%v)\n", C.GoString(C.fdb_get_error(e)), e)
}
}()
networkStarted = true
return nil
}
// StartNetwork initializes the FoundationDB client networking engine. It is not
// necessary to call StartNetwork when using the fdb.Open or fdb.OpenDefault
// functions to obtain a database handle. StartNetwork must not be called more
// than once.
func StartNetwork() error {
networkMutex.Lock()
defer networkMutex.Unlock()
if apiVersion == 0 {
return errAPIVersionUnset
}
return startNetwork()
}
// DefaultClusterFile should be passed to fdb.Open or fdb.CreateCluster to allow
// the FoundationDB C library to select the platform-appropriate default cluster
// file on the current machine.
const DefaultClusterFile string = ""
// OpenDefault returns a database handle to the default database from the
// FoundationDB cluster identified by the DefaultClusterFile on the current
// machine. The FoundationDB client networking engine will be initialized first,
// if necessary.
func OpenDefault() (Database, error) {
return Open(DefaultClusterFile, []byte("DB"))
}
// MustOpenDefault is like OpenDefault but panics if the default database cannot
// be opened.
func MustOpenDefault() Database {
db, err := OpenDefault()
if err != nil {
panic(err)
}
return db
}
// Open returns a database handle to the named database from the FoundationDB
// cluster identified by the provided cluster file and database name. The
// FoundationDB client networking engine will be initialized first, if
// necessary.
//
// In the current release, the database name must be []byte("DB").
func Open(clusterFile string, dbName []byte) (Database, error) {
networkMutex.Lock()
defer networkMutex.Unlock()
if apiVersion == 0 {
return Database{}, errAPIVersionUnset
}
var e error
if !networkStarted {
e = startNetwork()
if e != nil {
return Database{}, e
}
}
cluster, ok := openClusters[clusterFile]
if !ok {
cluster, e = createCluster(clusterFile)
if e != nil {
return Database{}, e
}
openClusters[clusterFile] = cluster
}
db, ok := openDatabases[DatabaseId{clusterFile, string(dbName)}]
if !ok {
db, e = cluster.OpenDatabase(dbName)
if e != nil {
return Database{}, e
}
openDatabases[DatabaseId{clusterFile, string(dbName)}] = db
}
return db, nil
}
// MustOpen is like Open but panics if the database cannot be opened.
func MustOpen(clusterFile string, dbName []byte) Database {
db, err := Open(clusterFile, dbName)
if err != nil {
panic(err)
}
return db
}
func createCluster(clusterFile string) (Cluster, error) {
var cf *C.char
if len(clusterFile) != 0 {
cf = C.CString(clusterFile)
defer C.free(unsafe.Pointer(cf))
}
f := C.fdb_create_cluster(cf)
fdb_future_block_until_ready(f)
var outc *C.FDBCluster
if err := C.fdb_future_get_cluster(f, &outc); err != 0 {
return Cluster{}, Error{int(err)}
}
C.fdb_future_destroy(f)
c := &cluster{outc}
runtime.SetFinalizer(c, (*cluster).destroy)
return Cluster{c}, nil
}
// CreateCluster returns a cluster handle to the FoundationDB cluster identified
// by the provided cluster file.
func CreateCluster(clusterFile string) (Cluster, error) {
networkMutex.Lock()
defer networkMutex.Unlock()
if apiVersion == 0 {
return Cluster{}, errAPIVersionUnset
}
if !networkStarted {
return Cluster{}, errNetworkNotSetup
}
return createCluster(clusterFile)
}
func byteSliceToPtr(b []byte) *C.uint8_t {
if len(b) > 0 {
return (*C.uint8_t)(unsafe.Pointer(&b[0]))
}
return nil
}
// A KeyConvertible can be converted to a FoundationDB Key. All functions in the
// FoundationDB API that address a specific key accept a KeyConvertible.
type KeyConvertible interface {
FDBKey() Key
}
// Key represents a FoundationDB key, a lexicographically-ordered sequence of
// bytes. Key implements the KeyConvertible interface.
type Key []byte
// FDBKey allows Key to (trivially) satisfy the KeyConvertible interface.
func (k Key) FDBKey() Key {
return k
}
func panicToError(e *error) {
if r := recover(); r != nil {
fe, ok := r.(Error)
if ok {
*e = fe
} else {
panic(r)
}
}
}

View file

@ -1,376 +0,0 @@
/*
* futures.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go API
package fdb
/*
#cgo LDFLAGS: -lfdb_c -lm
#define FDB_API_VERSION 610
#include <foundationdb/fdb_c.h>
#include <string.h>
extern void unlockMutex(void*);
void go_callback(FDBFuture* f, void* m) {
unlockMutex(m);
}
void go_set_callback(void* f, void* m) {
fdb_future_set_callback(f, (FDBCallback)&go_callback, m);
}
*/
import "C"
import (
"runtime"
"sync"
"unsafe"
)
// A Future represents a value (or error) to be available at some later
// time. Asynchronous FDB API functions return one of the types that implement
// the Future interface. All Future types additionally implement Get and MustGet
// methods with different return types. Calling BlockUntilReady, Get or MustGet
// will block the calling goroutine until the Future is ready.
type Future interface {
// BlockUntilReady blocks the calling goroutine until the future is ready. A
// future becomes ready either when it receives a value of its enclosed type
// (if any) or is set to an error state.
BlockUntilReady()
// IsReady returns true if the future is ready, and false otherwise, without
// blocking. A future is ready either when has received a value of its
// enclosed type (if any) or has been set to an error state.
IsReady() bool
// Cancel cancels a future and its associated asynchronous operation. If
// called before the future becomes ready, attempts to access the future
// will return an error. Cancel has no effect if the future is already
// ready.
//
// Note that even if a future is not ready, the associated asynchronous
// operation may already have completed and be unable to be cancelled.
Cancel()
}
type future struct {
ptr *C.FDBFuture
}
func newFuture(ptr *C.FDBFuture) *future {
f := &future{ptr}
runtime.SetFinalizer(f, func(f *future) { C.fdb_future_destroy(f.ptr) })
return f
}
func fdb_future_block_until_ready(f *C.FDBFuture) {
if C.fdb_future_is_ready(f) != 0 {
return
}
m := &sync.Mutex{}
m.Lock()
C.go_set_callback(unsafe.Pointer(f), unsafe.Pointer(m))
m.Lock()
}
func (f future) BlockUntilReady() {
fdb_future_block_until_ready(f.ptr)
}
func (f future) IsReady() bool {
return C.fdb_future_is_ready(f.ptr) != 0
}
func (f future) Cancel() {
C.fdb_future_cancel(f.ptr)
}
// FutureByteSlice represents the asynchronous result of a function that returns
// a value from a database. FutureByteSlice is a lightweight object that may be
// efficiently copied, and is safe for concurrent use by multiple goroutines.
type FutureByteSlice interface {
// Get returns a database value (or nil if there is no value), or an error
// if the asynchronous operation associated with this future did not
// successfully complete. The current goroutine will be blocked until the
// future is ready.
Get() ([]byte, error)
// MustGet returns a database value (or nil if there is no value), or panics
// if the asynchronous operation associated with this future did not
// successfully complete. The current goroutine will be blocked until the
// future is ready.
MustGet() []byte
Future
}
type futureByteSlice struct {
*future
v []byte
e error
o sync.Once
}
func (f *futureByteSlice) Get() ([]byte, error) {
f.o.Do(func() {
var present C.fdb_bool_t
var value *C.uint8_t
var length C.int
f.BlockUntilReady()
if err := C.fdb_future_get_value(f.ptr, &present, &value, &length); err != 0 {
f.e = Error{int(err)}
} else {
if present != 0 {
f.v = C.GoBytes(unsafe.Pointer(value), length)
}
}
C.fdb_future_release_memory(f.ptr)
})
return f.v, f.e
}
func (f *futureByteSlice) MustGet() []byte {
val, err := f.Get()
if err != nil {
panic(err)
}
return val
}
// FutureKey represents the asynchronous result of a function that returns a key
// from a database. FutureKey is a lightweight object that may be efficiently
// copied, and is safe for concurrent use by multiple goroutines.
type FutureKey interface {
// Get returns a database key or an error if the asynchronous operation
// associated with this future did not successfully complete. The current
// goroutine will be blocked until the future is ready.
Get() (Key, error)
// MustGet returns a database key, or panics if the asynchronous operation
// associated with this future did not successfully complete. The current
// goroutine will be blocked until the future is ready.
MustGet() Key
Future
}
type futureKey struct {
*future
k Key
e error
o sync.Once
}
func (f *futureKey) Get() (Key, error) {
f.o.Do(func() {
var value *C.uint8_t
var length C.int
f.BlockUntilReady()
if err := C.fdb_future_get_key(f.ptr, &value, &length); err != 0 {
f.e = Error{int(err)}
} else {
f.k = C.GoBytes(unsafe.Pointer(value), length)
}
C.fdb_future_release_memory(f.ptr)
})
return f.k, f.e
}
func (f *futureKey) MustGet() Key {
val, err := f.Get()
if err != nil {
panic(err)
}
return val
}
// FutureNil represents the asynchronous result of a function that has no return
// value. FutureNil is a lightweight object that may be efficiently copied, and
// is safe for concurrent use by multiple goroutines.
type FutureNil interface {
// Get returns an error if the asynchronous operation associated with this
// future did not successfully complete. The current goroutine will be
// blocked until the future is ready.
Get() error
// MustGet panics if the asynchronous operation associated with this future
// did not successfully complete. The current goroutine will be blocked
// until the future is ready.
MustGet()
Future
}
type futureNil struct {
*future
}
func (f futureNil) Get() error {
f.BlockUntilReady()
if err := C.fdb_future_get_error(f.ptr); err != 0 {
return Error{int(err)}
}
return nil
}
func (f futureNil) MustGet() {
if err := f.Get(); err != nil {
panic(err)
}
}
type futureKeyValueArray struct {
*future
}
func stringRefToSlice(ptr unsafe.Pointer) []byte {
size := *((*C.int)(unsafe.Pointer(uintptr(ptr) + 8)))
if size == 0 {
return []byte{}
}
src := unsafe.Pointer(*(**C.uint8_t)(unsafe.Pointer(ptr)))
return C.GoBytes(src, size)
}
func (f futureKeyValueArray) Get() ([]KeyValue, bool, error) {
f.BlockUntilReady()
var kvs *C.FDBKeyValue
var count C.int
var more C.fdb_bool_t
if err := C.fdb_future_get_keyvalue_array(f.ptr, &kvs, &count, &more); err != 0 {
return nil, false, Error{int(err)}
}
ret := make([]KeyValue, int(count))
for i := 0; i < int(count); i++ {
kvptr := unsafe.Pointer(uintptr(unsafe.Pointer(kvs)) + uintptr(i*24))
ret[i].Key = stringRefToSlice(kvptr)
ret[i].Value = stringRefToSlice(unsafe.Pointer(uintptr(kvptr) + 12))
}
return ret, (more != 0), nil
}
// FutureInt64 represents the asynchronous result of a function that returns a
// database version. FutureInt64 is a lightweight object that may be efficiently
// copied, and is safe for concurrent use by multiple goroutines.
type FutureInt64 interface {
// Get returns a database version or an error if the asynchronous operation
// associated with this future did not successfully complete. The current
// goroutine will be blocked until the future is ready.
Get() (int64, error)
// MustGet returns a database version, or panics if the asynchronous
// operation associated with this future did not successfully complete. The
// current goroutine will be blocked until the future is ready.
MustGet() int64
Future
}
type futureInt64 struct {
*future
}
func (f futureInt64) Get() (int64, error) {
f.BlockUntilReady()
var ver C.int64_t
if err := C.fdb_future_get_version(f.ptr, &ver); err != 0 {
return 0, Error{int(err)}
}
return int64(ver), nil
}
func (f futureInt64) MustGet() int64 {
val, err := f.Get()
if err != nil {
panic(err)
}
return val
}
// FutureStringSlice represents the asynchronous result of a function that
// returns a slice of strings. FutureStringSlice is a lightweight object that
// may be efficiently copied, and is safe for concurrent use by multiple
// goroutines.
type FutureStringSlice interface {
// Get returns a slice of strings or an error if the asynchronous operation
// associated with this future did not successfully complete. The current
// goroutine will be blocked until the future is ready.
Get() ([]string, error)
// MustGet returns a slice of strings or panics if the asynchronous
// operation associated with this future did not successfully complete. The
// current goroutine will be blocked until the future is ready.
MustGet() []string
Future
}
type futureStringSlice struct {
*future
}
func (f futureStringSlice) Get() ([]string, error) {
f.BlockUntilReady()
var strings **C.char
var count C.int
if err := C.fdb_future_get_string_array(f.ptr, (***C.char)(unsafe.Pointer(&strings)), &count); err != 0 {
return nil, Error{int(err)}
}
ret := make([]string, int(count))
for i := 0; i < int(count); i++ {
ret[i] = C.GoString((*C.char)(*(**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(strings)) + uintptr(i*8)))))
}
return ret, nil
}
func (f futureStringSlice) MustGet() []string {
val, err := f.Get()
if err != nil {
panic(err)
}
return val
}

View file

@ -1,554 +0,0 @@
/*
* generated.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// DO NOT EDIT THIS FILE BY HAND. This file was generated using
// translate_fdb_options.go, part of the FoundationDB repository, and a copy of
// the fdb.options file (installed as part of the FoundationDB client, typically
// found as /usr/include/foundationdb/fdb.options).
// To regenerate this file, from the top level of a FoundationDB repository
// checkout, run:
// $ go run bindings/go/src/_util/translate_fdb_options.go < fdbclient/vexillographer/fdb.options > bindings/go/src/fdb/generated.go
package fdb
import (
"bytes"
"encoding/binary"
)
func int64ToBytes(i int64) ([]byte, error) {
buf := new(bytes.Buffer)
if e := binary.Write(buf, binary.LittleEndian, i); e != nil {
return nil, e
}
return buf.Bytes(), nil
}
// Deprecated
//
// Parameter: IP:PORT
func (o NetworkOptions) SetLocalAddress(param string) error {
return o.setOpt(10, []byte(param))
}
// Deprecated
//
// Parameter: path to cluster file
func (o NetworkOptions) SetClusterFile(param string) error {
return o.setOpt(20, []byte(param))
}
// Enables trace output to a file in a directory of the clients choosing
//
// Parameter: path to output directory (or NULL for current working directory)
func (o NetworkOptions) SetTraceEnable(param string) error {
return o.setOpt(30, []byte(param))
}
// Sets the maximum size in bytes of a single trace output file. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on individual file size. The default is a maximum size of 10,485,760 bytes.
//
// Parameter: max size of a single trace output file
func (o NetworkOptions) SetTraceRollSize(param int64) error {
b, e := int64ToBytes(param)
if e != nil {
return e
}
return o.setOpt(31, b)
}
// Sets the maximum size of all the trace output files put together. This value should be in the range ``[0, INT64_MAX]``. If the value is set to 0, there is no limit on the total size of the files. The default is a maximum size of 104,857,600 bytes. If the default roll size is used, this means that a maximum of 10 trace files will be written at a time.
//
// Parameter: max total size of trace files
func (o NetworkOptions) SetTraceMaxLogsSize(param int64) error {
b, e := int64ToBytes(param)
if e != nil {
return e
}
return o.setOpt(32, b)
}
// Sets the 'LogGroup' attribute with the specified value for all events in the trace output files. The default log group is 'default'.
//
// Parameter: value of the LogGroup attribute
func (o NetworkOptions) SetTraceLogGroup(param string) error {
return o.setOpt(33, []byte(param))
}
// Set internal tuning or debugging knobs
//
// Parameter: knob_name=knob_value
func (o NetworkOptions) SetKnob(param string) error {
return o.setOpt(40, []byte(param))
}
// Deprecated
//
// Parameter: file path or linker-resolved name
func (o NetworkOptions) SetTLSPlugin(param string) error {
return o.setOpt(41, []byte(param))
}
// Set the certificate chain
//
// Parameter: certificates
func (o NetworkOptions) SetTLSCertBytes(param []byte) error {
return o.setOpt(42, param)
}
// Set the file from which to load the certificate chain
//
// Parameter: file path
func (o NetworkOptions) SetTLSCertPath(param string) error {
return o.setOpt(43, []byte(param))
}
// Set the private key corresponding to your own certificate
//
// Parameter: key
func (o NetworkOptions) SetTLSKeyBytes(param []byte) error {
return o.setOpt(45, param)
}
// Set the file from which to load the private key corresponding to your own certificate
//
// Parameter: file path
func (o NetworkOptions) SetTLSKeyPath(param string) error {
return o.setOpt(46, []byte(param))
}
// Set the peer certificate field verification criteria
//
// Parameter: verification pattern
func (o NetworkOptions) SetTLSVerifyPeers(param []byte) error {
return o.setOpt(47, param)
}
// Not yet implemented.
func (o NetworkOptions) SetBuggifyEnable() error {
return o.setOpt(48, nil)
}
// Not yet implemented.
func (o NetworkOptions) SetBuggifyDisable() error {
return o.setOpt(49, nil)
}
// Set the probability of a BUGGIFY section being active for the current execution. Only applies to code paths first traversed AFTER this option is changed.
//
// Parameter: probability expressed as a percentage between 0 and 100
func (o NetworkOptions) SetBuggifySectionActivatedProbability(param int64) error {
b, e := int64ToBytes(param)
if e != nil {
return e
}
return o.setOpt(50, b)
}
// Set the probability of an active BUGGIFY section being fired
//
// Parameter: probability expressed as a percentage between 0 and 100
func (o NetworkOptions) SetBuggifySectionFiredProbability(param int64) error {
b, e := int64ToBytes(param)
if e != nil {
return e
}
return o.setOpt(51, b)
}
// Set the ca bundle
//
// Parameter: ca bundle
func (o NetworkOptions) SetTLSCaBytes(param []byte) error {
return o.setOpt(52, param)
}
// Set the file from which to load the certificate authority bundle
//
// Parameter: file path
func (o NetworkOptions) SetTLSCaPath(param string) error {
return o.setOpt(53, []byte(param))
}
// Set the passphrase for encrypted private key. Password should be set before setting the key for the password to be used.
//
// Parameter: key passphrase
func (o NetworkOptions) SetTLSPassword(param string) error {
return o.setOpt(54, []byte(param))
}
// Disables the multi-version client API and instead uses the local client directly. Must be set before setting up the network.
func (o NetworkOptions) SetDisableMultiVersionClientApi() error {
return o.setOpt(60, nil)
}
// If set, callbacks from external client libraries can be called from threads created by the FoundationDB client library. Otherwise, callbacks will be called from either the thread used to add the callback or the network thread. Setting this option can improve performance when connected using an external client, but may not be safe to use in all environments. Must be set before setting up the network. WARNING: This feature is considered experimental at this time.
func (o NetworkOptions) SetCallbacksOnExternalThreads() error {
return o.setOpt(61, nil)
}
// Adds an external client library for use by the multi-version client API. Must be set before setting up the network.
//
// Parameter: path to client library
func (o NetworkOptions) SetExternalClientLibrary(param string) error {
return o.setOpt(62, []byte(param))
}
// Searches the specified path for dynamic libraries and adds them to the list of client libraries for use by the multi-version client API. Must be set before setting up the network.
//
// Parameter: path to directory containing client libraries
func (o NetworkOptions) SetExternalClientDirectory(param string) error {
return o.setOpt(63, []byte(param))
}
// Prevents connections through the local client, allowing only connections through externally loaded client libraries. Intended primarily for testing.
func (o NetworkOptions) SetDisableLocalClient() error {
return o.setOpt(64, nil)
}
// Disables logging of client statistics, such as sampled transaction activity.
func (o NetworkOptions) SetDisableClientStatisticsLogging() error {
return o.setOpt(70, nil)
}
// Enables debugging feature to perform slow task profiling. Requires trace logging to be enabled. WARNING: this feature is not recommended for use in production.
func (o NetworkOptions) SetEnableSlowTaskProfiling() error {
return o.setOpt(71, nil)
}
// Set the size of the client location cache. Raising this value can boost performance in very large databases where clients access data in a near-random pattern. Defaults to 100000.
//
// Parameter: Max location cache entries
func (o DatabaseOptions) SetLocationCacheSize(param int64) error {
b, e := int64ToBytes(param)
if e != nil {
return e
}
return o.setOpt(10, b)
}
// Set the maximum number of watches allowed to be outstanding on a database connection. Increasing this number could result in increased resource usage. Reducing this number will not cancel any outstanding watches. Defaults to 10000 and cannot be larger than 1000000.
//
// Parameter: Max outstanding watches
func (o DatabaseOptions) SetMaxWatches(param int64) error {
b, e := int64ToBytes(param)
if e != nil {
return e
}
return o.setOpt(20, b)
}
// Specify the machine ID that was passed to fdbserver processes running on the same machine as this client, for better location-aware load balancing.
//
// Parameter: Hexadecimal ID
func (o DatabaseOptions) SetMachineId(param string) error {
return o.setOpt(21, []byte(param))
}
// Specify the datacenter ID that was passed to fdbserver processes running in the same datacenter as this client, for better location-aware load balancing.
//
// Parameter: Hexadecimal ID
func (o DatabaseOptions) SetDatacenterId(param string) error {
return o.setOpt(22, []byte(param))
}
// The transaction, if not self-conflicting, may be committed a second time after commit succeeds, in the event of a fault
func (o TransactionOptions) SetCausalWriteRisky() error {
return o.setOpt(10, nil)
}
// The read version will be committed, and usually will be the latest committed, but might not be the latest committed in the event of a fault or partition
func (o TransactionOptions) SetCausalReadRisky() error {
return o.setOpt(20, nil)
}
// Not yet implemented.
func (o TransactionOptions) SetCausalReadDisable() error {
return o.setOpt(21, nil)
}
// The next write performed on this transaction will not generate a write conflict range. As a result, other transactions which read the key(s) being modified by the next write will not conflict with this transaction. Care needs to be taken when using this option on a transaction that is shared between multiple threads. When setting this option, write conflict ranges will be disabled on the next write operation, regardless of what thread it is on.
func (o TransactionOptions) SetNextWriteNoWriteConflictRange() error {
return o.setOpt(30, nil)
}
// Reads performed by a transaction will not see any prior mutations that occured in that transaction, instead seeing the value which was in the database at the transaction's read version. This option may provide a small performance benefit for the client, but also disables a number of client-side optimizations which are beneficial for transactions which tend to read and write the same keys within a single transaction.
func (o TransactionOptions) SetReadYourWritesDisable() error {
return o.setOpt(51, nil)
}
// Deprecated
func (o TransactionOptions) SetReadAheadDisable() error {
return o.setOpt(52, nil)
}
// Not yet implemented.
func (o TransactionOptions) SetDurabilityDatacenter() error {
return o.setOpt(110, nil)
}
// Not yet implemented.
func (o TransactionOptions) SetDurabilityRisky() error {
return o.setOpt(120, nil)
}
// Deprecated
func (o TransactionOptions) SetDurabilityDevNullIsWebScale() error {
return o.setOpt(130, nil)
}
// Specifies that this transaction should be treated as highest priority and that lower priority transactions should block behind this one. Use is discouraged outside of low-level tools
func (o TransactionOptions) SetPrioritySystemImmediate() error {
return o.setOpt(200, nil)
}
// Specifies that this transaction should be treated as low priority and that default priority transactions should be processed first. Useful for doing batch work simultaneously with latency-sensitive work
func (o TransactionOptions) SetPriorityBatch() error {
return o.setOpt(201, nil)
}
// This is a write-only transaction which sets the initial configuration. This option is designed for use by database system tools only.
func (o TransactionOptions) SetInitializeNewDatabase() error {
return o.setOpt(300, nil)
}
// Allows this transaction to read and modify system keys (those that start with the byte 0xFF)
func (o TransactionOptions) SetAccessSystemKeys() error {
return o.setOpt(301, nil)
}
// Allows this transaction to read system keys (those that start with the byte 0xFF)
func (o TransactionOptions) SetReadSystemKeys() error {
return o.setOpt(302, nil)
}
// Not yet implemented.
func (o TransactionOptions) SetDebugRetryLogging(param string) error {
return o.setOpt(401, []byte(param))
}
// Enables tracing for this transaction and logs results to the client trace logs. Client trace logging must be enabled to get log output.
//
// Parameter: String identifier to be used in the logs when tracing this transaction. The identifier must not exceed 100 characters.
func (o TransactionOptions) SetTransactionLoggingEnable(param string) error {
return o.setOpt(402, []byte(param))
}
// Set a timeout in milliseconds which, when elapsed, will cause the transaction automatically to be cancelled. Valid parameter values are ``[0, INT_MAX]``. If set to 0, will disable all timeouts. All pending and any future uses of the transaction will throw an exception. The transaction can be used again after it is reset. Like all transaction options, a timeout must be reset after a call to onError. This behavior allows the user to make the timeout dynamic.
//
// Parameter: value in milliseconds of timeout
func (o TransactionOptions) SetTimeout(param int64) error {
b, e := int64ToBytes(param)
if e != nil {
return e
}
return o.setOpt(500, b)
}
// Set a maximum number of retries after which additional calls to onError will throw the most recently seen error code. Valid parameter values are ``[-1, INT_MAX]``. If set to -1, will disable the retry limit. Like all transaction options, the retry limit must be reset after a call to onError. This behavior allows the user to make the retry limit dynamic.
//
// Parameter: number of times to retry
func (o TransactionOptions) SetRetryLimit(param int64) error {
b, e := int64ToBytes(param)
if e != nil {
return e
}
return o.setOpt(501, b)
}
// Set the maximum amount of backoff delay incurred in the call to onError if the error is retryable. Defaults to 1000 ms. Valid parameter values are ``[0, INT_MAX]``. Like all transaction options, the maximum retry delay must be reset after a call to onError. If the maximum retry delay is less than the current retry delay of the transaction, then the current retry delay will be clamped to the maximum retry delay.
//
// Parameter: value in milliseconds of maximum delay
func (o TransactionOptions) SetMaxRetryDelay(param int64) error {
b, e := int64ToBytes(param)
if e != nil {
return e
}
return o.setOpt(502, b)
}
// Snapshot read operations will see the results of writes done in the same transaction.
func (o TransactionOptions) SetSnapshotRywEnable() error {
return o.setOpt(600, nil)
}
// Snapshot read operations will not see the results of writes done in the same transaction.
func (o TransactionOptions) SetSnapshotRywDisable() error {
return o.setOpt(601, nil)
}
// The transaction can read and write to locked databases, and is resposible for checking that it took the lock.
func (o TransactionOptions) SetLockAware() error {
return o.setOpt(700, nil)
}
// By default, operations that are performed on a transaction while it is being committed will not only fail themselves, but they will attempt to fail other in-flight operations (such as the commit) as well. This behavior is intended to help developers discover situations where operations could be unintentionally executed after the transaction has been reset. Setting this option removes that protection, causing only the offending operation to fail.
func (o TransactionOptions) SetUsedDuringCommitProtectionDisable() error {
return o.setOpt(701, nil)
}
// The transaction can read from locked databases.
func (o TransactionOptions) SetReadLockAware() error {
return o.setOpt(702, nil)
}
type StreamingMode int
const (
// Client intends to consume the entire range and would like it all
// transferred as early as possible.
StreamingModeWantAll StreamingMode = -1
// The default. The client doesn't know how much of the range it is likely
// to used and wants different performance concerns to be balanced. Only a
// small portion of data is transferred to the client initially (in order to
// minimize costs if the client doesn't read the entire range), and as the
// caller iterates over more items in the range larger batches will be
// transferred in order to minimize latency.
StreamingModeIterator StreamingMode = 0
// Infrequently used. The client has passed a specific row limit and wants
// that many rows delivered in a single batch. Because of iterator operation
// in client drivers make request batches transparent to the user, consider
// ``WANT_ALL`` StreamingMode instead. A row limit must be specified if this
// mode is used.
StreamingModeExact StreamingMode = 1
// Infrequently used. Transfer data in batches small enough to not be much
// more expensive than reading individual rows, to minimize cost if
// iteration stops early.
StreamingModeSmall StreamingMode = 2
// Infrequently used. Transfer data in batches sized in between small and
// large.
StreamingModeMedium StreamingMode = 3
// Infrequently used. Transfer data in batches large enough to be, in a
// high-concurrency environment, nearly as efficient as possible. If the
// client stops iteration early, some disk and network bandwidth may be
// wasted. The batch size may still be too small to allow a single client to
// get high throughput from the database, so if that is what you need
// consider the SERIAL StreamingMode.
StreamingModeLarge StreamingMode = 4
// Transfer data in batches large enough that an individual client can get
// reasonable read bandwidth from the database. If the client stops
// iteration early, considerable disk and network bandwidth may be wasted.
StreamingModeSerial StreamingMode = 5
)
// Performs an addition of little-endian integers. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The integers to be added must be stored in a little-endian representation. They can be signed in two's complement representation or unsigned. You can add to an integer at a known offset in the value by prepending the appropriate number of zero bytes to ``param`` and padding with zero bytes to match the length of the value. However, this offset technique requires that you know the addition will not cause the integer field within the value to overflow.
func (t Transaction) Add(key KeyConvertible, param []byte) {
t.atomicOp(key.FDBKey(), param, 2)
}
// Deprecated
func (t Transaction) And(key KeyConvertible, param []byte) {
t.atomicOp(key.FDBKey(), param, 6)
}
// Performs a bitwise ``and`` operation. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``.
func (t Transaction) BitAnd(key KeyConvertible, param []byte) {
t.atomicOp(key.FDBKey(), param, 6)
}
// Deprecated
func (t Transaction) Or(key KeyConvertible, param []byte) {
t.atomicOp(key.FDBKey(), param, 7)
}
// Performs a bitwise ``or`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``.
func (t Transaction) BitOr(key KeyConvertible, param []byte) {
t.atomicOp(key.FDBKey(), param, 7)
}
// Deprecated
func (t Transaction) Xor(key KeyConvertible, param []byte) {
t.atomicOp(key.FDBKey(), param, 8)
}
// Performs a bitwise ``xor`` operation. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``.
func (t Transaction) BitXor(key KeyConvertible, param []byte) {
t.atomicOp(key.FDBKey(), param, 8)
}
// Appends ``param`` to the end of the existing value already in the database at the given key (or creates the key and sets the value to ``param`` if the key is empty). This will only append the value if the final concatenated value size is less than or equal to the maximum value size (i.e., if it fits). WARNING: No error is surfaced back to the user if the final value is too large because the mutation will not be applied until after the transaction has been committed. Therefore, it is only safe to use this mutation type if one can guarantee that one will keep the total value size under the maximum size.
func (t Transaction) AppendIfFits(key KeyConvertible, param []byte) {
t.atomicOp(key.FDBKey(), param, 9)
}
// Performs a little-endian comparison of byte strings. If the existing value in the database is not present or shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The larger of the two values is then stored in the database.
func (t Transaction) Max(key KeyConvertible, param []byte) {
t.atomicOp(key.FDBKey(), param, 12)
}
// Performs a little-endian comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored in the database. If the existing value in the database is shorter than ``param``, it is first extended to the length of ``param`` with zero bytes. If ``param`` is shorter than the existing value in the database, the existing value is truncated to match the length of ``param``. The smaller of the two values is then stored in the database.
func (t Transaction) Min(key KeyConvertible, param []byte) {
t.atomicOp(key.FDBKey(), param, 13)
}
// Transforms ``key`` using a versionstamp for the transaction. Sets the transformed key in the database to ``param``. The key is transformed by removing the final four bytes from the key and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the key from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the key is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java and Python bindings. Also, note that prior to API version 520, the offset was computed from only the final two bytes rather than the final four bytes.
func (t Transaction) SetVersionstampedKey(key KeyConvertible, param []byte) {
t.atomicOp(key.FDBKey(), param, 14)
}
// Transforms ``param`` using a versionstamp for the transaction. Sets the ``key`` given to the transformed ``param``. The parameter is transformed by removing the final four bytes from ``param`` and reading those as a little-Endian 32-bit integer to get a position ``pos``. The 10 bytes of the parameter from ``pos`` to ``pos + 10`` are replaced with the versionstamp of the transaction used. The first byte of the parameter is position 0. A versionstamp is a 10 byte, unique, monotonically (but not sequentially) increasing value for each committed transaction. The first 8 bytes are the committed version of the database (serialized in big-Endian order). The last 2 bytes are monotonic in the serialization order for transactions. WARNING: At this time, versionstamps are compatible with the Tuple layer only in the Java and Python bindings. Also, note that prior to API version 520, the versionstamp was always placed at the beginning of the parameter rather than computing an offset.
func (t Transaction) SetVersionstampedValue(key KeyConvertible, param []byte) {
t.atomicOp(key.FDBKey(), param, 15)
}
// Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the smaller of the two values is then stored in the database.
func (t Transaction) ByteMin(key KeyConvertible, param []byte) {
t.atomicOp(key.FDBKey(), param, 16)
}
// Performs lexicographic comparison of byte strings. If the existing value in the database is not present, then ``param`` is stored. Otherwise the larger of the two values is then stored in the database.
func (t Transaction) ByteMax(key KeyConvertible, param []byte) {
t.atomicOp(key.FDBKey(), param, 17)
}
type conflictRangeType int
const (
// Used to add a read conflict range
conflictRangeTypeRead conflictRangeType = 0
// Used to add a write conflict range
conflictRangeTypeWrite conflictRangeType = 1
)
type ErrorPredicate int
const (
// Returns ``true`` if the error indicates the operations in the
// transactions should be retried because of transient error.
ErrorPredicateRetryable ErrorPredicate = 50000
// Returns ``true`` if the error indicates the transaction may have
// succeeded, though not in a way the system can verify.
ErrorPredicateMaybeCommitted ErrorPredicate = 50001
// Returns ``true`` if the error indicates the transaction has not
// committed, though in a way that can be retried.
ErrorPredicateRetryableNotCommitted ErrorPredicate = 50002
)

View file

@ -1,74 +0,0 @@
/*
* keyselector.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go API
package fdb
// A Selectable can be converted to a FoundationDB KeySelector. All functions in
// the FoundationDB API that resolve a key selector to a key accept Selectable.
type Selectable interface {
FDBKeySelector() KeySelector
}
// KeySelector represents a description of a key in a FoundationDB database. A
// KeySelector may be resolved to a specific key with the GetKey method, or used
// as the endpoints of a SelectorRange to be used with a GetRange function.
//
// The most common key selectors are constructed with the functions documented
// below. For details of how KeySelectors are specified and resolved, see
// https://apple.github.io/foundationdb/developer-guide.html#key-selectors.
type KeySelector struct {
Key KeyConvertible
OrEqual bool
Offset int
}
func (ks KeySelector) FDBKeySelector() KeySelector {
return ks
}
// LastLessThan returns the KeySelector specifying the lexigraphically greatest
// key present in the database which is lexigraphically strictly less than the
// given key.
func LastLessThan(key KeyConvertible) KeySelector {
return KeySelector{key, false, 0}
}
// LastLessOrEqual returns the KeySelector specifying the lexigraphically
// greatest key present in the database which is lexigraphically less than or
// equal to the given key.
func LastLessOrEqual(key KeyConvertible) KeySelector {
return KeySelector{key, true, 0}
}
// FirstGreaterThan returns the KeySelector specifying the lexigraphically least
// key present in the database which is lexigraphically strictly greater than
// the given key.
func FirstGreaterThan(key KeyConvertible) KeySelector {
return KeySelector{key, true, 1}
}
// FirstGreaterOrEqual returns the KeySelector specifying the lexigraphically
// least key present in the database which is lexigraphically greater than or
// equal to the given key.
func FirstGreaterOrEqual(key KeyConvertible) KeySelector {
return KeySelector{key, false, 1}
}

View file

@ -1,317 +0,0 @@
/*
* range.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go API
package fdb
/*
#define FDB_API_VERSION 610
#include <foundationdb/fdb_c.h>
*/
import "C"
import (
"fmt"
)
// KeyValue represents a single key-value pair in the database.
type KeyValue struct {
Key Key
Value []byte
}
// RangeOptions specify how a database range read operation is carried
// out. RangeOptions objects are passed to GetRange methods of Database,
// Transaction and Snapshot.
//
// The zero value of RangeOptions represents the default range read
// configuration (no limit, lexicographic order, to be used as an iterator).
type RangeOptions struct {
// Limit restricts the number of key-value pairs returned as part of a range
// read. A value of 0 indicates no limit.
Limit int
// Mode sets the streaming mode of the range read, allowing the database to
// balance latency and bandwidth for this read.
Mode StreamingMode
// Reverse indicates that the read should be performed in lexicographic
// (false) or reverse lexicographic (true) order. When Reverse is true and
// Limit is non-zero, the last Limit key-value pairs in the range are
// returned.
Reverse bool
}
// A Range describes all keys between a begin (inclusive) and end (exclusive)
// key selector.
type Range interface {
// FDBRangeKeySelectors returns a pair of key selectors that describe the
// beginning and end of a range.
FDBRangeKeySelectors() (begin, end Selectable)
}
// An ExactRange describes all keys between a begin (inclusive) and end
// (exclusive) key. If you need to specify an ExactRange and you have only a
// Range, you must resolve the selectors returned by
// (Range).FDBRangeKeySelectors to keys using the (Transaction).GetKey method.
//
// Any object that implements ExactRange also implements Range, and may be used
// accordingly.
type ExactRange interface {
// FDBRangeKeys returns a pair of keys that describe the beginning and end
// of a range.
FDBRangeKeys() (begin, end KeyConvertible)
// An object that implements ExactRange must also implement Range
// (logically, by returning FirstGreaterOrEqual of the keys returned by
// FDBRangeKeys).
Range
}
// KeyRange is an ExactRange constructed from a pair of KeyConvertibles. Note
// that the default zero-value of KeyRange specifies an empty range before all
// keys in the database.
type KeyRange struct {
Begin, End KeyConvertible
}
// FDBRangeKeys allows KeyRange to satisfy the ExactRange interface.
func (kr KeyRange) FDBRangeKeys() (KeyConvertible, KeyConvertible) {
return kr.Begin, kr.End
}
// FDBRangeKeySelectors allows KeyRange to satisfy the Range interface.
func (kr KeyRange) FDBRangeKeySelectors() (Selectable, Selectable) {
return FirstGreaterOrEqual(kr.Begin), FirstGreaterOrEqual(kr.End)
}
// SelectorRange is a Range constructed directly from a pair of Selectable
// objects. Note that the default zero-value of SelectorRange specifies an empty
// range before all keys in the database.
type SelectorRange struct {
Begin, End Selectable
}
// FDBRangeKeySelectors allows SelectorRange to satisfy the Range interface.
func (sr SelectorRange) FDBRangeKeySelectors() (Selectable, Selectable) {
return sr.Begin, sr.End
}
// RangeResult is a handle to the asynchronous result of a range
// read. RangeResult is safe for concurrent use by multiple goroutines.
//
// A RangeResult should not be returned from a transactional function passed to
// the Transact method of a Transactor.
type RangeResult struct {
t *transaction
sr SelectorRange
options RangeOptions
snapshot bool
f *futureKeyValueArray
}
// GetSliceWithError returns a slice of KeyValue objects satisfying the range
// specified in the read that returned this RangeResult, or an error if any of
// the asynchronous operations associated with this result did not successfully
// complete. The current goroutine will be blocked until all reads have
// completed.
func (rr RangeResult) GetSliceWithError() ([]KeyValue, error) {
var ret []KeyValue
ri := rr.Iterator()
if rr.options.Limit != 0 {
ri.options.Mode = StreamingModeExact
} else {
ri.options.Mode = StreamingModeWantAll
}
for ri.Advance() {
if ri.err != nil {
return nil, ri.err
}
ret = append(ret, ri.kvs...)
ri.index = len(ri.kvs)
ri.fetchNextBatch()
}
return ret, nil
}
// GetSliceOrPanic returns a slice of KeyValue objects satisfying the range
// specified in the read that returned this RangeResult, or panics if any of the
// asynchronous operations associated with this result did not successfully
// complete. The current goroutine will be blocked until all reads have
// completed.
func (rr RangeResult) GetSliceOrPanic() []KeyValue {
kvs, e := rr.GetSliceWithError()
if e != nil {
panic(e)
}
return kvs
}
// Iterator returns a RangeIterator over the key-value pairs satisfying the
// range specified in the read that returned this RangeResult.
func (rr RangeResult) Iterator() *RangeIterator {
return &RangeIterator{
t: rr.t,
f: rr.f,
sr: rr.sr,
options: rr.options,
iteration: 1,
snapshot: rr.snapshot,
}
}
// RangeIterator returns the key-value pairs in the database (as KeyValue
// objects) satisfying the range specified in a range read. RangeIterator is
// constructed with the (RangeResult).Iterator method.
//
// You must call Advance and get a true result prior to calling Get or MustGet.
//
// RangeIterator should not be copied or used concurrently from multiple
// goroutines, but multiple RangeIterators may be constructed from a single
// RangeResult and used concurrently. RangeIterator should not be returned from
// a transactional function passed to the Transact method of a Transactor.
type RangeIterator struct {
t *transaction
f *futureKeyValueArray
sr SelectorRange
options RangeOptions
iteration int
done bool
more bool
kvs []KeyValue
index int
err error
snapshot bool
}
// Advance attempts to advance the iterator to the next key-value pair. Advance
// returns true if there are more key-value pairs satisfying the range, or false
// if the range has been exhausted. You must call this before every call to Get
// or MustGet.
func (ri *RangeIterator) Advance() bool {
if ri.done {
return false
}
if ri.f == nil {
return true
}
ri.kvs, ri.more, ri.err = ri.f.Get()
ri.index = 0
ri.f = nil
if ri.err != nil || len(ri.kvs) > 0 {
return true
}
return false
}
func (ri *RangeIterator) fetchNextBatch() {
if !ri.more || ri.index == ri.options.Limit {
ri.done = true
return
}
if ri.options.Limit > 0 {
// Not worried about this being zero, checked equality above
ri.options.Limit -= ri.index
}
if ri.options.Reverse {
ri.sr.End = FirstGreaterOrEqual(ri.kvs[ri.index-1].Key)
} else {
ri.sr.Begin = FirstGreaterThan(ri.kvs[ri.index-1].Key)
}
ri.iteration++
f := ri.t.doGetRange(ri.sr, ri.options, ri.snapshot, ri.iteration)
ri.f = &f
}
// Get returns the next KeyValue in a range read, or an error if one of the
// asynchronous operations associated with this range did not successfully
// complete. The Advance method of this RangeIterator must have returned true
// prior to calling Get.
func (ri *RangeIterator) Get() (kv KeyValue, e error) {
if ri.err != nil {
e = ri.err
return
}
kv = ri.kvs[ri.index]
ri.index++
if ri.index == len(ri.kvs) {
ri.fetchNextBatch()
}
return
}
// MustGet returns the next KeyValue in a range read, or panics if one of the
// asynchronous operations associated with this range did not successfully
// complete. The Advance method of this RangeIterator must have returned true
// prior to calling MustGet.
func (ri *RangeIterator) MustGet() KeyValue {
kv, e := ri.Get()
if e != nil {
panic(e)
}
return kv
}
func Strinc(prefix []byte) ([]byte, error) {
for i := len(prefix) - 1; i >= 0; i-- {
if prefix[i] != 0xFF {
ret := make([]byte, i+1)
copy(ret, prefix[:i+1])
ret[i]++
return ret, nil
}
}
return nil, fmt.Errorf("Key must contain at least one byte not equal to 0xFF")
}
// PrefixRange returns the KeyRange describing the range of keys k such that
// bytes.HasPrefix(k, prefix) is true. PrefixRange returns an error if prefix is
// empty or entirely 0xFF bytes.
//
// Do not use PrefixRange on objects that already implement the Range or
// ExactRange interfaces. The prefix range of the byte representation of these
// objects may not correspond to their logical range.
func PrefixRange(prefix []byte) (KeyRange, error) {
begin := make([]byte, len(prefix))
copy(begin, prefix)
end, e := Strinc(begin)
if e != nil {
return KeyRange{}, nil
}
return KeyRange{Key(begin), Key(end)}, nil
}

View file

@ -1,88 +0,0 @@
/*
* snapshot.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go API
package fdb
// Snapshot is a handle to a FoundationDB transaction snapshot, suitable for
// performing snapshot reads. Snapshot reads offer a more relaxed isolation
// level than FoundationDB's default serializable isolation, reducing
// transaction conflicts but making it harder to reason about concurrency.
//
// For more information on snapshot reads, see
// https://apple.github.io/foundationdb/developer-guide.html#snapshot-reads.
type Snapshot struct {
*transaction
}
// ReadTransact executes the caller-provided function, passing it the Snapshot
// receiver object (as a ReadTransaction).
//
// A panic of type Error during execution of the function will be recovered and
// returned to the caller as an error, but ReadTransact will not retry the
// function.
//
// By satisfying the ReadTransactor interface, Snapshot may be passed to a
// read-only transactional function from another (possibly read-only)
// transactional function, allowing composition.
//
// See the ReadTransactor interface for an example of using ReadTransact with
// Transaction, Snapshot and Database objects.
func (s Snapshot) ReadTransact(f func(ReadTransaction) (interface{}, error)) (r interface{}, e error) {
defer panicToError(&e)
r, e = f(s)
return
}
// Snapshot returns the receiver and allows Snapshot to satisfy the
// ReadTransaction interface.
func (s Snapshot) Snapshot() Snapshot {
return s
}
// Get is equivalent to (Transaction).Get, performed as a snapshot read.
func (s Snapshot) Get(key KeyConvertible) FutureByteSlice {
return s.get(key.FDBKey(), 1)
}
// GetKey is equivalent to (Transaction).GetKey, performed as a snapshot read.
func (s Snapshot) GetKey(sel Selectable) FutureKey {
return s.getKey(sel.FDBKeySelector(), 1)
}
// GetRange is equivalent to (Transaction).GetRange, performed as a snapshot
// read.
func (s Snapshot) GetRange(r Range, options RangeOptions) RangeResult {
return s.getRange(r, options, true)
}
// GetReadVersion is equivalent to (Transaction).GetReadVersion, performed as
// a snapshot read.
func (s Snapshot) GetReadVersion() FutureInt64 {
return s.getReadVersion()
}
// GetDatabase returns a handle to the database with which this snapshot is
// interacting.
func (s Snapshot) GetDatabase() Database {
return s.transaction.db
}

View file

@ -1,141 +0,0 @@
/*
* subspace.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go Subspace Layer
// Package subspace provides a convenient way to use FoundationDB tuples to
// define namespaces for different categories of data. The namespace is
// specified by a prefix tuple which is prepended to all tuples packed by the
// subspace. When unpacking a key with the subspace, the prefix tuple will be
// removed from the result.
//
// As a best practice, API clients should use at least one subspace for
// application data. For general guidance on subspace usage, see the Subspaces
// section of the Developer Guide
// (https://apple.github.io/foundationdb/developer-guide.html#subspaces).
package subspace
import (
"bytes"
"errors"
"github.com/apple/foundationdb/bindings/go/src/fdb"
"github.com/apple/foundationdb/bindings/go/src/fdb/tuple"
)
// Subspace represents a well-defined region of keyspace in a FoundationDB
// database.
type Subspace interface {
// Sub returns a new Subspace whose prefix extends this Subspace with the
// encoding of the provided element(s). If any of the elements are not a
// valid tuple.TupleElement, Sub will panic.
Sub(el ...tuple.TupleElement) Subspace
// Bytes returns the literal bytes of the prefix of this Subspace.
Bytes() []byte
// Pack returns the key encoding the specified Tuple with the prefix of this
// Subspace prepended.
Pack(t tuple.Tuple) fdb.Key
// Unpack returns the Tuple encoded by the given key with the prefix of this
// Subspace removed. Unpack will return an error if the key is not in this
// Subspace or does not encode a well-formed Tuple.
Unpack(k fdb.KeyConvertible) (tuple.Tuple, error)
// Contains returns true if the provided key starts with the prefix of this
// Subspace, indicating that the Subspace logically contains the key.
Contains(k fdb.KeyConvertible) bool
// All Subspaces implement fdb.KeyConvertible and may be used as
// FoundationDB keys (corresponding to the prefix of this Subspace).
fdb.KeyConvertible
// All Subspaces implement fdb.ExactRange and fdb.Range, and describe all
// keys logically in this Subspace.
fdb.ExactRange
}
type subspace struct {
b []byte
}
// AllKeys returns the Subspace corresponding to all keys in a FoundationDB
// database.
func AllKeys() Subspace {
return subspace{}
}
// Sub returns a new Subspace whose prefix is the encoding of the provided
// element(s). If any of the elements are not a valid tuple.TupleElement, a
// runtime panic will occur.
func Sub(el ...tuple.TupleElement) Subspace {
return subspace{tuple.Tuple(el).Pack()}
}
// FromBytes returns a new Subspace from the provided bytes.
func FromBytes(b []byte) Subspace {
s := make([]byte, len(b))
copy(s, b)
return subspace{s}
}
func (s subspace) Sub(el ...tuple.TupleElement) Subspace {
return subspace{concat(s.Bytes(), tuple.Tuple(el).Pack()...)}
}
func (s subspace) Bytes() []byte {
return s.b
}
func (s subspace) Pack(t tuple.Tuple) fdb.Key {
return fdb.Key(concat(s.b, t.Pack()...))
}
func (s subspace) Unpack(k fdb.KeyConvertible) (tuple.Tuple, error) {
key := k.FDBKey()
if !bytes.HasPrefix(key, s.b) {
return nil, errors.New("key is not in subspace")
}
return tuple.Unpack(key[len(s.b):])
}
func (s subspace) Contains(k fdb.KeyConvertible) bool {
return bytes.HasPrefix(k.FDBKey(), s.b)
}
func (s subspace) FDBKey() fdb.Key {
return fdb.Key(s.b)
}
func (s subspace) FDBRangeKeys() (fdb.KeyConvertible, fdb.KeyConvertible) {
return fdb.Key(concat(s.b, 0x00)), fdb.Key(concat(s.b, 0xFF))
}
func (s subspace) FDBRangeKeySelectors() (fdb.Selectable, fdb.Selectable) {
begin, end := s.FDBRangeKeys()
return fdb.FirstGreaterOrEqual(begin), fdb.FirstGreaterOrEqual(end)
}
func concat(a []byte, b ...byte) []byte {
r := make([]byte, len(a)+len(b))
copy(r, a)
copy(r[len(a):], b)
return r
}

View file

@ -1,458 +0,0 @@
/*
* transaction.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go API
package fdb
/*
#define FDB_API_VERSION 610
#include <foundationdb/fdb_c.h>
*/
import "C"
// A ReadTransaction can asynchronously read from a FoundationDB
// database. Transaction and Snapshot both satisfy the ReadTransaction
// interface.
//
// All ReadTransactions satisfy the ReadTransactor interface and may be used
// with read-only transactional functions.
type ReadTransaction interface {
Get(key KeyConvertible) FutureByteSlice
GetKey(sel Selectable) FutureKey
GetRange(r Range, options RangeOptions) RangeResult
GetReadVersion() FutureInt64
GetDatabase() Database
Snapshot() Snapshot
ReadTransactor
}
// Transaction is a handle to a FoundationDB transaction. Transaction is a
// lightweight object that may be efficiently copied, and is safe for concurrent
// use by multiple goroutines.
//
// In FoundationDB, a transaction is a mutable snapshot of a database. All read
// and write operations on a transaction see and modify an otherwise-unchanging
// version of the database and only change the underlying database if and when
// the transaction is committed. Read operations do see the effects of previous
// write operations on the same transaction. Committing a transaction usually
// succeeds in the absence of conflicts.
//
// Transactions group operations into a unit with the properties of atomicity,
// isolation, and durability. Transactions also provide the ability to maintain
// an applications invariants or integrity constraints, supporting the property
// of consistency. Together these properties are known as ACID.
//
// Transactions are also causally consistent: once a transaction has been
// successfully committed, all subsequently created transactions will see the
// modifications made by it.
type Transaction struct {
*transaction
}
type transaction struct {
ptr *C.FDBTransaction
db Database
}
// TransactionOptions is a handle with which to set options that affect a
// Transaction object. A TransactionOptions instance should be obtained with the
// (Transaction).Options method.
type TransactionOptions struct {
transaction *transaction
}
func (opt TransactionOptions) setOpt(code int, param []byte) error {
return setOpt(func(p *C.uint8_t, pl C.int) C.fdb_error_t {
return C.fdb_transaction_set_option(opt.transaction.ptr, C.FDBTransactionOption(code), p, pl)
}, param)
}
func (t *transaction) destroy() {
C.fdb_transaction_destroy(t.ptr)
}
// GetDatabase returns a handle to the database with which this transaction is
// interacting.
func (t Transaction) GetDatabase() Database {
return t.transaction.db
}
// Transact executes the caller-provided function, passing it the Transaction
// receiver object.
//
// A panic of type Error during execution of the function will be recovered and
// returned to the caller as an error, but Transact will not retry the function
// or commit the Transaction after the caller-provided function completes.
//
// By satisfying the Transactor interface, Transaction may be passed to a
// transactional function from another transactional function, allowing
// composition. The outermost transactional function must have been provided a
// Database, or else the transaction will never be committed.
//
// See the Transactor interface for an example of using Transact with
// Transaction and Database objects.
func (t Transaction) Transact(f func(Transaction) (interface{}, error)) (r interface{}, e error) {
defer panicToError(&e)
r, e = f(t)
return
}
// ReadTransact executes the caller-provided function, passing it the
// Transaction receiver object (as a ReadTransaction).
//
// A panic of type Error during execution of the function will be recovered and
// returned to the caller as an error, but ReadTransact will not retry the
// function.
//
// By satisfying the ReadTransactor interface, Transaction may be passed to a
// read-only transactional function from another (possibly read-only)
// transactional function, allowing composition.
//
// See the ReadTransactor interface for an example of using ReadTransact with
// Transaction, Snapshot and Database objects.
func (t Transaction) ReadTransact(f func(ReadTransaction) (interface{}, error)) (r interface{}, e error) {
defer panicToError(&e)
r, e = f(t)
return
}
// Cancel cancels a transaction. All pending or future uses of the transaction
// will encounter an error. The Transaction object may be reused after calling
// (Transaction).Reset.
//
// Be careful if you are using (Transaction).Reset and (Transaction).Cancel
// concurrently with the same transaction. Since they negate each others
// effects, a race condition between these calls will leave the transaction in
// an unknown state.
//
// If your program attempts to cancel a transaction after (Transaction).Commit
// has been called but before it returns, unpredictable behavior will
// result. While it is guaranteed that the transaction will eventually end up in
// a cancelled state, the commit may or may not occur. Moreover, even if the
// call to (Transaction).Commit appears to return a transaction_cancelled
// error, the commit may have occurred or may occur in the future. This can make
// it more difficult to reason about the order in which transactions occur.
func (t Transaction) Cancel() {
C.fdb_transaction_cancel(t.ptr)
}
// (Infrequently used) SetReadVersion sets the database version that the transaction will read from
// the database. The database cannot guarantee causal consistency if this method
// is used (the transactions reads will be causally consistent only if the
// provided read version has that property).
func (t Transaction) SetReadVersion(version int64) {
C.fdb_transaction_set_read_version(t.ptr, C.int64_t(version))
}
// Snapshot returns a Snapshot object, suitable for performing snapshot
// reads. Snapshot reads offer a more relaxed isolation level than
// FoundationDB's default serializable isolation, reducing transaction conflicts
// but making it harder to reason about concurrency.
//
// For more information on snapshot reads, see
// https://apple.github.io/foundationdb/developer-guide.html#snapshot-reads.
func (t Transaction) Snapshot() Snapshot {
return Snapshot{t.transaction}
}
// OnError determines whether an error returned by a Transaction method is
// retryable. Waiting on the returned future will return the same error when
// fatal, or return nil (after blocking the calling goroutine for a suitable
// delay) for retryable errors.
//
// Typical code will not use OnError directly. (Database).Transact uses
// OnError internally to implement a correct retry loop.
func (t Transaction) OnError(e Error) FutureNil {
return &futureNil{newFuture(C.fdb_transaction_on_error(t.ptr, C.fdb_error_t(e.Code)))}
}
// Commit attempts to commit the modifications made in the transaction to the
// database. Waiting on the returned future will block the calling goroutine
// until the transaction has either been committed successfully or an error is
// encountered. Any error should be passed to (Transaction).OnError to determine
// if the error is retryable or not.
//
// As with other client/server databases, in some failure scenarios a client may
// be unable to determine whether a transaction succeeded. For more information,
// see
// https://apple.github.io/foundationdb/developer-guide.html#transactions-with-unknown-results.
func (t Transaction) Commit() FutureNil {
return &futureNil{newFuture(C.fdb_transaction_commit(t.ptr))}
}
// Watch creates a watch and returns a FutureNil that will become ready when the
// watch reports a change to the value of the specified key.
//
// A watchs behavior is relative to the transaction that created it. A watch
// will report a change in relation to the keys value as readable by that
// transaction. The initial value used for comparison is either that of the
// transactions read version or the value as modified by the transaction itself
// prior to the creation of the watch. If the value changes and then changes
// back to its initial value, the watch might not report the change.
//
// Until the transaction that created it has been committed, a watch will not
// report changes made by other transactions. In contrast, a watch will
// immediately report changes made by the transaction itself. Watches cannot be
// created if the transaction has called SetReadYourWritesDisable on the
// Transaction options, and an attempt to do so will return a watches_disabled
// error.
//
// If the transaction used to create a watch encounters an error during commit,
// then the watch will be set with that error. A transaction whose commit
// result is unknown will set all of its watches with the commit_unknown_result
// error. If an uncommitted transaction is reset or destroyed, then any watches
// it created will be set with the transaction_cancelled error.
//
// By default, each database connection can have no more than 10,000 watches
// that have not yet reported a change. When this number is exceeded, an attempt
// to create a watch will return a too_many_watches error. This limit can be
// changed using SetMaxWatches on the Database. Because a watch outlives the
// transaction that creates it, any watch that is no longer needed should be
// cancelled by calling (FutureNil).Cancel on its returned future.
func (t Transaction) Watch(key KeyConvertible) FutureNil {
kb := key.FDBKey()
return &futureNil{newFuture(C.fdb_transaction_watch(t.ptr, byteSliceToPtr(kb), C.int(len(kb))))}
}
func (t *transaction) get(key []byte, snapshot int) FutureByteSlice {
return &futureByteSlice{future: newFuture(C.fdb_transaction_get(t.ptr, byteSliceToPtr(key), C.int(len(key)), C.fdb_bool_t(snapshot)))}
}
// Get returns the (future) value associated with the specified key. The read is
// performed asynchronously and does not block the calling goroutine. The future
// will become ready when the read is complete.
func (t Transaction) Get(key KeyConvertible) FutureByteSlice {
return t.get(key.FDBKey(), 0)
}
func (t *transaction) doGetRange(r Range, options RangeOptions, snapshot bool, iteration int) futureKeyValueArray {
begin, end := r.FDBRangeKeySelectors()
bsel := begin.FDBKeySelector()
esel := end.FDBKeySelector()
bkey := bsel.Key.FDBKey()
ekey := esel.Key.FDBKey()
return futureKeyValueArray{newFuture(C.fdb_transaction_get_range(t.ptr, byteSliceToPtr(bkey), C.int(len(bkey)), C.fdb_bool_t(boolToInt(bsel.OrEqual)), C.int(bsel.Offset), byteSliceToPtr(ekey), C.int(len(ekey)), C.fdb_bool_t(boolToInt(esel.OrEqual)), C.int(esel.Offset), C.int(options.Limit), C.int(0), C.FDBStreamingMode(options.Mode-1), C.int(iteration), C.fdb_bool_t(boolToInt(snapshot)), C.fdb_bool_t(boolToInt(options.Reverse))))}
}
func (t *transaction) getRange(r Range, options RangeOptions, snapshot bool) RangeResult {
f := t.doGetRange(r, options, snapshot, 1)
begin, end := r.FDBRangeKeySelectors()
return RangeResult{
t: t,
sr: SelectorRange{begin, end},
options: options,
snapshot: snapshot,
f: &f,
}
}
// GetRange performs a range read. The returned RangeResult represents all
// KeyValue objects kv where beginKey <= kv.Key < endKey, ordered by kv.Key
// (where beginKey and endKey are the keys described by the key selectors
// returned by r.FDBKeySelectors). All reads performed as a result of GetRange
// are asynchronous and do not block the calling goroutine.
func (t Transaction) GetRange(r Range, options RangeOptions) RangeResult {
return t.getRange(r, options, false)
}
func (t *transaction) getReadVersion() FutureInt64 {
return &futureInt64{newFuture(C.fdb_transaction_get_read_version(t.ptr))}
}
// (Infrequently used) GetReadVersion returns the (future) transaction read version. The read is
// performed asynchronously and does not block the calling goroutine. The future
// will become ready when the read version is available.
func (t Transaction) GetReadVersion() FutureInt64 {
return t.getReadVersion()
}
// Set associated the given key and value, overwriting any previous association
// with key. Set returns immediately, having modified the snapshot of the
// database represented by the transaction.
func (t Transaction) Set(key KeyConvertible, value []byte) {
kb := key.FDBKey()
C.fdb_transaction_set(t.ptr, byteSliceToPtr(kb), C.int(len(kb)), byteSliceToPtr(value), C.int(len(value)))
}
// Clear removes the specified key (and any associated value), if it
// exists. Clear returns immediately, having modified the snapshot of the
// database represented by the transaction.
func (t Transaction) Clear(key KeyConvertible) {
kb := key.FDBKey()
C.fdb_transaction_clear(t.ptr, byteSliceToPtr(kb), C.int(len(kb)))
}
// ClearRange removes all keys k such that begin <= k < end, and their
// associated values. ClearRange returns immediately, having modified the
// snapshot of the database represented by the transaction.
func (t Transaction) ClearRange(er ExactRange) {
begin, end := er.FDBRangeKeys()
bkb := begin.FDBKey()
ekb := end.FDBKey()
C.fdb_transaction_clear_range(t.ptr, byteSliceToPtr(bkb), C.int(len(bkb)), byteSliceToPtr(ekb), C.int(len(ekb)))
}
// (Infrequently used) GetCommittedVersion returns the version number at which a
// successful commit modified the database. This must be called only after the
// successful (non-error) completion of a call to Commit on this Transaction, or
// the behavior is undefined. Read-only transactions do not modify the database
// when committed and will have a committed version of -1. Keep in mind that a
// transaction which reads keys and then sets them to their current values may
// be optimized to a read-only transaction.
func (t Transaction) GetCommittedVersion() (int64, error) {
var version C.int64_t
if err := C.fdb_transaction_get_committed_version(t.ptr, &version); err != 0 {
return 0, Error{int(err)}
}
return int64(version), nil
}
// (Infrequently used) Returns a future which will contain the versionstamp
// which was used by any versionstamp operations in this transaction. The
// future will be ready only after the successful completion of a call to Commit
// on this Transaction. Read-only transactions do not modify the database when
// committed and will result in the future completing with an error. Keep in
// mind that a transaction which reads keys and then sets them to their current
// values may be optimized to a read-only transaction.
func (t Transaction) GetVersionstamp() FutureKey {
return &futureKey{future: newFuture(C.fdb_transaction_get_versionstamp(t.ptr))}
}
// Reset rolls back a transaction, completely resetting it to its initial
// state. This is logically equivalent to destroying the transaction and
// creating a new one.
func (t Transaction) Reset() {
C.fdb_transaction_reset(t.ptr)
}
func boolToInt(b bool) int {
if b {
return 1
}
return 0
}
func (t *transaction) getKey(sel KeySelector, snapshot int) FutureKey {
key := sel.Key.FDBKey()
return &futureKey{future: newFuture(C.fdb_transaction_get_key(t.ptr, byteSliceToPtr(key), C.int(len(key)), C.fdb_bool_t(boolToInt(sel.OrEqual)), C.int(sel.Offset), C.fdb_bool_t(snapshot)))}
}
// GetKey returns the future key referenced by the provided key selector. The
// read is performed asynchronously and does not block the calling
// goroutine. The future will become ready when the read version is available.
//
// By default, the key is cached for the duration of the transaction, providing
// a potential performance benefit. However, the value of the key is also
// retrieved, using network bandwidth. Invoking
// (TransactionOptions).SetReadYourWritesDisable will avoid both the caching and
// the increased network bandwidth.
func (t Transaction) GetKey(sel Selectable) FutureKey {
return t.getKey(sel.FDBKeySelector(), 0)
}
func (t Transaction) atomicOp(key []byte, param []byte, code int) {
C.fdb_transaction_atomic_op(t.ptr, byteSliceToPtr(key), C.int(len(key)), byteSliceToPtr(param), C.int(len(param)), C.FDBMutationType(code))
}
func addConflictRange(t *transaction, er ExactRange, crtype conflictRangeType) error {
begin, end := er.FDBRangeKeys()
bkb := begin.FDBKey()
ekb := end.FDBKey()
if err := C.fdb_transaction_add_conflict_range(t.ptr, byteSliceToPtr(bkb), C.int(len(bkb)), byteSliceToPtr(ekb), C.int(len(ekb)), C.FDBConflictRangeType(crtype)); err != 0 {
return Error{int(err)}
}
return nil
}
// AddReadConflictRange adds a range of keys to the transactions read conflict
// ranges as if you had read the range. As a result, other transactions that
// write a key in this range could cause the transaction to fail with a
// conflict.
//
// For more information on conflict ranges, see
// https://apple.github.io/foundationdb/developer-guide.html#conflict-ranges.
func (t Transaction) AddReadConflictRange(er ExactRange) error {
return addConflictRange(t.transaction, er, conflictRangeTypeRead)
}
func copyAndAppend(orig []byte, b byte) []byte {
ret := make([]byte, len(orig)+1)
copy(ret, orig)
ret[len(orig)] = b
return ret
}
// AddReadConflictKey adds a key to the transactions read conflict ranges as if
// you had read the key. As a result, other transactions that concurrently write
// this key could cause the transaction to fail with a conflict.
//
// For more information on conflict ranges, see
// https://apple.github.io/foundationdb/developer-guide.html#conflict-ranges.
func (t Transaction) AddReadConflictKey(key KeyConvertible) error {
return addConflictRange(t.transaction, KeyRange{key, Key(copyAndAppend(key.FDBKey(), 0x00))}, conflictRangeTypeRead)
}
// AddWriteConflictRange adds a range of keys to the transactions write
// conflict ranges as if you had cleared the range. As a result, other
// transactions that concurrently read a key in this range could fail with a
// conflict.
//
// For more information on conflict ranges, see
// https://apple.github.io/foundationdb/developer-guide.html#conflict-ranges.
func (t Transaction) AddWriteConflictRange(er ExactRange) error {
return addConflictRange(t.transaction, er, conflictRangeTypeWrite)
}
// AddWriteConflictKey adds a key to the transactions write conflict ranges as
// if you had written the key. As a result, other transactions that concurrently
// read this key could fail with a conflict.
//
// For more information on conflict ranges, see
// https://apple.github.io/foundationdb/developer-guide.html#conflict-ranges.
func (t Transaction) AddWriteConflictKey(key KeyConvertible) error {
return addConflictRange(t.transaction, KeyRange{key, Key(copyAndAppend(key.FDBKey(), 0x00))}, conflictRangeTypeWrite)
}
// Options returns a TransactionOptions instance suitable for setting options
// specific to this transaction.
func (t Transaction) Options() TransactionOptions {
return TransactionOptions{t.transaction}
}
func localityGetAddressesForKey(t *transaction, key KeyConvertible) FutureStringSlice {
kb := key.FDBKey()
return &futureStringSlice{newFuture(C.fdb_transaction_get_addresses_for_key(t.ptr, byteSliceToPtr(kb), C.int(len(kb))))}
}
// LocalityGetAddressesForKey returns the (future) public network addresses of
// each of the storage servers responsible for storing key and its associated
// value. The read is performed asynchronously and does not block the calling
// goroutine. The future will become ready when the read is complete.
func (t Transaction) LocalityGetAddressesForKey(key KeyConvertible) FutureStringSlice {
return localityGetAddressesForKey(t.transaction, key)
}

View file

@ -1,438 +0,0 @@
/*
* tuple.go
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// FoundationDB Go Tuple Layer
// Package tuple provides a layer for encoding and decoding multi-element tuples
// into keys usable by FoundationDB. The encoded key maintains the same sort
// order as the original tuple: sorted first by the first element, then by the
// second element, etc. This makes the tuple layer ideal for building a variety
// of higher-level data models.
//
// For general guidance on tuple usage, see the Tuple section of Data Modeling
// (https://apple.github.io/foundationdb/data-modeling.html#tuples).
//
// FoundationDB tuples can currently encode byte and unicode strings, integers,
// floats, doubles, booleans, UUIDs, tuples, and NULL values. In Go these are
// represented as []byte (or fdb.KeyConvertible), string, int64 (or int),
// float32, float64, bool, UUID, Tuple, and nil.
package tuple
import (
"bytes"
"encoding/binary"
"fmt"
"math"
"github.com/apple/foundationdb/bindings/go/src/fdb"
)
// A TupleElement is one of the types that may be encoded in FoundationDB
// tuples. Although the Go compiler cannot enforce this, it is a programming
// error to use an unsupported types as a TupleElement (and will typically
// result in a runtime panic).
//
// The valid types for TupleElement are []byte (or fdb.KeyConvertible), string,
// int64 (or int), float, double, bool, UUID, Tuple, and nil.
type TupleElement interface{}
// Tuple is a slice of objects that can be encoded as FoundationDB tuples. If
// any of the TupleElements are of unsupported types, a runtime panic will occur
// when the Tuple is packed.
//
// Given a Tuple T containing objects only of these types, then T will be
// identical to the Tuple returned by unpacking the byte slice obtained by
// packing T (modulo type normalization to []byte and int64).
type Tuple []TupleElement
// UUID wraps a basic byte array as a UUID. We do not provide any special
// methods for accessing or generating the UUID, but as Go does not provide
// a built-in UUID type, this simple wrapper allows for other libraries
// to write the output of their UUID type as a 16-byte array into
// an instance of this type.
type UUID [16]byte
// Type codes: These prefix the different elements in a packed Tuple
// to indicate what type they are.
const nilCode = 0x00
const bytesCode = 0x01
const stringCode = 0x02
const nestedCode = 0x05
const intZeroCode = 0x14
const posIntEnd = 0x1c
const negIntStart = 0x0c
const floatCode = 0x20
const doubleCode = 0x21
const falseCode = 0x26
const trueCode = 0x27
const uuidCode = 0x30
var sizeLimits = []uint64{
1<<(0*8) - 1,
1<<(1*8) - 1,
1<<(2*8) - 1,
1<<(3*8) - 1,
1<<(4*8) - 1,
1<<(5*8) - 1,
1<<(6*8) - 1,
1<<(7*8) - 1,
1<<(8*8) - 1,
}
func bisectLeft(u uint64) int {
var n int
for sizeLimits[n] < u {
n++
}
return n
}
func adjustFloatBytes(b []byte, encode bool) {
if (encode && b[0]&0x80 != 0x00) || (!encode && b[0]&0x80 == 0x00) {
// Negative numbers: flip all of the bytes.
for i := 0; i < len(b); i++ {
b[i] = b[i] ^ 0xff
}
} else {
// Positive number: flip just the sign bit.
b[0] = b[0] ^ 0x80
}
}
type packer struct {
buf []byte
}
func (p *packer) putByte(b byte) {
p.buf = append(p.buf, b)
}
func (p *packer) putBytes(b []byte) {
p.buf = append(p.buf, b...)
}
func (p *packer) putBytesNil(b []byte, i int) {
for i >= 0 {
p.putBytes(b[:i+1])
p.putByte(0xFF)
b = b[i+1:]
i = bytes.IndexByte(b, 0x00)
}
p.putBytes(b)
}
func (p *packer) encodeBytes(code byte, b []byte) {
p.putByte(code)
if i := bytes.IndexByte(b, 0x00); i >= 0 {
p.putBytesNil(b, i)
} else {
p.putBytes(b)
}
p.putByte(0x00)
}
func (p *packer) encodeInt(i int64) {
if i == 0 {
p.putByte(0x14)
return
}
var n int
var scratch [8]byte
switch {
case i > 0:
n = bisectLeft(uint64(i))
p.putByte(byte(intZeroCode + n))
binary.BigEndian.PutUint64(scratch[:], uint64(i))
case i < 0:
n = bisectLeft(uint64(-i))
p.putByte(byte(0x14 - n))
offsetEncoded := int64(sizeLimits[n]) + i
binary.BigEndian.PutUint64(scratch[:], uint64(offsetEncoded))
}
p.putBytes(scratch[8-n:])
}
func (p *packer) encodeFloat(f float32) {
var scratch [4]byte
binary.BigEndian.PutUint32(scratch[:], math.Float32bits(f))
adjustFloatBytes(scratch[:], true)
p.putByte(floatCode)
p.putBytes(scratch[:])
}
func (p *packer) encodeDouble(d float64) {
var scratch [8]byte
binary.BigEndian.PutUint64(scratch[:], math.Float64bits(d))
adjustFloatBytes(scratch[:], true)
p.putByte(doubleCode)
p.putBytes(scratch[:])
}
func (p *packer) encodeUUID(u UUID) {
p.putByte(uuidCode)
p.putBytes(u[:])
}
func (p *packer) encodeTuple(t Tuple, nested bool) {
if nested {
p.putByte(nestedCode)
}
for i, e := range t {
switch e := e.(type) {
case Tuple:
p.encodeTuple(e, true)
case nil:
p.putByte(nilCode)
if nested {
p.putByte(0xff)
}
case int64:
p.encodeInt(e)
case int:
p.encodeInt(int64(e))
case []byte:
p.encodeBytes(bytesCode, e)
case fdb.KeyConvertible:
p.encodeBytes(bytesCode, []byte(e.FDBKey()))
case string:
p.encodeBytes(stringCode, []byte(e))
case float32:
p.encodeFloat(e)
case float64:
p.encodeDouble(e)
case bool:
if e {
p.putByte(trueCode)
} else {
p.putByte(falseCode)
}
case UUID:
p.encodeUUID(e)
default:
panic(fmt.Sprintf("unencodable element at index %d (%v, type %T)", i, t[i], t[i]))
}
}
if nested {
p.putByte(0x00)
}
}
// Pack returns a new byte slice encoding the provided tuple. Pack will panic if
// the tuple contains an element of any type other than []byte,
// fdb.KeyConvertible, string, int64, int, float32, float64, bool, tuple.UUID,
// nil, or a Tuple with elements of valid types.
//
// Tuple satisfies the fdb.KeyConvertible interface, so it is not necessary to
// call Pack when using a Tuple with a FoundationDB API function that requires a
// key.
func (t Tuple) Pack() []byte {
p := packer{buf: make([]byte, 0, 64)}
p.encodeTuple(t, false)
return p.buf
}
func findTerminator(b []byte) int {
bp := b
var length int
for {
idx := bytes.IndexByte(bp, 0x00)
length += idx
if idx+1 == len(bp) || bp[idx+1] != 0xFF {
break
}
length += 2
bp = bp[idx+2:]
}
return length
}
func decodeBytes(b []byte) ([]byte, int) {
idx := findTerminator(b[1:])
return bytes.Replace(b[1:idx+1], []byte{0x00, 0xFF}, []byte{0x00}, -1), idx + 2
}
func decodeString(b []byte) (string, int) {
bp, idx := decodeBytes(b)
return string(bp), idx
}
func decodeInt(b []byte) (int64, int) {
if b[0] == intZeroCode {
return 0, 1
}
var neg bool
n := int(b[0]) - intZeroCode
if n < 0 {
n = -n
neg = true
}
bp := make([]byte, 8)
copy(bp[8-n:], b[1:n+1])
var ret int64
binary.Read(bytes.NewBuffer(bp), binary.BigEndian, &ret)
if neg {
ret -= int64(sizeLimits[n])
}
return ret, n + 1
}
func decodeFloat(b []byte) (float32, int) {
bp := make([]byte, 4)
copy(bp, b[1:])
adjustFloatBytes(bp, false)
var ret float32
binary.Read(bytes.NewBuffer(bp), binary.BigEndian, &ret)
return ret, 5
}
func decodeDouble(b []byte) (float64, int) {
bp := make([]byte, 8)
copy(bp, b[1:])
adjustFloatBytes(bp, false)
var ret float64
binary.Read(bytes.NewBuffer(bp), binary.BigEndian, &ret)
return ret, 9
}
func decodeUUID(b []byte) (UUID, int) {
var u UUID
copy(u[:], b[1:])
return u, 17
}
func decodeTuple(b []byte, nested bool) (Tuple, int, error) {
var t Tuple
var i int
for i < len(b) {
var el interface{}
var off int
switch {
case b[i] == nilCode:
if !nested {
el = nil
off = 1
} else if i+1 < len(b) && b[i+1] == 0xff {
el = nil
off = 2
} else {
return t, i + 1, nil
}
case b[i] == bytesCode:
el, off = decodeBytes(b[i:])
case b[i] == stringCode:
el, off = decodeString(b[i:])
case negIntStart <= b[i] && b[i] <= posIntEnd:
el, off = decodeInt(b[i:])
case b[i] == floatCode:
if i+5 > len(b) {
return nil, i, fmt.Errorf("insufficient bytes to decode float starting at position %d of byte array for tuple", i)
}
el, off = decodeFloat(b[i:])
case b[i] == doubleCode:
if i+9 > len(b) {
return nil, i, fmt.Errorf("insufficient bytes to decode double starting at position %d of byte array for tuple", i)
}
el, off = decodeDouble(b[i:])
case b[i] == trueCode:
el = true
off = 1
case b[i] == falseCode:
el = false
off = 1
case b[i] == uuidCode:
if i+17 > len(b) {
return nil, i, fmt.Errorf("insufficient bytes to decode UUID starting at position %d of byte array for tuple", i)
}
el, off = decodeUUID(b[i:])
case b[i] == nestedCode:
var err error
el, off, err = decodeTuple(b[i+1:], true)
if err != nil {
return nil, i, err
}
off++
default:
return nil, i, fmt.Errorf("unable to decode tuple element with unknown typecode %02x", b[i])
}
t = append(t, el)
i += off
}
return t, i, nil
}
// Unpack returns the tuple encoded by the provided byte slice, or an error if
// the key does not correctly encode a FoundationDB tuple.
func Unpack(b []byte) (Tuple, error) {
t, _, err := decodeTuple(b, false)
return t, err
}
// FDBKey returns the packed representation of a Tuple, and allows Tuple to
// satisfy the fdb.KeyConvertible interface. FDBKey will panic in the same
// circumstances as Pack.
func (t Tuple) FDBKey() fdb.Key {
return t.Pack()
}
// FDBRangeKeys allows Tuple to satisfy the fdb.ExactRange interface. The range
// represents all keys that encode tuples strictly starting with a Tuple (that
// is, all tuples of greater length than the Tuple of which the Tuple is a
// prefix).
func (t Tuple) FDBRangeKeys() (fdb.KeyConvertible, fdb.KeyConvertible) {
p := t.Pack()
return fdb.Key(concat(p, 0x00)), fdb.Key(concat(p, 0xFF))
}
// FDBRangeKeySelectors allows Tuple to satisfy the fdb.Range interface. The
// range represents all keys that encode tuples strictly starting with a Tuple
// (that is, all tuples of greater length than the Tuple of which the Tuple is a
// prefix).
func (t Tuple) FDBRangeKeySelectors() (fdb.Selectable, fdb.Selectable) {
b, e := t.FDBRangeKeys()
return fdb.FirstGreaterOrEqual(b), fdb.FirstGreaterOrEqual(e)
}
func concat(a []byte, b ...byte) []byte {
r := make([]byte, len(a)+len(b))
copy(r, a)
copy(r[len(a):], b)
return r
}

26
vendor/vendor.json vendored
View file

@ -1,6 +1,6 @@
{ {
"comment": "", "comment": "",
"ignore": "test", "ignore": "test github.com/apple/foundationdb/bindings/go/src/fdb/",
"package": [ "package": [
{ {
"path": "appengine", "path": "appengine",
@ -372,30 +372,6 @@
"revision": "c5825d35a94a5a25d936ebe6bed5779255d02cf4", "revision": "c5825d35a94a5a25d936ebe6bed5779255d02cf4",
"revisionTime": "2018-10-12T12:19:43Z" "revisionTime": "2018-10-12T12:19:43Z"
}, },
{
"checksumSHA1": "x5pRbco8m4cXGS3u/NnbzIZJ1Hg=",
"path": "github.com/apple/foundationdb/bindings/go/src/fdb",
"revision": "c255feb76dedc9ba15e2064bd78465166822c2b5",
"revisionTime": "2018-10-15T20:09:47Z"
},
{
"checksumSHA1": "VxeCD1wWXix+y3N83t8PHmXDTIo=",
"path": "github.com/apple/foundationdb/bindings/go/src/fdb/directory",
"revision": "c255feb76dedc9ba15e2064bd78465166822c2b5",
"revisionTime": "2018-10-15T20:09:47Z"
},
{
"checksumSHA1": "im/gTxCCpiNchCB6gNYZez+4pYI=",
"path": "github.com/apple/foundationdb/bindings/go/src/fdb/subspace",
"revision": "c255feb76dedc9ba15e2064bd78465166822c2b5",
"revisionTime": "2018-10-15T20:09:47Z"
},
{
"checksumSHA1": "EkxFtJERI529/WjdfgoLGzQ/yJM=",
"path": "github.com/apple/foundationdb/bindings/go/src/fdb/tuple",
"revision": "c255feb76dedc9ba15e2064bd78465166822c2b5",
"revisionTime": "2018-10-15T20:09:47Z"
},
{ {
"checksumSHA1": "DUX4pOK9NKSAzC6RRXniLviyByA=", "checksumSHA1": "DUX4pOK9NKSAzC6RRXniLviyByA=",
"path": "github.com/armon/go-metrics", "path": "github.com/armon/go-metrics",

View file

@ -11,7 +11,7 @@ description: |-
# FoundationDB Storage Backend # FoundationDB Storage Backend
The FoundationDB storage backend is used to persist Vault's data in The FoundationDB storage backend is used to persist Vault's data in
[FoundationDB][foundationdb] table. [FoundationDB][foundationdb].
The backend needs to be explicitly enabled at build time, and is not available The backend needs to be explicitly enabled at build time, and is not available
in the standard Vault binary distribution. Please refer to the documentation in the standard Vault binary distribution. Please refer to the documentation
@ -29,8 +29,15 @@ accompanying the backend's source in the Vault source tree.
```hcl ```hcl
storage "foundationdb" { storage "foundationdb" {
api_version = 510 api_version = 520
cluster_file = "/path/to/fdb.cluster" cluster_file = "/path/to/fdb.cluster"
tls_verify_peers = "I.CN=MyTrustedIssuer,I.O=MyCompany\, Inc.,I.OU=Certification Authority"
tls_ca_file = "/path/to/ca_bundle.pem"
tls_cert_file = "/path/to/cert.pem"
tls_key_file = "/path/to/key.pem"
tls_password = "PrivateKeyPassword"
path = "vault-top-level-directory" path = "vault-top-level-directory"
ha_enabled = "true" ha_enabled = "true"
} }
@ -39,13 +46,30 @@ storage "foundationdb" {
## `foundationdb` Parameters ## `foundationdb` Parameters
- `api_version` `(int)` - The FoundationDB API version to use; this is a - `api_version` `(int)` - The FoundationDB API version to use; this is a
required parameter and doesn't have a default value. Future versions will required parameter and doesn't have a default value. The minimum required API
impose a minimum API version to access newer features. version is 520.
- `cluster_file` `(string)` - The path to the cluster file containing the - `cluster_file` `(string)` - The path to the cluster file containing the
connection data for the target cluster; this is a required parameter and connection data for the target cluster; this is a required parameter and
doesn't have a default value. doesn't have a default value.
- `tls_verify_peers` `(string)` - The peer certificate verification criteria;
this parameter is mandatory if TLS is enabled. Refer to the [FoundationDB TLS]
[fdb-tls] documentation.
- `tls_ca_file` `(string)` - The path to the CA certificate bundle file; this
parameter is mandatory if TLS is enabled.
- `tls_cert_file` `(string)` - The path to the certificate file; specifying this
parameter together with `tls_key_file` will enable TLS support.
- `tls_key_file` `(string)` - The path to the key file; specifying this
parameter together with `tls_cert_file` will enable TLS support.
- `tls_password` `(string)` - The password needed to decrypt `tls_key_file`, if
it is encrypted; optional. This can also be specified via the
`FDB_TLS_PASSWORD` environment variable.
- `path` `(string: "vault")` - The path of the top-level FoundationDB directory - `path` `(string: "vault")` - The path of the top-level FoundationDB directory
(using the directory layer) under which the Vault data will reside. (using the directory layer) under which the Vault data will reside.
@ -73,8 +97,9 @@ version; during cluster upgrades, multiple server versions will be running
in the cluster, and the client must cope with that situation. in the cluster, and the client must cope with that situation.
This is handled by the (primary) client library having the ability to load This is handled by the (primary) client library having the ability to load
a different version of the client library to connect to a particular server; a different, later version of the client library to connect to a particular
it is referred to as the [multi-version client][multi-ver-client] feature. server; it is referred to as the [multi-version client][multi-ver-client]
feature.
#### Client setup with `LD_LIBRARY_PATH` #### Client setup with `LD_LIBRARY_PATH`
@ -137,4 +162,5 @@ $ /path/to/bin/vault ...
``` ```
[foundationdb]: https://www.foundationdb.org [foundationdb]: https://www.foundationdb.org
[fdb-tls]: https://apple.github.io/foundationdb/tls.html
[multi-ver-client]: https://apple.github.io/foundationdb/api-general.html#multi-version-client-api [multi-ver-client]: https://apple.github.io/foundationdb/api-general.html#multi-version-client-api