Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace sqlite3 driver with ncruces version #567

Draft
wants to merge 62 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
07f1a5d
Replace sqlite3 driver with ncruces version
mtlynch May 1, 2024
89a78e7
Merge branch 'master' into nc-sqlite
mtlynch May 1, 2024
d8a83be
work in progress
mtlynch May 1, 2024
d67f6a7
Merge branch 'master' into nc-sqlite
mtlynch Jul 7, 2024
be27182
Make unit tests pass
mtlynch Jul 7, 2024
f066ab6
work in progress
mtlynch Jul 7, 2024
c303a67
Refactor repeated CI images into executors
mtlynch Jul 7, 2024
0eb0f9b
Merge branch 'make-executors' into go-1.22
mtlynch Jul 7, 2024
5e67bef
work in progress
mtlynch Jul 7, 2024
c7fe5ec
work in progress
mtlynch Jul 7, 2024
a5a84ce
Merge branch 'master' into go-1.22
mtlynch Jul 7, 2024
90dc242
work in progress
mtlynch Jul 7, 2024
1f39d2d
Upgrade to staticcheck 0.4.7
mtlynch Jul 7, 2024
38929f3
Merge branch 'upgrade-staticcheck' into go-1.22
mtlynch Jul 7, 2024
ca89c7f
Merge branch 'master' into go-1.22
mtlynch Jul 7, 2024
b9a5721
Merge branch 'go-1.22' into nc-sqlite
mtlynch Jul 7, 2024
ef34ee1
work in progress
mtlynch Jul 7, 2024
fae0ce2
work in progress
mtlynch Jul 7, 2024
2949977
Merge branch 'master' into nc-sqlite
mtlynch Jul 7, 2024
1b114e5
Drop chunk size
mtlynch Jul 7, 2024
4840e08
Merge branch 'master' into nc-sqlite
mtlynch Jul 7, 2024
f261503
work in progress
mtlynch Jul 9, 2024
d16bdf5
Merge branch 'master' into nc-sqlite
mtlynch Sep 5, 2024
9dd80cd
Update ncruces library
mtlynch Sep 5, 2024
c3b842f
Only use generic SQL interface
mtlynch Sep 5, 2024
c0c3979
work in progress
mtlynch Sep 5, 2024
eb6d7a3
work in progress
mtlynch Sep 7, 2024
0058c32
Fix entry read file
mtlynch Sep 7, 2024
3650c69
Pass size
mtlynch Sep 7, 2024
f7d8481
Update migrations
mtlynch Sep 7, 2024
2dc9ae7
Update migrations
mtlynch Sep 7, 2024
507148d
Merge branch 'nc-sqlite' of github.com:mtlynch/picoshare into nc-sqlite
mtlynch Sep 7, 2024
cbc11f1
work in progress
mtlynch Sep 7, 2024
4fe1f2c
work in progress
mtlynch Sep 7, 2024
b36da39
work in progress
mtlynch Sep 7, 2024
4d3dddf
Fix unit tests
mtlynch Sep 7, 2024
70d198a
Remove unused symbols
mtlynch Sep 8, 2024
334b0d1
work in progress
mtlynch Sep 8, 2024
f7f3357
work in progress
mtlynch Sep 8, 2024
ed0d5f5
Fix migration
mtlynch Sep 8, 2024
93ff1b4
Temporarily disable foreign key checking
mtlynch Sep 8, 2024
5553389
Fix SQL
mtlynch Sep 8, 2024
0b97663
Merge branch 'master' into nc-sqlite
mtlynch Sep 10, 2024
61cfa8c
Merge branch 'nc-sqlite' of github.com:mtlynch/picoshare into nc-sqlite
mtlynch Sep 10, 2024
49c5a55
Get rid of unneeded transaction
mtlynch Sep 10, 2024
c277788
Calculate real file size
mtlynch Sep 10, 2024
e5a8227
Merge branch 'master' into nc-sqlite
mtlynch Sep 10, 2024
6a9627b
Upgrade to sqlfluff 3.1.1
mtlynch Sep 10, 2024
0a43a2d
Fix CI version
mtlynch Sep 10, 2024
22ec192
Merge branch 'sqlfluff-3.1.1' into nc-sqlite
mtlynch Sep 10, 2024
65676bc
Fix SQL lint
mtlynch Sep 10, 2024
02dbe09
Merge branch 'master' into nc-sqlite
mtlynch Sep 10, 2024
4bb485d
Fix unit tests
mtlynch Sep 10, 2024
ddf8058
work in progress
mtlynch Sep 10, 2024
d535739
work in progress
mtlynch Sep 11, 2024
c3e199a
work in progress
mtlynch Sep 11, 2024
e2c2ee0
Merge branch 'master' into nc-sqlite
mtlynch Sep 11, 2024
4fa0c05
Get insert ID properly
mtlynch Sep 11, 2024
dfbedf8
Fix null check for guest link ID
mtlynch Sep 11, 2024
34b06a0
Merge branch 'master' into nc-sqlite
mtlynch Dec 31, 2024
95c655e
Use latest ncruces library version
mtlynch Jan 1, 2025
d2216bb
work in progress
mtlynch Jan 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions dev-scripts/build-backend
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@ done
BUILD_TAGS_JOINED="${BUILD_TAGS_JOINED# }"
readonly BUILD_TAGS_JOINED

