github.com/Accefy/pop@v0.0.0-20230428174248-e9f677eab5b9/dialect_common.go (about) 1 package pop 2 3 import ( 4 "bytes" 5 "database/sql" 6 "encoding/gob" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "os/exec" 12 "strings" 13 14 "github.com/gobuffalo/pop/v6/columns" 15 "github.com/gobuffalo/pop/v6/logging" 16 "github.com/gofrs/uuid" 17 "github.com/jmoiron/sqlx" 18 ) 19 20 func init() { 21 gob.Register(uuid.UUID{}) 22 } 23 24 type commonDialect struct { 25 ConnectionDetails *ConnectionDetails 26 } 27 28 func (commonDialect) Lock(fn func() error) error { 29 return fn() 30 } 31 32 func (commonDialect) Quote(key string) string { 33 parts := strings.Split(key, ".") 34 35 for i, part := range parts { 36 part = strings.Trim(part, `"`) 37 part = strings.TrimSpace(part) 38 39 parts[i] = fmt.Sprintf(`"%v"`, part) 40 } 41 42 return strings.Join(parts, ".") 43 } 44 45 func genericCreate(c *Connection, model *Model, cols columns.Columns, quoter quotable) error { 46 keyType, err := model.PrimaryKeyType() 47 if err != nil { 48 return err 49 } 50 switch keyType { 51 case "int", "int64": 52 var id int64 53 cols.Remove(model.IDField()) 54 w := cols.Writeable() 55 query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", quoter.Quote(model.TableName()), w.QuotedString(quoter), w.SymbolizedString()) 56 txlog(logging.SQL, c, query, model.Value) 57 res, err := c.Store.NamedExecContext(model.ctx, query, model.Value) 58 if err != nil { 59 return err 60 } 61 id, err = res.LastInsertId() 62 if err == nil { 63 model.setID(id) 64 } 65 if err != nil { 66 return err 67 } 68 return nil 69 case "UUID", "string": 70 if keyType == "UUID" { 71 if model.ID() == emptyUUID { 72 u, err := uuid.NewV4() 73 if err != nil { 74 return err 75 } 76 model.setID(u) 77 } 78 } else if model.ID() == "" { 79 return fmt.Errorf("missing ID value") 80 } 81 w := cols.Writeable() 82 w.Add(model.IDField()) 83 query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", quoter.Quote(model.TableName()), w.QuotedString(quoter), w.SymbolizedString()) 84 txlog(logging.SQL, c, query, model.Value) 85 if _, err := c.Store.NamedExecContext(model.ctx, query, model.Value); err != nil { 86 return fmt.Errorf("named insert: %w", err) 87 } 88 return nil 89 } 90 return fmt.Errorf("can not use %s as a primary key type!", keyType) 91 } 92 93 func genericUpdate(c *Connection, model *Model, cols columns.Columns, quoter quotable) error { 94 stmt := fmt.Sprintf("UPDATE %s AS %s SET %s WHERE %s", quoter.Quote(model.TableName()), model.Alias(), cols.Writeable().QuotedUpdateString(quoter), model.WhereNamedID()) 95 txlog(logging.SQL, c, stmt, model.ID()) 96 _, err := c.Store.NamedExecContext(model.ctx, stmt, model.Value) 97 if err != nil { 98 return err 99 } 100 return nil 101 } 102 103 func genericUpdateQuery(c *Connection, model *Model, cols columns.Columns, quoter quotable, query Query, bindType int) (int64, error) { 104 q := fmt.Sprintf("UPDATE %s AS %s SET %s", quoter.Quote(model.TableName()), model.Alias(), cols.Writeable().QuotedUpdateString(quoter)) 105 106 q, updateArgs, err := sqlx.Named(q, model.Value) 107 if err != nil { 108 return 0, err 109 } 110 111 sb := query.toSQLBuilder(model) 112 q = sb.buildWhereClauses(q) 113 114 q = sqlx.Rebind(bindType, q) 115 116 result, err := genericExec(c, q, append(updateArgs, sb.args...)...) 117 if err != nil { 118 return 0, err 119 } 120 121 n, err := result.RowsAffected() 122 if err != nil { 123 return 0, err 124 } 125 126 return n, err 127 } 128 129 func genericDestroy(c *Connection, model *Model, quoter quotable) error { 130 stmt := fmt.Sprintf("DELETE FROM %s AS %s WHERE %s", quoter.Quote(model.TableName()), model.Alias(), model.WhereID()) 131 _, err := genericExec(c, stmt, model.ID()) 132 if err != nil { 133 return err 134 } 135 return nil 136 } 137 138 func genericDelete(c *Connection, model *Model, query Query) error { 139 sqlQuery, args := query.ToSQL(model) 140 _, err := genericExec(c, sqlQuery, args...) 141 return err 142 } 143 144 func genericExec(c *Connection, stmt string, args ...interface{}) (sql.Result, error) { 145 txlog(logging.SQL, c, stmt, args...) 146 res, err := c.Store.ExecContext(c.Context(), stmt, args...) 147 return res, err 148 } 149 150 func genericSelectOne(c *Connection, model *Model, query Query) error { 151 sqlQuery, args := query.ToSQL(model) 152 txlog(logging.SQL, query.Connection, sqlQuery, args...) 153 err := c.Store.GetContext(model.ctx, model.Value, sqlQuery, args...) 154 if err != nil { 155 return err 156 } 157 return nil 158 } 159 160 func genericSelectMany(c *Connection, models *Model, query Query) error { 161 sqlQuery, args := query.ToSQL(models) 162 txlog(logging.SQL, query.Connection, sqlQuery, args...) 163 err := c.Store.SelectContext(models.ctx, models.Value, sqlQuery, args...) 164 if err != nil { 165 return err 166 } 167 return nil 168 } 169 170 func genericLoadSchema(d dialect, r io.Reader) error { 171 deets := d.Details() 172 173 // Open DB connection on the target DB 174 db, err := openPotentiallyInstrumentedConnection(d, d.MigrationURL()) 175 if err != nil { 176 return fmt.Errorf("unable to load schema for %s: %w", deets.Database, err) 177 } 178 defer db.Close() 179 180 // Get reader contents 181 contents, err := ioutil.ReadAll(r) 182 if err != nil { 183 return err 184 } 185 186 if len(contents) == 0 { 187 log(logging.Info, "schema is empty for %s, skipping", deets.Database) 188 return nil 189 } 190 191 _, err = db.Exec(string(contents)) 192 if err != nil { 193 return fmt.Errorf("unable to load schema for %s: %w", deets.Database, err) 194 } 195 196 log(logging.Info, "loaded schema for %s", deets.Database) 197 return nil 198 } 199 200 func genericDumpSchema(deets *ConnectionDetails, cmd *exec.Cmd, w io.Writer) error { 201 log(logging.SQL, strings.Join(cmd.Args, " ")) 202 203 bb := &bytes.Buffer{} 204 mw := io.MultiWriter(w, bb) 205 206 cmd.Stdout = mw 207 cmd.Stderr = os.Stderr 208 209 err := cmd.Run() 210 if err != nil { 211 return err 212 } 213 214 x := bytes.TrimSpace(bb.Bytes()) 215 if len(x) == 0 { 216 return fmt.Errorf("unable to dump schema for %s", deets.Database) 217 } 218 219 log(logging.Info, "dumped schema for %s", deets.Database) 220 return nil 221 }