In development. Use with care, schema are about to change in the future.
Before you start STORED document, a layer must be inited. This way you will describe all the objects and indexes. Init part should be executed before the actual part of your application. Example:
var dbUser, dbChat *stored.Object
var dbUserChat *stored.Relation
func init() { // init database
cluster := stored.Connect("./fdb.cluster")
err := cluster.Err()
if err != nil {
fmt.Println("DB Problem", err)
// no "return" needed, if db will wake up, client library will catch it up
}
db := cluster.Directory("test")
user := db.Object("user", User{})
chat := db.Object("chat", Chat{})
dbUserChat = user.N2N(chat)
dbUser = user.Done() // finish object
dbChat = chat.Done()
}
It is required to create a variable for each database object. This may seems like unnecessary code but this approach allowes you to have much more control over your source code. For example it makes easy to find all usage of specific object in your codebase.
All the steps will be described below:
cluster := stored.Connect("./fdb.cluster")
All objects should be stored in a directory. Using directories you are able to separate different logical parts of application.
db := cluster.Directory("test")
STORED could use any struct defined in your app as a schema, all you need is to add annotations, like stored:"online,mutable"
mutual tag means this field will be changed often, so should not be packed.
type dbUser struct {
ID int64 `stored:"id"`
Name string `stored:"name"`
Login string `stored:"login"`
Online bool `stored:"online,mutable"`
}
List of options available:
- mutable indicates that field should kept separately if it going to be changed frequently (not implemented yet)
Objects is a main workhorse of stored FoundationDB layer. You should init objects for all the objects in your application at the initialization part of application.
user := db.Object("user", User{}) // User could be any struct in your project
Alternative to setting primary in struct define annotation is setting it directly.
user.Primary("id")
Primary index could be multiple, for example:
user.Primary("chat_id", "message_id")
In this case the combination of values will be the primary key. Fields order should not change.
This is the best way to generate new indexes for objects with fields like int64. IDDate is most suitable way to generate unique identifiers in most cases. IDDate will generate int64 identifier based on current timestamp plus some random seed. This way ID could serve double purpose storing ID plus storing timestamp of adding the object. Since int64 is not precise enough to completely illuminate collisions, if field is in primary index at the moment of Add STORED will check that no other key with such ID presented.
user.IDDate("id") // field id should be int64 or uint64
You should use IDRandom when you do not want your ID to unveil timestamp of object creation. Since int64 is not precise enough to completely illuminate collisions, if field is in primary index at the moment of Add STORED will check that no other key with such ID presented.
user.IDRandom("id") // field id should be int64 or uint64
Autoincrement is an easy way to provide automatically incremented values to an field. At the moment of each Add new object will be written with incremented counter, 1,2,3 etc..
But autoincrement should be used with care, since incrementing of a counter creates collision of transactions you should not use this options when there are more than 100 Adds per second
Any key could be setup as autoincremented.
user.AutoIncrement("id")
this way the value of this field will be set automaticly if Add dbUser.Add(&user)
method triggered.
Unique creates unique index. You could fetch document directly using this index. Add and Set methods would fail if other item with same unique index presented.
user.Unique("login")
Index creates regular index. Could be many rows with this index. You are able to fetch first row or list of rows.
user.Index("login")
N2N is the most usefull type of relations between database objects. N2N represents many to many type of connection.
dbUserChat := user.N2N(chat)
In this example dbUserChat represents relation when any user has unlimited amount of connected chats and any chat has unlimited amount of connected users. Also it is available to set any data value to each connection (user to chat and chat to user)
If database is successfully inited and schema is set up, you are ok to work with defined database objects. Make sure that init section is triggered once and before any work with database.
This way stored will write user object in set of keys each for each field with stored:"some_key"
type annotation
user := User{1, "John", "john"}
err := dbUser.Set(user).Err()
If you have autoincrement option at your primary field you are able to Add new rows
user := User{0, "John", "john"}
err := dbUser.Add(&user).Err() // after this user.ID will be 1
You could use method Get to fetch any object from stored by primary key
user := User{1}
err := dbUser.Get(&user).Err()
Also you could perform multiget. This way a lot of items will be requested simultaneously
users := []*User{&User{1},&User{2},&User{3},&User{4}}
err := dbUser.MultiGet(users).Err()
user := User{}
err := dbUser.GetBy("login", "john").Scan(&user)
Before using the connection you should create new relation at #init section
dbUserChat.Set(user, chat)
user and chat objects should contain primary index values There are cases when inside the relation you want to store some data, for example say:
- in user-chat connection you want to store last message_id user read
- in chat-user connection you want to store user join date in this chat
dbUserChat.ClientData("last_message") // last_message is the field at the client (Chat) object
dbUserChat.HostData("join_date") // last_message is the field at the client (Chat) object
Data object could be any type, even the struct. But complicated struct object could got
Say you have N2N relation between users and chats.
- GetClients allow you to fetch all objects using host of this relation
chats := []Chat{}
err = dbUserChat.GetClients(user, nil, 100).ScanAll(&chats)
- GetHosts allow you to fetch all objects using client of this relation
users := []User{}
err = dbUserChat.GetHosts(chat, nil, 100).ScanAll(&users)
Stored has set of unit tests, you can easily run to check that everything set up properly. Use this simple code snippet to run tests on your database.
dbDriver := stored.Connect("./fdb.cluster")
stored.TestsRun(dbDriver)
- Indexes
- AutoIncrement
- Multiple primary
- Store schema inside FoundationDB
- Schema migration (delete each item with old schema and set with new one)