├── README ├── go.mod ├── go.sum ├── LICENSE ├── db_test.go └── db.go /README: -------------------------------------------------------------------------------- 1 | go get [-u] rsc.io/dbstore 2 | https://rsc.io/dbstore 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module rsc.io/dbstore 2 | 3 | go 1.13 4 | 5 | require rsc.io/sqlite v0.5.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | rsc.io/sqlite v0.5.0 h1:HG63YxeP0eALjqorwnJ9ENxUUOUR6NYJ4FHEKFJ7aVk= 2 | rsc.io/sqlite v0.5.0/go.mod h1:fqHuveM9iIqMzjD0WiZIvKYMty/WqTo2bxE9+zC54WE= 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /db_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package dbstore 6 | 7 | import ( 8 | "database/sql" 9 | "testing" 10 | "time" 11 | 12 | _ "rsc.io/sqlite" 13 | ) 14 | 15 | type Data1 struct { 16 | Create string 17 | Table string 18 | On string 19 | } 20 | 21 | func TestBasic(t *testing.T) { 22 | db, err := sql.Open("sqlite3", ":memory:") 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | defer db.Close() 27 | 28 | storage := new(Storage) 29 | storage.Register(new(Data1)) 30 | if err := storage.CreateTables(db); err != nil { 31 | t.Fatal(err) 32 | } 33 | 34 | d1a := Data1{Create: "key", Table: "hello, world", On: "other world"} 35 | if err := storage.Insert(db, &d1a); err != nil { 36 | t.Fatal(err) 37 | } 38 | 39 | var d1b Data1 40 | if err := storage.Select(db, &d1b, ""); err != nil { 41 | t.Fatal(err) 42 | } 43 | 44 | if d1b != d1a { 45 | t.Errorf("wrong value from Select: have %+v want %+v", d1b, d1a) 46 | } 47 | 48 | var d1c Data1 49 | d1c.Create = "key" 50 | if err := storage.Read(db, &d1c, "Table"); err != nil { 51 | t.Fatal(err) 52 | } 53 | if d1c.Table != d1a.Table || d1c.On != "" { 54 | t.Errorf("wrong value from Read: have %q, %q want %q, %q", d1c.Table, d1c.On, d1a.Table, "") 55 | } 56 | 57 | d1a.On = "new" 58 | d1a.Table = "hi" 59 | if err := storage.Write(db, &d1a, "On"); err != nil { 60 | t.Fatal(err) 61 | } 62 | 63 | var d1d Data1 64 | d1d.Create = "key" 65 | if err := storage.Read(db, &d1c, "Table"); err != nil { 66 | t.Fatal(err) 67 | } 68 | if d1c.Table != "hello, world" || d1c.On != "" { 69 | t.Errorf("wrong value from Read: have %q, %q want %q, %q", d1c.Table, d1c.On, "hello, world", "") 70 | } 71 | } 72 | 73 | func TestRowidInsert(t *testing.T) { 74 | Debug = true 75 | db, err := sql.Open("sqlite3", ":memory:") 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | defer db.Close() 80 | 81 | storage := new(Storage) 82 | storage.Register(new(Msg)) 83 | if err := storage.CreateTables(db); err != nil { 84 | t.Fatal(err) 85 | } 86 | 87 | var t1 Msg 88 | t1.X = 123 89 | if err := storage.Insert(db, &t1); err != nil { 90 | t.Fatal(err) 91 | } 92 | t1.X = 234 93 | if err := storage.Insert(db, &t1); err != nil { 94 | t.Fatal(err) 95 | } 96 | 97 | var all []Msg 98 | if err := storage.Select(db, &all, "order by X"); err != nil { 99 | t.Fatal(err) 100 | } 101 | 102 | if len(all) != 2 || all[0].X != 123 || all[1].X != 234 { 103 | t.Fatalf("wrong results: %v", all) 104 | } 105 | } 106 | 107 | type Msg struct { 108 | X int64 `dbstore:",rowid"` 109 | Y time.Time 110 | Z bool 111 | } 112 | -------------------------------------------------------------------------------- /db.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package dbstore stores and retrieves Go data structures as rows in a SQL database. 6 | // 7 | // Each struct type is stored in its own table, and each field is a separate column of that table. 8 | // This package makes it easy to store structs into such a database and to read them back out. 9 | // 10 | // UNFINISHED! USE AT YOUR OWN RISK! 11 | // 12 | package dbstore // import "rsc.io/dbstore" 13 | 14 | import ( 15 | "bytes" 16 | "database/sql" 17 | "errors" 18 | "fmt" 19 | "os" 20 | "reflect" 21 | "strings" 22 | "time" 23 | "unicode/utf8" 24 | ) 25 | 26 | // A Storage records information about the data structures being stored. 27 | // It must be initialized by one or more calls to Register before the other methods are called. 28 | type Storage struct { 29 | types []*dtype 30 | typeByReflect map[reflect.Type]*dtype 31 | } 32 | 33 | type dtype struct { 34 | name string 35 | fields []*field 36 | fieldByName map[string]*field 37 | keys []*field 38 | rowid *field 39 | fts4 bool 40 | } 41 | 42 | type field struct { 43 | key bool 44 | rowid bool 45 | autoinc bool 46 | utf8 bool 47 | name string 48 | dbtype string 49 | index []int 50 | } 51 | 52 | // A Context represents the underlying SQL database. 53 | // Typically a *sql.DB is used as the Context implementation, 54 | // but the interface allows debugging adapters to be substituted. 55 | type Context interface { 56 | Exec(query string, args ...interface{}) (sql.Result, error) 57 | Query(query string, args ...interface{}) (*sql.Rows, error) 58 | } 59 | 60 | const ( 61 | ptrStruct = iota 62 | ptrPtrStruct 63 | ptrSliceStruct 64 | ptrSlicePtrStruct 65 | ) 66 | 67 | type debugCtxt struct { 68 | ctxt Context 69 | } 70 | 71 | func (d *debugCtxt) Exec(query string, args ...interface{}) (sql.Result, error) { 72 | fmt.Fprintf(os.Stderr, "SQL: %s %v\n", query, args) 73 | return d.ctxt.Exec(query, args...) 74 | } 75 | 76 | func (d *debugCtxt) Query(query string, args ...interface{}) (*sql.Rows, error) { 77 | fmt.Fprintf(os.Stderr, "SQL: %s %v\n", query, args) 78 | return d.ctxt.Query(query, args...) 79 | } 80 | 81 | // If Debug is set to true, each Storage method will print a log of the SQL 82 | // commands being executed. 83 | var Debug = false 84 | 85 | func debugContext(ctxt Context) Context { 86 | if Debug { 87 | return &debugCtxt{ctxt} 88 | } 89 | return ctxt 90 | } 91 | 92 | func (db *Storage) findType(val interface{}, op string) (*dtype, int, error) { 93 | t := reflect.TypeOf(val) 94 | if t.Kind() != reflect.Ptr { 95 | return nil, 0, fmt.Errorf("invalid type %T - must be pointer", val) 96 | } 97 | t = t.Elem() 98 | kind := ptrStruct 99 | if op == "Select" { 100 | if t.Kind() == reflect.Slice { 101 | kind = ptrSliceStruct 102 | t = t.Elem() 103 | } 104 | if t.Kind() == reflect.Ptr { 105 | kind++ 106 | t = t.Elem() 107 | } 108 | } 109 | if t.Kind() != reflect.Struct { 110 | return nil, 0, fmt.Errorf("invalid type %T - %s should be struct", val, t.String()) 111 | } 112 | dt := db.typeByReflect[t] 113 | if dt == nil { 114 | return nil, 0, fmt.Errorf("type %s not registered", t.String()) 115 | } 116 | return dt, kind, nil 117 | } 118 | 119 | // Register records that the storage should store values with the type of val, 120 | // which should be a pointer to a struct with exported fields. 121 | func (db *Storage) Register(val interface{}) { 122 | t := reflect.TypeOf(val) 123 | if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct || t.Elem().Name() == "" { 124 | panic(fmt.Sprintf("dbstore.Register: type %T Is not pointer to named struct", t)) 125 | } 126 | t = t.Elem() 127 | dt := &dtype{ 128 | name: t.Name(), 129 | fieldByName: make(map[string]*field), 130 | } 131 | first := true 132 | haveKey := false 133 | haveRowid := false 134 | for i := 0; i < t.NumField(); i++ { 135 | f := t.Field(i) 136 | if f.PkgPath != "" { 137 | continue 138 | } 139 | tag := f.Tag.Get("dbstore") 140 | if tag == "-" { 141 | continue 142 | } 143 | xname := f.Name 144 | x := strings.Split(tag, ",") 145 | if x[0] != "" { 146 | xname = x[0] 147 | } 148 | df := &field{ 149 | name: xname, 150 | index: []int{i}, 151 | } 152 | for _, attr := range x[1:] { 153 | switch attr { 154 | case "fts4": 155 | if !first { 156 | panic(fmt.Sprintf("dbstore.Register %s: fts4 must be attribute on first field", dt.name)) 157 | } 158 | dt.fts4 = true 159 | case "rowid": 160 | if haveKey { 161 | panic(fmt.Sprintf("dbstore.Register %s: cannot use rowid and key attributes in same struct", dt.name)) 162 | } 163 | if haveRowid { 164 | panic(fmt.Sprintf("dbstore.Register %s: cannot use rowid attribute ion multiple fields", dt.name)) 165 | } 166 | df.rowid = true 167 | case "key": 168 | if haveRowid { 169 | panic(fmt.Sprintf("dbstore.Register %s: cannot use rowid and key attributes in same struct", dt.name)) 170 | } 171 | df.key = true 172 | case "autoinc": 173 | df.autoinc = true 174 | case "utf8": 175 | df.utf8 = true 176 | if f.Type.Kind() != reflect.String { 177 | panic(fmt.Sprintf("dbstore.Register %s: field %s has attr utf8 but type %s", dt.name, f.Name, f.Type)) 178 | } 179 | } 180 | } 181 | if df.autoinc && !df.rowid { 182 | panic(fmt.Sprintf("dbstore.Register %s: cannot use autoinc without rowid attribute", dt.name)) 183 | } 184 | if df.rowid && f.Type.Kind() != reflect.Int64 { 185 | panic(fmt.Sprintf("dbstore.Register %s: rowid attribute must be used with int64 field", dt.name)) 186 | } 187 | 188 | switch f.Type.Kind() { 189 | default: 190 | panic(fmt.Sprintf("dbstore.Register %s: invalid field %s %s", dt.name, f.Name, f.Type)) 191 | case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int, 192 | reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint, reflect.Uintptr: 193 | df.dbtype = "integer" 194 | if df.rowid { 195 | df.dbtype += " primary key" 196 | if df.autoinc { 197 | df.dbtype += " autoincrement" 198 | } 199 | } 200 | case reflect.Float32, reflect.Float64: 201 | df.dbtype = "real" 202 | case reflect.Bool, reflect.String: 203 | // ok 204 | case reflect.Struct: 205 | if f.Type != reflect.TypeOf(time.Time{}) { 206 | panic(fmt.Sprintf("dbstore.Register %s: invalid field %s %s", dt.name, f.Name, f.Type)) 207 | } 208 | df.dbtype = "timestamp" 209 | case reflect.Slice: 210 | if f.Type.Elem() != reflect.TypeOf(byte(0)) { 211 | panic(fmt.Sprintf("dbstore.Register %s: invalid field %s %s", dt.name, f.Name, f.Type)) 212 | } 213 | df.dbtype = "blob" 214 | } 215 | 216 | if dt.fts4 { 217 | df.dbtype = "" 218 | } 219 | first = false 220 | 221 | dt.fields = append(dt.fields, df) 222 | dt.fieldByName[xname] = df 223 | if df.rowid { 224 | dt.rowid = df 225 | } 226 | if df.key { 227 | dt.keys = append(dt.keys, df) 228 | } 229 | if dt.fts4 && df.rowid { 230 | df.name = "docid" // required name for FTS4 rowid column 231 | } 232 | } 233 | 234 | if len(dt.fields) == 0 { 235 | panic(fmt.Sprintf("dbstore.Register %s: no fields to store", dt.name)) 236 | } 237 | 238 | if len(dt.keys) == 0 { 239 | // retroactively make the first field a key 240 | dt.keys = append(dt.keys, dt.fields[0]) 241 | dt.fields[0].key = true 242 | } 243 | 244 | db.types = append(db.types, dt) 245 | if db.typeByReflect == nil { 246 | db.typeByReflect = make(map[reflect.Type]*dtype) 247 | } 248 | db.typeByReflect[t] = dt 249 | } 250 | 251 | // NOTE: All these %q should really be a custom quoting mechanism 252 | // that emits sqlite3-double-quoted escapes, but since we don't expect 253 | // to see double quotes or backslashes in the names, using %q is fine. 254 | 255 | // CreateTables creates the tables to hold the registered types. 256 | // It only needs to be called when creating a new database. 257 | // Each table is named for the type it stores, in the form "full/import/path.TypeName". 258 | func (db *Storage) CreateTables(ctxt Context) error { 259 | ctxt = debugContext(ctxt) 260 | var buf bytes.Buffer 261 | for _, t := range db.types { 262 | buf.Reset() 263 | if t.fts4 { 264 | fmt.Fprintf(&buf, "create virtual table %q using fts4", t.name) 265 | } else { 266 | fmt.Fprintf(&buf, "create table %q", t.name) 267 | } 268 | 269 | fmt.Fprintf(&buf, " (") 270 | sep := "" 271 | for _, col := range t.fields { 272 | if t.fts4 && col.rowid { 273 | continue // fts4 rowid is implicit 274 | } 275 | fmt.Fprintf(&buf, "%s%q %s", sep, col.name, col.dbtype) 276 | sep = "," 277 | } 278 | if len(t.keys) > 0 && t.rowid == nil { 279 | fmt.Fprintf(&buf, ", unique (") 280 | for i, col := range t.keys { 281 | if i > 0 { 282 | fmt.Fprintf(&buf, ", ") 283 | } 284 | fmt.Fprintf(&buf, "%q", col.name) 285 | } 286 | fmt.Fprintf(&buf, ") on conflict replace") 287 | } 288 | fmt.Fprintf(&buf, ")") 289 | 290 | if _, err := ctxt.Exec(buf.String()); err != nil { 291 | return fmt.Errorf("creating table %s [%s]: %v", t.name, buf.String(), err) 292 | } 293 | } 294 | 295 | return nil 296 | } 297 | 298 | // Insert inserts the value into the database. 299 | func (db *Storage) Insert(ctxt Context, val interface{}) error { 300 | ctxt = debugContext(ctxt) 301 | t, _, err := db.findType(val, "Insert") 302 | if err != nil { 303 | return err 304 | } 305 | 306 | var args []interface{} 307 | rval := reflect.ValueOf(val).Elem() 308 | var rowcol *field 309 | for _, col := range t.fields { 310 | rv := rval.FieldByIndex(col.index) 311 | if col.rowid && rv.Int() == 0 { 312 | rowcol = col 313 | args = append(args, nil) 314 | } else { 315 | args = append(args, rval.FieldByIndex(col.index).Interface()) 316 | } 317 | } 318 | 319 | var buf bytes.Buffer 320 | if t.fts4 { 321 | fmt.Fprintf(&buf, "update %q", t.name) 322 | fmt.Fprintf(&buf, " set ") 323 | sep := "" 324 | var args1 []interface{} 325 | for i, col := range t.fields { 326 | if !col.key { 327 | fmt.Fprintf(&buf, "%s%q = ?", sep, col.name) 328 | sep = ", " 329 | args1 = append(args1, args[i]) 330 | } 331 | } 332 | fmt.Fprintf(&buf, " where ") 333 | sep = "" 334 | for i, col := range t.fields { 335 | if col.key { 336 | fmt.Fprintf(&buf, "%s%q = ?", sep, col.name) 337 | sep = ", " 338 | args1 = append(args1, args[i]) 339 | } 340 | } 341 | 342 | res, err := ctxt.Exec(buf.String(), args1...) 343 | if err != nil { 344 | return err 345 | } 346 | count, err := res.RowsAffected() 347 | if err != nil { 348 | return err 349 | } 350 | if count > 0 { 351 | return nil 352 | } 353 | // fall through to ordinary insert command 354 | buf.Reset() 355 | } 356 | 357 | fmt.Fprintf(&buf, "insert or replace into %q (", t.name) 358 | for i, col := range t.fields { 359 | if i > 0 { 360 | fmt.Fprintf(&buf, ", ") 361 | } 362 | fmt.Fprintf(&buf, "%q", col.name) 363 | } 364 | fmt.Fprintf(&buf, ") values (") 365 | for i := range t.fields { 366 | if i > 0 { 367 | fmt.Fprintf(&buf, ", ") 368 | } 369 | fmt.Fprintf(&buf, "?") 370 | } 371 | fmt.Fprintf(&buf, ")") 372 | 373 | res, err := ctxt.Exec(buf.String(), args...) 374 | if err != nil { 375 | return err 376 | } 377 | if rowcol != nil { 378 | id, err := res.LastInsertId() 379 | if err != nil { 380 | return err 381 | } 382 | rval.FieldByIndex(rowcol.index).SetInt(id) 383 | } 384 | return nil 385 | } 386 | 387 | // Delete deletes the value from the database. 388 | // The unique identification fields in val must be set. 389 | // 390 | // Delete executes a command like: 391 | // delete from Structs 392 | // where Key1 = val.Key1 and Key2 = val.Key2 393 | func (db *Storage) Delete(ctxt Context, val interface{}) error { 394 | ctxt = debugContext(ctxt) 395 | t, _, err := db.findType(val, "Delete") 396 | if err != nil { 397 | return err 398 | } 399 | 400 | var buf bytes.Buffer 401 | var args []interface{} 402 | rval := reflect.ValueOf(val).Elem() 403 | 404 | fmt.Fprintf(&buf, "delete from %q where ", t.name) 405 | sep := "" 406 | for _, col := range t.fields { 407 | if !col.key { 408 | continue 409 | } 410 | args = append(args, rval.FieldByIndex(col.index).Interface()) 411 | fmt.Fprintf(&buf, "%s%q = ?", sep, col.name) 412 | sep = " and " 413 | } 414 | 415 | _, err = ctxt.Exec(buf.String(), args...) 416 | return err 417 | } 418 | 419 | // Read reads the named columns from the database into val. 420 | // The key fields in val must already be set. 421 | // 422 | // Read executes a command like: 423 | // select columns from Structs 424 | // where Key1 = val.Key1 AND Key2 = val.Key2 425 | func (db *Storage) Read(ctxt Context, val interface{}, columns ...string) error { 426 | ctxt = debugContext(ctxt) 427 | t, _, err := db.findType(val, "Read") 428 | if err != nil { 429 | return err 430 | } 431 | 432 | var buf bytes.Buffer 433 | var args, scanargs []interface{} 434 | rval := reflect.ValueOf(val).Elem() 435 | 436 | want := make(map[string]bool) 437 | for _, name := range columns { 438 | want[name] = true 439 | } 440 | 441 | fmt.Fprintf(&buf, "select ") 442 | sep := "" 443 | var fixes []func() 444 | for _, col := range t.fields { 445 | if !want[col.name] && !want["ALL"] { 446 | continue 447 | } 448 | delete(want, col.name) 449 | if col.key { 450 | continue // already set 451 | } 452 | fmt.Fprintf(&buf, "%s%q", sep, col.name) 453 | sep = ", " 454 | scanargs = append(scanargs, rval.FieldByIndex(col.index).Addr().Interface()) 455 | if col.utf8 { 456 | fixes = append(fixes, func() { 457 | v := rval.FieldByIndex(col.index) 458 | s := v.String() 459 | if !utf8.ValidString(s) { 460 | v.SetString(string([]rune(s))) 461 | } 462 | }) 463 | } 464 | } 465 | isCount := false 466 | if sep == "" { 467 | // nothing to select, but want to provide error if not there. 468 | // select count of rows. 469 | fmt.Fprintf(&buf, "count(*)") 470 | scanargs = append(scanargs, new(int)) 471 | isCount = true 472 | } 473 | 474 | delete(want, "ALL") 475 | if len(want) != 0 { 476 | // some column wasn't found 477 | for _, name := range columns { 478 | if want[name] { 479 | return fmt.Errorf("unknown column %q", name) 480 | } 481 | } 482 | } 483 | 484 | fmt.Fprintf(&buf, " from %q where ", t.name) 485 | sep = "" 486 | for _, col := range t.fields { 487 | if !col.key { 488 | continue 489 | } 490 | fmt.Fprintf(&buf, "%s%q = ?", sep, col.name) 491 | sep = " and " 492 | args = append(args, rval.FieldByIndex(col.index).Interface()) 493 | } 494 | 495 | rows, err := ctxt.Query(buf.String(), args...) 496 | if err != nil { 497 | return err 498 | } 499 | defer rows.Close() 500 | 501 | if !rows.Next() { 502 | if err := rows.Err(); err != nil { 503 | return err 504 | } 505 | return ErrNotFound 506 | } 507 | 508 | if err := rows.Scan(scanargs...); err != nil { 509 | return err 510 | } 511 | 512 | if isCount && *scanargs[0].(*int) == 0 { 513 | return ErrNotFound 514 | } 515 | 516 | for _, fix := range fixes { 517 | fix() 518 | } 519 | 520 | return nil 521 | } 522 | 523 | // Write writes the named columns from val into the database. 524 | // The key fields in val must already be set and the value must already exist. 525 | // 526 | // Write executes a command like: 527 | // update Structs 528 | // set column1 = val.Column1, column2 = val.Column2 529 | // where Key1 = val.Key1 AND Key2 = val.Key2 530 | func (db *Storage) Write(ctxt Context, val interface{}, columns ...string) error { 531 | ctxt = debugContext(ctxt) 532 | t, _, err := db.findType(val, "Write") 533 | if err != nil { 534 | return err 535 | } 536 | 537 | var buf bytes.Buffer 538 | var args []interface{} 539 | rval := reflect.ValueOf(val).Elem() 540 | 541 | want := make(map[string]bool) 542 | for _, name := range columns { 543 | want[name] = true 544 | } 545 | 546 | fmt.Fprintf(&buf, "update %q set ", t.name) 547 | sep := "" 548 | for _, col := range t.fields { 549 | if !want[col.name] { 550 | continue 551 | } 552 | delete(want, col.name) 553 | if col.key { 554 | continue // already set 555 | } 556 | fmt.Fprintf(&buf, "%s%q = ?", sep, col.name) 557 | sep = ", " 558 | args = append(args, rval.FieldByIndex(col.index).Interface()) 559 | } 560 | if sep == "" { 561 | // nothing to set, but want to provide error if not there. 562 | return db.Read(ctxt, val) 563 | } 564 | 565 | if len(want) != 0 { 566 | // some column wasn't found 567 | for _, name := range columns { 568 | if want[name] { 569 | return fmt.Errorf("unknown column %q", name) 570 | } 571 | } 572 | } 573 | 574 | fmt.Fprintf(&buf, " where ") 575 | sep = "" 576 | for _, col := range t.fields { 577 | if !col.key { 578 | continue 579 | } 580 | fmt.Fprintf(&buf, "%s%q = ?", sep, col.name) 581 | sep = " and " 582 | args = append(args, rval.FieldByIndex(col.index).Interface()) 583 | } 584 | 585 | res, err := ctxt.Exec(buf.String(), args...) 586 | if err != nil { 587 | return err 588 | } 589 | 590 | count, err := res.RowsAffected() 591 | if err != nil { 592 | return err 593 | } 594 | 595 | if count == 0 { 596 | return ErrNotFound 597 | } 598 | 599 | return nil 600 | } 601 | 602 | // ErrNotFound is the error returned by Read, Select, and Write when 603 | // there are no matching records in the database. 604 | var ErrNotFound = errors.New("database record not found") 605 | 606 | // Select executes a select command to read one or more rows into val. 607 | // To read values of type Example, val may take any of these types: 608 | // *Example - read a single Example, returning ErrNotFound if not found 609 | // **Example - allocate and read a single Example, setting it to nil if not found 610 | // *[]Example - read a slice of Examples 611 | // *[]*Example - read a slice of Examples 612 | // 613 | // Select executes a command like 614 | // select Key1, Key2, Field3, Field4 from Structs 615 | // 616 | func (db *Storage) Select(ctxt Context, val interface{}, query string, args ...interface{}) error { 617 | ctxt = debugContext(ctxt) 618 | t, kind, err := db.findType(val, "Select") 619 | if err != nil { 620 | return err 621 | } 622 | 623 | var buf bytes.Buffer 624 | fmt.Fprintf(&buf, "select ") 625 | sep := "" 626 | var indexes [][]int 627 | for _, col := range t.fields { 628 | fmt.Fprintf(&buf, "%s%q", sep, col.name) 629 | sep = ", " 630 | indexes = append(indexes, col.index) 631 | } 632 | fmt.Fprintf(&buf, " from %q %s", t.name, query) 633 | 634 | rows, err := ctxt.Query(buf.String(), args...) 635 | if err != nil { 636 | return err 637 | } 638 | defer rows.Close() 639 | 640 | rval := reflect.ValueOf(val).Elem() 641 | rval.Set(reflect.Zero(rval.Type())) 642 | switch kind { 643 | case ptrStruct: 644 | if !rows.Next() { 645 | return ErrNotFound 646 | } 647 | return scan1(rows, t, rval) 648 | 649 | case ptrPtrStruct: 650 | if !rows.Next() { 651 | rval.Set(reflect.Zero(rval.Type())) 652 | return nil 653 | } 654 | rval.Set(reflect.New(rval.Type().Elem())) 655 | return scan1(rows, t, rval.Elem()) 656 | 657 | case ptrSliceStruct: 658 | for rows.Next() { 659 | n := rval.Len() 660 | rval.Set(reflect.Append(rval, reflect.Zero(rval.Type().Elem()))) 661 | if err := scan1(rows, t, rval.Index(n)); err != nil { 662 | return err 663 | } 664 | } 665 | return nil 666 | 667 | case ptrSlicePtrStruct: 668 | for rows.Next() { 669 | n := rval.Len() 670 | rval.Set(reflect.Append(rval, reflect.New(rval.Type().Elem().Elem()))) 671 | if err := scan1(rows, t, rval.Index(n).Elem()); err != nil { 672 | return err 673 | } 674 | } 675 | return nil 676 | } 677 | 678 | panic("dbstore: internal error: unexpected kind") 679 | } 680 | 681 | func scan1(rows *sql.Rows, t *dtype, rval reflect.Value) error { 682 | var args []interface{} 683 | for _, col := range t.fields { 684 | args = append(args, rval.FieldByIndex(col.index).Addr().Interface()) 685 | } 686 | if err := rows.Scan(args...); err != nil { 687 | return err 688 | } 689 | 690 | for _, col := range t.fields { 691 | if col.utf8 { 692 | v := rval.FieldByIndex(col.index) 693 | s := v.String() 694 | if !utf8.ValidString(s) { 695 | v.SetString(string([]rune(s))) 696 | } 697 | } 698 | } 699 | 700 | return nil 701 | } 702 | --------------------------------------------------------------------------------