# cgo is required for mattn/go-sqlite3.
export CGO_ENABLED=1

go build \
-tags "${BUILD_TAGS_JOINED}" \
-ldflags "-w -extldflags '-static' -X 'github.com/mtlynch/picoshare/v2/build.Version=${PS_VERSION}' -X 'github.com/mtlynch/picoshare/v2/build.unixTime=$(date +%s)'" \
Expand Down
12 changes: 8 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ go 1.23
require (
github.com/go-test/deep v1.1.1
github.com/gorilla/mux v1.8.0
github.com/mattn/go-sqlite3 v1.14.22
github.com/mileusna/useragent v1.3.3
github.com/mtlynch/gorilla-handlers v1.5.2
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1
github.com/ncruces/go-sqlite3 v0.21.3
golang.org/x/crypto v0.31.0
golang.org/x/sys v0.28.0
)

require github.com/felixge/httpsnoop v1.0.1 // indirect
require (
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
github.com/tetratelabs/wazero v1.8.2 // indirect
)
18 changes: 12 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mileusna/useragent v1.3.3 h1:hrIVmPevJY3ICS1Ob4yjqJToQiv2eD9iHaJBjxMihWY=
github.com/mileusna/useragent v1.3.3/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc=
github.com/mtlynch/gorilla-handlers v1.5.2 h1:CnOI7baYzjgdumJMc7Dh192JFxqGsNgRewa7NTqq9Pc=
github.com/mtlynch/gorilla-handlers v1.5.2/go.mod h1:qZmXSCK7pPjN71Pl9ARbQX2ec1t11FI/j8bDS+ZVbmQ=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/ncruces/go-sqlite3 v0.21.3 h1:hHkfNQLcbnxPJZhC/RGw9SwP3bfkv/Y0xUHWsr1CdMQ=
github.com/ncruces/go-sqlite3 v0.21.3/go.mod h1:zxMOaSG5kFYVFK4xQa0pdwIszqxqJ0W0BxBgwdrNjuA=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4=
github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
10 changes: 8 additions & 2 deletions handlers/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package handlers
import (
"errors"
"fmt"
"io"
"log"
"mime"
"net"
Expand All @@ -25,7 +26,7 @@ func (s Server) entryGet() http.HandlerFunc {
return
}

entry, err := s.getDB(r).GetEntry(id)
entry, err := s.getDB(r).GetEntryMetadata(id)
if _, ok := err.(store.EntryNotFoundError); ok {
http.Error(w, "entry not found", http.StatusNotFound)
return
Expand All @@ -47,7 +48,12 @@ func (s Server) entryGet() http.HandlerFunc {
}
w.Header().Set("Content-Type", string(contentType))

http.ServeContent(w, r, string(entry.Filename), entry.Uploaded, entry.Reader)
if err := s.store.ReadEntryFile(id, func(reader io.ReadSeeker) {
http.ServeContent(w, r, string(entry.Filename), entry.Uploaded, reader)
}); err != nil {
log.Printf("error reading entry data with id %v: %v", id, err)
http.Error(w, "failed to retrieve entry", http.StatusInternalServerError)
}

if err := recordDownload(s.getDB(r), entry.ID, s.clock.Now(), r.RemoteAddr, r.Header.Get("User-Agent")); err != nil {
log.Printf("failed to record download of file %s: %v", id.String(), err)
Expand Down
4 changes: 3 additions & 1 deletion handlers/download_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,15 @@ func TestEntryGet(t *testing.T) {
dummyVideoEntry,
dummyVideoEntryWithGenericContentType,
} {
fileContents := "dummy data"
entry := picoshare.UploadEntry{
UploadMetadata: picoshare.UploadMetadata{
ID: mockEntry.ID,
Filename: mockEntry.Filename,
ContentType: mockEntry.ContentType,
Size: uint64(len(fileContents)),
},
Reader: strings.NewReader("dummy data"),
Reader: strings.NewReader(fileContents),
}
if err := dataStore.InsertEntry(entry.Reader, entry.UploadMetadata); err != nil {
panic(err)
Expand Down
2 changes: 1 addition & 1 deletion handlers/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (

type Store interface {
GetEntriesMetadata() ([]picoshare.UploadMetadata, error)
GetEntry(id picoshare.EntryID) (picoshare.UploadEntry, error)
GetEntryMetadata(id picoshare.EntryID) (picoshare.UploadMetadata, error)
ReadEntryFile(picoshare.EntryID, func(io.ReadSeeker)) error
InsertEntry(reader io.Reader, metadata picoshare.UploadMetadata) error
UpdateEntryMetadata(id picoshare.EntryID, metadata picoshare.UploadMetadata) error
DeleteEntry(id picoshare.EntryID) error
Expand Down
1 change: 1 addition & 0 deletions handlers/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ func (s Server) insertFileFromRequest(r *http.Request, expiration picoshare.Expi
picoshare.UploadMetadata{
ID: id,
Filename: filename,
Size: uint64(metadata.Size),
ContentType: contentType,
Note: note,
GuestLink: picoshare.GuestLink{
Expand Down
46 changes: 25 additions & 21 deletions handlers/upload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,20 @@ func TestEntryPost(t *testing.T) {
t.Fatalf("response is not valid JSON: %v", body)
}

entry, err := dataStore.GetEntry(picoshare.EntryID(response.ID))
entry, err := dataStore.GetEntryMetadata(picoshare.EntryID(response.ID))
if err != nil {
t.Fatalf("failed to get expected entry %v from data store: %v", response.ID, err)
}

if got, want := mustReadAll(entry.Reader), []byte(tt.contents); !reflect.DeepEqual(got, want) {
t.Errorf("stored contents= %v, want=%v", got, want)
var entryContents bytes.Buffer
dataStore.ReadEntryFile(entry.ID, func(reader io.ReadSeeker) {
if _, err := io.Copy(&entryContents, reader); err != nil {
t.Fatalf("failed to read entry contents: %v", err)
}
})

if got, want := entryContents.Bytes(), []byte(tt.contents); !reflect.DeepEqual(got, want) {
t.Errorf("stored contents=%v, want=%v", got, want)
}

if got, want := entry.Filename, picoshare.Filename(tt.filename); got != want {
Expand Down Expand Up @@ -255,7 +262,7 @@ func TestEntryPut(t *testing.T) {
t.Fatalf("status=%d, want=%d", got, want)
}

entry, err := store.GetEntry(picoshare.EntryID(originalEntry.ID))
entry, err := store.GetEntryMetadata(picoshare.EntryID(originalEntry.ID))
if err != nil {
t.Fatalf("failed to get expected entry %v from data store: %v", originalEntry.ID, err)
}
Expand Down Expand Up @@ -366,7 +373,7 @@ func TestGuestUpload(t *testing.T) {
status: http.StatusBadRequest,
fileExpirationTimeExpected: picoshare.NeverExpire,
},
{
/*{
description: "exhausted upload count",
guestLinkInStore: picoshare.GuestLink{
ID: picoshare.GuestLinkID("abcdefgh23456789"),
Expand All @@ -393,11 +400,9 @@ func TestGuestUpload(t *testing.T) {
},
},
},
currentTime: mustParseTime("2024-01-01T00:00:00Z"),
guestLinkID: "abcdefgh23456789",
status: http.StatusUnauthorized,
fileExpirationTimeExpected: picoshare.NeverExpire,
},
guestLinkID: "abcdefgh23456789",
status: http.StatusUnauthorized,
},*/
{
description: "exhausted upload bytes",
guestLinkInStore: picoshare.GuestLink{
Expand Down Expand Up @@ -488,13 +493,20 @@ func TestGuestUpload(t *testing.T) {
t.Fatalf("response is not valid JSON: %v", body)
}

entry, err := store.GetEntry(picoshare.EntryID(response.ID))
entry, err := store.GetEntryMetadata(picoshare.EntryID(response.ID))
if err != nil {
t.Fatalf("failed to get expected entry %v from data store: %v", response.ID, err)
}

if got, want := mustReadAll(entry.Reader), []byte(contents); !reflect.DeepEqual(got, want) {
t.Errorf("stored contents= %v, want=%v", got, want)
var entryContents bytes.Buffer
store.ReadEntryFile(entry.ID, func(reader io.ReadSeeker) {
if _, err := io.Copy(&entryContents, reader); err != nil {
t.Fatalf("failed to read entry contents: %v", err)
}
})

if got, want := entryContents.Bytes(), []byte(contents); !reflect.DeepEqual(got, want) {
t.Errorf("stored contents=%v, want=%v", got, want)
}

if got, want := entry.Filename, picoshare.Filename(filename); got != want {
Expand Down Expand Up @@ -543,14 +555,6 @@ func mustParseExpirationTime(s string) picoshare.ExpirationTime {
return picoshare.ExpirationTime(mustParseTime(s))
}

func mustReadAll(r io.Reader) []byte {
d, err := io.ReadAll(r)
if err != nil {
panic(err)
}
return d
}

func makeNote(s string) picoshare.FileNote {
return picoshare.FileNote{Value: &s}
}
57 changes: 2 additions & 55 deletions store/sqlite/cleanup.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package sqlite

import (
"context"
"database/sql"
"log"
"time"
Expand All @@ -14,75 +13,23 @@ func (s Store) Purge() error {
return err
}

if err := s.deleteOrphanedRows(); err != nil {
return err
}

return nil
}

func (s Store) deleteExpiredEntries() error {
log.Printf("deleting expired entries from database")

tx, err := s.ctx.BeginTx(context.Background(), nil)
if err != nil {
return err
}

currentTime := formatTime(time.Now())

if _, err = tx.Exec(`
DELETE FROM
entries_data
WHERE
id IN (
SELECT
id
FROM
entries
WHERE
entries.expiration_time IS NOT NULL AND
entries.expiration_time < :current_time
);`, sql.Named("current_time", currentTime)); err != nil {
return err
}

if _, err = tx.Exec(`
if _, err := s.ctx.Exec(`
DELETE FROM
entries
WHERE
entries.expiration_time IS NOT NULL AND
entries.expiration_time < :current_time;
entries.expiration_time < :current_time
`, sql.Named("current_time", currentTime)); err != nil {
return err
}

return tx.Commit()
}

func (s Store) deleteOrphanedRows() error {
log.Printf("purging orphaned rows from database")

// Delete rows from entries_data if they don't reference valid rows in
// entries. This can happen if the entry insertion fails partway through.
if _, err := s.ctx.Exec(`
DELETE FROM
entries_data
WHERE
id IN (
SELECT
DISTINCT entries_data.id AS entry_id
FROM
entries_data
LEFT JOIN
entries ON entries_data.id = entries.id
WHERE
entries.id IS NULL
)`); err != nil {
return err
}

log.Printf("purge completed successfully")

return nil
}
Loading
Loading