func (s *S) TestNewObjectId(c *C) { // Generate 10 ids ids := make([]bson.ObjectId, 10) for i := 0; i < 10; i++ { ids[i] = bson.NewObjectId() } for i := 1; i < 10; i++ { prevId := ids[i-1] id := ids[i] // Test for uniqueness among all other 9 generated ids for j, tid := range ids { if j != i { c.Assert(id, Not(Equals), tid, Bug("Generated ObjectId is not unique")) } } // Check that timestamp was incremented and is within 30 seconds of the previous one td := id.Timestamp() - prevId.Timestamp() c.Assert((td >= 0 && td <= 30), Equals, true, Bug("Wrong timestamp in generated ObjectId")) // Check that machine ids are the same c.Assert(id.Machine(), Equals, prevId.Machine()) // Check that pids are the same c.Assert(id.Pid(), Equals, prevId.Pid()) // Test for proper increment delta := int(id.Counter() - prevId.Counter()) c.Assert(delta, Equals, 1, Bug("Wrong increment in generated ObjectId")) } }
// Create creates a new file with the provided name in the GridFS. If the file // name already exists, a new version will be inserted with an up-to-date // uploadDate that will cause it to be atomically visible to the Open and // OpenId methods. If the file name is not important, an empty name may be // provided and the file Id used instead. // // It's important to Close files whether they are being written to // or read from, and to check the err result to ensure the operation // completed successfully. // // A simple example inserting a new file: // // func check(err os.Error) { // if err != nil { // panic(err.String()) // } // } // file, err := db.GridFS("fs").Create("myfile.txt") // check(err) // n, err := file.Write([]byte("Hello world!") // check(err) // err = file.Close() // check(err) // fmt.Printf("%d bytes written\n", n) // // The io.Writer interface is implemented by *GridFile and may be used to // help on the file creation. For example: // // file, err := db.GridFS("fs").Create("myfile.txt") // check(err) // messages, err := os.Open("/var/log/messages") // check(err) // defer messages.Close() // err = io.Copy(file, messages) // check(err) // err = file.Close() // check(err) // func (gfs GridFS) Create(name string) (file *GridFile, err os.Error) { file = gfs.newFile() file.mode = gfsWriting file.wsum = md5.New() file.doc = gfsFile{Id: bson.NewObjectId(), ChunkSize: 256 * 1024, Filename: name} return }
func (file *GridFile) insertChunk(data []byte) { n := file.chunk file.chunk++ debugf("GridFile %p: adding to checksum: %q", file, string(data)) file.wsum.Write(data) for file.doc.ChunkSize*file.wpending >= 1024*1024 { // Hold on.. we got a MB pending. file.c.Wait() if file.err != nil { return } } file.wpending++ debugf("GridFile %p: inserting chunk %d with %d bytes", file, n, len(data)) // We may not own the memory of data, so rather than // simply copying it, we'll marshal the document ahead of time. data, err := bson.Marshal(gfsChunk{bson.NewObjectId(), file.doc.Id, n, data}) if err != nil { file.err = err return } go func() { err := file.gfs.Chunks.Insert(bson.Raw{Data: data}) file.m.Lock() file.wpending-- if err != nil && file.err == nil { file.err = err } file.c.Broadcast() file.m.Unlock() }() }
func genRoutes() []artichoke.Route { /* Order of the requests are important. They will be executed top-down. */ return []artichoke.Route{ /* GET requests */ artichoke.Route{ Method: "GET", Pattern: "/:db/:collection/:docid", Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool { // create a new session based on the global one; this allows different login details s := session.New() defer s.Close() query := m["query"].(url.Values) params := m["params"].(map[string]string) c := session.DB(params["db"]).C(params["collection"]) id, resEnded := decodeId(params["docid"], query, w) if resEnded { return true } // we'll need this query later q := c.Find(bson.M{"_id": id}) n, err := q.Count() if err != nil { writeError(w, 500, "Error counting results") return true } else if n == 0 { writeError(w, 404, "Document not found") return true } // we don't know the structure before-hand, but we know it's a JSON object var out map[string]interface{} err = q.One(&out) if err != nil { writeError(w, 500, "Error getting document by id") return true } res, err := json.Marshal(out) if err != nil { writeError(w, 500, "Error stringifying query result") return true } writeJSON(w, 200, string(res)) return true }, }, artichoke.Route{ Method: "GET", Pattern: "/:db/:collection", Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool { // create a new session based on the global one; this allows different login details s := session.New() defer s.Close() params := m["params"].(map[string]string) query := m["query"].(url.Values) // get the collection the user specified c := session.DB(params["db"]).C(params["collection"]) // check for query params if len(query) > 0 { // not yet implemented writeError(w, 500, "Query params not supported yet") return true } else { // no query parameters means they want everything in the collection var out []map[string]interface{} err := c.Find(nil).All(&out) if err != nil { writeError(w, 500, "Error getting all documents") return true } res, err := json.Marshal(out) if err != nil { writeError(w, 500, "Error stringifying response") return true } writeJSON(w, 200, string(res)) return true } return false }, }, artichoke.Route{ Method: "GET", Pattern: "/:db", Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool { // create a new session per connection s := session.New() defer s.Close() params := m["params"].(map[string]string) names, err := session.DB(params["db"]).CollectionNames() if err != nil { writeError(w, 500, "Error getting collection names") return true } writeJSON(w, 200, fmt.Sprintf("[%s]", strings.Join(names, ","))) return true }, }, artichoke.Route{ Method: "GET", Pattern: "/", Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool { // create a new session per connection s := session.New() defer s.Close() names, err := s.DatabaseNames() if err != nil { writeError(w, 500, "Error getting database names") return true } writeJSON(w, 200, fmt.Sprintf("[%s]", strings.Join(names, ","))) return true }, }, /* DELETE Requests */ artichoke.Route{ Method: "DELETE", Pattern: "/:db/:collection/:docid", Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool { if m["bodyJson"] != nil { writeError(w, 400, "DELETE requests on document ids takes no parameters, and thus should have no body or query parameters.") return true } // create a new session per connection s := session.New() defer s.Close() // get the params and query objects query := m["query"].(url.Values) params := m["params"].(map[string]string) c := s.DB(params["db"]).C(params["collection"]) id, resEnded := decodeId(params["docid"], query, w) if resEnded { return true } obj := bson.M{"_id": id} n, err := c.Find(obj).Count() if err != nil { writeError(w, 500, "Error communicating with database") return true } if n == 0 { writeError(w, 404, "The id provided was not found in this database. Either the document has been deleted already or it never existed.") return true } err = c.Remove(obj) if err != nil { writeError(w, 500, "Error removing item from database") return true } w.Header().Add("Content-Length", "0") w.WriteHeader(204) return true }, }, artichoke.Route{ Method: "DELETE", Pattern: "/:db/:collection", Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool { if m["bodyJson"] != nil { writeError(w, 500, "DELETE requests with parameters is not supported yet") return true } // create a new session per connection s := session.New() defer s.Close() params := m["params"].(map[string]string) err := s.DB(params["db"]).C(params["collection"]).DropCollection() if err != nil { writeError(w, 500, "Error dropping collection") return true } w.Header().Add("Content-Length", "0") w.WriteHeader(204) return true }, }, artichoke.Route{ Method: "DELETE", Pattern: "/:db", Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool { if m["bodyJson"] != nil { writeError(w, 500, "DELETE requests with parameters is not supported yet") return true } // create a new session per connection s := session.New() defer s.Close() params := m["params"].(map[string]string) err := s.DB(params["db"]).DropDatabase() if err != nil { writeError(w, 500, "Error dropping database") return true } w.Header().Add("Content-Length", "0") w.WriteHeader(204) return true }, }, /* POST Requests */ // Update a document in place. This will return an error if the document does not exist. artichoke.Route{ Method: "POST", Pattern: "/:db/:collection", Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool { fmt.Println("Post handler called") // create a new session per connection s := session.New() defer s.Close() // get the params and query objects params := m["params"].(map[string]string) query := m["query"].(url.Values) // get the colletion c := s.DB(params["db"]).C(params["collection"]) body := m["bodyJson"].(map[string]interface{}) if body["_id"] != nil { id, resEnded := decodeId(body["_id"].(string), query, w) if resEnded { return true } // ensure that body has the correct id body["_id"] = id // update, if possible err := c.Update(bson.M{"_id": id}, body) if err == mgo.NotFound { insert(body, &c, w) return true } else if err != nil { writeError(w, 500, "Error updating document in database") return true } else { // update succeeded // TODO: make this return the current data in the database? w.Header().Add("Content-Length", "0") w.WriteHeader(204) return true } } else { // generate a new ObjectId body["_id"] = bson.NewObjectId() // and create it insert(body, &c, w) return true } return false }, }, /* PUT Requests */ // Since databases and collections are created on-the-fly, there's no PUT for a database or collection. // // A PUT request will update the URI (overwriting an existing resource), so Insert is used. // Note that the document id is mandatory. For an auto-generated ID, POST to the collection instead. // // For updates, use POST instead (on an existing resource). artichoke.Route{ Method: "PUT", Pattern: "/:db/:collection/:docid", Handler: func(w http.ResponseWriter, r *http.Request, m artichoke.Data) bool { _, bodyExists := m["bodyJson"] if !bodyExists { writeError(w, 400, "No valid body supplied or body sent with the wrong content-type. Must send as application/json.") return true } // create a new session per connection s := session.New() defer s.Close() // get the params and query objects params := m["params"].(map[string]string) query := m["query"].(url.Values) id, resEnded := decodeId(params["docid"], query, w) if resEnded { return true } // get the collection so we can check if the id exists c := s.DB(params["db"]).C(params["collection"]) // try finding as hex n, err := c.Find(bson.M{"_id": id}).Count() if err != nil { writeError(w, 500, "Error communicating with database") return true } // put the id in body := m["bodyJson"].(map[string]interface{}) body["_id"] = id // get the preferred status code var status int if n > 0 { status = 204 } else { status = 201 } // insert into the database // we won't use the one defined above, because we don't want to return anything to the user err = c.Insert(body) if err != nil { writeError(w, 500, "Error inserting document into database") return true } // PUT requests should not return anything on success w.Header().Add("Content-Length", "0") w.WriteHeader(status) return true }, }, } }