good morning!!!!

Skip to content
Snippets Groups Projects
Commit 6d369d1b authored by José Carlos Nieto's avatar José Carlos Nieto
Browse files

sqlite: Adding support for ConnectURL.

parent 04f1190b
Branches
Tags
No related merge requests found
// Copyright (c) 2012-2014 José Carlos Nieto, https://menteslibres.net/xiam
//
// 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 sqlite
import (
"fmt"
"net/url"
"path/filepath"
"strings"
)
const connectionScheme = `file`
type ConnectionURL struct {
Database string
Options map[string]string
}
func (c ConnectionURL) String() (s string) {
vv := url.Values{}
if c.Database == "" {
return ""
}
// Does the database have an existing name?
if strings.HasPrefix(c.Database, "/") == false {
c.Database, _ = filepath.Abs(c.Database)
}
// Do we have any options?
if c.Options == nil {
c.Options = map[string]string{}
}
// Using cache=shared by default.
if _, ok := c.Options["cache"]; !ok {
c.Options["cache"] = "shared"
}
// Converting options into URL values.
for k, v := range c.Options {
vv.Set(k, v)
}
// Building URL.
u := url.URL{
Scheme: connectionScheme,
Path: c.Database,
RawQuery: vv.Encode(),
}
return u.String()
}
func ParseURL(s string) (conn ConnectionURL, err error) {
var u *url.URL
if strings.HasPrefix(s, connectionScheme+"://") == false {
return conn, fmt.Errorf(`Expecting file:// connection scheme.`)
}
if u, err = url.Parse(s); err != nil {
return conn, err
}
conn.Database = u.Host + u.Path
conn.Options = map[string]string{}
var vv url.Values
if vv, err = url.ParseQuery(u.RawQuery); err != nil {
return conn, err
}
for k := range vv {
conn.Options[k] = vv.Get(k)
}
if _, ok := conn.Options["cache"]; !ok {
conn.Options["cache"] = "shared"
}
return conn, err
}
// Copyright (c) 2012-2014 José Carlos Nieto, https://menteslibres.net/xiam
//
// 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 sqlite
import (
"path/filepath"
"testing"
)
func TestConnectionURL(t *testing.T) {
c := ConnectionURL{}
// Default connection string is only the protocol.
if c.String() != "" {
t.Fatal(`Expecting default connectiong string to be empty, got:`, c.String())
}
// Adding a database name.
c.Database = "myfilename"
absoluteName, _ := filepath.Abs(c.Database)
if c.String() != "file://"+absoluteName+"?cache=shared" {
t.Fatal(`Test failed, got:`, c.String())
}
// Adding an option.
c.Options = map[string]string{
"cache": "foobar",
"mode": "ro",
}
if c.String() != "file://"+absoluteName+"?cache=foobar&mode=ro" {
t.Fatal(`Test failed, got:`, c.String())
}
// Setting another database.
c.Database = "/another/database"
if c.String() != `file:///another/database?cache=foobar&mode=ro` {
t.Fatal(`Test failed, got:`, c.String())
}
}
func TestParseConnectionURL(t *testing.T) {
var u ConnectionURL
var s string
var err error
s = "file://mydatabase.db"
if u, err = ParseURL(s); err != nil {
t.Fatal(err)
}
if u.Database != "mydatabase.db" {
t.Fatal("Failed to parse database.")
}
if u.Options["cache"] != "shared" {
t.Fatal("If not defined, cache should be shared by default.")
}
s = "file:///path/to/my/database.db?mode=ro&cache=foobar"
if u, err = ParseURL(s); err != nil {
t.Fatal(err)
}
if u.Database != "/path/to/my/database.db" {
t.Fatal("Failed to parse username.")
}
if u.Options["cache"] != "foobar" {
t.Fatal("Expecting option.")
}
if u.Options["mode"] != "ro" {
t.Fatal("Expecting option.")
}
s = "http://example.org"
if _, err = ParseURL(s); err == nil {
t.Fatal("Expecting error.")
}
}
......@@ -55,7 +55,7 @@ var (
)
type source struct {
config db.Settings
connURL db.ConnectionURL
session *sql.DB
tx *tx
schema *schema.DatabaseSchema
......@@ -121,7 +121,13 @@ func (s *source) populateSchema() (err error) {
s.schema = schema.NewDatabaseSchema()
s.schema.Name = s.config.Database
var conn ConnectionURL
if conn, err = ParseURL(s.connURL.String()); err != nil {
return err
}
s.schema.Name = conn.Database
// The Collections() call will populate schema if its nil.
if collections, err = s.Collections(); err != nil {
......@@ -249,7 +255,7 @@ func (s *source) doRawQuery(query string, args ...interface{}) (*sql.Rows, error
// Returns the string name of the database.
func (s *source) Name() string {
return s.config.Database
return s.schema.Name
}
// Ping verifies a connection to the database is still alive,
......@@ -260,7 +266,7 @@ func (s *source) Ping() error {
func (s *source) clone() (*source, error) {
src := &source{}
src.Setup(s.config)
src.Setup(s.connURL)
if err := src.Open(); err != nil {
return nil, err
......@@ -294,8 +300,8 @@ func (s *source) Transaction() (db.Tx, error) {
}
// Stores database settings.
func (s *source) Setup(config db.Settings) error {
s.config = config
func (s *source) Setup(conn db.ConnectionURL) error {
s.connURL = conn
return s.Open()
}
......@@ -308,11 +314,22 @@ func (s *source) Driver() interface{} {
func (s *source) Open() error {
var err error
if s.config.Database == `` {
return db.ErrMissingDatabaseName
// Before db.ConnectionURL we used a unified db.Settings struct. This
// condition checks for that type and provides backwards compatibility.
if settings, ok := s.connURL.(db.Settings); ok {
// User is providing a db.Settings struct, let's translate it into a
// ConnectionURL{}.
conn := ConnectionURL{
Database: settings.Database,
Options: map[string]string{
"cache": "shared",
},
}
s.connURL = conn
}
if s.session, err = sql.Open(`sqlite3`, fmt.Sprintf(`file:%s?cache=shared`, s.config.Database)); err != nil {
if s.session, err = sql.Open(`sqlite3`, s.connURL.String()); err != nil {
return err
}
......@@ -332,20 +349,23 @@ func (s *source) Close() error {
}
// Changes the active database.
func (s *source) Use(database string) error {
s.config.Database = database
func (s *source) Use(database string) (err error) {
var conn ConnectionURL
if conn, err = ParseURL(s.connURL.String()); err != nil {
return err
}
conn.Database = database
s.connURL = conn
return s.Open()
}
// Drops the currently active database.
func (s *source) Drop() error {
_, err := s.doQuery(sqlgen.Statement{
Type: sqlgen.SqlDropDatabase,
Database: sqlgen.Database{s.config.Database},
})
return err
return db.ErrUnsupported
}
// Collections() Returns a list of non-system tables/collections contained
......
......@@ -46,7 +46,7 @@ const (
database = `_dumps/gotest.sqlite3.db`
)
var settings = db.Settings{
var settings = ConnectionURL{
Database: database,
}
......@@ -111,6 +111,43 @@ func TestOpenFailed(t *testing.T) {
}
}
// Old settings must be compatible.
func TestOldSettings(t *testing.T) {
var err error
var sess db.Database
oldSettings := db.Settings{
Database: database,
}
// Opening database.
if sess, err = db.Open(Adapter, oldSettings); err != nil {
t.Fatal(err)
}
// Closing database.
sess.Close()
}
// Test USE
func TestUse(t *testing.T) {
var err error
var sess db.Database
// Opening database, no error expected.
if sess, err = db.Open(Adapter, settings); err != nil {
t.Fatal(err)
}
// Connecting to another database, error expected.
if err = sess.Use("."); err == nil {
t.Fatal("This is not a database")
}
// Closing connection.
sess.Close()
}
// Attempts to get all collections and truncate each one of them.
func TestTruncate(t *testing.T) {
var err error
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment