diff --git a/db.go b/db.go index a8d222a4df73e5d96ebd9b45162ea7d3ac41fcb3..c2b401dbd8b8b9f06d2d4c75d7a1b47097a3e2ae 100644 --- a/db.go +++ b/db.go @@ -170,6 +170,12 @@ type Database interface { // Close() closes the currently active connection to the database. Close() error + // C is a short-hand to Collection(). If the given collection does not exists + // subsequent calls to any Collection or Result method that expect the + // collection to exists will fail returning the original error a call to + // Collection() would have returned. + C(...string) Collection + // Collection() returns a `db.Collection{}` struct by name. Some databases // support collections of more than one source or table, refer to the // documentation of the specific database adapter to see if using multiple diff --git a/mongo/database.go b/mongo/database.go index e7b3cabef1f6a1f6a37f5cb2de2cb2fb0804a7ce..21445be17a329538adc955d20623d0d3d6ec577f 100644 --- a/mongo/database.go +++ b/mongo/database.go @@ -28,6 +28,7 @@ import ( "gopkg.in/mgo.v2" "upper.io/db" + "upper.io/db/util/adapter" ) // Adapter holds the name of the mongodb adapter. @@ -177,6 +178,15 @@ func (s *Source) Collections() (cols []string, err error) { return cols, nil } +// C returns a collection interface. +func (s *Source) C(names ...string) db.Collection { + if len(names) > 1 { + return &adapter.NonExistentCollection{Err: db.ErrUnsupported} + } + c, _ := s.Collection(names...) + return c +} + // Collection returns a collection by name. func (s *Source) Collection(names ...string) (db.Collection, error) { var err error diff --git a/mongo/database_test.go b/mongo/database_test.go index a473ba225f117537b073ed2ec2745a05e2680af5..57336d667003f489f28c00c5a30e21a9fbe3cb76 100644 --- a/mongo/database_test.go +++ b/mongo/database_test.go @@ -487,6 +487,33 @@ func TestGroup(t *testing.T) { } +// Attempts to count all rows in a table that does not exist. +func TestResultNonExistentCount(t *testing.T) { + sess, err := db.Open(Adapter, settings) + + if err != nil { + t.Fatal(err) + } + + defer sess.Close() + + total, err := sess.C("notartist").Find().Count() + + if err != nil { + t.Fatal("MongoDB should not care about a non-existent collecton.", err) + } + + if total != 0 { + t.Fatal("Counter should be zero") + } + + _, err = sess.C("notartist", "neinnein").Find().Count() + + if err != db.ErrUnsupported { + t.Fatal("MongoDB should not allow multiple collections.", err) + } +} + // This test uses and result and tries to fetch items one by one. func TestResultFetch(t *testing.T) { diff --git a/mysql/database.go b/mysql/database.go index 960bc15c20b369810010bc52a7b9811801daaede..85fd6a68f68c49585ea2175b0e9ec746999ac668 100644 --- a/mysql/database.go +++ b/mysql/database.go @@ -30,6 +30,7 @@ import ( "github.com/jmoiron/sqlx" "upper.io/cache" "upper.io/db" + "upper.io/db/util/adapter" "upper.io/db/util/schema" "upper.io/db/util/sqlgen" "upper.io/db/util/sqlutil" @@ -199,6 +200,15 @@ func (d *database) Close() error { return nil } +// C returns a collection interface. +func (d *database) C(names ...string) db.Collection { + c, err := d.Collection(names...) + if err != nil { + return &adapter.NonExistentCollection{Err: err} + } + return c +} + // Collection returns a table by name. func (d *database) Collection(names ...string) (db.Collection, error) { var err error diff --git a/mysql/database_test.go b/mysql/database_test.go index fa5d9adade0a0fd82ac2228b27dd517218dcf5aa..0dc9f8d686e3bb2df17815e8d113ed1008b16d6a 100644 --- a/mysql/database_test.go +++ b/mysql/database_test.go @@ -566,6 +566,27 @@ func TestResultCount(t *testing.T) { } } +// Attempts to count all rows in a table that does not exist. +func TestResultNonExistentCount(t *testing.T) { + sess, err := db.Open(Adapter, settings) + + if err != nil { + t.Fatal(err) + } + + defer sess.Close() + + total, err := sess.C("notartist").Find().Count() + + if err != db.ErrCollectionDoesNotExist { + t.Fatal("Expecting a specific error, got", err) + } + + if total != 0 { + t.Fatal("Counter should be zero") + } +} + // Attempts to fetch results one by one. func TestResultFetch(t *testing.T) { var err error diff --git a/postgresql/database.go b/postgresql/database.go index 0872939404746dd009cf465b014af5b5114960dc..b8048a92c0819656d7ee7cc58083bb28ca5b528e 100644 --- a/postgresql/database.go +++ b/postgresql/database.go @@ -31,6 +31,7 @@ import ( _ "github.com/lib/pq" // PostgreSQL driver. "upper.io/cache" "upper.io/db" + "upper.io/db/util/adapter" "upper.io/db/util/schema" "upper.io/db/util/sqlgen" "upper.io/db/util/sqlutil" @@ -189,6 +190,15 @@ func (d *database) Close() error { return nil } +// C returns a collection interface. +func (d *database) C(names ...string) db.Collection { + c, err := d.Collection(names...) + if err != nil { + return &adapter.NonExistentCollection{Err: err} + } + return c +} + // Collection returns a table by name. func (d *database) Collection(names ...string) (db.Collection, error) { var err error diff --git a/postgresql/database_test.go b/postgresql/database_test.go index 95a22c985e009af3d7f92f0fa2202c2181fa880a..65c3443dcfda754ec67ee589d7cd52dc77269493 100644 --- a/postgresql/database_test.go +++ b/postgresql/database_test.go @@ -446,6 +446,27 @@ func TestResultCount(t *testing.T) { } } +// Attempts to count all rows in a table that does not exist. +func TestResultNonExistentCount(t *testing.T) { + sess, err := db.Open(Adapter, settings) + + if err != nil { + t.Fatal(err) + } + + defer sess.Close() + + total, err := sess.C("notartist").Find().Count() + + if err != db.ErrCollectionDoesNotExist { + t.Fatal("Expecting a specific error, got", err) + } + + if total != 0 { + t.Fatal("Counter should be zero") + } +} + // Attempts to fetch results one by one. func TestResultFetch(t *testing.T) { var err error diff --git a/ql/database.go b/ql/database.go index 949d79386b65c0aa250fd2ae49b90e9b46267140..e091fe7b35df696a42ddebedeffb57c10b464973 100644 --- a/ql/database.go +++ b/ql/database.go @@ -31,6 +31,7 @@ import ( "github.com/jmoiron/sqlx" "upper.io/cache" "upper.io/db" + "upper.io/db/util/adapter" "upper.io/db/util/schema" "upper.io/db/util/sqlgen" "upper.io/db/util/sqlutil" @@ -183,6 +184,15 @@ func (d *database) Close() error { return nil } +// C returns a collection interface. +func (d *database) C(names ...string) db.Collection { + c, err := d.Collection(names...) + if err != nil { + return &adapter.NonExistentCollection{Err: err} + } + return c +} + // Collection returns a table by name. func (d *database) Collection(names ...string) (db.Collection, error) { var err error diff --git a/ql/database_test.go b/ql/database_test.go index 9c94cb7173fc7319e2a9ab1c980c9ab8c4344f0c..326f7691b0f1756712c40d4c38f2c3593e3803a5 100644 --- a/ql/database_test.go +++ b/ql/database_test.go @@ -379,6 +379,27 @@ func TestResultCount(t *testing.T) { } } +// Attempts to count all rows in a table that does not exist. +func TestResultNonExistentCount(t *testing.T) { + sess, err := db.Open(Adapter, settings) + + if err != nil { + t.Fatal(err) + } + + defer sess.Close() + + total, err := sess.C("notartist").Find().Count() + + if err != db.ErrCollectionDoesNotExist { + t.Fatal("Expecting a specific error, got", err) + } + + if total != 0 { + t.Fatal("Counter should be zero") + } +} + // Attempts to fetch results one by one. func TestResultFetch(t *testing.T) { var err error diff --git a/sqlite/database.go b/sqlite/database.go index c75247d364220f541c16673e62f4ec2306e181e6..f725a86168ffdbbded7fdbdf0cf72c879b19cbd2 100644 --- a/sqlite/database.go +++ b/sqlite/database.go @@ -31,6 +31,7 @@ import ( _ "github.com/mattn/go-sqlite3" // SQLite3 driver. "upper.io/cache" "upper.io/db" + "upper.io/db/util/adapter" "upper.io/db/util/schema" "upper.io/db/util/sqlgen" "upper.io/db/util/sqlutil" @@ -178,6 +179,15 @@ func (d *database) Close() error { return nil } +// C returns a collection interface. +func (d *database) C(names ...string) db.Collection { + c, err := d.Collection(names...) + if err != nil { + return &adapter.NonExistentCollection{Err: err} + } + return c +} + // Collection returns a table by name. func (d *database) Collection(names ...string) (db.Collection, error) { var err error diff --git a/sqlite/database_test.go b/sqlite/database_test.go index 7ec1a81b64a41fc918ac2d937c6bf479cdf48ae6..082b8434b2f444c459e5547fb0a726359bf698a9 100644 --- a/sqlite/database_test.go +++ b/sqlite/database_test.go @@ -497,6 +497,27 @@ func TestResultCount(t *testing.T) { } } +// Attempts to count all rows in a table that does not exist. +func TestResultNonExistentCount(t *testing.T) { + sess, err := db.Open(Adapter, settings) + + if err != nil { + t.Fatal(err) + } + + defer sess.Close() + + total, err := sess.C("notartist").Find().Count() + + if err != db.ErrCollectionDoesNotExist { + t.Fatal("Expecting a specific error, got", err) + } + + if total != 0 { + t.Fatal("Counter should be zero") + } +} + // Attempts to fetch results one by one. func TestResultFetch(t *testing.T) { var err error diff --git a/util/adapter/nonexistent.go b/util/adapter/nonexistent.go new file mode 100644 index 0000000000000000000000000000000000000000..2139ca755a66d22fdc34747693a821bf8775606f --- /dev/null +++ b/util/adapter/nonexistent.go @@ -0,0 +1,144 @@ +// Copyright (c) 2012-2015 The upper.io/db authors. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package adapter + +import ( + "fmt" + + "upper.io/db" +) + +var ( + _ = db.Result(&NonExistentResult{}) + _ = db.Collection(&NonExistentCollection{}) +) + +func err(e error) error { + if e == nil { + return db.ErrCollectionDoesNotExist + } + return e +} + +// NonExistentCollection represents a collection that does not exist. +type NonExistentCollection struct { + Err error +} + +// NonExistentResult represents a result set that was based on a non existent +// collection and therefore does not exist. +type NonExistentResult struct { + Err error +} + +// Append returns error. +func (c *NonExistentCollection) Append(interface{}) (interface{}, error) { + return nil, err(c.Err) +} + +// Exists returns false. +func (c *NonExistentCollection) Exists() bool { + return false +} + +// Find returns a NonExistentResult. +func (c *NonExistentCollection) Find(...interface{}) db.Result { + if c.Err != nil { + return &NonExistentResult{Err: c.Err} + } + return &NonExistentResult{Err: fmt.Errorf("Collection reported an error: %q", err(c.Err))} +} + +// Truncate returns error. +func (c *NonExistentCollection) Truncate() error { + return err(c.Err) +} + +// Name returns an empty string. +func (c *NonExistentCollection) Name() string { + return "" +} + +// Limit returns a NonExistentResult. +func (r *NonExistentResult) Limit(uint) db.Result { + return r +} + +// Skip returns a NonExistentResult. +func (r *NonExistentResult) Skip(uint) db.Result { + return r +} + +// Sort returns a NonExistentResult. +func (r *NonExistentResult) Sort(...interface{}) db.Result { + return r +} + +// Select returns a NonExistentResult. +func (r *NonExistentResult) Select(...interface{}) db.Result { + return r +} + +// Where returns a NonExistentResult. +func (r *NonExistentResult) Where(...interface{}) db.Result { + return r +} + +// Group returns a NonExistentResult. +func (r *NonExistentResult) Group(...interface{}) db.Result { + return r +} + +// Remove returns error. +func (r *NonExistentResult) Remove() error { + return err(r.Err) +} + +// Update returns error. +func (r *NonExistentResult) Update(interface{}) error { + return err(r.Err) +} + +// Count returns 0 and error. +func (r *NonExistentResult) Count() (uint64, error) { + return 0, err(r.Err) +} + +// Next returns error. +func (r *NonExistentResult) Next(interface{}) error { + return err(r.Err) +} + +// One returns error. +func (r *NonExistentResult) One(interface{}) error { + return err(r.Err) +} + +// All returns error. +func (r *NonExistentResult) All(interface{}) error { + return err(r.Err) +} + +// Close returns error. +func (r *NonExistentResult) Close() error { + return err(r.Err) +}