Get rid of surrogate keys

This commit is contained in:
Brandon Dyck 2020-09-29 09:25:21 -06:00
parent 0ff6402c34
commit 62a717c806
4 changed files with 64 additions and 86 deletions

View File

@ -3,7 +3,6 @@ package back
import (
"context"
"errors"
"fmt"
"log"
"os"
@ -72,29 +71,24 @@ func (s *Store) Close() error {
const schema = `
CREATE TABLE event (
id INTEGER PRIMARY KEY,
alpha_id TEXT NOT NULL,
id TEXT NOT NULL PRIMARY KEY,
admin_code TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT NOT NULL,
earliest_date DATE NOT NULL,
latest_date DATE NOT NULL,
UNIQUE (alpha_id)
latest_date DATE NOT NULL
);
CREATE TABLE response (
id INTEGER PRIMARY KEY,
alpha_id TEXT NOT NULL,
event_id INTEGER NOT NULL,
id TEXT NOT NULL PRIMARY KEY,
event_id TEXT NOT NULL,
guest_name TEXT NOT NULL,
UNIQUE (alpha_id),
FOREIGN KEY (event_id) REFERENCES event(id)
);
CREATE TABLE response_time (
response_id INTEGER NOT NULL,
response_id TEXT NOT NULL,
date DATE NOT NULL,
time INTEGER NOT NULL,
@ -128,19 +122,19 @@ type CreateEventCommand struct {
}
type CreateEventResult struct {
AlphaID, AdminCode string
EventID, AdminCode string
}
const dbDateLayout = "2006-01-02"
func (s *Store) CreateEvent(ctx context.Context, cmd CreateEventCommand) (result CreateEventResult, err error) {
const alphaIDLength = 10
const idLength = 10
const adminCodeLength = 10
conn := s.pool.Get(ctx)
defer s.pool.Put(conn)
alphaID, err := s.genString(alphaIDLength)
id, err := s.genString(idLength)
if err != nil {
return
}
@ -151,10 +145,10 @@ func (s *Store) CreateEvent(ctx context.Context, cmd CreateEventCommand) (result
}
const query = `
INSERT INTO event(alpha_id, admin_code, name, description, earliest_date, latest_date)
INSERT INTO event(id, admin_code, name, description, earliest_date, latest_date)
VALUES (?, ?, ?, ?, ?, ?);`
err = sqlitex.Exec(conn, query, nil,
alphaID,
id,
adminCode,
cmd.Name,
cmd.Description,
@ -166,12 +160,12 @@ func (s *Store) CreateEvent(ctx context.Context, cmd CreateEventCommand) (result
}
result.AdminCode = adminCode
result.AlphaID = alphaID
result.EventID = id
return
}
type GetEventMetadataQuery struct {
AlphaID string
EventID string
}
type GetEventMetadataResult struct {
@ -187,7 +181,7 @@ func (s *Store) GetEventMetadata(ctx context.Context, query GetEventMetadataQuer
const dbQuery = `
SELECT name, description, earliest_date, latest_date
FROM event
WHERE alpha_id = ?;`
WHERE id = ?;`
var result GetEventMetadataResult
var found bool
err := sqlitex.Exec(conn, dbQuery,
@ -206,7 +200,7 @@ func (s *Store) GetEventMetadata(ctx context.Context, query GetEventMetadataQuer
}
result.Latest, err = date.Parse(dbDateLayout, latestDateString)
return err
}, query.AlphaID)
}, query.EventID)
if err != nil {
return GetEventMetadataResult{}, err
}
@ -217,7 +211,7 @@ func (s *Store) GetEventMetadata(ctx context.Context, query GetEventMetadataQuer
}
type GetEventResponseSummaryQuery struct {
AlphaID string
EventID string
}
type GetEventResponseSummaryResult struct {
@ -229,18 +223,16 @@ func (s *Store) GetEventResponseSummary(ctx context.Context, query GetEventRespo
defer s.pool.Put(conn)
var found bool
var eventID int
const eventIDQuery = `
SELECT id
SELECT 1
FROM event
WHERE alpha_id = ?`
WHERE id = ?`
var err = sqlitex.Exec(conn, eventIDQuery,
func(stmt *sqlite.Stmt) error {
found = true
eventID = stmt.ColumnInt(0)
return nil
}, query.AlphaID)
}, query.EventID)
if err != nil {
return GetEventResponseSummaryResult{}, err
}
@ -257,7 +249,7 @@ func (s *Store) GetEventResponseSummary(ctx context.Context, query GetEventRespo
func(stmt *sqlite.Stmt) error {
result.TotalResponses = stmt.ColumnInt(0)
return nil
}, eventID)
}, query.EventID)
if err != nil {
return GetEventResponseSummaryResult{}, err
}
@ -265,46 +257,37 @@ func (s *Store) GetEventResponseSummary(ctx context.Context, query GetEventRespo
}
type CreateEventResponseCommand struct {
EventAlphaID string
EventID string
GuestName string
DateHours map[date.Date]map[int]struct{}
}
type CreateEventResponseResult struct {
ResponseAlphaID string
ResponseID string
}
func (s *Store) CreateEventResponse(ctx context.Context, cmd CreateEventResponseCommand) (result CreateEventResponseResult, err error) {
const responseAlphaIDLength = 10
const responseIDLength = 10
fmt.Printf("creating response: %+v\n", cmd)
conn := s.pool.Get(ctx)
defer s.pool.Put(conn)
defer sqlitex.Save(conn)(&err)
responseAlphaID, err := s.genString(responseAlphaIDLength)
responseID, err := s.genString(responseIDLength)
if err != nil {
return
}
const responseQuery = `
INSERT INTO response(event_id, alpha_id, guest_name)
SELECT event.id AS event_id, ? AS alpha_id, ? as guest_name
FROM event
WHERE event.alpha_id = ?;`
err = sqlitex.Exec(conn, responseQuery, nil, responseAlphaID, cmd.GuestName, cmd.EventAlphaID)
const responseQuery = `INSERT INTO response(event_id, id, guest_name) VALUES (?, ?, ?);`
err = sqlitex.Exec(conn, responseQuery, nil, cmd.EventID, responseID, cmd.GuestName)
if err != nil {
return
}
const responseTimeQuery = `
INSERT INTO response_time(response_id, date, time)
SELECT response.id AS response_id, ? AS date, ? AS time
FROM response
WHERE response.alpha_id = ?;`
const responseTimeQuery = `INSERT INTO response_time(response_id, date, time) VALUES (?, ?, ?);`
for d, hs := range cmd.DateHours {
for h := range hs {
err = sqlitex.Exec(conn, responseTimeQuery, nil, d.Format(dbDateLayout), h, responseAlphaID)
err = sqlitex.Exec(conn, responseTimeQuery, nil, responseID, d.Format(dbDateLayout), h)
if err != nil {
return
}
@ -312,13 +295,13 @@ func (s *Store) CreateEventResponse(ctx context.Context, cmd CreateEventResponse
}
return CreateEventResponseResult{
ResponseAlphaID: responseAlphaID,
ResponseID: responseID,
}, nil
}
type GetEventResponseQuery struct {
EventAlphaID string
ResponseAlphaID string
EventID string
ResponseID string
}
type GetEventResponseResult struct {
@ -332,20 +315,17 @@ func (s *Store) GetEventResponse(ctx context.Context, query GetEventResponseQuer
var found bool
var result GetEventResponseResult
var responseID int
const responseQuery = `
SELECT guest_name, response.id
SELECT guest_name
FROM response
JOIN event ON response.event_id = event.id
WHERE response.alpha_id = ? AND event.alpha_id = ?;`
WHERE id = ? AND event_id = ?;`
var err = sqlitex.Exec(conn, responseQuery,
func(stmt *sqlite.Stmt) error {
found = true
result.GuestName = stmt.ColumnText(0)
responseID = stmt.ColumnInt(1)
return nil
}, query.ResponseAlphaID, query.EventAlphaID)
}, query.ResponseID, query.EventID)
if err != nil {
return GetEventResponseResult{}, err
}
@ -369,7 +349,7 @@ func (s *Store) GetEventResponse(ctx context.Context, query GetEventResponseQuer
}
result.DateHours[d][stmt.ColumnInt(1)] = struct{}{}
return nil
}, responseID)
}, query.ResponseID)
if err != nil {
return GetEventResponseResult{}, err
}

