scopes
2015-12-13
scopes/scope_postgres.go
Port over Postgres storage and tests. Do the minimum possible port of the code from auth to get Postgres working and make the tests run and pass again. This leaves a bug where the ErrScopeAlreadyExists type, when populatd from Postgres, contains the entire error string returned from the database, instead of parsing the ID itself out. Which is a thing we should do and add a test for.
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/scope_postgres.go Sun Dec 13 20:42:48 2015 -0800 1.3 @@ -0,0 +1,157 @@ 1.4 +package scopes 1.5 + 1.6 +import ( 1.7 + "code.secondbit.org/scopes.hg/types" 1.8 + "golang.org/x/net/context" 1.9 + 1.10 + "github.com/lib/pq" 1.11 + "github.com/secondbit/pan" 1.12 +) 1.13 + 1.14 +func (p *postgres) createScopesSQL(scopes []scopeTypes.Scope) *pan.Query { 1.15 + fields, _ := pan.GetFields(scopes[0]) 1.16 + query := pan.New(pan.POSTGRES, "INSERT INTO "+pan.GetTableName(scopes[0])) 1.17 + query.Include("(" + pan.QueryList(fields) + ")") 1.18 + query.Include("VALUES") 1.19 + query.FlushExpressions(" ") 1.20 + for _, scope := range scopes { 1.21 + _, values := pan.GetFields(scope) 1.22 + query.Include("("+pan.VariableList(len(values))+")", values...) 1.23 + } 1.24 + return query.FlushExpressions(", ") 1.25 +} 1.26 + 1.27 +func (p *postgres) CreateScopes(scopes []scopeTypes.Scope, ctx context.Context) error { 1.28 + if len(scopes) < 1 { 1.29 + return nil 1.30 + } 1.31 + query := p.createScopesSQL(scopes) 1.32 + _, err := p.db.Exec(query.String(), query.Args...) 1.33 + if e, ok := err.(*pq.Error); ok && e.Constraint == "scopes_pkey" { 1.34 + // BUG(paddy): we need to parse e.Detail to pull out the duplicate Scope ID 1.35 + err = ErrScopeAlreadyExists(e.Detail) 1.36 + } 1.37 + return err 1.38 +} 1.39 + 1.40 +func (p *postgres) getScopesSQL(ids []string) *pan.Query { 1.41 + var scope scopeTypes.Scope 1.42 + intids := make([]interface{}, len(ids)) 1.43 + for pos, id := range ids { 1.44 + intids[pos] = id 1.45 + } 1.46 + fields, _ := pan.GetFields(scope) 1.47 + query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(scope)) 1.48 + query.IncludeWhere() 1.49 + query.Include(pan.GetUnquotedColumn(scope, "ID") + " IN") 1.50 + query.Include("("+pan.VariableList(len(ids))+")", intids...) 1.51 + return query.FlushExpressions(" ") 1.52 +} 1.53 + 1.54 +func (p *postgres) GetScopes(ids []string, ctx context.Context) (map[string]scopeTypes.Scope, error) { 1.55 + query := p.getScopesSQL(ids) 1.56 + rows, err := p.db.Query(query.String(), query.Args...) 1.57 + if err != nil { 1.58 + return map[string]scopeTypes.Scope{}, err 1.59 + } 1.60 + scopes := map[string]scopeTypes.Scope{} 1.61 + for rows.Next() { 1.62 + var scope scopeTypes.Scope 1.63 + err := pan.Unmarshal(rows, &scope) 1.64 + if err != nil { 1.65 + return scopes, err 1.66 + } 1.67 + scopes[scope.ID] = scope 1.68 + } 1.69 + if err = rows.Err(); err != nil { 1.70 + return scopes, err 1.71 + } 1.72 + return scopes, nil 1.73 +} 1.74 + 1.75 +func (p *postgres) updateScopeSQL(change scopeTypes.ScopeChange) *pan.Query { 1.76 + var scope scopeTypes.Scope 1.77 + query := pan.New(pan.POSTGRES, "UPDATE "+pan.GetTableName(scope)+" SET ") 1.78 + query.IncludeIfNotNil(pan.GetUnquotedColumn(scope, "Name")+" = ?", change.Name) 1.79 + query.IncludeIfNotNil(pan.GetUnquotedColumn(scope, "Description")+" = ?", change.Description) 1.80 + query.FlushExpressions(", ") 1.81 + query.IncludeWhere() 1.82 + query.Include(pan.GetUnquotedColumn(scope, "ID")+" = ?", change.ScopeID) 1.83 + return query.FlushExpressions(" ") 1.84 +} 1.85 + 1.86 +func (p *postgres) UpdateScope(change scopeTypes.ScopeChange, ctx context.Context) error { 1.87 + if change.Empty() { 1.88 + return nil 1.89 + } 1.90 + query := p.updateScopeSQL(change) 1.91 + res, err := p.db.Exec(query.String(), query.Args...) 1.92 + if err != nil { 1.93 + return err 1.94 + } 1.95 + rows, err := res.RowsAffected() 1.96 + if err != nil { 1.97 + return err 1.98 + } 1.99 + if rows < 1 { 1.100 + return ErrScopeNotFound 1.101 + } 1.102 + return err 1.103 +} 1.104 + 1.105 +func (p *postgres) removeScopesSQL(ids []string) *pan.Query { 1.106 + var scope scopeTypes.Scope 1.107 + intids := make([]interface{}, len(ids)) 1.108 + for pos, id := range ids { 1.109 + intids[pos] = id 1.110 + } 1.111 + query := pan.New(pan.POSTGRES, "DELETE FROM "+pan.GetTableName(scope)) 1.112 + query.IncludeWhere() 1.113 + query.Include(pan.GetUnquotedColumn(scope, "ID") + " IN") 1.114 + query.Include("("+pan.VariableList(len(ids))+")", intids...) 1.115 + return query.FlushExpressions(" ") 1.116 +} 1.117 + 1.118 +func (p *postgres) RemoveScopes(ids []string, ctx context.Context) error { 1.119 + query := p.removeScopesSQL(ids) 1.120 + res, err := p.db.Exec(query.String(), query.Args...) 1.121 + if err != nil { 1.122 + return err 1.123 + } 1.124 + rows, err := res.RowsAffected() 1.125 + if err != nil { 1.126 + return err 1.127 + } 1.128 + if rows < 1 { 1.129 + return ErrScopeNotFound 1.130 + } 1.131 + return nil 1.132 +} 1.133 + 1.134 +func (p *postgres) listScopesSQL() *pan.Query { 1.135 + var scope scopeTypes.Scope 1.136 + fields, _ := pan.GetFields(scope) 1.137 + query := pan.New(pan.POSTGRES, "SELECT "+pan.QueryList(fields)+" FROM "+pan.GetTableName(scope)) 1.138 + return query.FlushExpressions(" ") 1.139 +} 1.140 + 1.141 +func (p *postgres) ListScopes(ctx context.Context) ([]scopeTypes.Scope, error) { 1.142 + query := p.listScopesSQL() 1.143 + rows, err := p.db.Query(query.String(), query.Args...) 1.144 + if err != nil { 1.145 + return []scopeTypes.Scope{}, err 1.146 + } 1.147 + var scopes []scopeTypes.Scope 1.148 + for rows.Next() { 1.149 + var scope scopeTypes.Scope 1.150 + err = pan.Unmarshal(rows, &scope) 1.151 + if err != nil { 1.152 + return scopes, err 1.153 + } 1.154 + scopes = append(scopes, scope) 1.155 + } 1.156 + if err = rows.Err(); err != nil { 1.157 + return scopes, err 1.158 + } 1.159 + return scopes, nil 1.160 +}