fix(playlist): create a smart playlist through the writer
`CreateSmartPlaylist` issued its `INSERT ... RETURNING` through `QueryContext`, which routes to the query-only read pool, and failed with "attempt to write a readonly database (8)". No smart playlist could be created at all, in any real build. It was invisible because `NewTestDB` shares one in-memory connection and leaves `readDB` nil, so `reader()` hands back the *writer* under test: every unit test of that path exercised a handle production does not have. `TestNoWritesOnTheReadPool` walks the tree for the whole class, in the same spirit as `TestNoDirectRuntimeEmits` and for the same reason — a lint pass only sees one build configuration.
This commit is contained in:
@@ -2565,35 +2565,6 @@ func (s *Service) CreateSmartPlaylist(
|
||||
)
|
||||
}
|
||||
|
||||
// SAFETY: Hand-crafted INSERT for smart playlist with
|
||||
// is_smart and smart_rules columns not yet in sqlc schema.
|
||||
// All values are parameterized.
|
||||
rows, err := s.db.QueryContext(
|
||||
`INSERT INTO playlists (name, is_smart, smart_rules)
|
||||
VALUES (?, 1, ?)
|
||||
RETURNING id, name, created_at, updated_at`,
|
||||
trimmed, rulesJSON,
|
||||
)
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"Failed to create smart playlist",
|
||||
"name", trimmed, "err", err,
|
||||
)
|
||||
|
||||
return Summary{}, fmt.Errorf(
|
||||
"failed to create smart playlist: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
if !rows.Next() {
|
||||
_ = rows.Close()
|
||||
|
||||
return Summary{}, fmt.Errorf(
|
||||
"failed to create smart playlist: %w",
|
||||
errNoRowReturned,
|
||||
)
|
||||
}
|
||||
|
||||
var (
|
||||
id int64
|
||||
retName string
|
||||
@@ -2601,25 +2572,38 @@ func (s *Service) CreateSmartPlaylist(
|
||||
updatedAt string
|
||||
)
|
||||
|
||||
if err := rows.Scan(
|
||||
&id, &retName, &createdAt, &updatedAt,
|
||||
); err != nil {
|
||||
_ = rows.Close()
|
||||
|
||||
// SAFETY: Hand-crafted INSERT for smart playlist with
|
||||
// is_smart and smart_rules columns not yet in sqlc schema.
|
||||
// All values are parameterized.
|
||||
//
|
||||
// QueryRowWriter, not QueryContext: this is an INSERT wearing a
|
||||
// query's shape, and QueryContext routes to the query-only read
|
||||
// pool. Through that handle it failed with "attempt to write a
|
||||
// readonly database", i.e. no smart playlist could be created at
|
||||
// all. A RETURNING clause does not make a write a read.
|
||||
if err := s.db.QueryRowWriter(
|
||||
`INSERT INTO playlists (name, is_smart, smart_rules)
|
||||
VALUES (?, 1, ?)
|
||||
RETURNING id, name, created_at, updated_at`,
|
||||
trimmed, rulesJSON,
|
||||
).Scan(&id, &retName, &createdAt, &updatedAt); err != nil {
|
||||
s.logger.Error(
|
||||
"Failed to create smart playlist",
|
||||
"name", trimmed, "err", err,
|
||||
)
|
||||
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return Summary{}, fmt.Errorf(
|
||||
"failed to create smart playlist: %w",
|
||||
errNoRowReturned,
|
||||
)
|
||||
}
|
||||
|
||||
return Summary{}, fmt.Errorf(
|
||||
"failed to create smart playlist: %w", err,
|
||||
)
|
||||
}
|
||||
|
||||
// Close before RefreshSmartPlaylist issues its own queries
|
||||
// (MaxOpenConns=1 test DBs would deadlock).
|
||||
_ = rows.Close()
|
||||
|
||||
s.logger.Info(
|
||||
"Smart playlist created",
|
||||
"id", id, "name", retName,
|
||||
|
||||
Reference in New Issue
Block a user