github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/sorted/postgres/postgreskv.go (about) 1 /* 2 Copyright 2012 The Camlistore Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package postgres provides an implementation of sorted.KeyValue 18 // on top of PostgreSQL. 19 package postgres 20 21 import ( 22 "database/sql" 23 "fmt" 24 "os" 25 "regexp" 26 27 "camlistore.org/pkg/jsonconfig" 28 "camlistore.org/pkg/sorted" 29 "camlistore.org/pkg/sorted/sqlkv" 30 31 _ "camlistore.org/third_party/github.com/lib/pq" 32 ) 33 34 func init() { 35 sorted.RegisterKeyValue("postgres", newKeyValueFromJSONConfig) 36 } 37 38 func newKeyValueFromJSONConfig(cfg jsonconfig.Obj) (sorted.KeyValue, error) { 39 conninfo := fmt.Sprintf("user=%s dbname=%s host=%s password=%s sslmode=%s", 40 cfg.RequiredString("user"), 41 cfg.RequiredString("database"), 42 cfg.OptionalString("host", "localhost"), 43 cfg.OptionalString("password", ""), 44 cfg.OptionalString("sslmode", "require"), 45 ) 46 if err := cfg.Validate(); err != nil { 47 return nil, err 48 } 49 db, err := sql.Open("postgres", conninfo) 50 if err != nil { 51 return nil, err 52 } 53 for _, tableSql := range SQLCreateTables() { 54 if _, err := db.Exec(tableSql); err != nil { 55 return nil, fmt.Errorf("error creating table with %q: %v", tableSql, err) 56 } 57 } 58 for _, statement := range SQLDefineReplace() { 59 if _, err := db.Exec(statement); err != nil { 60 return nil, fmt.Errorf("error setting up replace statement with %q: %v", statement, err) 61 } 62 } 63 r, err := db.Query(fmt.Sprintf(`SELECT replaceintometa('version', '%d')`, SchemaVersion())) 64 if err != nil { 65 return nil, fmt.Errorf("error setting schema version: %v", err) 66 } 67 r.Close() 68 69 kv := &keyValue{ 70 db: db, 71 KeyValue: &sqlkv.KeyValue{ 72 DB: db, 73 SetFunc: altSet, 74 BatchSetFunc: altBatchSet, 75 PlaceHolderFunc: replacePlaceHolders, 76 }, 77 } 78 if err := kv.ping(); err != nil { 79 return nil, fmt.Errorf("PostgreSQL db unreachable: %v", err) 80 } 81 version, err := kv.SchemaVersion() 82 if err != nil { 83 return nil, fmt.Errorf("error getting schema version (need to init database?): %v", err) 84 } 85 if version != requiredSchemaVersion { 86 if os.Getenv("CAMLI_DEV_CAMLI_ROOT") != "" { 87 // Good signal that we're using the devcam server, so help out 88 // the user with a more useful tip: 89 return nil, fmt.Errorf("database schema version is %d; expect %d (run \"devcam server --wipe\" to wipe both your blobs and re-populate the database schema)", version, requiredSchemaVersion) 90 } 91 return nil, fmt.Errorf("database schema version is %d; expect %d (need to re-init/upgrade database?)", 92 version, requiredSchemaVersion) 93 } 94 95 return kv, nil 96 } 97 98 type keyValue struct { 99 *sqlkv.KeyValue 100 db *sql.DB 101 } 102 103 // postgres does not have REPLACE INTO (upsert), so we use that custom 104 // one for Set operations instead 105 func altSet(db *sql.DB, key, value string) error { 106 r, err := db.Query("SELECT replaceinto($1, $2)", key, value) 107 if err != nil { 108 return err 109 } 110 return r.Close() 111 } 112 113 // postgres does not have REPLACE INTO (upsert), so we use that custom 114 // one for Set operations in batch instead 115 func altBatchSet(tx *sql.Tx, key, value string) error { 116 r, err := tx.Query("SELECT replaceinto($1, $2)", key, value) 117 if err != nil { 118 return err 119 } 120 return r.Close() 121 } 122 123 var qmark = regexp.MustCompile(`\?`) 124 125 // replace all ? placeholders into the corresponding $n in queries 126 var replacePlaceHolders = func(query string) string { 127 i := 0 128 dollarInc := func(b []byte) []byte { 129 i++ 130 return []byte(fmt.Sprintf("$%d", i)) 131 } 132 return string(qmark.ReplaceAllFunc([]byte(query), dollarInc)) 133 } 134 135 func (kv *keyValue) ping() error { 136 _, err := kv.SchemaVersion() 137 return err 138 } 139 140 func (kv *keyValue) SchemaVersion() (version int, err error) { 141 err = kv.db.QueryRow("SELECT value FROM meta WHERE metakey='version'").Scan(&version) 142 return 143 }