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

View File

@ -36,7 +36,7 @@ func TestCreateEvent(t *testing.T) {
is.NoErr(err) is.NoErr(err)
metadataResult, err := store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{ metadataResult, err := store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{
AlphaID: createResult.AlphaID, EventID: createResult.EventID,
}) })
is.NoErr(err) is.NoErr(err)
is.Equal(metadataResult.Name, name) is.Equal(metadataResult.Name, name)
@ -58,12 +58,12 @@ func TestGetEventResponseSummary(t *testing.T) {
Latest: date.Today().Add(1), Latest: date.Today().Add(1),
}) })
is.NoErr(err) is.NoErr(err)
return event.AlphaID return event.EventID
} }
getTotalResponses := func(is *is.I, eventID string) int { getTotalResponses := func(is *is.I, eventID string) int {
responses, err := store.GetEventResponseSummary(context.Background(), back.GetEventResponseSummaryQuery{ responses, err := store.GetEventResponseSummary(context.Background(), back.GetEventResponseSummaryQuery{
AlphaID: eventID, EventID: eventID,
}) })
is.NoErr(err) is.NoErr(err)
return responses.TotalResponses return responses.TotalResponses
@ -71,7 +71,7 @@ func TestGetEventResponseSummary(t *testing.T) {
createEmptyResponse := func(is *is.I, eventID string) { createEmptyResponse := func(is *is.I, eventID string) {
_, err = store.CreateEventResponse(context.Background(), back.CreateEventResponseCommand{ _, err = store.CreateEventResponse(context.Background(), back.CreateEventResponseCommand{
EventAlphaID: eventID, EventID: eventID,
}) })
is.NoErr(err) is.NoErr(err)
} }
@ -117,7 +117,7 @@ func TestCreateEventResponse(t *testing.T) {
Latest: date.Today().Add(1), Latest: date.Today().Add(1),
}) })
is.NoErr(err) is.NoErr(err)
return event.AlphaID return event.EventID
} }
t.Run("saves GuestName", func(t *testing.T) { t.Run("saves GuestName", func(t *testing.T) {
@ -126,14 +126,14 @@ func TestCreateEventResponse(t *testing.T) {
const guestName = "Etaoin Shrdlu" const guestName = "Etaoin Shrdlu"
createResult, err := store.CreateEventResponse(context.Background(), back.CreateEventResponseCommand{ createResult, err := store.CreateEventResponse(context.Background(), back.CreateEventResponseCommand{
EventAlphaID: eventID, EventID: eventID,
GuestName: guestName, GuestName: guestName,
}) })
is.NoErr(err) is.NoErr(err)
getResult, err := store.GetEventResponse(context.Background(), back.GetEventResponseQuery{ getResult, err := store.GetEventResponse(context.Background(), back.GetEventResponseQuery{
EventAlphaID: eventID, EventID: eventID,
ResponseAlphaID: createResult.ResponseAlphaID, ResponseID: createResult.ResponseID,
}) })
is.NoErr(err) is.NoErr(err)
is.Equal(getResult.GuestName, guestName) is.Equal(getResult.GuestName, guestName)
@ -157,15 +157,15 @@ func TestCreateEventResponse(t *testing.T) {
latest: {2: {}, 4: {}, 6: {}}, latest: {2: {}, 4: {}, 6: {}},
} }
createResponseResult, err := store.CreateEventResponse(context.Background(), back.CreateEventResponseCommand{ createResponseResult, err := store.CreateEventResponse(context.Background(), back.CreateEventResponseCommand{
EventAlphaID: event.AlphaID, EventID: event.EventID,
GuestName: "Etaoin Shrdlu", GuestName: "Etaoin Shrdlu",
DateHours: dateHours, DateHours: dateHours,
}) })
is.NoErr(err) is.NoErr(err)
response, err := store.GetEventResponse(context.Background(), back.GetEventResponseQuery{ response, err := store.GetEventResponse(context.Background(), back.GetEventResponseQuery{
EventAlphaID: event.AlphaID, EventID: event.EventID,
ResponseAlphaID: createResponseResult.ResponseAlphaID, ResponseID: createResponseResult.ResponseID,
}) })
is.NoErr(err) is.NoErr(err)
is.Equal(dateHours, response.DateHours) 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) { func (h *handler) handleVote(w http.ResponseWriter, r *http.Request) {
eventAlphaID := r.URL.Query().Get(fieldNameEventID) eventAlphaID := r.URL.Query().Get(fieldNameEventID)
event, err := h.store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{ event, err := h.store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{
AlphaID: eventAlphaID, EventID: eventAlphaID,
}) })
if notFound(w, err, "Event not found") || internalServerError(w, err) { if notFound(w, err, "Event not found") || internalServerError(w, err) {
return return
@ -181,9 +181,9 @@ func (h *handler) handleDoVote(w http.ResponseWriter, r *http.Request) {
fmt.Printf("received form: %+v\n", r.Form) fmt.Printf("received form: %+v\n", r.Form)
cmd := back.CreateEventResponseCommand{ cmd := back.CreateEventResponseCommand{
EventAlphaID: r.Form.Get(fieldNameEventID), EventID: r.Form.Get(fieldNameEventID),
GuestName: r.Form.Get(fieldNameGuestName), GuestName: r.Form.Get(fieldNameGuestName),
DateHours: make(map[date.Date]map[int]struct{}), DateHours: make(map[date.Date]map[int]struct{}),
} }
for _, dateHourString := range r.Form[fieldNameDateHour] { for _, dateHourString := range r.Form[fieldNameDateHour] {
var dateString string var dateString string
@ -207,22 +207,22 @@ func (h *handler) handleDoVote(w http.ResponseWriter, r *http.Request) {
} }
var successQuery = make(url.Values) var successQuery = make(url.Values)
successQuery.Add(fieldNameEventID, cmd.EventAlphaID) successQuery.Add(fieldNameEventID, cmd.EventID)
successQuery.Add(fieldNameResponseID, eventResponse.ResponseAlphaID) successQuery.Add(fieldNameResponseID, eventResponse.ResponseID)
http.Redirect(w, r, pathVoteSuccess+"?"+successQuery.Encode(), http.StatusSeeOther) http.Redirect(w, r, pathVoteSuccess+"?"+successQuery.Encode(), http.StatusSeeOther)
} }
func (h *handler) handleVoteSuccess(w http.ResponseWriter, r *http.Request) { func (h *handler) handleVoteSuccess(w http.ResponseWriter, r *http.Request) {
eventResponse, err := h.store.GetEventResponse(r.Context(), back.GetEventResponseQuery{ eventResponse, err := h.store.GetEventResponse(r.Context(), back.GetEventResponseQuery{
EventAlphaID: r.URL.Query().Get(fieldNameEventID), EventID: r.URL.Query().Get(fieldNameEventID),
ResponseAlphaID: r.URL.Query().Get(fieldNameResponseID), ResponseID: r.URL.Query().Get(fieldNameResponseID),
}) })
if notFound(w, err, "Event response not found.") || internalServerError(w, err) { if notFound(w, err, "Event response not found.") || internalServerError(w, err) {
return return
} }
event, err := h.store.GetEventMetadata(r.Context(), back.GetEventMetadataQuery{ 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) { if notFound(w, err, "Event not found") || internalServerError(w, err) {
return return
@ -295,7 +295,7 @@ func (h *handler) handleDoCreate(w http.ResponseWriter, r *http.Request) {
} }
var successQuery = make(url.Values) var successQuery = make(url.Values)
successQuery.Add(fieldNameEventID, event.AlphaID) successQuery.Add(fieldNameEventID, event.EventID)
successQuery.Add(fieldNameAdminCode, event.AdminCode) successQuery.Add(fieldNameAdminCode, event.AdminCode)
http.Redirect(w, r, pathCreateSuccess+"?"+successQuery.Encode(), http.StatusSeeOther) 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) { func (h *handler) handleCreateSuccess(w http.ResponseWriter, r *http.Request) {
eventID := r.URL.Query().Get(fieldNameEventID) eventID := r.URL.Query().Get(fieldNameEventID)
event, err := h.store.GetEventMetadata(r.Context(), back.GetEventMetadataQuery{ event, err := h.store.GetEventMetadata(r.Context(), back.GetEventMetadataQuery{
AlphaID: eventID, EventID: eventID,
}) })
if notFound(w, err, "Event not found") || internalServerError(w, err) { if notFound(w, err, "Event not found") || internalServerError(w, err) {
return return
@ -351,14 +351,14 @@ func (h *handler) handleAdmin(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query() query := r.URL.Query()
eventID := query.Get(fieldNameEventID) eventID := query.Get(fieldNameEventID)
metadata, err := h.store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{ metadata, err := h.store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{
AlphaID: eventID, EventID: eventID,
}) })
if notFound(w, err, "Event not found") || internalServerError(w, err) { if notFound(w, err, "Event not found") || internalServerError(w, err) {
return return
} }
responses, err := h.store.GetEventResponseSummary(context.Background(), back.GetEventResponseSummaryQuery{ responses, err := h.store.GetEventResponseSummary(context.Background(), back.GetEventResponseSummaryQuery{
AlphaID: eventID, EventID: eventID,
}) })
if err != nil { if err != nil {
fmt.Fprint(w, err) fmt.Fprint(w, err)

View File

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