View File

@ -36,7 +36,7 @@ func TestCreateEvent(t *testing.T) {
is.NoErr(err)
metadataResult, err := store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{
AlphaID: createResult.AlphaID,
EventID: createResult.EventID,
})
is.NoErr(err)
is.Equal(metadataResult.Name, name)
@ -58,12 +58,12 @@ func TestGetEventResponseSummary(t *testing.T) {
Latest: date.Today().Add(1),
})
is.NoErr(err)
return event.AlphaID
return event.EventID
}
getTotalResponses := func(is *is.I, eventID string) int {
responses, err := store.GetEventResponseSummary(context.Background(), back.GetEventResponseSummaryQuery{
AlphaID: eventID,
EventID: eventID,
})
is.NoErr(err)
return responses.TotalResponses
@ -71,7 +71,7 @@ func TestGetEventResponseSummary(t *testing.T) {
createEmptyResponse := func(is *is.I, eventID string) {
_, err = store.CreateEventResponse(context.Background(), back.CreateEventResponseCommand{
EventAlphaID: eventID,
EventID: eventID,
})
is.NoErr(err)
}
@ -117,7 +117,7 @@ func TestCreateEventResponse(t *testing.T) {
Latest: date.Today().Add(1),
})
is.NoErr(err)
return event.AlphaID
return event.EventID
}
t.Run("saves GuestName", func(t *testing.T) {
@ -126,14 +126,14 @@ func TestCreateEventResponse(t *testing.T) {
const guestName = "Etaoin Shrdlu"
createResult, err := store.CreateEventResponse(context.Background(), back.CreateEventResponseCommand{
EventAlphaID: eventID,
EventID: eventID,
GuestName: guestName,
})
is.NoErr(err)
getResult, err := store.GetEventResponse(context.Background(), back.GetEventResponseQuery{
EventAlphaID: eventID,
ResponseAlphaID: createResult.ResponseAlphaID,
EventID: eventID,
ResponseID: createResult.ResponseID,
})
is.NoErr(err)
is.Equal(getResult.GuestName, guestName)
@ -157,15 +157,15 @@ func TestCreateEventResponse(t *testing.T) {
latest: {2: {}, 4: {}, 6: {}},
}
createResponseResult, err := store.CreateEventResponse(context.Background(), back.CreateEventResponseCommand{
EventAlphaID: event.AlphaID,
EventID: event.EventID,
GuestName: "Etaoin Shrdlu",
DateHours: dateHours,
})
is.NoErr(err)
response, err := store.GetEventResponse(context.Background(), back.GetEventResponseQuery{
EventAlphaID: event.AlphaID,
ResponseAlphaID: createResponseResult.ResponseAlphaID,
EventID: event.EventID,
ResponseID: createResponseResult.ResponseID,
})
is.NoErr(err)
is.Equal(dateHours, response.DateHours)

View File

@ -154,7 +154,7 @@ func internalServerError(w http.ResponseWriter, err error) bool {
func (h *handler) handleVote(w http.ResponseWriter, r *http.Request) {
eventAlphaID := r.URL.Query().Get(fieldNameEventID)
event, err := h.store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{
AlphaID: eventAlphaID,
EventID: eventAlphaID,
})
if notFound(w, err, "Event not found") || internalServerError(w, err) {
return
@ -181,7 +181,7 @@ func (h *handler) handleDoVote(w http.ResponseWriter, r *http.Request) {
fmt.Printf("received form: %+v\n", r.Form)
cmd := back.CreateEventResponseCommand{
EventAlphaID: r.Form.Get(fieldNameEventID),
EventID: r.Form.Get(fieldNameEventID),
GuestName: r.Form.Get(fieldNameGuestName),
DateHours: make(map[date.Date]map[int]struct{}),
}
@ -207,22 +207,22 @@ func (h *handler) handleDoVote(w http.ResponseWriter, r *http.Request) {
}
var successQuery = make(url.Values)
successQuery.Add(fieldNameEventID, cmd.EventAlphaID)
successQuery.Add(fieldNameResponseID, eventResponse.ResponseAlphaID)
successQuery.Add(fieldNameEventID, cmd.EventID)
successQuery.Add(fieldNameResponseID, eventResponse.ResponseID)
http.Redirect(w, r, pathVoteSuccess+"?"+successQuery.Encode(), http.StatusSeeOther)
}
func (h *handler) handleVoteSuccess(w http.ResponseWriter, r *http.Request) {
eventResponse, err := h.store.GetEventResponse(r.Context(), back.GetEventResponseQuery{
EventAlphaID: r.URL.Query().Get(fieldNameEventID),
ResponseAlphaID: r.URL.Query().Get(fieldNameResponseID),
EventID: r.URL.Query().Get(fieldNameEventID),
ResponseID: r.URL.Query().Get(fieldNameResponseID),
})
if notFound(w, err, "Event response not found.") || internalServerError(w, err) {
return
}
event, err := h.store.GetEventMetadata(r.Context(), back.GetEventMetadataQuery{
AlphaID: r.URL.Query().Get(fieldNameEventID),
EventID: r.URL.Query().Get(fieldNameEventID),
})
if notFound(w, err, "Event not found") || internalServerError(w, err) {
return
@ -295,7 +295,7 @@ func (h *handler) handleDoCreate(w http.ResponseWriter, r *http.Request) {
}
var successQuery = make(url.Values)
successQuery.Add(fieldNameEventID, event.AlphaID)
successQuery.Add(fieldNameEventID, event.EventID)
successQuery.Add(fieldNameAdminCode, event.AdminCode)
http.Redirect(w, r, pathCreateSuccess+"?"+successQuery.Encode(), http.StatusSeeOther)
}
@ -303,7 +303,7 @@ func (h *handler) handleDoCreate(w http.ResponseWriter, r *http.Request) {
func (h *handler) handleCreateSuccess(w http.ResponseWriter, r *http.Request) {
eventID := r.URL.Query().Get(fieldNameEventID)
event, err := h.store.GetEventMetadata(r.Context(), back.GetEventMetadataQuery{
AlphaID: eventID,
EventID: eventID,
})
if notFound(w, err, "Event not found") || internalServerError(w, err) {
return
@ -351,14 +351,14 @@ func (h *handler) handleAdmin(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
eventID := query.Get(fieldNameEventID)
metadata, err := h.store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{
AlphaID: eventID,
EventID: eventID,
})
if notFound(w, err, "Event not found") || internalServerError(w, err) {
return
}
responses, err := h.store.GetEventResponseSummary(context.Background(), back.GetEventResponseSummaryQuery{
AlphaID: eventID,
EventID: eventID,
})
if err != nil {
fmt.Fprint(w, err)

View File

@ -13,10 +13,7 @@ Consider redirecting from /create/do to admin
Cleanup:
------------
Make the sqlite pool size configurable
Get rid of the surrogate keys
Give response (and therefore response_time) a composite key
Rename all of the alpha ID nonsense
Get rid of the Stat call in NewStore.
I can't remember the details. I think the old version
of sqlitex was panicking if I failed to open a DB file.
@ -25,6 +22,7 @@ Get rid of the Stat call in NewStore.
Add optional subtitles to (*front.handler).writePage
Take a Page struct with title, subtitle, and contents.
Make titles optional in (*front.handler).writePage
Make the sqlite pool size configurable
More features: