mirror of
https://github.com/dolthub/dolt.git
synced 2026-02-10 18:49:02 -06:00
249 lines
5.7 KiB
Go
249 lines
5.7 KiB
Go
// Copyright 2016 Attic Labs, Inc. All rights reserved.
|
|
// Licensed under the Apache License, version 2.0:
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
package spec
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/attic-labs/noms/go/chunks"
|
|
"github.com/attic-labs/noms/go/d"
|
|
"github.com/attic-labs/noms/go/datas"
|
|
"github.com/attic-labs/noms/go/dataset"
|
|
"github.com/attic-labs/noms/go/types"
|
|
)
|
|
|
|
var (
|
|
datasetRe = regexp.MustCompile("^" + dataset.DatasetRe.String() + "$")
|
|
ldbStores = map[string]*refCountingLdbStore{}
|
|
)
|
|
|
|
func GetDatabase(str string) (datas.Database, error) {
|
|
sp, err := parseDatabaseSpec(str)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sp.Database()
|
|
}
|
|
|
|
func GetChunkStore(str string) (chunks.ChunkStore, error) {
|
|
sp, err := parseDatabaseSpec(str)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch sp.Protocol {
|
|
case "ldb":
|
|
return getLDBStore(sp.Path), nil
|
|
case "mem":
|
|
return chunks.NewMemoryStore(), nil
|
|
default:
|
|
return nil, fmt.Errorf("Unable to create chunkstore for protocol: %s", str)
|
|
}
|
|
}
|
|
|
|
func GetDataset(str string) (dataset.Dataset, error) {
|
|
sp, err := parseDatasetSpec(str)
|
|
if err != nil {
|
|
return dataset.Dataset{}, err
|
|
}
|
|
return sp.Dataset()
|
|
}
|
|
|
|
func GetPath(str string) (datas.Database, types.Value, error) {
|
|
sp, err := parsePathSpec(str)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return sp.Value()
|
|
}
|
|
|
|
type databaseSpec struct {
|
|
Protocol string
|
|
Path string
|
|
accessToken string
|
|
}
|
|
|
|
type datasetSpec struct {
|
|
DbSpec databaseSpec
|
|
DatasetName string
|
|
}
|
|
|
|
type pathSpec struct {
|
|
DbSpec databaseSpec
|
|
Path AbsolutePath
|
|
}
|
|
|
|
func parseDatabaseSpec(spec string) (databaseSpec, error) {
|
|
ldbDatabaseSpec := func(path string) (databaseSpec, error) {
|
|
if len(path) == 0 {
|
|
return databaseSpec{}, fmt.Errorf("Empty file system path")
|
|
}
|
|
return databaseSpec{Protocol: "ldb", Path: path}, nil
|
|
}
|
|
|
|
parts := strings.SplitN(spec, ":", 2) // [protocol] [, path]?
|
|
protocol := parts[0]
|
|
|
|
// If there was no ":" then this is either a mem spec, or a filesystem path.
|
|
// This is ambiguous if the file system path is "mem" but that just means the path needs to be explicitly "ldb:mem".
|
|
if len(parts) == 1 {
|
|
if protocol == "mem" {
|
|
return databaseSpec{Protocol: "mem"}, nil
|
|
}
|
|
return ldbDatabaseSpec(protocol)
|
|
}
|
|
|
|
path := parts[1]
|
|
|
|
switch protocol {
|
|
case "http", "https":
|
|
u, err := url.Parse(spec)
|
|
if err != nil || len(u.Host) == 0 {
|
|
return databaseSpec{}, fmt.Errorf("Invalid URL: %s", spec)
|
|
}
|
|
token := u.Query().Get("access_token")
|
|
return databaseSpec{Protocol: protocol, Path: path, accessToken: token}, nil
|
|
|
|
case "ldb":
|
|
return ldbDatabaseSpec(path)
|
|
|
|
case "mem":
|
|
return databaseSpec{}, fmt.Errorf(`In-memory database must be specified as "mem", not "mem:%s"`, path)
|
|
|
|
default:
|
|
return databaseSpec{}, fmt.Errorf("Invalid database protocol: %s", spec)
|
|
}
|
|
}
|
|
|
|
func splitAndParseDatabaseSpec(spec string) (databaseSpec, string, error) {
|
|
parts := strings.SplitN(spec, "::", 2)
|
|
if len(parts) != 2 {
|
|
return databaseSpec{}, "", fmt.Errorf("Missing :: separator between database and dataset: %s", spec)
|
|
}
|
|
|
|
dbSpec, err := parseDatabaseSpec(parts[0])
|
|
if err != nil {
|
|
return databaseSpec{}, "", err
|
|
}
|
|
|
|
return dbSpec, parts[1], nil
|
|
}
|
|
|
|
func parseDatasetSpec(spec string) (datasetSpec, error) {
|
|
dbSpec, dsName, err := splitAndParseDatabaseSpec(spec)
|
|
if err != nil {
|
|
return datasetSpec{}, err
|
|
}
|
|
|
|
if !datasetRe.MatchString(dsName) {
|
|
return datasetSpec{}, fmt.Errorf("Invalid dataset, must match %s: %s", dataset.DatasetRe.String(), dsName)
|
|
}
|
|
|
|
return datasetSpec{dbSpec, dsName}, nil
|
|
}
|
|
|
|
func parsePathSpec(spec string) (pathSpec, error) {
|
|
dbSpec, pathStr, err := splitAndParseDatabaseSpec(spec)
|
|
if err != nil {
|
|
return pathSpec{}, err
|
|
}
|
|
|
|
path, err := NewAbsolutePath(pathStr)
|
|
if err != nil {
|
|
return pathSpec{}, err
|
|
}
|
|
|
|
return pathSpec{dbSpec, path}, nil
|
|
}
|
|
|
|
func (s databaseSpec) String() string {
|
|
return s.Protocol + ":" + s.Path
|
|
}
|
|
|
|
func (spec databaseSpec) Database() (ds datas.Database, err error) {
|
|
switch spec.Protocol {
|
|
case "http", "https":
|
|
err = d.Unwrap(d.Try(func() {
|
|
ds = datas.NewRemoteDatabase(spec.String(), "Bearer "+spec.accessToken)
|
|
}))
|
|
case "ldb":
|
|
err = d.Unwrap(d.Try(func() {
|
|
ds = datas.NewDatabase(getLDBStore(spec.Path))
|
|
}))
|
|
case "mem":
|
|
ds = datas.NewDatabase(chunks.NewMemoryStore())
|
|
default:
|
|
err = fmt.Errorf("Invalid path prototocol: %s", spec.Protocol)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (spec datasetSpec) Dataset() (dataset.Dataset, error) {
|
|
store, err := spec.DbSpec.Database()
|
|
if err != nil {
|
|
return dataset.Dataset{}, err
|
|
}
|
|
|
|
return dataset.NewDataset(store, spec.DatasetName), nil
|
|
}
|
|
|
|
func (s datasetSpec) String() string {
|
|
return s.DbSpec.String() + "::" + s.DatasetName
|
|
}
|
|
|
|
func (spec datasetSpec) Value() (datas.Database, types.Value, error) {
|
|
dataset, err := spec.Dataset()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
commit, ok := dataset.MaybeHead()
|
|
if !ok {
|
|
dataset.Database().Close()
|
|
return nil, nil, fmt.Errorf("No head value for dataset: %s", spec.DatasetName)
|
|
}
|
|
|
|
return dataset.Database(), commit, nil
|
|
}
|
|
|
|
func (spec pathSpec) Value() (db datas.Database, val types.Value, err error) {
|
|
db, err = spec.DbSpec.Database()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
val = spec.Path.Resolve(db)
|
|
return
|
|
}
|
|
|
|
func RegisterDatabaseFlags(flags *flag.FlagSet) {
|
|
chunks.RegisterLevelDBFlags(flags)
|
|
}
|
|
|
|
func CreateDatabaseSpecString(protocol, path string) string {
|
|
return fmt.Sprintf("%s:%s", protocol, path)
|
|
}
|
|
|
|
func CreateValueSpecString(protocol, path, value string) string {
|
|
return fmt.Sprintf("%s:%s::%s", protocol, path, value)
|
|
}
|
|
|
|
func getLDBStore(path string) chunks.ChunkStore {
|
|
if store, ok := ldbStores[path]; ok {
|
|
store.AddRef()
|
|
return store
|
|
}
|
|
|
|
store := newRefCountingLdbStore(path, func() {
|
|
delete(ldbStores, path)
|
|
})
|
|
ldbStores[path] = store
|
|
return store
|
|
}
|