Last active
August 29, 2015 14:21
-
-
Save rgarcia/56dd05b1bc0786158c18 to your computer and use it in GitHub Desktop.
get a type definition from the result of an mgo query
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"log" | |
"os" | |
"os/signal" | |
"syscall" | |
"github.com/facebookgo/mgotest" | |
) | |
func main() { | |
sigs := make(chan os.Signal, 1) | |
done := make(chan bool, 1) | |
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) | |
go func() { | |
sig := <-sigs | |
fmt.Println() | |
fmt.Println(sig) | |
done <- true | |
}() | |
// create a replset | |
replset := mgotest.NewReplicaSet(1, log.New(os.Stdout, "mgotest", log.Flags())) | |
defer replset.Stop() | |
log.Printf("replset running:") | |
for i, server := range replset.Servers { | |
log.Printf("%d: %s", i+1, server.URL()) | |
} | |
// run rs.status() | |
session := replset.Session() | |
defer session.Close() | |
var result interface{} | |
var gen Generator | |
var typ Type | |
if err := session.DB("admin").Run("replSetGetStatus", &result); err != nil { | |
log.Print("error running rs.status: %s", err) | |
goto done | |
} | |
log.Printf("replset status: %v", result) | |
typ = NewType(result, &gen) | |
fmt.Printf("%s\n", typ.GoType(&gen)) | |
done: | |
log.Print("waiting") | |
<-done | |
fmt.Println("exiting") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// copied from https://github.com/facebookgo/mongoschema/blob/master/mongoschema.go | |
// commented out main() | |
package main | |
import ( | |
"bytes" | |
"errors" | |
"fmt" | |
"go/format" | |
"regexp" | |
"strings" | |
"time" | |
"unicode" | |
mgo "gopkg.in/mgo.v2" | |
"gopkg.in/mgo.v2/bson" | |
) | |
var errEmptyURL = errors.New("mongoschema: no URL specified") | |
/* | |
func main() { | |
var generator Generator | |
flag.StringVar(&generator.URL, "url", "", "mongo url for dial") | |
flag.StringVar(&generator.DB, "db", "", "database to use") | |
flag.StringVar(&generator.Collection, "collection", "", "collection to use") | |
flag.StringVar(&generator.Struct, "struct", "", "name of the struct") | |
flag.StringVar(&generator.Package, "package", "", "pkg for the generate code") | |
flag.BoolVar(&generator.Raw, "raw", false, "output pre-gofmt code") | |
flag.BoolVar(&generator.Comments, "comments", true, "output comments in code") | |
flag.UintVar(&generator.Limit, "limit", 0, "maximum number of documents to scan") | |
ignoredKeys := flag.String("ignored-keys", "", "comma separated list of key names to ignore") | |
flag.Parse() | |
generator.IgnoredKeys = strings.Split(*ignoredKeys, ",") | |
if err := generator.Generate(); err != nil { | |
fmt.Fprintln(os.Stderr, err) | |
os.Exit(2) | |
} | |
} | |
*/ | |
type Generator struct { | |
URL string | |
DB string | |
Collection string | |
Package string | |
Struct string | |
Raw bool | |
Comments bool | |
IgnoredKeys []string | |
Limit uint | |
} | |
func (s *Generator) connect() (*mgo.Session, *mgo.Collection, error) { | |
if s.URL == "" { | |
return nil, nil, errEmptyURL | |
} | |
session, err := mgo.Dial(s.URL) | |
if err != nil { | |
return nil, nil, err | |
} | |
session.EnsureSafe(&mgo.Safe{}) | |
session.SetBatch(1000) | |
session.SetMode(mgo.Eventual, true) | |
return session, session.DB(s.DB).C(s.Collection), nil | |
} | |
func (s *Generator) Generate() error { | |
session, collection, err := s.connect() | |
if err != nil { | |
return err | |
} | |
root := StructType{} | |
iter := collection.Find(nil).Iter() | |
m := bson.M{} | |
var seen uint | |
for iter.Next(m) { | |
if s.Limit != 0 && seen == s.Limit { | |
break | |
} | |
root.Merge(NewType(m, s), s) | |
m = bson.M{} | |
seen++ | |
} | |
if err := iter.Close(); err != nil { | |
return err | |
} | |
session.Close() | |
const srcFmt = "package %s\ntype %s %s" | |
src := fmt.Sprintf(srcFmt, s.Package, s.Struct, root.GoType(s)) | |
if s.Raw { | |
fmt.Println(src) | |
} | |
formatted, err := format.Source([]byte(src)) | |
if err != nil { | |
return err | |
} | |
fmt.Printf("%s\n", formatted) | |
return nil | |
} | |
type Type interface { | |
GoType(gen *Generator) string | |
Merge(t Type, gen *Generator) Type | |
} | |
type LiteralType struct { | |
Literal string | |
} | |
func (l LiteralType) GoType(gen *Generator) string { | |
return l.Literal | |
} | |
func (l LiteralType) Merge(t Type, gen *Generator) Type { | |
if l.GoType(gen) == t.GoType(gen) { | |
return l | |
} | |
return MixedType{l, t} | |
} | |
var NilType = LiteralType{Literal: "nil"} | |
type MixedType []Type | |
func (m MixedType) GoType(gen *Generator) string { | |
if !gen.Comments { | |
return "interface{}" | |
} | |
var b bytes.Buffer | |
fmt.Fprint(&b, "interface{} /* ") | |
for i, v := range m { | |
fmt.Fprint(&b, v.GoType(gen)) | |
if i != len(m)-1 { | |
fmt.Fprint(&b, ",") | |
} | |
fmt.Fprint(&b, " ") | |
} | |
fmt.Fprint(&b, " */") | |
return b.String() | |
} | |
func (m MixedType) Merge(t Type, gen *Generator) Type { | |
for _, e := range m { | |
if e.GoType(gen) == t.GoType(gen) { | |
return m | |
} | |
} | |
return append(m, t) | |
} | |
type PrimitiveType uint | |
const ( | |
PrimitiveBinary PrimitiveType = iota | |
PrimitiveBool | |
PrimitiveDouble | |
PrimitiveInt32 | |
PrimitiveInt64 | |
PrimitiveObjectId | |
PrimitiveString | |
PrimitiveTimestamp | |
) | |
func (p PrimitiveType) GoType(gen *Generator) string { | |
switch p { | |
case PrimitiveBinary: | |
return "bson.Binary" | |
case PrimitiveBool: | |
return "bool" | |
case PrimitiveDouble: | |
return "float64" | |
case PrimitiveInt32: | |
return "int32" | |
case PrimitiveInt64: | |
return "int64" | |
case PrimitiveString: | |
return "string" | |
case PrimitiveTimestamp: | |
return "time.Time" | |
case PrimitiveObjectId: | |
return "bson.ObjectId" | |
} | |
panic(fmt.Sprintf("unknown primitive: %d", uint(p))) | |
} | |
func (p PrimitiveType) Merge(t Type, gen *Generator) Type { | |
if p.GoType(gen) == t.GoType(gen) { | |
return p | |
} | |
return MixedType{p, t} | |
} | |
type SliceType struct { | |
Type | |
} | |
func (s SliceType) GoType(gen *Generator) string { | |
return fmt.Sprintf("[]%s", s.Type.GoType(gen)) | |
} | |
func (s SliceType) Merge(t Type, gen *Generator) Type { | |
if s.GoType(gen) == t.GoType(gen) { | |
return s | |
} | |
// If the target type is a slice of structs, we merge into the first struct | |
// type in our own slice type. | |
if targetSliceType, ok := t.(SliceType); ok { | |
if targetSliceStructType, ok := targetSliceType.Type.(StructType); ok { | |
// We're a slice of structs. | |
if ownSliceStructType, ok := s.Type.(StructType); ok { | |
s.Type = ownSliceStructType.Merge(targetSliceStructType, gen) | |
return s | |
} | |
// We're a slice of mixed types, one of which may or may not be a struct. | |
if sliceMixedType, ok := s.Type.(MixedType); ok { | |
for i, v := range sliceMixedType { | |
if vStructType, ok := v.(StructType); ok { | |
sliceMixedType[i] = vStructType.Merge(targetSliceStructType, gen) | |
return s | |
} | |
} | |
return SliceType{Type: append(sliceMixedType, targetSliceStructType)} | |
} | |
} | |
} | |
return MixedType{s, t} | |
} | |
type StructType map[string]Type | |
func (s StructType) GoType(gen *Generator) string { | |
var buf bytes.Buffer | |
fmt.Fprintln(&buf, "struct {") | |
for k, v := range s { | |
if isValidFieldName(k) { | |
var vGoType string | |
if sscontains(gen.IgnoredKeys, k) { | |
vGoType = "IgnoredField" | |
} else { | |
vGoType = v.GoType(gen) | |
} | |
fmt.Fprintf( | |
&buf, | |
"%s %s `bson:\"%s,omitempty\"`\n", | |
makeFieldName(k), | |
vGoType, | |
k, | |
) | |
} else { | |
if gen.Comments { | |
fmt.Fprintf(&buf, "// skipping invalid field name %s\n", k) | |
} | |
} | |
} | |
fmt.Fprint(&buf, "}") | |
return buf.String() | |
} | |
func (s StructType) Merge(t Type, gen *Generator) Type { | |
if o, ok := t.(StructType); ok { | |
for k, v := range o { | |
if e, ok := s[k]; ok { | |
s[k] = e.Merge(v, gen) | |
} else { | |
s[k] = v | |
} | |
} | |
return s | |
} | |
return MixedType{s, t} | |
} | |
func NewType(v interface{}, gen *Generator) Type { | |
switch i := v.(type) { | |
default: | |
panic(fmt.Sprintf("cannot determine type for %v with go type %T", v, v)) | |
case nil: | |
return NilType | |
case bson.ObjectId: | |
return PrimitiveObjectId | |
case bson.M: | |
return NewStructType(i, gen) | |
case []interface{}: | |
if len(i) == 0 { | |
return SliceType{Type: MixedType{}} | |
} | |
var s Type | |
for _, v := range i { | |
vt := NewType(v, gen) | |
if vt == NilType { | |
continue | |
} | |
if s == nil { | |
s = SliceType{Type: vt} | |
} else { | |
s.Merge(SliceType{Type: vt}, gen) | |
} | |
} | |
if s == nil { | |
return SliceType{Type: MixedType{}} | |
} | |
return s | |
case int, int64: | |
return PrimitiveInt64 | |
case int32: | |
return PrimitiveInt32 | |
case bool: | |
return PrimitiveBool | |
case string: | |
return PrimitiveString | |
case time.Time, bson.MongoTimestamp: | |
return PrimitiveTimestamp | |
case float32, float64: | |
return PrimitiveDouble | |
case bson.Binary: | |
return PrimitiveBinary | |
} | |
} | |
func NewStructType(m bson.M, gen *Generator) Type { | |
s := StructType{} | |
for k, v := range m { | |
t := NewType(v, gen) | |
if t == NilType { | |
continue | |
} | |
s[k] = t | |
} | |
return s | |
} | |
func isValidFieldName(n string) bool { | |
if n == "" { | |
return false | |
} | |
if strings.IndexAny(n, "!*") == -1 { | |
return true | |
} | |
return false | |
} | |
var ( | |
dashUnderscoreReplacer = strings.NewReplacer("-", " ", "_", " ") | |
capsRe = regexp.MustCompile(`([A-Z])`) | |
spaceRe = regexp.MustCompile(`(\w+)`) | |
forcedUpperCase = map[string]bool{"id": true, "url": true, "api": true} | |
) | |
func split(str string) []string { | |
str = dashUnderscoreReplacer.Replace(str) | |
str = capsRe.ReplaceAllString(str, " $1") | |
return spaceRe.FindAllString(str, -1) | |
} | |
func makeFieldName(s string) string { | |
parts := split(s) | |
for i, part := range parts { | |
if forcedUpperCase[strings.ToLower(part)] { | |
parts[i] = strings.ToUpper(part) | |
} else { | |
parts[i] = strings.Title(part) | |
} | |
} | |
camel := strings.Join(parts, "") | |
runes := []rune(camel) | |
for i, c := range runes { | |
ok := unicode.IsLetter(c) || unicode.IsDigit(c) | |
if i == 0 { | |
ok = unicode.IsLetter(c) | |
} | |
if !ok { | |
runes[i] = '_' | |
} | |
} | |
return string(runes) | |
} | |
func sscontains(l []string, v string) bool { | |
for _, e := range l { | |
if e == v { | |
return true | |
} | |
} | |
return false | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
vagrant@precise64:~/code/mongo-test-harness$ go run main.go mongoschema.go | |
2015/05/13 02:21:33 replset running: | |
2015/05/13 02:21:33 1: 127.0.0.1:58402 | |
2015/05/13 02:21:33 replset status: map[myState:1 members:[map[name:127.0.0.1:58402 state:1 optimeDate:2015-05-13 02:21:33 +0000 UTC self:true _id:0 health:1 stateStr:PRIMARY uptime:12 optime:6148175646192304129]] ok:1 set:rs date:2015-05-13 02:21:33 +0000 UTC] | |
struct { | |
Ok float64 `bson:"ok,omitempty"` | |
Set string `bson:"set,omitempty"` | |
Date time.Time `bson:"date,omitempty"` | |
MyState int64 `bson:"myState,omitempty"` | |
Members []struct { | |
Self bool `bson:"self,omitempty"` | |
ID int64 `bson:"_id,omitempty"` | |
Health float64 `bson:"health,omitempty"` | |
StateStr string `bson:"stateStr,omitempty"` | |
Uptime int64 `bson:"uptime,omitempty"` | |
Optime time.Time `bson:"optime,omitempty"` | |
OptimeDate time.Time `bson:"optimeDate,omitempty"` | |
Name string `bson:"name,omitempty"` | |
State int64 `bson:"state,omitempty"` | |
} `bson:"members,omitempty"` | |
} | |
2015/05/13 02:21:33 waiting | |
^C | |
interrupt | |
exiting |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment