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