Commit d645c4d3 authored by Juan Batiz-Benet's avatar Juan Batiz-Benet

added key logic.

parent 01e32a5c
package datastore
import (
"code.google.com/p/go-uuid/uuid"
"path"
"strings"
)
/*
A Key represents the unique identifier of an object.
Our Key scheme is inspired by file systems and Google App Engine key model.
Keys are meant to be unique across a system. Keys are hierarchical,
incorporating more and more specific namespaces. Thus keys can be deemed
'children' or 'ancestors' of other keys::
Key("/Comedy")
Key("/Comedy/MontyPython")
Also, every namespace can be parametrized to embed relevant object
information. For example, the Key `name` (most specific namespace) could
include the object type::
Key("/Comedy/MontyPython/Actor:JohnCleese")
Key("/Comedy/MontyPython/Sketch:CheeseShop")
Key("/Comedy/MontyPython/Sketch:CheeseShop/Character:Mousebender")
*/
type Key struct {
string
}
func NewKey(s string) Key {
k := Key{s}
k.Clean()
return k
}
// Cleans up a Key, using path.Clean.
func (k *Key) Clean() {
k.string = path.Clean("/" + k.string)
}
// Returns the string value of Key
func (k Key) String() string {
return k.string
}
// Returns the `list` representation of this Key.
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").List()
// ["Comedy", "MontyPythong", "Actor:JohnCleese"]
func (k Key) List() []string {
return strings.Split(k.string, "/")[1:]
}
// Returns the reverse of this Key.
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").Reverse()
// NewKey("/Actor:JohnCleese/MontyPython/Comedy")
func (k Key) Reverse() Key {
l := k.List()
r := make([]string, len(l), len(l))
for i, e := range l {
r[len(l) - i - 1] = e
}
return NewKey(strings.Join(r, "/"))
}
// Returns the `namespaces` making up this Key.
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").List()
// ["Comedy", "MontyPythong", "Actor:JohnCleese"]
func (k Key) Namespaces() []string {
return k.List()
}
// Returns the "base" namespace of this key (like path.Base(filename))
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").BaseNamespace()
// "Actor:JohnCleese"
func (k Key) BaseNamespace() string {
n := k.Namespaces()
return n[len(n) - 1]
}
// Returns the "type" of this key (value of last namespace).
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").List()
// "Actor"
func (k Key) Type() string {
return NamespaceType(k.BaseNamespace())
}
// Returns the "name" of this key (field of last namespace).
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").List()
// "Actor"
func (k Key) Name() string {
return NamespaceValue(k.BaseNamespace())
}
// Returns an "instance" of this type key (appends value to namespace).
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").List()
// "JohnCleese"
func (k Key) Instance(s string) Key {
return NewKey(k.string + ":" + s)
}
// Returns the "path" of this key (parent + type).
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").Path()
// NewKey("/Comedy/MontyPython/Actor")
func (k Key) Path() Key {
s := k.Parent().string + "/" + NamespaceType(k.BaseNamespace())
return NewKey(s)
}
// Returns the `parent` Key of this Key.
// NewKey("/Comedy/MontyPython/Actor:JohnCleese").Parent()
// NewKey("/Comedy/MontyPython")
func (k Key) Parent() Key {
n := k.List()
if len(n) == 1 {
return NewKey("/")
}
return NewKey(strings.Join(n[:len(n) - 1], "/"))
}
// Returns the `child` Key of this Key.
// NewKey("/Comedy/MontyPython").Child("Actor:JohnCleese")
// NewKey("/Comedy/MontyPython/Actor:JohnCleese")
func (k Key) Child(s string) Key {
return NewKey(k.string + "/" + s)
}
// Returns whether this key is an ancestor of `other`
// NewKey("/Comedy").IsAncestorOf("/Comedy/MontyPython")
// true
func (k Key) IsAncestorOf(other Key) bool {
if other.string == k.string {
return false
}
return strings.HasPrefix(other.string, k.string)
}
// Returns whether this key is a descendent of `other`
// NewKey("/Comedy/MontyPython").IsDescendantOf("/Comedy")
// true
func (k Key) IsDescendantOf(other Key) bool {
if other.string == k.string {
return false
}
return strings.HasPrefix(k.string, other.string)
}
func (k Key) IsTopLevel() bool {
return len(k.List()) == 1
}
// Returns a randomly (uuid) generated key.
// RandomKey()
// NewKey("/f98719ea086343f7b71f32ea9d9d521d")
func RandomKey() Key {
return NewKey(strings.Replace(uuid.New(), "-", "", -1))
}
/*
A Key Namespace is like a path element.
A namespace can optionally include a type (delimited by ':')
> NamespaceValue("Song:PhilosopherSong")
PhilosopherSong
> NamespaceType("Song:PhilosopherSong")
Song
> NamespaceType("Music:Song:PhilosopherSong")
Music:Song
*/
func NamespaceType(namespace string) string {
parts := strings.Split(namespace, ":")
if len(parts) < 2 {
return ""
}
return strings.Join(parts[0:len(parts)-1], ":")
}
func NamespaceValue(namespace string) string {
parts := strings.Split(namespace, ":")
return parts[len(parts)-1]
}
package datastore_test
import(
"bytes"
. "github.com/jbenet/datastore.go"
. "launchpad.net/gocheck"
"math/rand"
"path"
"testing"
"strings"
)
// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }
func randomString() string {
chars := "abcdefghijklmnopqrstuvwxyz1234567890"
var buf bytes.Buffer
l := rand.Intn(50)
for j := 0; j < l; j++ {
buf.WriteByte(chars[rand.Intn(len(chars))])
}
return buf.String()
}
type KeySuite struct{}
var _ = Suite(&KeySuite{})
func (ks *KeySuite) SubtestKey(s string, c *C) {
fixed := path.Clean("/" + s)
namespaces := strings.Split(fixed, "/")[1:]
lastNamespace := namespaces[len(namespaces) - 1]
lnparts := strings.Split(lastNamespace, ":")
ktype := ""
if len(lnparts) > 1 {
ktype = strings.Join(lnparts[:len(lnparts)-1], ":")
}
kname := lnparts[len(lnparts)-1]
kchild := path.Clean(fixed + "/cchildd")
kparent := "/" + strings.Join(append(namespaces[:len(namespaces)-1]), "/")
kpath := path.Clean(kparent +"/"+ ktype)
kinstance := fixed + ":" + "inst"
c.Log("Testing: ", NewKey(s))
c.Check(NewKey(s).String(), Equals, fixed)
c.Check(NewKey(s), Equals, NewKey(s))
c.Check(NewKey(s).String(), Equals, NewKey(s).String())
c.Check(NewKey(s).Name(), Equals, kname)
c.Check(NewKey(s).Type(), Equals, ktype)
c.Check(NewKey(s).Path().String(), Equals, kpath)
c.Check(NewKey(s).Instance("inst").String(), Equals, kinstance)
c.Check(NewKey(s).Child("cchildd").String(), Equals, kchild)
c.Check(NewKey(s).Child("cchildd").Parent().String(), Equals, fixed)
c.Check(NewKey(s).Parent().String(), Equals, kparent)
c.Check(len(NewKey(s).List()), Equals, len(namespaces))
c.Check(len(NewKey(s).Namespaces()), Equals, len(namespaces))
for i, e := range NewKey(s).List() {
c.Check(namespaces[i], Equals, e)
}
}
func (ks *KeySuite) TestKeyBasic(c *C) {
ks.SubtestKey("", c)
ks.SubtestKey("abcde", c)
ks.SubtestKey("disahfidsalfhduisaufidsail", c)
ks.SubtestKey("/fdisahfodisa/fdsa/fdsafdsafdsafdsa/fdsafdsa/", c)
ks.SubtestKey("4215432143214321432143214321", c)
ks.SubtestKey("/fdisaha////fdsa////fdsafdsafdsafdsa/fdsafdsa/", c)
ks.SubtestKey("abcde:fdsfd", c)
ks.SubtestKey("disahfidsalfhduisaufidsail:fdsa", c)
ks.SubtestKey("/fdisahfodisa/fdsa/fdsafdsafdsafdsa/fdsafdsa/:", c)
ks.SubtestKey("4215432143214321432143214321:", c)
ks.SubtestKey("fdisaha////fdsa////fdsafdsafdsafdsa/fdsafdsa/f:fdaf", c)
}
func CheckTrue(c *C, cond bool) {
c.Check(cond, Equals, true)
}
func (ks *KeySuite) TestKeyAncestry(c *C) {
k1 := NewKey("/A/B/C")
k2 := NewKey("/A/B/C/D")
c.Check(k1.String(), Equals, "/A/B/C")
c.Check(k2.String(), Equals, "/A/B/C/D")
CheckTrue(c, k1.IsAncestorOf(k2))
CheckTrue(c, k2.IsDescendantOf(k1))
CheckTrue(c, NewKey("/A").IsAncestorOf(k2))
CheckTrue(c, NewKey("/A").IsAncestorOf(k1))
CheckTrue(c, !NewKey("/A").IsDescendantOf(k2))
CheckTrue(c, !NewKey("/A").IsDescendantOf(k1))
CheckTrue(c, k2.IsDescendantOf(NewKey("/A")))
CheckTrue(c, k1.IsDescendantOf(NewKey("/A")))
CheckTrue(c, !k2.IsAncestorOf(NewKey("/A")))
CheckTrue(c, !k1.IsAncestorOf(NewKey("/A")))
CheckTrue(c, !k2.IsAncestorOf(k2))
CheckTrue(c, !k1.IsAncestorOf(k1))
c.Check(k1.Child("D").String(), Equals, k2.String())
c.Check(k1.String(), Equals, k2.Parent().String())
c.Check(k1.Path().String(), Equals, k2.Parent().Path().String())
}
func (ks *KeySuite) TestType(c *C) {
k1 := NewKey("/A/B/C:c")
k2 := NewKey("/A/B/C:c/D:d")
CheckTrue(c, k1.IsAncestorOf(k2))
CheckTrue(c, k2.IsDescendantOf(k1))
c.Check(k1.Type(), Equals, "C")
c.Check(k2.Type(), Equals, "D")
c.Check(k1.Type(), Equals, k2.Parent().Type())
}
func (ks *KeySuite) TestRandom(c *C) {
keys := map[Key]bool{}
for i := 0; i < 1000; i++ {
r := RandomKey()
_, found := keys[r]
CheckTrue(c, !found)
keys[r] = true
}
CheckTrue(c, len(keys) == 1000)
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment