blob: 7cbb528d0646fa91f302710f3647b73aaba45545 [file] [log] [blame]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package dbspec implements a string notation for referring to a database.
// A DB specification can take one of these forms:
//
// pebble:DIR[~VECTOR_NAMESPACE]
//
// A Pebble database in the directory DIR.
// DIR can be relative or absolute.
//
// firestore:PROJECT,DATABASE[~VECTOR_NAMESPACE]
//
// A Firestore DB in the given GCP project and Firestore database.
//
// mem[~VECTOR_NAMESPACE]
//
// An in-memory database.
//
// If a VECTOR_NAMESPACE is present, the spec refers to the vector DB portion of the database.
// The namespace can be empty, in which case the spec ends with a '~'.
package dbspec
import (
"errors"
"fmt"
"path/filepath"
"strings"
)
// A Spec is the parsed representation of a DB specification string.
type Spec struct {
Kind string // "pebble", "firestore", etc.
Location string // directory, project, etc.
Name string // database name, for firestore
IsVector bool // spec refers to the vector part of the database
Namespace string // namespace of vector DB, possibly empty
}
func (s *Spec) String() string {
var vs string
if s.IsVector {
vs = "~" + s.Namespace
}
switch s.Kind {
case "mem":
return "mem" + vs
case "pebble":
return "pebble:" + s.Location + vs
case "firestore":
return fmt.Sprintf("firestore:%s,%s%s", s.Location, s.Name, vs)
default:
return fmt.Sprintf("%#v", s)
}
}
// Parse parses a DB specification string into a [Spec].
func Parse(s string) (_ *Spec, err error) {
defer func() {
if err != nil {
err = fmt.Errorf("dbspec.Parse(%q): %v", s, err)
}
}()
var kind, middle, ns string
var hasTilde bool
hasColon := strings.ContainsRune(s, ':')
if hasColon {
kind, middle, _ = strings.Cut(s, ":")
middle, ns, hasTilde = strings.Cut(middle, "~")
} else {
kind, ns, hasTilde = strings.Cut(s, "~")
}
spec := &Spec{Kind: kind, IsVector: hasTilde, Namespace: ns}
switch kind {
case "mem":
if hasColon {
return nil, errors.New("invalid 'mem' spec: should be mem[~VECTOR_NAMESPACE]")
}
case "pebble":
if len(middle) == 0 {
return nil, errors.New("pebble spec missing directory; want pebble:DIR[~VECTOR_NAMESPACE]")
}
spec.Location = filepath.Clean(middle)
case "firestore":
proj, db, _ := strings.Cut(middle, ",")
if proj == "" || db == "" {
return nil, fmt.Errorf("invalid firestore spec; want firestore:PROJECT,DATABASE[~VECTOR_NAMESPACE]")
}
spec.Location = proj
spec.Name = db
default:
return nil, fmt.Errorf("unknown kind %q", kind)
}
return spec, nil
}