var ( ErrRecordNotFound = errors.New("record not found") ErrEditConflict = errors.New("edit conflict") )
// Update updates a specific record in the movie table. func (m MovieModel) Update(movie *Movie) error { query := `UPDATE movie SET title = $1, year = $2, runtime = $3, genres = $4, version = version + 1 WHERE id = $5 AND version = $6 RETURNING version` args := []any{ movie.Title, movie.Year, movie.Runtime, movie.Genres, movie.ID, movie.Version, // Add the expected movie version. } ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second) defer cancel() err := m.DCP.QueryRow(ctx, query, args...).Scan(&movie.Version) if err != nil { switch { case errors.Is(err, pgx.ErrNoRows): return ErrEditConflict default: return err } } return nil }
func (app *application) editConflictResponse(w http.ResponseWriter, r *http.Request) { message := "unable to update the record due to an edit conflict, please try again" app.errorResponse(w, r, http.StatusConflict, message) }
func (app *application) updateMovieHandler(w http.ResponseWriter, r *http.Request) { id, err := app.readIDParam(r) if err != nil { app.notFoundResponse(w, r) return } movie, err := app.models.Movie.Get(id) if err != nil { switch { case errors.Is(err, data.ErrRecordNotFound): app.notFoundResponse(w, r) default: app.serverErrorResponse(w, r, err) } return } var input struct { Title *string `json:"title"` Year *int32 `json:"year"` Runtime *data.Runtime `json:"runtime"` Genres []string `json:"genres"` } err = app.readJSON(w, r, &input) if err != nil { app.badRequestResponse(w, r, err) return } if input.Title != nil { movie.Title = *input.Title } if input.Year != nil { movie.Year = *input.Year } if input.Runtime != nil { movie.Runtime = *input.Runtime } if input.Genres != nil { movie.Genres = input.Genres // Note that we don't need to dereference a slice. } v := validator.New() if data.ValidateMovie(v, movie); !v.Valid() { app.failedValidationResponse(w, r, v.Errors) return } err = app.models.Movie.Update(movie) if err != nil { switch { case errors.Is(err, data.ErrEditConflict): app.editConflictResponse(w, r) default: app.serverErrorResponse(w, r, err) } return } err = app.writeJSON(w, http.StatusOK, envelope{"movie": movie}, nil) if err != nil { app.serverErrorResponse(w, r, err) } }
zzh@ZZHPC:~$ xargs -I % -P8 curl -X PATCH -d '{"runtime": "97 mins"}' "localhost:4000/v1/movies/4" < <(printf '%s\n' {1..8}) { "movie": { "id": 4, "title": "The Breakfast Club", "year": 1985, "runtime": "97 mins", "genres": [ "drama" ], "version": 3 } } { "movie": { "id": 4, "title": "The Breakfast Club", "year": 1985, "runtime": "97 mins", "genres": [ "drama" ], "version": 4 } } { "error": "unable to update the record due to an edit conflict, please try again" { } "error": "unable to update the record due to an edit conflict, please try again" } { "error": "unable to update the record due to an edit conflict, please try again" } { "movie": { "id": 4, "title": "The Breakfast Club", "year": 1985, "runtime": "97 mins", "genres": [ "drama" ], "version": 5 } } { "error": "unable to update the record due to an edit conflict, please try again" } { "movie": { "id": 4, "title": "The Breakfast Club", "year": 1985, "runtime": "97 mins", "genres": [ "drama" ], "version": 6 } }
-P max-procs, --max-procs=max-procs
Run up to max-procs processes at a time; the default is 1. If max-procs is 0, xargs will run as many processes as possible at a time.
func (app *application) updateMovieHandler(w http.ResponseWriter, r *http.Request) { id, err := app.readIDParam(r) if err != nil { app.notFoundResponse(w, r) return } movie, err := app.models.Movies.Get(id) if err != nil { switch { case errors.Is(err, data.ErrRecordNotFound): app.notFoundResponse(w, r) default: app.serverErrorResponse(w, r, err) } return } // If the request contains a X-Expected-Version header, verify that the movie // version in the database matches the expected version specified in the header. if r.Header.Get("X-Expected-Version") != "" { if strconv.Itoa(int(movie.Version)) != r.Header.Get("X-Expected-Version") { app.editConflictResponse(w, r) return } } ... }
func (m MovieModel) Get(id int64) (*Movie, error) { if id < 1 { return nil, ErrRecordNotFound } // Update the query to return pg_sleep(8) as the first value. query := ` SELECT pg_sleep(8), id, created_at, title, year, runtime, genres, version FROM movies WHERE id = $1` var movie Movie // Importantly, update the Scan() parameters so that the pg_sleep(8) return value // is scanned into a []byte slice. err := m.DB.QueryRow(query, id).Scan( &[]byte{}, // Add this line. &movie.ID, &movie.CreatedAt, &movie.Title, &movie.Year, &movie.Runtime, &movie.Genres, &movie.Version, ) if err != nil { switch { case errors.Is(err, pgx.ErrNoRows): return nil, ErrRecordNotFound default: return nil, err } } return &movie, nil }
func (m MovieModel) Get(id int64) (*Movie, error) { if id < 1 { return nil, ErrRecordNotFound } query := ` SELECT pg_sleep(8), id, created_at, title, year, runtime, genres, version FROM movies WHERE id = $1` var movie Movie // Use the context.WithTimeout() function to create a context.Context which carries a // 3-second timeout deadline. Note that we're using the empty context.Background() // as the 'parent' context. ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // Importantly, use defer to make sure that we cancel the context before the Get() // method returns. defer cancel() // Use the QueryRowContext() method to execute the query, passing in the context // with the deadline as the first argument. err := m.DB.QueryRow(ctx, query, id).Scan( &[]byte{}, &movie.ID, &movie.CreatedAt, &movie.Title, &movie.Year, &movie.Runtime, &movie.Genres, &movie.Version, ) if err != nil { switch { case errors.Is(err, pgx.ErrNoRows): return nil, ErrRecordNotFound default: return nil, err } } return &movie, nil }
The details behind this are very interesting, but also quite intricate and heavy-going. For that reason I’ve discussed it further in this appendix.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2023-11-16 Microservice - Project folder structure for hexagonal architecture