1/* 2 * fdb.go 3 * 4 * This source file is part of the FoundationDB open source project 5 * 6 * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors 7 * 8 * Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21// FoundationDB Go API 22 23package fdb 24 25/* 26 #define FDB_API_VERSION 610 27 #include <foundationdb/fdb_c.h> 28 #include <stdlib.h> 29*/ 30import "C" 31 32import ( 33 "bytes" 34 "fmt" 35 "log" 36 "runtime" 37 "sync" 38 "unsafe" 39) 40 41// Would put this in futures.go but for the documented issue with 42// exports and functions in preamble 43// (https://code.google.com/p/go-wiki/wiki/cgo#Global_functions) 44//export unlockMutex 45func unlockMutex(p unsafe.Pointer) { 46 m := (*sync.Mutex)(p) 47 m.Unlock() 48} 49 50// A Transactor can execute a function that requires a Transaction. Functions 51// written to accept a Transactor are called transactional functions, and may be 52// called with either a Database or a Transaction. 53type Transactor interface { 54 // Transact executes the caller-provided function, providing it with a 55 // Transaction (itself a Transactor, allowing composition of transactional 56 // functions). 57 Transact(func(Transaction) (interface{}, error)) (interface{}, error) 58 59 // All Transactors are also ReadTransactors, allowing them to be used with 60 // read-only transactional functions. 61 ReadTransactor 62} 63 64// A ReadTransactor can execute a function that requires a 65// ReadTransaction. Functions written to accept a ReadTransactor are called 66// read-only transactional functions, and may be called with a Database, 67// Transaction or Snapshot. 68type ReadTransactor interface { 69 // ReadTransact executes the caller-provided function, providing it with a 70 // ReadTransaction (itself a ReadTransactor, allowing composition of 71 // read-only transactional functions). 72 ReadTransact(func(ReadTransaction) (interface{}, error)) (interface{}, error) 73} 74 75func setOpt(setter func(*C.uint8_t, C.int) C.fdb_error_t, param []byte) error { 76 if err := setter(byteSliceToPtr(param), C.int(len(param))); err != 0 { 77 return Error{int(err)} 78 } 79 80 return nil 81} 82 83// NetworkOptions is a handle with which to set options that affect the entire 84// FoundationDB client. A NetworkOptions instance should be obtained with the 85// fdb.Options function. 86type NetworkOptions struct { 87} 88 89// Options returns a NetworkOptions instance suitable for setting options that 90// affect the entire FoundationDB client. 91func Options() NetworkOptions { 92 return NetworkOptions{} 93} 94 95func (opt NetworkOptions) setOpt(code int, param []byte) error { 96 networkMutex.Lock() 97 defer networkMutex.Unlock() 98 99 if apiVersion == 0 { 100 return errAPIVersionUnset 101 } 102 103 return setOpt(func(p *C.uint8_t, pl C.int) C.fdb_error_t { 104 return C.fdb_network_set_option(C.FDBNetworkOption(code), p, pl) 105 }, param) 106} 107 108// APIVersion determines the runtime behavior the fdb package. If the requested 109// version is not supported by both the fdb package and the FoundationDB C 110// library, an error will be returned. APIVersion must be called prior to any 111// other functions in the fdb package. 112// 113// Currently, this package supports API versions 200 through 610. 114// 115// Warning: When using the multi-version client API, setting an API version that 116// is not supported by a particular client library will prevent that client from 117// being used to connect to the cluster. In particular, you should not advance 118// the API version of your application after upgrading your client until the 119// cluster has also been upgraded. 120func APIVersion(version int) error { 121 headerVersion := 610 122 123 networkMutex.Lock() 124 defer networkMutex.Unlock() 125 126 if apiVersion != 0 { 127 if apiVersion == version { 128 return nil 129 } 130 return errAPIVersionAlreadySet 131 } 132 133 if version < 200 || version > 610 { 134 return errAPIVersionNotSupported 135 } 136 137 if e := C.fdb_select_api_version_impl(C.int(version), C.int(headerVersion)); e != 0 { 138 if e != 0 { 139 if e == 2203 { 140 maxSupportedVersion := C.fdb_get_max_api_version() 141 if headerVersion > int(maxSupportedVersion) { 142 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.", headerVersion, maxSupportedVersion) 143 } 144 return fmt.Errorf("API version %d is not supported by the installed FoundationDB C library.", version) 145 } 146 return Error{int(e)} 147 } 148 } 149 150 apiVersion = version 151 152 return nil 153} 154 155// Determines if an API version has already been selected, i.e., if 156// APIVersion or MustAPIVersion have already been called. 157func IsAPIVersionSelected() bool { 158 return apiVersion != 0 159} 160 161// Returns the API version that has been selected through APIVersion 162// or MustAPIVersion. If the version has already been selected, then 163// the first value returned is the API version and the error is 164// nil. If the API version has not yet been set, then the error 165// will be non-nil. 166func GetAPIVersion() (int, error) { 167 if IsAPIVersionSelected() { 168 return apiVersion, nil 169 } 170 return 0, errAPIVersionUnset 171} 172 173// MustAPIVersion is like APIVersion but panics if the API version is not 174// supported. 175func MustAPIVersion(version int) { 176 err := APIVersion(version) 177 if err != nil { 178 panic(err) 179 } 180} 181 182// MustGetAPIVersion is like GetAPIVersion but panics if the API version 183// has not yet been set. 184func MustGetAPIVersion() int { 185 apiVersion, err := GetAPIVersion() 186 if err != nil { 187 panic(err) 188 } 189 return apiVersion 190} 191 192var apiVersion int 193var networkStarted bool 194var networkMutex sync.Mutex 195 196var openDatabases map[string]Database 197 198func init() { 199 openDatabases = make(map[string]Database) 200} 201 202func startNetwork() error { 203 if e := C.fdb_setup_network(); e != 0 { 204 return Error{int(e)} 205 } 206 207 go func() { 208 e := C.fdb_run_network() 209 if e != 0 { 210 log.Printf("Unhandled error in FoundationDB network thread: %v (%v)\n", C.GoString(C.fdb_get_error(e)), e) 211 } 212 }() 213 214 networkStarted = true 215 216 return nil 217} 218 219// Deprecated: the network is started automatically when a database is opened. 220// StartNetwork initializes the FoundationDB client networking engine. StartNetwork 221// must not be called more than once. 222func StartNetwork() error { 223 networkMutex.Lock() 224 defer networkMutex.Unlock() 225 226 if apiVersion == 0 { 227 return errAPIVersionUnset 228 } 229 230 return startNetwork() 231} 232 233// DefaultClusterFile should be passed to fdb.Open to allow the FoundationDB C 234// library to select the platform-appropriate default cluster file on the current machine. 235const DefaultClusterFile string = "" 236 237// OpenDefault returns a database handle to the FoundationDB cluster identified 238// by the DefaultClusterFile on the current machine. The FoundationDB client 239// networking engine will be initialized first, if necessary. 240func OpenDefault() (Database, error) { 241 return OpenDatabase(DefaultClusterFile) 242} 243 244// MustOpenDefault is like OpenDefault but panics if the default database cannot 245// be opened. 246func MustOpenDefault() Database { 247 db, err := OpenDefault() 248 if err != nil { 249 panic(err) 250 } 251 return db 252} 253 254// Open returns a database handle to the FoundationDB cluster identified 255// by the provided cluster file and database name. 256func OpenDatabase(clusterFile string) (Database, error) { 257 networkMutex.Lock() 258 defer networkMutex.Unlock() 259 260 if apiVersion == 0 { 261 return Database{}, errAPIVersionUnset 262 } 263 264 var e error 265 266 if !networkStarted { 267 e = startNetwork() 268 if e != nil { 269 return Database{}, e 270 } 271 } 272 273 db, ok := openDatabases[clusterFile] 274 if !ok { 275 db, e = createDatabase(clusterFile) 276 if e != nil { 277 return Database{}, e 278 } 279 openDatabases[clusterFile] = db 280 } 281 282 return db, nil 283} 284 285func MustOpenDatabase(clusterFile string) Database { 286 db, err := OpenDatabase(clusterFile) 287 if err != nil { 288 panic(err) 289 } 290 return db 291} 292 293// Deprecated: Use OpenDatabase instead 294// The database name must be []byte("DB"). 295func Open(clusterFile string, dbName []byte) (Database, error) { 296 if bytes.Compare(dbName, []byte("DB")) != 0 { 297 return Database{}, Error{2013} // invalid_database_name 298 } 299 return OpenDatabase(clusterFile) 300} 301 302// Deprecated: Use MustOpenDatabase instead 303// MustOpen is like Open but panics if the database cannot be opened. 304func MustOpen(clusterFile string, dbName []byte) Database { 305 db, err := Open(clusterFile, dbName) 306 if err != nil { 307 panic(err) 308 } 309 return db 310} 311 312func createDatabase(clusterFile string) (Database, error) { 313 var cf *C.char 314 315 if len(clusterFile) != 0 { 316 cf = C.CString(clusterFile) 317 defer C.free(unsafe.Pointer(cf)) 318 } 319 320 var outdb *C.FDBDatabase 321 if err := C.fdb_create_database(cf, &outdb); err != 0 { 322 return Database{}, Error{int(err)} 323 } 324 325 db := &database{outdb} 326 runtime.SetFinalizer(db, (*database).destroy) 327 328 return Database{db}, nil 329} 330 331// Deprecated: Use OpenDatabase instead. 332// CreateCluster returns a cluster handle to the FoundationDB cluster identified 333// by the provided cluster file. 334func CreateCluster(clusterFile string) (Cluster, error) { 335 networkMutex.Lock() 336 defer networkMutex.Unlock() 337 338 if apiVersion == 0 { 339 return Cluster{}, errAPIVersionUnset 340 } 341 342 if !networkStarted { 343 return Cluster{}, errNetworkNotSetup 344 } 345 346 return Cluster{clusterFile}, nil 347} 348 349func byteSliceToPtr(b []byte) *C.uint8_t { 350 if len(b) > 0 { 351 return (*C.uint8_t)(unsafe.Pointer(&b[0])) 352 } 353 return nil 354} 355 356// A KeyConvertible can be converted to a FoundationDB Key. All functions in the 357// FoundationDB API that address a specific key accept a KeyConvertible. 358type KeyConvertible interface { 359 FDBKey() Key 360} 361 362// Key represents a FoundationDB key, a lexicographically-ordered sequence of 363// bytes. Key implements the KeyConvertible interface. 364type Key []byte 365 366// FDBKey allows Key to (trivially) satisfy the KeyConvertible interface. 367func (k Key) FDBKey() Key { 368 return k 369} 370 371// String describes the key as a human readable string. 372func (k Key) String() string { 373 return Printable(k) 374} 375 376// Printable returns a human readable version of a byte array. The bytes that correspond with 377// ASCII printable characters [32-127) are passed through. Other bytes are 378// replaced with \x followed by a two character zero-padded hex code for byte. 379func Printable(d []byte) string { 380 buf := new(bytes.Buffer) 381 for _, b := range d { 382 if b >= 32 && b < 127 && b != '\\' { 383 buf.WriteByte(b) 384 continue 385 } 386 if b == '\\' { 387 buf.WriteString("\\\\") 388 continue 389 } 390 buf.WriteString(fmt.Sprintf("\\x%02x", b)) 391 } 392 return buf.String() 393} 394 395func panicToError(e *error) { 396 if r := recover(); r != nil { 397 fe, ok := r.(Error) 398 if ok { 399 *e = fe 400 } else { 401 panic(r) 402 } 403 } 404} 405