gitee.com/liuxuezhan/go-micro-v1.18.0@v1.0.0/store/postgresql/postgresql.go (about) 1 // Package postgresql implements a micro Store backed by sql 2 package postgresql 3 4 import ( 5 "database/sql" 6 "fmt" 7 "strings" 8 "time" 9 "unicode" 10 11 "github.com/lib/pq" 12 "github.com/pkg/errors" 13 14 "gitee.com/liuxuezhan/go-micro-v1.18.0/config/options" 15 "gitee.com/liuxuezhan/go-micro-v1.18.0/store" 16 ) 17 18 // DefaultNamespace is the namespace that the sql store 19 // will use if no namespace is provided. 20 const DefaultNamespace = "micro" 21 22 type sqlStore struct { 23 db *sql.DB 24 25 table string 26 options.Options 27 } 28 29 // List all the known records 30 func (s *sqlStore) List() ([]*store.Record, error) { 31 q, err := s.db.Prepare(fmt.Sprintf("SELECT key, value, expiry FROM micro.%s;", s.table)) 32 if err != nil { 33 return nil, err 34 } 35 var records []*store.Record 36 var timehelper pq.NullTime 37 rows, err := q.Query() 38 if err != nil { 39 if err == sql.ErrNoRows { 40 return records, nil 41 } 42 return nil, err 43 } 44 defer rows.Close() 45 for rows.Next() { 46 record := &store.Record{} 47 if err := rows.Scan(&record.Key, &record.Value, &timehelper); err != nil { 48 return records, err 49 } 50 if timehelper.Valid { 51 if timehelper.Time.Before(time.Now()) { 52 // record has expired 53 go s.Delete(record.Key) 54 } else { 55 record.Expiry = time.Until(timehelper.Time) 56 records = append(records, record) 57 } 58 } else { 59 records = append(records, record) 60 } 61 62 } 63 rowErr := rows.Close() 64 if rowErr != nil { 65 // transaction rollback or something 66 return records, rowErr 67 } 68 if err := rows.Err(); err != nil { 69 return records, err 70 } 71 return records, nil 72 } 73 74 // Read all records with keys 75 func (s *sqlStore) Read(keys ...string) ([]*store.Record, error) { 76 q, err := s.db.Prepare(fmt.Sprintf("SELECT key, value, expiry FROM micro.%s WHERE key = $1;", s.table)) 77 if err != nil { 78 return nil, err 79 } 80 var records []*store.Record 81 var timehelper pq.NullTime 82 for _, key := range keys { 83 row := q.QueryRow(key) 84 record := &store.Record{} 85 if err := row.Scan(&record.Key, &record.Value, &timehelper); err != nil { 86 if err == sql.ErrNoRows { 87 return records, store.ErrNotFound 88 } 89 return records, err 90 } 91 if timehelper.Valid { 92 if timehelper.Time.Before(time.Now()) { 93 // record has expired 94 go s.Delete(key) 95 return records, store.ErrNotFound 96 } 97 record.Expiry = time.Until(timehelper.Time) 98 records = append(records, record) 99 } else { 100 records = append(records, record) 101 } 102 } 103 return records, nil 104 } 105 106 // Write records 107 func (s *sqlStore) Write(rec ...*store.Record) error { 108 q, err := s.db.Prepare(fmt.Sprintf(`INSERT INTO micro.%s(key, value, expiry) 109 VALUES ($1, $2::bytea, $3) 110 ON CONFLICT (key) 111 DO UPDATE 112 SET value = EXCLUDED.value, expiry = EXCLUDED.expiry;`, s.table)) 113 if err != nil { 114 return err 115 } 116 for _, r := range rec { 117 var err error 118 if r.Expiry != 0 { 119 _, err = q.Exec(r.Key, r.Value, time.Now().Add(r.Expiry)) 120 } else { 121 _, err = q.Exec(r.Key, r.Value, nil) 122 } 123 if err != nil { 124 return errors.Wrap(err, "Couldn't insert record "+r.Key) 125 } 126 } 127 128 return nil 129 } 130 131 // Delete records with keys 132 func (s *sqlStore) Delete(keys ...string) error { 133 q, err := s.db.Prepare(fmt.Sprintf("DELETE FROM micro.%s WHERE key = $1;", s.table)) 134 if err != nil { 135 return err 136 } 137 for _, key := range keys { 138 result, err := q.Exec(key) 139 if err != nil { 140 return err 141 } 142 _, err = result.RowsAffected() 143 if err != nil { 144 return err 145 } 146 } 147 return nil 148 } 149 150 func (s *sqlStore) initDB(options options.Options) error { 151 // Get the store.namespace option, or use sql.DefaultNamespace 152 namespaceOpt, found := options.Values().Get("store.namespace") 153 if !found { 154 s.table = DefaultNamespace 155 } else { 156 if namespace, ok := namespaceOpt.(string); ok { 157 s.table = namespace 158 } else { 159 return errors.New("store.namespace option must be a string") 160 } 161 } 162 163 // Create "micro" schema 164 schema, err := s.db.Prepare("CREATE SCHEMA IF NOT EXISTS micro ;") 165 if err != nil { 166 return err 167 } 168 _, err = schema.Exec() 169 if err != nil { 170 return errors.Wrap(err, "Couldn't create Schema") 171 } 172 173 // Create a table for the Store namespace 174 tableq, err := s.db.Prepare(fmt.Sprintf(`CREATE TABLE IF NOT EXISTS micro.%s 175 ( 176 key text COLLATE "default" NOT NULL, 177 value bytea, 178 expiry timestamp with time zone, 179 CONSTRAINT %s_pkey PRIMARY KEY (key) 180 );`, s.table, s.table)) 181 if err != nil { 182 return errors.Wrap(err, "SQL statement preparation failed") 183 } 184 _, err = tableq.Exec() 185 if err != nil { 186 return errors.Wrap(err, "Couldn't create table") 187 } 188 189 return nil 190 } 191 192 // New returns a new micro Store backed by sql 193 func New(opts ...options.Option) (store.Store, error) { 194 options := options.NewOptions(opts...) 195 driver, dataSourceName, err := validateOptions(options) 196 if err != nil { 197 return nil, err 198 } 199 if !strings.Contains(dataSourceName, " ") { 200 dataSourceName = fmt.Sprintf("host=%s", dataSourceName) 201 } 202 db, err := sql.Open(driver, dataSourceName) 203 if err != nil { 204 return nil, err 205 } 206 if err := db.Ping(); err != nil { 207 return nil, err 208 } 209 s := &sqlStore{ 210 db: db, 211 } 212 213 return s, s.initDB(options) 214 } 215 216 // validateOptions checks whether the provided options are valid, then returns the driver 217 // and data source name. 218 func validateOptions(options options.Options) (driver, dataSourceName string, err error) { 219 driverOpt, found := options.Values().Get("store.sql.driver") 220 if !found { 221 return "", "", errors.New("No store.sql.driver option specified") 222 } 223 nodesOpt, found := options.Values().Get("store.nodes") 224 if !found { 225 return "", "", errors.New("No store.nodes option specified (expected a database connection string)") 226 } 227 driver, ok := driverOpt.(string) 228 if !ok { 229 return "", "", errors.New("store.sql.driver option must be a string") 230 } 231 nodes, ok := nodesOpt.([]string) 232 if !ok { 233 return "", "", errors.New("store.nodes option must be a []string") 234 } 235 if len(nodes) != 1 { 236 return "", "", errors.New("expected only 1 store.nodes option") 237 } 238 namespaceOpt, found := options.Values().Get("store.namespace") 239 if found { 240 namespace, ok := namespaceOpt.(string) 241 if !ok { 242 return "", "", errors.New("store.namespace must me a string") 243 } 244 for _, r := range namespace { 245 if !unicode.IsLetter(r) { 246 return "", "", errors.New("store.namespace must only contain letters") 247 } 248 } 249 } 250 return driver, nodes[0], nil 251 }