Files
query-orchestration/internal/serviceconfig/objectstore/bucketkey.go
T
Michael McGuinness fee71e7740 Merged in feature/postprocessing (pull request #114)
Feature/postprocessing

* tests

* passtest

* fixshorttests

* mosttests

* improvingbasedockerfile

* testspeeds

* testing

* host

* canparallel

* clean

* passfullsuite

* singlepagemax

* test

* findfeatures

* findstables

* tbls

* tablestoo

* tablestoo

* lateraltests

* tableloc

* cleanup

* inlinetable

* childids

* cleanup

* tests
2025-04-22 14:40:16 +00:00

195 lines
4.2 KiB
Go

package objectstore
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"time"
"queryorchestration/internal/client"
"github.com/google/uuid"
)
type BucketLocation string
type BucketKey struct {
ClientID string
Location BucketLocation
CreatedAt time.Time
Part *uint16
EntityID uuid.UUID
FileType *string
}
const (
Import BucketLocation = "import"
Text BucketLocation = "text"
Export BucketLocation = "export"
)
var PartExclusions = []BucketLocation{
Export,
}
const TimeFormat = "2006-01-02T150405Z"
const DateFormat = "20060102"
func (c *BucketKey) String() string {
return fmt.Sprintf("%s/%s", c.Prefix(), c.Filename())
}
func (c *BucketKey) Prefix() string {
dateStr := c.CreatedAt.Format(DateFormat)
partStr := ""
isExcluded := false
for _, location := range PartExclusions {
if location == c.Location {
isExcluded = true
break
}
}
if !isExcluded {
if c.Part == nil {
part := uint16(0)
c.Part = &part
}
partStr = fmt.Sprintf("/%d", *c.Part)
}
return fmt.Sprintf("%s/%s/%s%s", c.ClientID, c.Location, dateStr, partStr)
}
func (c *BucketKey) Filename() string {
location := strings.ReplaceAll(string(c.Location), "/", "-")
name := fmt.Sprintf("%s_%s_%s_%s", c.CreatedAt.Format(TimeFormat), c.ClientID, location, c.EntityID)
if c.FileType != nil {
name = fmt.Sprintf("%s.%s", name, *c.FileType)
}
return name
}
func (c *BucketKey) parseFilename(name string) error {
parts := strings.Split(name, ".")
if len(parts) != 1 && len(parts) != 2 {
return errors.New("incorrect amount of dots")
} else if len(parts) == 2 {
c.parseFileType(parts[1])
}
metadata := strings.Split(parts[0], "_")
if len(metadata) != 4 {
return errors.New("incorrect amount of metadata elements")
}
err := c.parseTime(metadata[0])
if err != nil {
return err
}
err = c.parseClientID(metadata[1])
if err != nil {
return err
}
err = c.parseLocation(metadata[2])
if err != nil {
return err
}
err = c.parseEntityID(metadata[3])
if err != nil {
return err
}
return nil
}
func (c *BucketKey) parseFileType(name string) {
c.FileType = &name
}
func (c *BucketKey) parseTime(name string) error {
parsedTime, err := time.Parse(TimeFormat, name)
if err != nil {
return fmt.Errorf("failed to parse date: %w", err)
}
c.CreatedAt = parsedTime
return nil
}
func (c *BucketKey) parseClientID(name string) error {
c.ClientID = name
pattern := fmt.Sprintf(`^(%s)$`, client.CLIENT_ID_REGEX)
reg := regexp.MustCompile(pattern)
matches := reg.FindStringSubmatch(c.ClientID)
if len(matches) != 2 {
return errors.New("invalid client id")
}
return nil
}
func (c *BucketKey) parseLocation(name string) error {
c.Location = BucketLocation(strings.ReplaceAll(name, "-", "/"))
pattern := fmt.Sprintf(`^(%s|%s|%s)$`, Import, Text, Export)
reg := regexp.MustCompile(pattern)
matches := reg.FindStringSubmatch(string(c.Location))
if len(matches) != 2 {
return errors.New("invalid location")
}
return nil
}
func (c *BucketKey) parseEntityID(name string) error {
entityId, err := uuid.Parse(name)
if err != nil {
return err
}
c.EntityID = entityId
return nil
}
func ParseBucketKey(key string) (BucketKey, error) {
locations := "[a-z]+/?[a-z]+?"
pattern := fmt.Sprintf(`^(%s)/(%s)/([0-9]{8})/(.+)$`, client.CLIENT_ID_REGEX, locations)
reg := regexp.MustCompile(pattern)
matches := reg.FindStringSubmatch(key)
if len(matches) != 5 {
return BucketKey{}, errors.New("does not match expectation")
}
formattedKey := BucketKey{}
filename := `.+`
reg = regexp.MustCompile(fmt.Sprintf(`^([0-9]+)/(%s)$`, filename))
subMatches := reg.FindStringSubmatch(matches[4])
if len(subMatches) == 3 {
part, err := strconv.ParseUint(subMatches[1], 10, 8)
if err != nil {
return BucketKey{}, err
}
safePart := uint16(part)
formattedKey.Part = &safePart
err = formattedKey.parseFilename(subMatches[2])
if err != nil {
return BucketKey{}, err
}
} else {
reg = regexp.MustCompile(fmt.Sprintf(`^(%s)$`, filename))
subMatches := reg.FindStringSubmatch(matches[4])
if len(subMatches) != 2 {
return BucketKey{}, errors.New("does not match expectation")
}
err := formattedKey.parseFilename(subMatches[1])
if err != nil {
return BucketKey{}, err
}
}
return formattedKey, nil
}