1// mgo - MongoDB driver for Go
2//
3// Copyright (c) 2010-2012 - Gustavo Niemeyer <gustavo@niemeyer.net>
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are met:
9//
10// 1. Redistributions of source code must retain the above copyright notice, this
11//    list of conditions and the following disclaimer.
12// 2. Redistributions in binary form must reproduce the above copyright notice,
13//    this list of conditions and the following disclaimer in the documentation
14//    and/or other materials provided with the distribution.
15//
16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27package mgo_test
28
29import (
30	"io"
31	"os"
32	"time"
33
34	. "gopkg.in/check.v1"
35	"gopkg.in/mgo.v2"
36	"gopkg.in/mgo.v2/bson"
37)
38
39func (s *S) TestGridFSCreate(c *C) {
40	session, err := mgo.Dial("localhost:40011")
41	c.Assert(err, IsNil)
42	defer session.Close()
43
44	db := session.DB("mydb")
45
46	before := bson.Now()
47
48	gfs := db.GridFS("fs")
49	file, err := gfs.Create("")
50	c.Assert(err, IsNil)
51
52	n, err := file.Write([]byte("some data"))
53	c.Assert(err, IsNil)
54	c.Assert(n, Equals, 9)
55
56	err = file.Close()
57	c.Assert(err, IsNil)
58
59	after := bson.Now()
60
61	// Check the file information.
62	result := M{}
63	err = db.C("fs.files").Find(nil).One(result)
64	c.Assert(err, IsNil)
65
66	fileId, ok := result["_id"].(bson.ObjectId)
67	c.Assert(ok, Equals, true)
68	c.Assert(fileId.Valid(), Equals, true)
69	result["_id"] = "<id>"
70
71	ud, ok := result["uploadDate"].(time.Time)
72	c.Assert(ok, Equals, true)
73	c.Assert(ud.After(before) && ud.Before(after), Equals, true)
74	result["uploadDate"] = "<timestamp>"
75
76	expected := M{
77		"_id":        "<id>",
78		"length":     9,
79		"chunkSize":  255 * 1024,
80		"uploadDate": "<timestamp>",
81		"md5":        "1e50210a0202497fb79bc38b6ade6c34",
82	}
83	c.Assert(result, DeepEquals, expected)
84
85	// Check the chunk.
86	result = M{}
87	err = db.C("fs.chunks").Find(nil).One(result)
88	c.Assert(err, IsNil)
89
90	chunkId, ok := result["_id"].(bson.ObjectId)
91	c.Assert(ok, Equals, true)
92	c.Assert(chunkId.Valid(), Equals, true)
93	result["_id"] = "<id>"
94
95	expected = M{
96		"_id":      "<id>",
97		"files_id": fileId,
98		"n":        0,
99		"data":     []byte("some data"),
100	}
101	c.Assert(result, DeepEquals, expected)
102
103	// Check that an index was created.
104	indexes, err := db.C("fs.chunks").Indexes()
105	c.Assert(err, IsNil)
106	c.Assert(len(indexes), Equals, 2)
107	c.Assert(indexes[1].Key, DeepEquals, []string{"files_id", "n"})
108}
109
110func (s *S) TestGridFSFileDetails(c *C) {
111	session, err := mgo.Dial("localhost:40011")
112	c.Assert(err, IsNil)
113	defer session.Close()
114
115	db := session.DB("mydb")
116
117	gfs := db.GridFS("fs")
118
119	file, err := gfs.Create("myfile1.txt")
120	c.Assert(err, IsNil)
121
122	n, err := file.Write([]byte("some"))
123	c.Assert(err, IsNil)
124	c.Assert(n, Equals, 4)
125
126	c.Assert(file.Size(), Equals, int64(4))
127
128	n, err = file.Write([]byte(" data"))
129	c.Assert(err, IsNil)
130	c.Assert(n, Equals, 5)
131
132	c.Assert(file.Size(), Equals, int64(9))
133
134	id, _ := file.Id().(bson.ObjectId)
135	c.Assert(id.Valid(), Equals, true)
136	c.Assert(file.Name(), Equals, "myfile1.txt")
137	c.Assert(file.ContentType(), Equals, "")
138
139	var info interface{}
140	err = file.GetMeta(&info)
141	c.Assert(err, IsNil)
142	c.Assert(info, IsNil)
143
144	file.SetId("myid")
145	file.SetName("myfile2.txt")
146	file.SetContentType("text/plain")
147	file.SetMeta(M{"any": "thing"})
148
149	c.Assert(file.Id(), Equals, "myid")
150	c.Assert(file.Name(), Equals, "myfile2.txt")
151	c.Assert(file.ContentType(), Equals, "text/plain")
152
153	err = file.GetMeta(&info)
154	c.Assert(err, IsNil)
155	c.Assert(info, DeepEquals, bson.M{"any": "thing"})
156
157	err = file.Close()
158	c.Assert(err, IsNil)
159
160	c.Assert(file.MD5(), Equals, "1e50210a0202497fb79bc38b6ade6c34")
161
162	ud := file.UploadDate()
163	now := time.Now()
164	c.Assert(ud.Before(now), Equals, true)
165	c.Assert(ud.After(now.Add(-3*time.Second)), Equals, true)
166
167	result := M{}
168	err = db.C("fs.files").Find(nil).One(result)
169	c.Assert(err, IsNil)
170
171	result["uploadDate"] = "<timestamp>"
172
173	expected := M{
174		"_id":         "myid",
175		"length":      9,
176		"chunkSize":   255 * 1024,
177		"uploadDate":  "<timestamp>",
178		"md5":         "1e50210a0202497fb79bc38b6ade6c34",
179		"filename":    "myfile2.txt",
180		"contentType": "text/plain",
181		"metadata":    M{"any": "thing"},
182	}
183	c.Assert(result, DeepEquals, expected)
184}
185
186func (s *S) TestGridFSSetUploadDate(c *C) {
187	session, err := mgo.Dial("localhost:40011")
188	c.Assert(err, IsNil)
189	defer session.Close()
190
191	db := session.DB("mydb")
192
193	gfs := db.GridFS("fs")
194	file, err := gfs.Create("")
195	c.Assert(err, IsNil)
196
197	t := time.Date(2014, 1, 1, 1, 1, 1, 0, time.Local)
198	file.SetUploadDate(t)
199
200	err = file.Close()
201	c.Assert(err, IsNil)
202
203	// Check the file information.
204	result := M{}
205	err = db.C("fs.files").Find(nil).One(result)
206	c.Assert(err, IsNil)
207
208	ud := result["uploadDate"].(time.Time)
209	if !ud.Equal(t) {
210		c.Fatalf("want upload date %s, got %s", t, ud)
211	}
212}
213
214func (s *S) TestGridFSCreateWithChunking(c *C) {
215	session, err := mgo.Dial("localhost:40011")
216	c.Assert(err, IsNil)
217	defer session.Close()
218
219	db := session.DB("mydb")
220
221	gfs := db.GridFS("fs")
222
223	file, err := gfs.Create("")
224	c.Assert(err, IsNil)
225
226	file.SetChunkSize(5)
227
228	// Smaller than the chunk size.
229	n, err := file.Write([]byte("abc"))
230	c.Assert(err, IsNil)
231	c.Assert(n, Equals, 3)
232
233	// Boundary in the middle.
234	n, err = file.Write([]byte("defg"))
235	c.Assert(err, IsNil)
236	c.Assert(n, Equals, 4)
237
238	// Boundary at the end.
239	n, err = file.Write([]byte("hij"))
240	c.Assert(err, IsNil)
241	c.Assert(n, Equals, 3)
242
243	// Larger than the chunk size, with 3 chunks.
244	n, err = file.Write([]byte("klmnopqrstuv"))
245	c.Assert(err, IsNil)
246	c.Assert(n, Equals, 12)
247
248	err = file.Close()
249	c.Assert(err, IsNil)
250
251	// Check the file information.
252	result := M{}
253	err = db.C("fs.files").Find(nil).One(result)
254	c.Assert(err, IsNil)
255
256	fileId, _ := result["_id"].(bson.ObjectId)
257	c.Assert(fileId.Valid(), Equals, true)
258	result["_id"] = "<id>"
259	result["uploadDate"] = "<timestamp>"
260
261	expected := M{
262		"_id":        "<id>",
263		"length":     22,
264		"chunkSize":  5,
265		"uploadDate": "<timestamp>",
266		"md5":        "44a66044834cbe55040089cabfc102d5",
267	}
268	c.Assert(result, DeepEquals, expected)
269
270	// Check the chunks.
271	iter := db.C("fs.chunks").Find(nil).Sort("n").Iter()
272	dataChunks := []string{"abcde", "fghij", "klmno", "pqrst", "uv"}
273	for i := 0; ; i++ {
274		result = M{}
275		if !iter.Next(result) {
276			if i != 5 {
277				c.Fatalf("Expected 5 chunks, got %d", i)
278			}
279			break
280		}
281		c.Assert(iter.Close(), IsNil)
282
283		result["_id"] = "<id>"
284
285		expected = M{
286			"_id":      "<id>",
287			"files_id": fileId,
288			"n":        i,
289			"data":     []byte(dataChunks[i]),
290		}
291		c.Assert(result, DeepEquals, expected)
292	}
293}
294
295func (s *S) TestGridFSAbort(c *C) {
296	session, err := mgo.Dial("localhost:40011")
297	c.Assert(err, IsNil)
298	defer session.Close()
299
300	db := session.DB("mydb")
301
302	gfs := db.GridFS("fs")
303	file, err := gfs.Create("")
304	c.Assert(err, IsNil)
305
306	file.SetChunkSize(5)
307
308	n, err := file.Write([]byte("some data"))
309	c.Assert(err, IsNil)
310	c.Assert(n, Equals, 9)
311
312	var count int
313	for i := 0; i < 10; i++ {
314		count, err = db.C("fs.chunks").Count()
315		if count > 0 || err != nil {
316			break
317		}
318	}
319	c.Assert(err, IsNil)
320	c.Assert(count, Equals, 1)
321
322	file.Abort()
323
324	err = file.Close()
325	c.Assert(err, ErrorMatches, "write aborted")
326
327	count, err = db.C("fs.chunks").Count()
328	c.Assert(err, IsNil)
329	c.Assert(count, Equals, 0)
330}
331
332func (s *S) TestGridFSCloseConflict(c *C) {
333	session, err := mgo.Dial("localhost:40011")
334	c.Assert(err, IsNil)
335	defer session.Close()
336
337	db := session.DB("mydb")
338
339	db.C("fs.files").EnsureIndex(mgo.Index{Key: []string{"filename"}, Unique: true})
340
341	// For a closing-time conflict
342	err = db.C("fs.files").Insert(M{"filename": "foo.txt"})
343	c.Assert(err, IsNil)
344
345	gfs := db.GridFS("fs")
346	file, err := gfs.Create("foo.txt")
347	c.Assert(err, IsNil)
348
349	_, err = file.Write([]byte("some data"))
350	c.Assert(err, IsNil)
351
352	err = file.Close()
353	c.Assert(mgo.IsDup(err), Equals, true)
354
355	count, err := db.C("fs.chunks").Count()
356	c.Assert(err, IsNil)
357	c.Assert(count, Equals, 0)
358}
359
360func (s *S) TestGridFSOpenNotFound(c *C) {
361	session, err := mgo.Dial("localhost:40011")
362	c.Assert(err, IsNil)
363	defer session.Close()
364
365	db := session.DB("mydb")
366
367	gfs := db.GridFS("fs")
368	file, err := gfs.OpenId("non-existent")
369	c.Assert(err == mgo.ErrNotFound, Equals, true)
370	c.Assert(file, IsNil)
371
372	file, err = gfs.Open("non-existent")
373	c.Assert(err == mgo.ErrNotFound, Equals, true)
374	c.Assert(file, IsNil)
375}
376
377func (s *S) TestGridFSReadAll(c *C) {
378	session, err := mgo.Dial("localhost:40011")
379	c.Assert(err, IsNil)
380	defer session.Close()
381
382	db := session.DB("mydb")
383
384	gfs := db.GridFS("fs")
385	file, err := gfs.Create("")
386	c.Assert(err, IsNil)
387	id := file.Id()
388
389	file.SetChunkSize(5)
390
391	n, err := file.Write([]byte("abcdefghijklmnopqrstuv"))
392	c.Assert(err, IsNil)
393	c.Assert(n, Equals, 22)
394
395	err = file.Close()
396	c.Assert(err, IsNil)
397
398	file, err = gfs.OpenId(id)
399	c.Assert(err, IsNil)
400
401	b := make([]byte, 30)
402	n, err = file.Read(b)
403	c.Assert(n, Equals, 22)
404	c.Assert(err, IsNil)
405
406	n, err = file.Read(b)
407	c.Assert(n, Equals, 0)
408	c.Assert(err == io.EOF, Equals, true)
409
410	err = file.Close()
411	c.Assert(err, IsNil)
412}
413
414func (s *S) TestGridFSReadChunking(c *C) {
415	session, err := mgo.Dial("localhost:40011")
416	c.Assert(err, IsNil)
417	defer session.Close()
418
419	db := session.DB("mydb")
420
421	gfs := db.GridFS("fs")
422
423	file, err := gfs.Create("")
424	c.Assert(err, IsNil)
425
426	id := file.Id()
427
428	file.SetChunkSize(5)
429
430	n, err := file.Write([]byte("abcdefghijklmnopqrstuv"))
431	c.Assert(err, IsNil)
432	c.Assert(n, Equals, 22)
433
434	err = file.Close()
435	c.Assert(err, IsNil)
436
437	file, err = gfs.OpenId(id)
438	c.Assert(err, IsNil)
439
440	b := make([]byte, 30)
441
442	// Smaller than the chunk size.
443	n, err = file.Read(b[:3])
444	c.Assert(err, IsNil)
445	c.Assert(n, Equals, 3)
446	c.Assert(b[:3], DeepEquals, []byte("abc"))
447
448	// Boundary in the middle.
449	n, err = file.Read(b[:4])
450	c.Assert(err, IsNil)
451	c.Assert(n, Equals, 4)
452	c.Assert(b[:4], DeepEquals, []byte("defg"))
453
454	// Boundary at the end.
455	n, err = file.Read(b[:3])
456	c.Assert(err, IsNil)
457	c.Assert(n, Equals, 3)
458	c.Assert(b[:3], DeepEquals, []byte("hij"))
459
460	// Larger than the chunk size, with 3 chunks.
461	n, err = file.Read(b)
462	c.Assert(err, IsNil)
463	c.Assert(n, Equals, 12)
464	c.Assert(b[:12], DeepEquals, []byte("klmnopqrstuv"))
465
466	n, err = file.Read(b)
467	c.Assert(n, Equals, 0)
468	c.Assert(err == io.EOF, Equals, true)
469
470	err = file.Close()
471	c.Assert(err, IsNil)
472}
473
474func (s *S) TestGridFSOpen(c *C) {
475	session, err := mgo.Dial("localhost:40011")
476	c.Assert(err, IsNil)
477	defer session.Close()
478
479	db := session.DB("mydb")
480
481	gfs := db.GridFS("fs")
482
483	file, err := gfs.Create("myfile.txt")
484	c.Assert(err, IsNil)
485	file.Write([]byte{'1'})
486	file.Close()
487
488	file, err = gfs.Create("myfile.txt")
489	c.Assert(err, IsNil)
490	file.Write([]byte{'2'})
491	file.Close()
492
493	file, err = gfs.Open("myfile.txt")
494	c.Assert(err, IsNil)
495	defer file.Close()
496
497	var b [1]byte
498
499	_, err = file.Read(b[:])
500	c.Assert(err, IsNil)
501	c.Assert(string(b[:]), Equals, "2")
502}
503
504func (s *S) TestGridFSSeek(c *C) {
505	session, err := mgo.Dial("localhost:40011")
506	c.Assert(err, IsNil)
507	defer session.Close()
508
509	db := session.DB("mydb")
510
511	gfs := db.GridFS("fs")
512	file, err := gfs.Create("")
513	c.Assert(err, IsNil)
514	id := file.Id()
515
516	file.SetChunkSize(5)
517
518	n, err := file.Write([]byte("abcdefghijklmnopqrstuv"))
519	c.Assert(err, IsNil)
520	c.Assert(n, Equals, 22)
521
522	err = file.Close()
523	c.Assert(err, IsNil)
524
525	b := make([]byte, 5)
526
527	file, err = gfs.OpenId(id)
528	c.Assert(err, IsNil)
529
530	o, err := file.Seek(3, os.SEEK_SET)
531	c.Assert(err, IsNil)
532	c.Assert(o, Equals, int64(3))
533	_, err = file.Read(b)
534	c.Assert(err, IsNil)
535	c.Assert(b, DeepEquals, []byte("defgh"))
536
537	o, err = file.Seek(5, os.SEEK_CUR)
538	c.Assert(err, IsNil)
539	c.Assert(o, Equals, int64(13))
540	_, err = file.Read(b)
541	c.Assert(err, IsNil)
542	c.Assert(b, DeepEquals, []byte("nopqr"))
543
544	o, err = file.Seek(0, os.SEEK_END)
545	c.Assert(err, IsNil)
546	c.Assert(o, Equals, int64(22))
547	n, err = file.Read(b)
548	c.Assert(err, Equals, io.EOF)
549	c.Assert(n, Equals, 0)
550
551	o, err = file.Seek(-10, os.SEEK_END)
552	c.Assert(err, IsNil)
553	c.Assert(o, Equals, int64(12))
554	_, err = file.Read(b)
555	c.Assert(err, IsNil)
556	c.Assert(b, DeepEquals, []byte("mnopq"))
557
558	o, err = file.Seek(8, os.SEEK_SET)
559	c.Assert(err, IsNil)
560	c.Assert(o, Equals, int64(8))
561	_, err = file.Read(b)
562	c.Assert(err, IsNil)
563	c.Assert(b, DeepEquals, []byte("ijklm"))
564
565	// Trivial seek forward within same chunk. Already
566	// got the data, shouldn't touch the database.
567	sent := mgo.GetStats().SentOps
568	o, err = file.Seek(1, os.SEEK_CUR)
569	c.Assert(err, IsNil)
570	c.Assert(o, Equals, int64(14))
571	c.Assert(mgo.GetStats().SentOps, Equals, sent)
572	_, err = file.Read(b)
573	c.Assert(err, IsNil)
574	c.Assert(b, DeepEquals, []byte("opqrs"))
575
576	// Try seeking past end of file.
577	file.Seek(3, os.SEEK_SET)
578	o, err = file.Seek(23, os.SEEK_SET)
579	c.Assert(err, ErrorMatches, "seek past end of file")
580	c.Assert(o, Equals, int64(3))
581}
582
583func (s *S) TestGridFSRemoveId(c *C) {
584	session, err := mgo.Dial("localhost:40011")
585	c.Assert(err, IsNil)
586	defer session.Close()
587
588	db := session.DB("mydb")
589
590	gfs := db.GridFS("fs")
591
592	file, err := gfs.Create("myfile.txt")
593	c.Assert(err, IsNil)
594	file.Write([]byte{'1'})
595	file.Close()
596
597	file, err = gfs.Create("myfile.txt")
598	c.Assert(err, IsNil)
599	file.Write([]byte{'2'})
600	id := file.Id()
601	file.Close()
602
603	err = gfs.RemoveId(id)
604	c.Assert(err, IsNil)
605
606	file, err = gfs.Open("myfile.txt")
607	c.Assert(err, IsNil)
608	defer file.Close()
609
610	var b [1]byte
611
612	_, err = file.Read(b[:])
613	c.Assert(err, IsNil)
614	c.Assert(string(b[:]), Equals, "1")
615
616	n, err := db.C("fs.chunks").Find(M{"files_id": id}).Count()
617	c.Assert(err, IsNil)
618	c.Assert(n, Equals, 0)
619}
620
621func (s *S) TestGridFSRemove(c *C) {
622	session, err := mgo.Dial("localhost:40011")
623	c.Assert(err, IsNil)
624	defer session.Close()
625
626	db := session.DB("mydb")
627
628	gfs := db.GridFS("fs")
629
630	file, err := gfs.Create("myfile.txt")
631	c.Assert(err, IsNil)
632	file.Write([]byte{'1'})
633	file.Close()
634
635	file, err = gfs.Create("myfile.txt")
636	c.Assert(err, IsNil)
637	file.Write([]byte{'2'})
638	file.Close()
639
640	err = gfs.Remove("myfile.txt")
641	c.Assert(err, IsNil)
642
643	_, err = gfs.Open("myfile.txt")
644	c.Assert(err == mgo.ErrNotFound, Equals, true)
645
646	n, err := db.C("fs.chunks").Find(nil).Count()
647	c.Assert(err, IsNil)
648	c.Assert(n, Equals, 0)
649}
650
651func (s *S) TestGridFSOpenNext(c *C) {
652	session, err := mgo.Dial("localhost:40011")
653	c.Assert(err, IsNil)
654	defer session.Close()
655
656	db := session.DB("mydb")
657
658	gfs := db.GridFS("fs")
659
660	file, err := gfs.Create("myfile1.txt")
661	c.Assert(err, IsNil)
662	file.Write([]byte{'1'})
663	file.Close()
664
665	file, err = gfs.Create("myfile2.txt")
666	c.Assert(err, IsNil)
667	file.Write([]byte{'2'})
668	file.Close()
669
670	var f *mgo.GridFile
671	var b [1]byte
672
673	iter := gfs.Find(nil).Sort("-filename").Iter()
674
675	ok := gfs.OpenNext(iter, &f)
676	c.Assert(ok, Equals, true)
677	c.Check(f.Name(), Equals, "myfile2.txt")
678
679	_, err = f.Read(b[:])
680	c.Assert(err, IsNil)
681	c.Assert(string(b[:]), Equals, "2")
682
683	ok = gfs.OpenNext(iter, &f)
684	c.Assert(ok, Equals, true)
685	c.Check(f.Name(), Equals, "myfile1.txt")
686
687	_, err = f.Read(b[:])
688	c.Assert(err, IsNil)
689	c.Assert(string(b[:]), Equals, "1")
690
691	ok = gfs.OpenNext(iter, &f)
692	c.Assert(ok, Equals, false)
693	c.Assert(iter.Close(), IsNil)
694	c.Assert(f, IsNil)
695
696	// Do it again with a more restrictive query to make sure
697	// it's actually taken into account.
698	iter = gfs.Find(bson.M{"filename": "myfile1.txt"}).Iter()
699
700	ok = gfs.OpenNext(iter, &f)
701	c.Assert(ok, Equals, true)
702	c.Check(f.Name(), Equals, "myfile1.txt")
703
704	ok = gfs.OpenNext(iter, &f)
705	c.Assert(ok, Equals, false)
706	c.Assert(iter.Close(), IsNil)
707	c.Assert(f, IsNil)
708}
709