Skip to content

Commit

Permalink
Add read tile in MySQL storage implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
roger2hk committed Jul 10, 2024
1 parent a312398 commit ee3cfb1
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 2 deletions.
62 changes: 62 additions & 0 deletions cmd/example-mysql/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ import (
"crypto/sha256"
"database/sql"
"flag"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"

tessera "github.com/transparency-dev/trillian-tessera"
Expand Down Expand Up @@ -69,6 +72,35 @@ func main() {
}
})

http.HandleFunc("GET /tile/{level}/{index...}", func(w http.ResponseWriter, r *http.Request) {
level, index, err := parseTileLevelIndex(r.PathValue("level"), r.PathValue("index"))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if _, werr := w.Write([]byte(err.Error())); werr != nil {
klog.Errorf("/tile/{level}/{index...}: %v", werr)
}
return
}

tile, err := storage.ReadTile(r.Context(), level, index)
if err != nil {
if err == sql.ErrNoRows {
w.WriteHeader(http.StatusNotFound)
return
}

klog.Errorf("/tile/{level}/{index...}: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
if _, err := w.Write(tile); err != nil {
klog.Errorf("/tile/{level}/{index...}: %v", err)
return
}
})

http.HandleFunc("POST /add", func(w http.ResponseWriter, r *http.Request) {
b, err := io.ReadAll(r.Body)
if err != nil {
Expand All @@ -87,3 +119,33 @@ func main() {
klog.Exitf("ListenAndServe: %v", err)
}
}

// parseTileLevelIndex takes level and index in string, validates and returns them in uint64.
//
// Examples:
// "/tile/0/x001/x234/067" means level 0 and index 1234067 of a full tile.
// "/tile/0/x001/x234/067.p/8" means level 0, index 1234067 and width 8 of a partial tile.
func parseTileLevelIndex(level, index string) (uint64, uint64, error) {
l, err := strconv.ParseUint(level, 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("malformed URL: failed to parse tile level")
}

var i uint64
switch splittedIndexPath := strings.Split(index, "/"); len(splittedIndexPath) {
// Full tile = 3
// Partial tile = 4
case 3, 4:
indexPath := strings.Join(splittedIndexPath[0:3], "")
indexPath = strings.ReplaceAll(indexPath, "x", "")
indexPath = strings.ReplaceAll(indexPath, ".p", "")
i, err = strconv.ParseUint(indexPath, 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("malformed URL: failed to parse tile index")
}
default:
return 0, 0, fmt.Errorf("malformed URL: failed to parse tile index")
}

return l, i, nil
}
15 changes: 13 additions & 2 deletions storage/mysql/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import (
)

const (
selectCheckpointByIDSQL = "SELECT `note` FROM `Checkpoint` WHERE `id` = ?"
replaceCheckpointSQL = "REPLACE INTO `Checkpoint` (`id`, `note`) VALUES (?, ?)"
selectCheckpointByIDSQL = "SELECT `note` FROM `Checkpoint` WHERE `id` = ?"
replaceCheckpointSQL = "REPLACE INTO `Checkpoint` (`id`, `note`) VALUES (?, ?)"
selectSubtreeByLevelAndIndexSQL = "SELECT `nodes` FROM `Subtree` WHERE `level` = ? AND `index` = ?"

checkpointID = 0
)
Expand Down Expand Up @@ -95,3 +96,13 @@ func (s *Storage) writeCheckpoint(ctx context.Context, rawCheckpoint []byte) err
// Commit the transaction.
return tx.Commit()
}

func (s *Storage) ReadTile(ctx context.Context, level, index uint64) ([]byte, error) {
row := s.db.QueryRowContext(ctx, selectSubtreeByLevelAndIndexSQL, level, index)
if err := row.Err(); err != nil {
return nil, err
}

var tile []byte
return tile, row.Scan(&tile)
}
11 changes: 11 additions & 0 deletions storage/mysql/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,14 @@ CREATE TABLE IF NOT EXISTS `Checkpoint` (
`note` MEDIUMBLOB NOT NULL,
PRIMARY KEY(`id`)
);

-- "Subtree" table is an internal tile consisting of hashes. There is one row for each internal tile, and this is updated until it is completed, at which point it is immutable.
CREATE TABLE IF NOT EXISTS `Subtree` (
-- level is the level of the tile.
`level` INT UNSIGNED NOT NULL,
-- index is the index of the tile.
`index` BIGINT UNSIGNED NOT NULL,
-- nodes stores the hashes of the leaves.
`nodes` MEDIUMBLOB NOT NULL,
PRIMARY KEY(`level`, `index`)
);

0 comments on commit ee3cfb1

Please sign in to comment.