mirror of
https://github.com/dolthub/dolt.git
synced 2026-04-23 05:13:00 -05:00
Merge pull request #9924 from dolthub/nicktobey/global-tables
Implement dolt_global_tables system table
This commit is contained in:
@@ -61,7 +61,7 @@ require (
|
||||
github.com/dolthub/dolt-mcp v0.2.2-0.20250917171427-13e4520d1c36
|
||||
github.com/dolthub/eventsapi_schema v0.0.0-20250915094920-eadfd39051ca
|
||||
github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2
|
||||
github.com/dolthub/go-mysql-server v0.20.1-0.20251003202417-9979526e55c8
|
||||
github.com/dolthub/go-mysql-server v0.20.1-0.20251006192807-f7a3e3850abc
|
||||
github.com/dolthub/gozstd v0.0.0-20240423170813-23a2903bca63
|
||||
github.com/edsrzf/mmap-go v1.2.0
|
||||
github.com/esote/minmaxheap v1.0.0
|
||||
|
||||
@@ -213,8 +213,8 @@ github.com/dolthub/fslock v0.0.3 h1:iLMpUIvJKMKm92+N1fmHVdxJP5NdyDK5bK7z7Ba2s2U=
|
||||
github.com/dolthub/fslock v0.0.3/go.mod h1:QWql+P17oAAMLnL4HGB5tiovtDuAjdDTPbuqx7bYfa0=
|
||||
github.com/dolthub/go-icu-regex v0.0.0-20250916051405-78a38d478790 h1:zxMsH7RLiG+dlZ/y0LgJHTV26XoiSJcuWq+em6t6VVc=
|
||||
github.com/dolthub/go-icu-regex v0.0.0-20250916051405-78a38d478790/go.mod h1:F3cnm+vMRK1HaU6+rNqQrOCyR03HHhR1GWG2gnPOqaE=
|
||||
github.com/dolthub/go-mysql-server v0.20.1-0.20251003202417-9979526e55c8 h1:r54ksXOt1SDgztJsHU3r+W9ZYZjYUTIguGYUcrM9bMk=
|
||||
github.com/dolthub/go-mysql-server v0.20.1-0.20251003202417-9979526e55c8/go.mod h1:EeYR0apo+8j2Dyxmn2ghkPlirO2S5mT1xHBrA+Efys8=
|
||||
github.com/dolthub/go-mysql-server v0.20.1-0.20251006192807-f7a3e3850abc h1:kEbuDqqQ++5R/ExeKcNcQEe7MKYSn2ZJE2lBxoYQqjw=
|
||||
github.com/dolthub/go-mysql-server v0.20.1-0.20251006192807-f7a3e3850abc/go.mod h1:EeYR0apo+8j2Dyxmn2ghkPlirO2S5mT1xHBrA+Efys8=
|
||||
github.com/dolthub/gozstd v0.0.0-20240423170813-23a2903bca63 h1:OAsXLAPL4du6tfbBgK0xXHZkOlos63RdKYS3Sgw/dfI=
|
||||
github.com/dolthub/gozstd v0.0.0-20240423170813-23a2903bca63/go.mod h1:lV7lUeuDhH5thVGDCKXbatwKy2KW80L4rMT46n+Y2/Q=
|
||||
github.com/dolthub/ishell v0.0.0-20240701202509-2b217167d718 h1:lT7hE5k+0nkBdj/1UOSFwjWpNxf+LCApbRHgnCA17XE=
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable"
|
||||
@@ -203,43 +202,6 @@ func IdentifyIgnoredTables(ctx context.Context, roots Roots, tables []TableName)
|
||||
return ignoredTables, nil
|
||||
}
|
||||
|
||||
// compilePattern takes a dolt_ignore pattern and generate a Regexp that matches against the same table names as the pattern.
|
||||
func compilePattern(pattern string) (*regexp.Regexp, error) {
|
||||
pattern = "^" + regexp.QuoteMeta(pattern) + "$"
|
||||
pattern = strings.Replace(pattern, "\\?", ".", -1)
|
||||
pattern = strings.Replace(pattern, "\\*", ".*", -1)
|
||||
pattern = strings.Replace(pattern, "%", ".*", -1)
|
||||
return regexp.Compile(pattern)
|
||||
}
|
||||
|
||||
// getMoreSpecificPatterns takes a dolt_ignore pattern and generates a Regexp that matches against all patterns
|
||||
// that are "more specific" than it. (a pattern A is more specific than a pattern B if all names that match A also
|
||||
// match pattern B, but not vice versa.)
|
||||
func getMoreSpecificPatterns(lessSpecific string) (*regexp.Regexp, error) {
|
||||
pattern := "^" + regexp.QuoteMeta(lessSpecific) + "$"
|
||||
// A ? can expand to any character except for a * or %, since that also has special meaning in patterns.
|
||||
|
||||
pattern = strings.Replace(pattern, "\\?", "[^\\*%]", -1)
|
||||
pattern = strings.Replace(pattern, "\\*", ".*", -1)
|
||||
pattern = strings.Replace(pattern, "%", ".*", -1)
|
||||
return regexp.Compile(pattern)
|
||||
}
|
||||
|
||||
// normalizePattern generates an equivalent pattern, such that all equivalent patterns have the same normalized pattern.
|
||||
// It accomplishes this by replacing all * with %, and removing multiple adjacent %.
|
||||
// This will get a lot harder to implement once we support escaped characters in patterns.
|
||||
func normalizePattern(pattern string) string {
|
||||
pattern = strings.Replace(pattern, "*", "%", -1)
|
||||
for {
|
||||
newPattern := strings.Replace(pattern, "%%", "%", -1)
|
||||
if newPattern == pattern {
|
||||
break
|
||||
}
|
||||
pattern = newPattern
|
||||
}
|
||||
return pattern
|
||||
}
|
||||
|
||||
func resolveConflictingPatterns(trueMatches, falseMatches []string, tableName TableName) (IgnoreResult, error) {
|
||||
trueMatchesToRemove := map[string]struct{}{}
|
||||
falseMatchesToRemove := map[string]struct{}{}
|
||||
@@ -314,11 +276,11 @@ func (ip *IgnorePatterns) IsTableNameIgnored(tableName TableName) (IgnoreResult,
|
||||
for _, patternIgnore := range *ip {
|
||||
pattern := patternIgnore.Pattern
|
||||
ignore := patternIgnore.Ignore
|
||||
patternRegExp, err := compilePattern(pattern)
|
||||
matchesPattern, err := MatchTablePattern(pattern, tableName.Name)
|
||||
if err != nil {
|
||||
return ErrorOccurred, err
|
||||
}
|
||||
if patternRegExp.MatchString(tableName.Name) {
|
||||
if matchesPattern {
|
||||
if ignore {
|
||||
trueMatches = append(trueMatches, pattern)
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2025 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package doltdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable"
|
||||
"github.com/dolthub/dolt/go/store/types"
|
||||
"github.com/dolthub/dolt/go/store/val"
|
||||
)
|
||||
|
||||
// GetNonlocalTablesRef is a function that reads the "ref" column from dolt_nonlocal_tables. This is used to handle the Doltgres extended string type.
|
||||
var GetNonlocalTablesRef = getNonlocalTablesRef
|
||||
|
||||
// GetNonlocalTablesNameColumn is a function that reads the "table_name" column from dolt_nonlocal_tables. This is used to handle the Doltgres extended string type.
|
||||
var GetNonlocalTablesNameColumn = getNonlocalTablesNameColumn
|
||||
|
||||
func getNonlocalTablesNameColumn(_ context.Context, keyDesc val.TupleDesc, keyTuple val.Tuple) (string, error) {
|
||||
key, ok := keyDesc.GetString(0, keyTuple)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("failed to read global table")
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
type NonlocalTableEntry struct {
|
||||
Ref string
|
||||
NewTableName string
|
||||
Options string
|
||||
}
|
||||
|
||||
func getNonlocalTablesRef(_ context.Context, valDesc val.TupleDesc, valTuple val.Tuple) (result NonlocalTableEntry) {
|
||||
result.Ref, _ = valDesc.GetString(0, valTuple)
|
||||
result.NewTableName, _ = valDesc.GetString(1, valTuple)
|
||||
result.Options, _ = valDesc.GetString(2, valTuple)
|
||||
return result
|
||||
}
|
||||
|
||||
func GetGlobalTablePatterns(ctx context.Context, root RootValue, schema string, cb func(string)) error {
|
||||
table_name := TableName{Name: NonlocalTableName, Schema: schema}
|
||||
table, found, err := root.GetTable(ctx, table_name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
// dolt_global_tables doesn't exist, so don't filter any tables.
|
||||
return nil
|
||||
}
|
||||
|
||||
index, err := table.GetRowData(ctx)
|
||||
if table.Format() == types.Format_LD_1 {
|
||||
// dolt_global_tables is not supported for the legacy storage format.
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ignoreTableSchema, err := table.GetSchema(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m := durable.MapFromIndex(index)
|
||||
keyDesc, _ := ignoreTableSchema.GetMapDescriptors(m.NodeStore())
|
||||
|
||||
ignoreTableMap, err := m.IterAll(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
keyTuple, _, err := ignoreTableMap.Next(ctx)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
globalTableName, err := GetNonlocalTablesNameColumn(ctx, keyDesc, keyTuple)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cb(globalTableName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -217,6 +217,19 @@ const (
|
||||
QueryCatalogDescriptionCol = "description"
|
||||
)
|
||||
|
||||
const (
|
||||
// NonlocalTableName is the name of the query catalog table
|
||||
NonlocalTableName = "dolt_nonlocal_tables"
|
||||
|
||||
NonlocalTableTableNameCol = "table_name"
|
||||
|
||||
NonlocalTableRefCol = "target_ref"
|
||||
|
||||
NonlocalTablesRefTableCol = "ref_table"
|
||||
|
||||
NonlocalTablesOptionsCol = "options"
|
||||
)
|
||||
|
||||
const (
|
||||
// SchemasTableName is the name of the dolt schema fragment table
|
||||
SchemasTableName = "dolt_schemas"
|
||||
@@ -378,6 +391,8 @@ var GetStashesTableName = func() string {
|
||||
|
||||
var GetQueryCatalogTableName = func() string { return DoltQueryCatalogTableName }
|
||||
|
||||
var GetNonlocalTablesTableName = func() string { return NonlocalTableName }
|
||||
|
||||
var GetTestsTableName = func() string {
|
||||
return TestsTableName
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2025 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package doltdb
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
)
|
||||
|
||||
// compilePattern takes a dolt_ignore pattern and generate a Regexp that matches against the same table names as the pattern.
|
||||
func compilePattern(pattern string) (*regexp.Regexp, error) {
|
||||
pattern = "^" + regexp.QuoteMeta(pattern) + "$"
|
||||
pattern = strings.Replace(pattern, "\\?", ".", -1)
|
||||
pattern = strings.Replace(pattern, "\\*", ".*", -1)
|
||||
pattern = strings.Replace(pattern, "%", ".*", -1)
|
||||
return regexp.Compile(pattern)
|
||||
}
|
||||
|
||||
func patternContainsSpecialCharacters(pattern string) bool {
|
||||
return strings.ContainsAny(pattern, "?*%")
|
||||
}
|
||||
|
||||
// MatchTablePattern returns whether a table name matches a table name pattern
|
||||
func MatchTablePattern(pattern string, table string) (bool, error) {
|
||||
re, err := compilePattern(pattern)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return re.MatchString(table), nil
|
||||
}
|
||||
|
||||
// GetMatchingTables returns all tables that match a pattern
|
||||
func GetMatchingTables(ctx *sql.Context, root RootValue, schemaName string, pattern string) (results []string, err error) {
|
||||
// If the pattern doesn't contain any special characters, look up that name.
|
||||
if !patternContainsSpecialCharacters(pattern) {
|
||||
_, exists, err := root.GetTable(ctx, TableName{Name: pattern, Schema: schemaName})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exists {
|
||||
return []string{pattern}, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
tables, err := root.GetTableNames(ctx, schemaName, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Otherwise, iterate over each table on the branch to see if they match.
|
||||
for _, tbl := range tables {
|
||||
matches, err := MatchTablePattern(pattern, tbl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if matches {
|
||||
results = append(results, tbl)
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// getMoreSpecificPatterns takes a dolt_ignore pattern and generates a Regexp that matches against all patterns
|
||||
// that are "more specific" than it. (a pattern A is more specific than a pattern B if all names that match A also
|
||||
// match pattern B, but not vice versa.)
|
||||
func getMoreSpecificPatterns(lessSpecific string) (*regexp.Regexp, error) {
|
||||
pattern := "^" + regexp.QuoteMeta(lessSpecific) + "$"
|
||||
// A ? can expand to any character except for a * or %, since that also has special meaning in patterns.
|
||||
|
||||
pattern = strings.Replace(pattern, "\\?", "[^\\*%]", -1)
|
||||
pattern = strings.Replace(pattern, "\\*", ".*", -1)
|
||||
pattern = strings.Replace(pattern, "%", ".*", -1)
|
||||
return regexp.Compile(pattern)
|
||||
}
|
||||
|
||||
// normalizePattern generates an equivalent pattern, such that all equivalent patterns have the same normalized pattern.
|
||||
// It accomplishes this by replacing all * with %, and removing multiple adjacent %.
|
||||
// This will get a lot harder to implement once we support escaped characters in patterns.
|
||||
func normalizePattern(pattern string) string {
|
||||
pattern = strings.Replace(pattern, "*", "%", -1)
|
||||
for {
|
||||
newPattern := strings.Replace(pattern, "%%", "%", -1)
|
||||
if newPattern == pattern {
|
||||
break
|
||||
}
|
||||
pattern = newPattern
|
||||
}
|
||||
return pattern
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import (
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/branch_control"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/diff"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/env"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/env/actions/commitwalk"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/rebase"
|
||||
@@ -59,6 +60,14 @@ var ErrInvalidTableName = errors.NewKind("Invalid table name %s.")
|
||||
var ErrReservedTableName = errors.NewKind("Invalid table name %s. Table names beginning with `dolt_` are reserved for internal use")
|
||||
var ErrReservedDiffTableName = errors.NewKind("Invalid table name %s. Table names beginning with `__DATABASE__` are reserved for internal use")
|
||||
var ErrSystemTableAlter = errors.NewKind("Cannot alter table %s: system tables cannot be dropped or altered")
|
||||
var ErrInvalidNonlocalTableOptions = errors.NewKind("Invalid nonlocal table options %s: only valid value is 'immediate'.")
|
||||
|
||||
type readNonlocalTablesFlag bool
|
||||
|
||||
const (
|
||||
doReadNonlocalTables readNonlocalTablesFlag = true
|
||||
dontReadNonlocalTables readNonlocalTablesFlag = false
|
||||
)
|
||||
|
||||
// Database implements sql.Database for a dolt DB.
|
||||
type Database struct {
|
||||
@@ -268,6 +277,10 @@ func (db Database) GetGlobalState() globalstate.GlobalState {
|
||||
// GetTableInsensitive is used when resolving tables in queries. It returns a best-effort case-insensitive match for
|
||||
// the table name given.
|
||||
func (db Database) GetTableInsensitive(ctx *sql.Context, tblName string) (sql.Table, bool, error) {
|
||||
return db.getTableInsensitive(ctx, tblName, doReadNonlocalTables)
|
||||
}
|
||||
|
||||
func (db Database) getTableInsensitive(ctx *sql.Context, tblName string, readNonlocalTables readNonlocalTablesFlag) (sql.Table, bool, error) {
|
||||
// We start by first checking whether the input table is a temporary table. Temporary tables with name `x` take
|
||||
// priority over persisted tables of name `x`.
|
||||
ds := dsess.DSessFromSess(ctx.Session)
|
||||
@@ -280,11 +293,15 @@ func (db Database) GetTableInsensitive(ctx *sql.Context, tblName string) (sql.Ta
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return db.getTableInsensitive(ctx, nil, ds, root, tblName, "")
|
||||
return db.getTableInsensitiveWithRoot(ctx, nil, ds, root, tblName, "", readNonlocalTables)
|
||||
}
|
||||
|
||||
func (db Database) GetTableInsensitiveAsOf(ctx *sql.Context, tableName string, asOf interface{}) (sql.Table, bool, error) {
|
||||
return db.getTableInsensitiveAsOf(ctx, tableName, asOf, true)
|
||||
}
|
||||
|
||||
// GetTableInsensitiveAsOf implements sql.VersionedDatabase
|
||||
func (db Database) GetTableInsensitiveAsOf(ctx *sql.Context, tableName string, asOf interface{}) (sql.Table, bool, error) {
|
||||
func (db Database) getTableInsensitiveAsOf(ctx *sql.Context, tableName string, asOf interface{}, readNonlocalTables readNonlocalTablesFlag) (sql.Table, bool, error) {
|
||||
if asOf == nil {
|
||||
return db.GetTableInsensitive(ctx, tableName)
|
||||
}
|
||||
@@ -297,7 +314,7 @@ func (db Database) GetTableInsensitiveAsOf(ctx *sql.Context, tableName string, a
|
||||
|
||||
sess := dsess.DSessFromSess(ctx.Session)
|
||||
|
||||
table, ok, err := db.getTableInsensitive(ctx, head, sess, root, tableName, asOf)
|
||||
table, ok, err := db.getTableInsensitiveWithRoot(ctx, head, sess, root, tableName, asOf, readNonlocalTables)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -307,7 +324,7 @@ func (db Database) GetTableInsensitiveAsOf(ctx *sql.Context, tableName string, a
|
||||
|
||||
if doltdb.IsReadOnlySystemTable(doltdb.TableName{Name: tableName, Schema: db.schemaName}) {
|
||||
// currently, system tables do not need to be "locked to root"
|
||||
// see comment below in getTableInsensitive
|
||||
// see comment below in getTableInsensitiveWithRoot
|
||||
return table, ok, nil
|
||||
}
|
||||
|
||||
@@ -330,9 +347,19 @@ func (db Database) GetTableInsensitiveAsOf(ctx *sql.Context, tableName string, a
|
||||
}
|
||||
}
|
||||
|
||||
func (db Database) getTableInsensitive(ctx *sql.Context, head *doltdb.Commit, ds *dsess.DoltSession, root doltdb.RootValue, tblName string, asOf interface{}) (sql.Table, bool, error) {
|
||||
func (db Database) getTableInsensitiveWithRoot(ctx *sql.Context, head *doltdb.Commit, ds *dsess.DoltSession, root doltdb.RootValue, tblName string, asOf interface{}, readNonlocalTables readNonlocalTablesFlag) (sql.Table, bool, error) {
|
||||
lwrName := strings.ToLower(tblName)
|
||||
|
||||
if readNonlocalTables {
|
||||
nonlocalTable, exists, err := db.getNonlocalTable(ctx, root, lwrName)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if exists {
|
||||
return nonlocalTable, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: these tables that cache a root value at construction time should not, they need to get it from the session
|
||||
// at runtime
|
||||
switch {
|
||||
@@ -476,7 +503,7 @@ func (db Database) getTableInsensitive(ctx *sql.Context, head *doltdb.Commit, ds
|
||||
}
|
||||
}
|
||||
|
||||
srcTable, ok, err := db.getTableInsensitive(ctx, head, ds, root, tname.Name, asOf)
|
||||
srcTable, ok, err := db.getTableInsensitiveWithRoot(ctx, head, ds, root, tname.Name, asOf, readNonlocalTables)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
} else if !ok {
|
||||
@@ -868,6 +895,17 @@ func (db Database) getTableInsensitive(ctx *sql.Context, head *doltdb.Commit, ds
|
||||
versionableTable := backingTable.(dtables.VersionableTable)
|
||||
dt, found = dtables.NewQueryCatalogTable(ctx, versionableTable), true
|
||||
}
|
||||
case doltdb.NonlocalTableName, doltdb.GetNonlocalTablesTableName():
|
||||
backingTable, _, err := db.getTable(ctx, root, doltdb.NonlocalTableName)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if backingTable == nil {
|
||||
dt, found = dtables.NewEmptyNonlocalTablesTable(ctx), true
|
||||
} else {
|
||||
versionableTable := backingTable.(dtables.VersionableTable)
|
||||
dt, found = dtables.NewNonlocallTablesTable(ctx, versionableTable), true
|
||||
}
|
||||
case doltdb.GetTestsTableName():
|
||||
backingTable, _, err := db.getTable(ctx, root, doltdb.GetTestsTableName())
|
||||
if err != nil {
|
||||
@@ -905,6 +943,89 @@ func (db Database) getTableInsensitive(ctx *sql.Context, head *doltdb.Commit, ds
|
||||
return resolveOverriddenNonexistentTable(ctx, tblName, db)
|
||||
}
|
||||
|
||||
// getNonlocalTable checks whether the table name maps onto a table in another root via the dolt_nonlocal_tables system table
|
||||
func (db Database) getNonlocalTable(ctx *sql.Context, root doltdb.RootValue, lwrName string) (sql.Table, bool, error) {
|
||||
_, nonlocalsTable, nonlocalsTableExists, err := db.resolveUserTable(ctx, root, doltdb.GetNonlocalTablesTableName())
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if !nonlocalsTableExists {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
index, err := nonlocalsTable.GetRowData(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
nonlocalTablesSchema, err := nonlocalsTable.GetSchema(ctx)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
m := durable.MapFromIndex(index)
|
||||
keyDesc, valueDesc := nonlocalTablesSchema.GetMapDescriptors(m.NodeStore())
|
||||
|
||||
nonlocalTablesMap, err := m.IterAll(ctx)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
var nonlocalTableEntry doltdb.NonlocalTableEntry
|
||||
// check if there's an entry for this table. If so, resolve that reference.
|
||||
for {
|
||||
keyTuple, valueTuple, err := nonlocalTablesMap.Next(ctx)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
globalsEntryTableName, err := doltdb.GetNonlocalTablesNameColumn(ctx, keyDesc, keyTuple)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
globalsEntryTableName = strings.ToLower(globalsEntryTableName)
|
||||
matchesPattern, err := doltdb.MatchTablePattern(globalsEntryTableName, lwrName)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !matchesPattern {
|
||||
continue
|
||||
}
|
||||
|
||||
nonlocalTableEntry = doltdb.GetNonlocalTablesRef(ctx, valueDesc, valueTuple)
|
||||
if nonlocalTableEntry.NewTableName == "" {
|
||||
nonlocalTableEntry.NewTableName = lwrName
|
||||
}
|
||||
|
||||
if nonlocalTableEntry.Ref == "" {
|
||||
nonlocalTableEntry.Ref = db.revision
|
||||
}
|
||||
|
||||
if nonlocalTableEntry.Options != "immediate" {
|
||||
return nil, false, ErrInvalidNonlocalTableOptions.New(nonlocalTableEntry.Options)
|
||||
}
|
||||
|
||||
// If the ref is a branch, we get the working set, not the head.
|
||||
_, exists, err := isBranch(ctx, db, nonlocalTableEntry.Ref)
|
||||
if exists {
|
||||
referencedBranch, err := RevisionDbForBranch(ctx, db, nonlocalTableEntry.Ref, db.requestedName)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return referencedBranch.(Database).getTableInsensitive(ctx, nonlocalTableEntry.NewTableName, dontReadNonlocalTables)
|
||||
} else {
|
||||
// If we couldn't resolve it as a database revision, treat it as a noms ref.
|
||||
// This lets us resolve branch heads like 'heads/$branchName' or remotes refs like '$remote/$branchName'
|
||||
return db.getTableInsensitiveAsOf(ctx, nonlocalTableEntry.NewTableName, nonlocalTableEntry.Ref, false)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// getRootsForBranch uses the specified |ddb| to look up a branch named |branch|, and return the
|
||||
// Roots for it. If there is no branch named |branch|, then an error is returned.
|
||||
func getRootsForBranch(ctx *sql.Context, ddb *doltdb.DoltDB, branch string) (doltdb.Roots, error) {
|
||||
|
||||
@@ -15,17 +15,12 @@
|
||||
package dtables
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
sqlTypes "github.com/dolthub/go-mysql-server/sql/types"
|
||||
"github.com/dolthub/vitess/go/sqltypes"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/index"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/resolve"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
)
|
||||
|
||||
var _ sql.Table = (*DocsTable)(nil)
|
||||
@@ -37,25 +32,28 @@ var _ sql.IndexAddressableTable = (*DocsTable)(nil)
|
||||
|
||||
// DocsTable is the system table that stores Dolt docs, such as LICENSE and README.
|
||||
type DocsTable struct {
|
||||
backingTable VersionableTable
|
||||
UserSpaceSystemTable
|
||||
}
|
||||
|
||||
// NewDocsTable creates a DocsTable
|
||||
func NewDocsTable(_ *sql.Context, backingTable VersionableTable) sql.Table {
|
||||
return &DocsTable{backingTable: backingTable}
|
||||
return &DocsTable{
|
||||
UserSpaceSystemTable: UserSpaceSystemTable{
|
||||
backingTable: backingTable,
|
||||
tableName: getDoltDocsTableName(),
|
||||
schema: GetDocsSchema(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewEmptyDocsTable creates a DocsTable
|
||||
func NewEmptyDocsTable(_ *sql.Context) sql.Table {
|
||||
return &DocsTable{}
|
||||
}
|
||||
|
||||
func (dt *DocsTable) Name() string {
|
||||
return doltdb.GetDocTableName()
|
||||
}
|
||||
|
||||
func (dt *DocsTable) String() string {
|
||||
return doltdb.GetDocTableName()
|
||||
return &DocsTable{
|
||||
UserSpaceSystemTable: UserSpaceSystemTable{
|
||||
tableName: getDoltDocsTableName(),
|
||||
schema: GetDocsSchema(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const defaultStringsLen = 16383 / 16
|
||||
@@ -71,39 +69,27 @@ func getDoltDocsSchema() sql.Schema {
|
||||
}
|
||||
}
|
||||
|
||||
// Schema is a sql.Table interface function that gets the sql.Schema of the dolt_docs system table.
|
||||
func (dt *DocsTable) Schema() sql.Schema {
|
||||
return GetDocsSchema()
|
||||
}
|
||||
|
||||
func (dt *DocsTable) Collation() sql.CollationID {
|
||||
return sql.Collation_Default
|
||||
}
|
||||
|
||||
// Partitions is a sql.Table interface function that returns a partition of the data.
|
||||
func (dt *DocsTable) Partitions(context *sql.Context) (sql.PartitionIter, error) {
|
||||
func (dt *DocsTable) LockedToRoot(ctx *sql.Context, root doltdb.RootValue) (sql.IndexAddressableTable, error) {
|
||||
if dt.backingTable == nil {
|
||||
// no backing table; return an empty iter.
|
||||
return index.SinglePartitionIterFromNomsMap(nil), nil
|
||||
return dt, nil
|
||||
}
|
||||
return dt.backingTable.Partitions(context)
|
||||
backingTableLockedToRoot, err := dt.backingTable.LockedToRoot(ctx, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &docsTableAsOf{backingTableLockedToRoot}, nil
|
||||
}
|
||||
|
||||
func (dt *DocsTable) PartitionRows(ctx *sql.Context, partition sql.Partition) (sql.RowIter, error) {
|
||||
var rowIter sql.RowIter
|
||||
if dt.backingTable == nil {
|
||||
// no backing table; empty iter.
|
||||
rowIter = sql.RowsToRowIter()
|
||||
} else {
|
||||
var err error
|
||||
rowIter, err = dt.backingTable.PartitionRows(ctx, partition)
|
||||
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
underlyingIter, err := dt.UserSpaceSystemTable.PartitionRows(ctx, partition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return makeDoltDocRows(ctx, underlyingIter)
|
||||
}
|
||||
|
||||
rows, err := sql.RowIterToRows(ctx, rowIter)
|
||||
func makeDoltDocRows(ctx *sql.Context, underlyingIter sql.RowIter) (sql.RowIter, error) {
|
||||
rows, err := sql.RowIterToRows(ctx, underlyingIter)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -129,99 +115,7 @@ func (dt *DocsTable) PartitionRows(ctx *sql.Context, partition sql.Partition) (s
|
||||
})
|
||||
}
|
||||
|
||||
rowIter = sql.RowsToRowIter(rows...)
|
||||
|
||||
return rowIter, nil
|
||||
}
|
||||
|
||||
// Replacer returns a RowReplacer for this table. The RowReplacer will have Insert and optionally Delete called once
|
||||
// for each row, followed by a call to Close() when all rows have been processed.
|
||||
func (dt *DocsTable) Replacer(ctx *sql.Context) sql.RowReplacer {
|
||||
return newDocsWriter(dt)
|
||||
}
|
||||
|
||||
// Updater returns a RowUpdater for this table. The RowUpdater will have Update called once for each row to be
|
||||
// updated, followed by a call to Close() when all rows have been processed.
|
||||
func (dt *DocsTable) Updater(ctx *sql.Context) sql.RowUpdater {
|
||||
return newDocsWriter(dt)
|
||||
}
|
||||
|
||||
// Inserter returns an Inserter for this table. The Inserter will get one call to Insert() for each row to be
|
||||
// inserted, and will end with a call to Close() to finalize the insert operation.
|
||||
func (dt *DocsTable) Inserter(*sql.Context) sql.RowInserter {
|
||||
return newDocsWriter(dt)
|
||||
}
|
||||
|
||||
// Deleter returns a RowDeleter for this table. The RowDeleter will get one call to Delete for each row to be deleted,
|
||||
// and will end with a call to Close() to finalize the delete operation.
|
||||
func (dt *DocsTable) Deleter(*sql.Context) sql.RowDeleter {
|
||||
return newDocsWriter(dt)
|
||||
}
|
||||
|
||||
func (dt *DocsTable) LockedToRoot(ctx *sql.Context, root doltdb.RootValue) (sql.IndexAddressableTable, error) {
|
||||
if dt.backingTable == nil {
|
||||
return dt, nil
|
||||
}
|
||||
return dt.backingTable.LockedToRoot(ctx, root)
|
||||
}
|
||||
|
||||
// IndexedAccess implements IndexAddressableTable, but DocsTables has no indexes.
|
||||
// Thus, this should never be called.
|
||||
func (dt *DocsTable) IndexedAccess(ctx *sql.Context, lookup sql.IndexLookup) sql.IndexedTable {
|
||||
panic("Unreachable")
|
||||
}
|
||||
|
||||
// GetIndexes implements IndexAddressableTable, but DocsTables has no indexes.
|
||||
func (dt *DocsTable) GetIndexes(ctx *sql.Context) ([]sql.Index, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (dt *DocsTable) PreciseMatch() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var _ sql.RowReplacer = (*docsWriter)(nil)
|
||||
var _ sql.RowUpdater = (*docsWriter)(nil)
|
||||
var _ sql.RowInserter = (*docsWriter)(nil)
|
||||
var _ sql.RowDeleter = (*docsWriter)(nil)
|
||||
|
||||
type docsWriter struct {
|
||||
it *DocsTable
|
||||
errDuringStatementBegin error
|
||||
prevHash *hash.Hash
|
||||
tableWriter dsess.TableWriter
|
||||
}
|
||||
|
||||
func newDocsWriter(it *DocsTable) *docsWriter {
|
||||
return &docsWriter{it, nil, nil, nil}
|
||||
}
|
||||
|
||||
// Insert inserts the row given, returning an error if it cannot. Insert will be called once for each row to process
|
||||
// for the insert operation, which may involve many rows. After all rows in an operation have been processed, Close
|
||||
// is called.
|
||||
func (iw *docsWriter) Insert(ctx *sql.Context, r sql.Row) error {
|
||||
if err := iw.errDuringStatementBegin; err != nil {
|
||||
return err
|
||||
}
|
||||
return iw.tableWriter.Insert(ctx, r)
|
||||
}
|
||||
|
||||
// Update the given row. Provides both the old and new rows.
|
||||
func (iw *docsWriter) Update(ctx *sql.Context, old sql.Row, new sql.Row) error {
|
||||
if err := iw.errDuringStatementBegin; err != nil {
|
||||
return err
|
||||
}
|
||||
return iw.tableWriter.Update(ctx, old, new)
|
||||
}
|
||||
|
||||
// Delete deletes the given row. Returns ErrDeleteRowNotFound if the row was not found. Delete will be called once for
|
||||
// each row to process for the delete operation, which may involve many rows. After all rows have been processed,
|
||||
// Close is called.
|
||||
func (iw *docsWriter) Delete(ctx *sql.Context, r sql.Row) error {
|
||||
if err := iw.errDuringStatementBegin; err != nil {
|
||||
return err
|
||||
}
|
||||
return iw.tableWriter.Delete(ctx, r)
|
||||
return sql.RowsToRowIter(rows...), nil
|
||||
}
|
||||
|
||||
func getDoltDocsTableName() doltdb.TableName {
|
||||
@@ -231,40 +125,14 @@ func getDoltDocsTableName() doltdb.TableName {
|
||||
return doltdb.TableName{Name: doltdb.GetDocTableName()}
|
||||
}
|
||||
|
||||
// StatementBegin is called before the first operation of a statement. Integrators should mark the state of the data
|
||||
// in some way that it may be returned to in the case of an error.
|
||||
func (iw *docsWriter) StatementBegin(ctx *sql.Context) {
|
||||
name := getDoltDocsTableName()
|
||||
prevHash, tableWriter, err := createWriteableSystemTable(ctx, name, iw.it.Schema())
|
||||
type docsTableAsOf struct {
|
||||
sql.IndexAddressableTable
|
||||
}
|
||||
|
||||
func (dt *docsTableAsOf) PartitionRows(ctx *sql.Context, partition sql.Partition) (sql.RowIter, error) {
|
||||
underlyingIter, err := dt.IndexAddressableTable.PartitionRows(ctx, partition)
|
||||
if err != nil {
|
||||
iw.errDuringStatementBegin = err
|
||||
return nil, err
|
||||
}
|
||||
iw.prevHash = prevHash
|
||||
iw.tableWriter = tableWriter
|
||||
}
|
||||
|
||||
// DiscardChanges is called if a statement encounters an error, and all current changes since the statement beginning
|
||||
// should be discarded.
|
||||
func (iw *docsWriter) DiscardChanges(ctx *sql.Context, errorEncountered error) error {
|
||||
if iw.tableWriter != nil {
|
||||
return iw.tableWriter.DiscardChanges(ctx, errorEncountered)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StatementComplete is called after the last operation of the statement, indicating that it has successfully completed.
|
||||
// The mark set in StatementBegin may be removed, and a new one should be created on the next StatementBegin.
|
||||
func (iw *docsWriter) StatementComplete(ctx *sql.Context) error {
|
||||
if iw.tableWriter != nil {
|
||||
return iw.tableWriter.StatementComplete(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close finalizes the delete operation, persisting the result.
|
||||
func (iw docsWriter) Close(ctx *sql.Context) error {
|
||||
if iw.tableWriter != nil {
|
||||
return iw.tableWriter.Close(ctx)
|
||||
}
|
||||
return nil
|
||||
return makeDoltDocRows(ctx, underlyingIter)
|
||||
}
|
||||
|
||||
@@ -15,39 +15,12 @@
|
||||
package dtables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
sqlTypes "github.com/dolthub/go-mysql-server/sql/types"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/index"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
)
|
||||
|
||||
var _ sql.Table = (*IgnoreTable)(nil)
|
||||
var _ sql.UpdatableTable = (*IgnoreTable)(nil)
|
||||
var _ sql.DeletableTable = (*IgnoreTable)(nil)
|
||||
var _ sql.InsertableTable = (*IgnoreTable)(nil)
|
||||
var _ sql.ReplaceableTable = (*IgnoreTable)(nil)
|
||||
var _ sql.IndexAddressableTable = (*IgnoreTable)(nil)
|
||||
|
||||
// IgnoreTable is the system table that stores patterns for table names that should not be committed.
|
||||
type IgnoreTable struct {
|
||||
backingTable VersionableTable
|
||||
schemaName string
|
||||
}
|
||||
|
||||
func (i *IgnoreTable) Name() string {
|
||||
return doltdb.IgnoreTableName
|
||||
}
|
||||
|
||||
func (i *IgnoreTable) String() string {
|
||||
return doltdb.IgnoreTableName
|
||||
}
|
||||
|
||||
func doltIgnoreSchema() sql.Schema {
|
||||
return []*sql.Column{
|
||||
{Name: "pattern", Type: sqlTypes.Text, Source: doltdb.IgnoreTableName, PrimaryKey: true},
|
||||
@@ -59,239 +32,25 @@ func doltIgnoreSchema() sql.Schema {
|
||||
// by Doltgres to update the dolt_ignore schema using Doltgres types.
|
||||
var GetDoltIgnoreSchema = doltIgnoreSchema
|
||||
|
||||
// Schema is a sql.Table interface function that gets the sql.Schema of the dolt_ignore system table.
|
||||
func (i *IgnoreTable) Schema() sql.Schema {
|
||||
return GetDoltIgnoreSchema()
|
||||
}
|
||||
|
||||
func (i *IgnoreTable) Collation() sql.CollationID {
|
||||
return sql.Collation_Default
|
||||
}
|
||||
|
||||
// Partitions is a sql.Table interface function that returns a partition of the data.
|
||||
func (i *IgnoreTable) Partitions(context *sql.Context) (sql.PartitionIter, error) {
|
||||
if i.backingTable == nil {
|
||||
// no backing table; return an empty iter.
|
||||
return index.SinglePartitionIterFromNomsMap(nil), nil
|
||||
}
|
||||
return i.backingTable.Partitions(context)
|
||||
}
|
||||
|
||||
func (i *IgnoreTable) PartitionRows(context *sql.Context, partition sql.Partition) (sql.RowIter, error) {
|
||||
if i.backingTable == nil {
|
||||
// no backing table; return an empty iter.
|
||||
return sql.RowsToRowIter(), nil
|
||||
}
|
||||
|
||||
return i.backingTable.PartitionRows(context, partition)
|
||||
}
|
||||
|
||||
// NewIgnoreTable creates an IgnoreTable
|
||||
// NewIgnoreTable creates a dolt_ignore table
|
||||
func NewIgnoreTable(_ *sql.Context, backingTable VersionableTable, schemaName string) sql.Table {
|
||||
return &IgnoreTable{backingTable: backingTable, schemaName: schemaName}
|
||||
return &UserSpaceSystemTable{
|
||||
backingTable: backingTable,
|
||||
tableName: doltdb.TableName{
|
||||
Name: doltdb.IgnoreTableName,
|
||||
Schema: schemaName,
|
||||
},
|
||||
schema: GetDoltIgnoreSchema(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewEmptyIgnoreTable creates an IgnoreTable
|
||||
// NewEmptyIgnoreTable creates an empty dolt_ignore table
|
||||
func NewEmptyIgnoreTable(_ *sql.Context, schemaName string) sql.Table {
|
||||
return &IgnoreTable{schemaName: schemaName}
|
||||
}
|
||||
|
||||
// Replacer returns a RowReplacer for this table. The RowReplacer will have Insert and optionally Delete called once
|
||||
// for each row, followed by a call to Close() when all rows have been processed.
|
||||
func (it *IgnoreTable) Replacer(ctx *sql.Context) sql.RowReplacer {
|
||||
return newIgnoreWriter(it)
|
||||
}
|
||||
|
||||
// Updater returns a RowUpdater for this table. The RowUpdater will have Update called once for each row to be
|
||||
// updated, followed by a call to Close() when all rows have been processed.
|
||||
func (it *IgnoreTable) Updater(ctx *sql.Context) sql.RowUpdater {
|
||||
return newIgnoreWriter(it)
|
||||
}
|
||||
|
||||
// Inserter returns an Inserter for this table. The Inserter will get one call to Insert() for each row to be
|
||||
// inserted, and will end with a call to Close() to finalize the insert operation.
|
||||
func (it *IgnoreTable) Inserter(*sql.Context) sql.RowInserter {
|
||||
return newIgnoreWriter(it)
|
||||
}
|
||||
|
||||
// Deleter returns a RowDeleter for this table. The RowDeleter will get one call to Delete for each row to be deleted,
|
||||
// and will end with a call to Close() to finalize the delete operation.
|
||||
func (it *IgnoreTable) Deleter(*sql.Context) sql.RowDeleter {
|
||||
return newIgnoreWriter(it)
|
||||
}
|
||||
|
||||
func (it *IgnoreTable) LockedToRoot(ctx *sql.Context, root doltdb.RootValue) (sql.IndexAddressableTable, error) {
|
||||
if it.backingTable == nil {
|
||||
return it, nil
|
||||
return &UserSpaceSystemTable{
|
||||
tableName: doltdb.TableName{
|
||||
Name: doltdb.IgnoreTableName,
|
||||
Schema: schemaName,
|
||||
},
|
||||
schema: GetDoltIgnoreSchema(),
|
||||
}
|
||||
return it.backingTable.LockedToRoot(ctx, root)
|
||||
}
|
||||
|
||||
// IndexedAccess implements IndexAddressableTable, but IgnoreTables has no indexes.
|
||||
// Thus, this should never be called.
|
||||
func (it *IgnoreTable) IndexedAccess(ctx *sql.Context, lookup sql.IndexLookup) sql.IndexedTable {
|
||||
panic("Unreachable")
|
||||
}
|
||||
|
||||
// GetIndexes implements IndexAddressableTable, but IgnoreTables has no indexes.
|
||||
func (it *IgnoreTable) GetIndexes(ctx *sql.Context) ([]sql.Index, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (i *IgnoreTable) PreciseMatch() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var _ sql.RowReplacer = (*ignoreWriter)(nil)
|
||||
var _ sql.RowUpdater = (*ignoreWriter)(nil)
|
||||
var _ sql.RowInserter = (*ignoreWriter)(nil)
|
||||
var _ sql.RowDeleter = (*ignoreWriter)(nil)
|
||||
|
||||
type ignoreWriter struct {
|
||||
it *IgnoreTable
|
||||
errDuringStatementBegin error
|
||||
prevHash *hash.Hash
|
||||
tableWriter dsess.TableWriter
|
||||
}
|
||||
|
||||
func newIgnoreWriter(it *IgnoreTable) *ignoreWriter {
|
||||
return &ignoreWriter{it, nil, nil, nil}
|
||||
}
|
||||
|
||||
// Insert inserts the row given, returning an error if it cannot. Insert will be called once for each row to process
|
||||
// for the insert operation, which may involve many rows. After all rows in an operation have been processed, Close
|
||||
// is called.
|
||||
func (iw *ignoreWriter) Insert(ctx *sql.Context, r sql.Row) error {
|
||||
if err := iw.errDuringStatementBegin; err != nil {
|
||||
return err
|
||||
}
|
||||
return iw.tableWriter.Insert(ctx, r)
|
||||
}
|
||||
|
||||
// Update the given row. Provides both the old and new rows.
|
||||
func (iw *ignoreWriter) Update(ctx *sql.Context, old sql.Row, new sql.Row) error {
|
||||
if err := iw.errDuringStatementBegin; err != nil {
|
||||
return err
|
||||
}
|
||||
return iw.tableWriter.Update(ctx, old, new)
|
||||
}
|
||||
|
||||
// Delete deletes the given row. Returns ErrDeleteRowNotFound if the row was not found. Delete will be called once for
|
||||
// each row to process for the delete operation, which may involve many rows. After all rows have been processed,
|
||||
// Close is called.
|
||||
func (iw *ignoreWriter) Delete(ctx *sql.Context, r sql.Row) error {
|
||||
if err := iw.errDuringStatementBegin; err != nil {
|
||||
return err
|
||||
}
|
||||
return iw.tableWriter.Delete(ctx, r)
|
||||
}
|
||||
|
||||
// StatementBegin is called before the first operation of a statement. Integrators should mark the state of the data
|
||||
// in some way that it may be returned to in the case of an error.
|
||||
func (iw *ignoreWriter) StatementBegin(ctx *sql.Context) {
|
||||
name := doltdb.TableName{Name: doltdb.IgnoreTableName, Schema: iw.it.schemaName}
|
||||
prevHash, tableWriter, err := createWriteableSystemTable(ctx, name, iw.it.Schema())
|
||||
if err != nil {
|
||||
iw.errDuringStatementBegin = err
|
||||
return
|
||||
}
|
||||
iw.prevHash = prevHash
|
||||
iw.tableWriter = tableWriter
|
||||
}
|
||||
|
||||
// DiscardChanges is called if a statement encounters an error, and all current changes since the statement beginning
|
||||
// should be discarded.
|
||||
func (iw *ignoreWriter) DiscardChanges(ctx *sql.Context, errorEncountered error) error {
|
||||
if iw.tableWriter != nil {
|
||||
return iw.tableWriter.DiscardChanges(ctx, errorEncountered)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StatementComplete is called after the last operation of the statement, indicating that it has successfully completed.
|
||||
// The mark set in StatementBegin may be removed, and a new one should be created on the next StatementBegin.
|
||||
func (iw *ignoreWriter) StatementComplete(ctx *sql.Context) error {
|
||||
if iw.tableWriter != nil {
|
||||
return iw.tableWriter.StatementComplete(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close finalizes the delete operation, persisting the result.
|
||||
func (iw ignoreWriter) Close(ctx *sql.Context) error {
|
||||
if iw.tableWriter != nil {
|
||||
return iw.tableWriter.Close(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateWriteableSystemTable is a helper function that creates a writeable system table (dolt_ignore, dolt_docs...) if it does not exist
|
||||
// Then returns the hash of the previous working root, and a TableWriter.
|
||||
func createWriteableSystemTable(ctx *sql.Context, tblName doltdb.TableName, tblSchema sql.Schema) (*hash.Hash, dsess.TableWriter, error) {
|
||||
dbName := ctx.GetCurrentDatabase()
|
||||
dSess := dsess.DSessFromSess(ctx.Session)
|
||||
|
||||
roots, _ := dSess.GetRoots(ctx, dbName)
|
||||
dbState, ok, err := dSess.LookupDbState(ctx, dbName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
}
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("no root value found in session")
|
||||
}
|
||||
|
||||
prevHash, err := roots.Working.HashOf()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
found, err := roots.Working.HasTable(ctx, tblName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if !found {
|
||||
sch := sql.NewPrimaryKeySchema(tblSchema)
|
||||
doltSch, err := sqlutil.ToDoltSchema(ctx, roots.Working, tblName, sch, roots.Head, sql.Collation_Default)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// underlying table doesn't exist. Record this, then create the table.
|
||||
newRootValue, err := doltdb.CreateEmptyTable(ctx, roots.Working, tblName, doltSch)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if dbState.WorkingSet() == nil {
|
||||
return nil, nil, doltdb.ErrOperationNotSupportedInDetachedHead
|
||||
}
|
||||
|
||||
// We use WriteSession.SetWorkingSet instead of DoltSession.SetWorkingRoot because we want to avoid modifying the root
|
||||
// until the end of the transaction, but we still want the WriteSession to be able to find the newly
|
||||
// created table.
|
||||
if ws := dbState.WriteSession(); ws != nil {
|
||||
err = ws.SetWorkingSet(ctx, dbState.WorkingSet().WithWorkingRoot(newRootValue))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("could not create dolt_ignore table, database does not allow writing")
|
||||
}
|
||||
}
|
||||
|
||||
var tableWriter dsess.TableWriter
|
||||
if ws := dbState.WriteSession(); ws != nil {
|
||||
tableWriter, err = ws.GetTableWriter(ctx, tblName, dbName, dSess.SetWorkingRoot, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
tableWriter.StatementBegin(ctx)
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("could not create dolt_ignore table, database does not allow writing")
|
||||
}
|
||||
|
||||
return &prevHash, tableWriter, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2025 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package dtables
|
||||
|
||||
import (
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
sqlTypes "github.com/dolthub/go-mysql-server/sql/types"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/resolve"
|
||||
)
|
||||
|
||||
func doltNonlocalTablesSchema() sql.Schema {
|
||||
return []*sql.Column{
|
||||
{Name: doltdb.NonlocalTableTableNameCol, Type: sqlTypes.VarChar, Source: doltdb.GetNonlocalTablesTableName(), PrimaryKey: true},
|
||||
{Name: doltdb.NonlocalTableRefCol, Type: sqlTypes.VarChar, Source: doltdb.GetNonlocalTablesTableName(), Nullable: true},
|
||||
{Name: doltdb.NonlocalTablesRefTableCol, Type: sqlTypes.VarChar, Source: doltdb.GetNonlocalTablesTableName(), Nullable: true},
|
||||
{Name: doltdb.NonlocalTablesOptionsCol, Type: sqlTypes.VarChar, Source: doltdb.GetNonlocalTablesTableName(), Nullable: true},
|
||||
}
|
||||
}
|
||||
|
||||
var GetDoltNonlocalTablesSchema = doltNonlocalTablesSchema
|
||||
|
||||
// NewNonlocallTablesTable creates a new dolt_table_aliases table
|
||||
func NewNonlocallTablesTable(_ *sql.Context, backingTable VersionableTable) sql.Table {
|
||||
return &UserSpaceSystemTable{
|
||||
backingTable: backingTable,
|
||||
tableName: getDoltNonlocalTablesName(),
|
||||
schema: GetDoltNonlocalTablesSchema(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewEmptyNonlocalTablesTable creates an empty dolt_table_aliases table
|
||||
func NewEmptyNonlocalTablesTable(_ *sql.Context) sql.Table {
|
||||
return &UserSpaceSystemTable{
|
||||
tableName: getDoltNonlocalTablesName(),
|
||||
schema: GetDoltNonlocalTablesSchema(),
|
||||
}
|
||||
}
|
||||
|
||||
func getDoltNonlocalTablesName() doltdb.TableName {
|
||||
if resolve.UseSearchPath {
|
||||
return doltdb.TableName{Schema: doltdb.DoltNamespace, Name: doltdb.GetNonlocalTablesTableName()}
|
||||
}
|
||||
return doltdb.TableName{Name: doltdb.GetNonlocalTablesTableName()}
|
||||
}
|
||||
@@ -19,33 +19,9 @@ import (
|
||||
sqlTypes "github.com/dolthub/go-mysql-server/sql/types"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/index"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/resolve"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
)
|
||||
|
||||
var _ sql.Table = (*QueryCatalogTable)(nil)
|
||||
var _ sql.UpdatableTable = (*QueryCatalogTable)(nil)
|
||||
var _ sql.DeletableTable = (*QueryCatalogTable)(nil)
|
||||
var _ sql.InsertableTable = (*QueryCatalogTable)(nil)
|
||||
var _ sql.ReplaceableTable = (*QueryCatalogTable)(nil)
|
||||
var _ VersionableTable = (*QueryCatalogTable)(nil)
|
||||
var _ sql.IndexAddressableTable = (*QueryCatalogTable)(nil)
|
||||
|
||||
// QueryCatalogTable is the system table that stores saved queries.
|
||||
type QueryCatalogTable struct {
|
||||
backingTable VersionableTable
|
||||
}
|
||||
|
||||
func (qct *QueryCatalogTable) Name() string {
|
||||
return doltdb.DoltQueryCatalogTableName
|
||||
}
|
||||
|
||||
func (qct *QueryCatalogTable) String() string {
|
||||
return doltdb.DoltQueryCatalogTableName
|
||||
}
|
||||
|
||||
func doltQueryCatalogSchema() sql.Schema {
|
||||
return []*sql.Column{
|
||||
{Name: doltdb.QueryCatalogIdCol, Type: sqlTypes.LongText, Source: doltdb.GetQueryCatalogTableName(), PrimaryKey: true},
|
||||
@@ -58,139 +34,21 @@ func doltQueryCatalogSchema() sql.Schema {
|
||||
|
||||
var GetDoltQueryCatalogSchema = doltQueryCatalogSchema
|
||||
|
||||
func (qct *QueryCatalogTable) Schema() sql.Schema {
|
||||
return GetDoltQueryCatalogSchema()
|
||||
}
|
||||
|
||||
func (qct *QueryCatalogTable) Collation() sql.CollationID {
|
||||
return sql.Collation_Default
|
||||
}
|
||||
|
||||
func (qct *QueryCatalogTable) Partitions(context *sql.Context) (sql.PartitionIter, error) {
|
||||
if qct.backingTable == nil {
|
||||
// no backing table; return an empty iter.
|
||||
return index.SinglePartitionIterFromNomsMap(nil), nil
|
||||
}
|
||||
return qct.backingTable.Partitions(context)
|
||||
}
|
||||
|
||||
func (qct *QueryCatalogTable) PartitionRows(context *sql.Context, partition sql.Partition) (sql.RowIter, error) {
|
||||
if qct.backingTable == nil {
|
||||
// no backing table; return an empty iter.
|
||||
return sql.RowsToRowIter(), nil
|
||||
}
|
||||
return qct.backingTable.PartitionRows(context, partition)
|
||||
}
|
||||
|
||||
// NewQueryCatalogTable creates a QueryCatalogTable
|
||||
func NewQueryCatalogTable(_ *sql.Context, backingTable VersionableTable) sql.Table {
|
||||
return &QueryCatalogTable{backingTable: backingTable}
|
||||
return &UserSpaceSystemTable{
|
||||
backingTable: backingTable,
|
||||
tableName: getDoltQueryCatalogTableName(),
|
||||
schema: GetDoltQueryCatalogSchema(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewEmptyQueryCatalogTable creates an QueryCatalogTable
|
||||
// NewEmptyQueryCatalogTable creates an empty QueryCatalogTable
|
||||
func NewEmptyQueryCatalogTable(_ *sql.Context) sql.Table {
|
||||
return &QueryCatalogTable{}
|
||||
}
|
||||
|
||||
func (qct *QueryCatalogTable) Replacer(_ *sql.Context) sql.RowReplacer {
|
||||
return newQueryCatalogWriter(qct)
|
||||
}
|
||||
|
||||
// Updater returns a RowUpdater for this table. The RowUpdater will have Update called once for each row to be
|
||||
// updated, followed by a call to Close() when all rows have been processed.
|
||||
func (qct *QueryCatalogTable) Updater(_ *sql.Context) sql.RowUpdater {
|
||||
return newQueryCatalogWriter(qct)
|
||||
}
|
||||
|
||||
// Inserter returns an Inserter for this table. The Inserter will get one call to Insert() for each row to be
|
||||
// inserted, and will end with a call to Close() to finalize the insert operation.
|
||||
func (qct *QueryCatalogTable) Inserter(*sql.Context) sql.RowInserter {
|
||||
return newQueryCatalogWriter(qct)
|
||||
}
|
||||
|
||||
// Deleter returns a RowDeleter for this table. The RowDeleter will get one call to Delete for each row to be deleted,
|
||||
// and will end with a call to Close() to finalize the delete operation.
|
||||
func (qct *QueryCatalogTable) Deleter(*sql.Context) sql.RowDeleter {
|
||||
return newQueryCatalogWriter(qct)
|
||||
}
|
||||
|
||||
func (qct *QueryCatalogTable) LockedToRoot(ctx *sql.Context, root doltdb.RootValue) (sql.IndexAddressableTable, error) {
|
||||
if qct.backingTable == nil {
|
||||
return qct, nil
|
||||
return &UserSpaceSystemTable{
|
||||
tableName: getDoltQueryCatalogTableName(),
|
||||
schema: GetDoltQueryCatalogSchema(),
|
||||
}
|
||||
return qct.backingTable.LockedToRoot(ctx, root)
|
||||
}
|
||||
|
||||
// IndexedAccess implements IndexAddressableTable, but QueryCatalogTable has no indexes.
|
||||
// Thus, this should never be called.
|
||||
func (qct *QueryCatalogTable) IndexedAccess(ctx *sql.Context, lookup sql.IndexLookup) sql.IndexedTable {
|
||||
panic("Unreachable")
|
||||
}
|
||||
|
||||
// GetIndexes implements IndexAddressableTable, but QueryCatalogTable has no indexes.
|
||||
func (qct *QueryCatalogTable) GetIndexes(ctx *sql.Context) ([]sql.Index, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (qct *QueryCatalogTable) PreciseMatch() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var _ sql.RowReplacer = (*queryCatalogWriter)(nil)
|
||||
var _ sql.RowUpdater = (*queryCatalogWriter)(nil)
|
||||
var _ sql.RowInserter = (*queryCatalogWriter)(nil)
|
||||
var _ sql.RowDeleter = (*queryCatalogWriter)(nil)
|
||||
|
||||
type queryCatalogWriter struct {
|
||||
qct *QueryCatalogTable
|
||||
errDuringStatementBegin error
|
||||
prevHash *hash.Hash
|
||||
tableWriter dsess.TableWriter
|
||||
}
|
||||
|
||||
func newQueryCatalogWriter(qct *QueryCatalogTable) *queryCatalogWriter {
|
||||
return &queryCatalogWriter{qct, nil, nil, nil}
|
||||
}
|
||||
|
||||
// Insert inserts the row given, returning an error if it cannot. Insert will be called once for each row to process
|
||||
// for the insert operation, which may involve many rows. After all rows in an operation have been processed, Close
|
||||
// is called.
|
||||
func (qw *queryCatalogWriter) Insert(ctx *sql.Context, r sql.Row) error {
|
||||
if err := qw.errDuringStatementBegin; err != nil {
|
||||
return err
|
||||
}
|
||||
return qw.tableWriter.Insert(ctx, r)
|
||||
}
|
||||
|
||||
// Update the given row. Provides both the old and new rows.
|
||||
func (qw *queryCatalogWriter) Update(ctx *sql.Context, old sql.Row, new sql.Row) error {
|
||||
if err := qw.errDuringStatementBegin; err != nil {
|
||||
return err
|
||||
}
|
||||
return qw.tableWriter.Update(ctx, old, new)
|
||||
}
|
||||
|
||||
// Delete deletes the given row. Returns ErrDeleteRowNotFound if the row was not found. Delete will be called once for
|
||||
// each row to process for the delete operation, which may involve many rows. After all rows have been processed,
|
||||
// Close is called.
|
||||
func (qw *queryCatalogWriter) Delete(ctx *sql.Context, r sql.Row) error {
|
||||
if err := qw.errDuringStatementBegin; err != nil {
|
||||
return err
|
||||
}
|
||||
return qw.tableWriter.Delete(ctx, r)
|
||||
}
|
||||
|
||||
// StatementBegin is called before the first operation of a statement. Integrators should mark the state of the data
|
||||
// in some way that it may be returned to in the case of an error.
|
||||
func (qw *queryCatalogWriter) StatementBegin(ctx *sql.Context) {
|
||||
name := getDoltQueryCatalogTableName()
|
||||
prevHash, tableWriter, err := createWriteableSystemTable(ctx, name, qw.qct.Schema())
|
||||
if err != nil {
|
||||
qw.errDuringStatementBegin = err
|
||||
return
|
||||
}
|
||||
qw.prevHash = prevHash
|
||||
qw.tableWriter = tableWriter
|
||||
}
|
||||
|
||||
func getDoltQueryCatalogTableName() doltdb.TableName {
|
||||
@@ -199,29 +57,3 @@ func getDoltQueryCatalogTableName() doltdb.TableName {
|
||||
}
|
||||
return doltdb.TableName{Name: doltdb.GetQueryCatalogTableName()}
|
||||
}
|
||||
|
||||
// DiscardChanges is called if a statement encounters an error, and all current changes since the statement beginning
|
||||
// should be discarded.
|
||||
func (qw *queryCatalogWriter) DiscardChanges(ctx *sql.Context, errorEncountered error) error {
|
||||
if qw.tableWriter != nil {
|
||||
return qw.tableWriter.DiscardChanges(ctx, errorEncountered)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StatementComplete is called after the last operation of the statement, indicating that it has successfully completed.
|
||||
// The mark set in StatementBegin may be removed, and a new one should be created on the next StatementBegin.
|
||||
func (qw *queryCatalogWriter) StatementComplete(ctx *sql.Context) error {
|
||||
if qw.tableWriter != nil {
|
||||
return qw.tableWriter.StatementComplete(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close finalizes the delete operation, persisting the result.
|
||||
func (qw *queryCatalogWriter) Close(ctx *sql.Context) error {
|
||||
if qw.tableWriter != nil {
|
||||
return qw.tableWriter.Close(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
// Copyright 2025 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package dtables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/index"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
)
|
||||
|
||||
var _ sql.Table = (*UserSpaceSystemTable)(nil)
|
||||
var _ sql.UpdatableTable = (*UserSpaceSystemTable)(nil)
|
||||
var _ sql.DeletableTable = (*UserSpaceSystemTable)(nil)
|
||||
var _ sql.InsertableTable = (*UserSpaceSystemTable)(nil)
|
||||
var _ sql.ReplaceableTable = (*UserSpaceSystemTable)(nil)
|
||||
var _ sql.IndexAddressableTable = (*UserSpaceSystemTable)(nil)
|
||||
|
||||
// A UserSpaceSystemTable is a system table backed by a normal table in storage.
|
||||
// Like other system tables, it always exists. If the backing table doesn't exist, then reads return an empty table,
|
||||
// and writes will create the table.
|
||||
type UserSpaceSystemTable struct {
|
||||
backingTable VersionableTable
|
||||
tableName doltdb.TableName
|
||||
schema sql.Schema
|
||||
}
|
||||
|
||||
func (bst *UserSpaceSystemTable) Name() string {
|
||||
return bst.tableName.Name
|
||||
}
|
||||
|
||||
func (bst *UserSpaceSystemTable) String() string {
|
||||
return bst.tableName.Name
|
||||
}
|
||||
|
||||
func (bst *UserSpaceSystemTable) Schema() sql.Schema {
|
||||
return bst.schema
|
||||
}
|
||||
|
||||
func (bst *UserSpaceSystemTable) Collation() sql.CollationID {
|
||||
return sql.Collation_Default
|
||||
}
|
||||
|
||||
// Partitions is a sql.Table interface function that returns a partition of the data.
|
||||
func (bst *UserSpaceSystemTable) Partitions(context *sql.Context) (sql.PartitionIter, error) {
|
||||
if bst.backingTable == nil {
|
||||
// no backing table; return an empty iter.
|
||||
return index.SinglePartitionIterFromNomsMap(nil), nil
|
||||
}
|
||||
return bst.backingTable.Partitions(context)
|
||||
}
|
||||
|
||||
func (bst *UserSpaceSystemTable) PartitionRows(context *sql.Context, partition sql.Partition) (sql.RowIter, error) {
|
||||
if bst.backingTable == nil {
|
||||
// no backing table; return an empty iter.
|
||||
return sql.RowsToRowIter(), nil
|
||||
}
|
||||
|
||||
return bst.backingTable.PartitionRows(context, partition)
|
||||
}
|
||||
|
||||
// Replacer returns a RowReplacer for this table. The RowReplacer will have Insert and optionally Delete called once
|
||||
// for each row, followed by a call to Close() when all rows have been processed.
|
||||
func (bst *UserSpaceSystemTable) Replacer(ctx *sql.Context) sql.RowReplacer {
|
||||
return newBackedSystemTableWriter(bst)
|
||||
}
|
||||
|
||||
// Updater returns a RowUpdater for this table. The RowUpdater will have Update called once for each row to be
|
||||
// updated, followed by a call to Close() when all rows have been processed.
|
||||
func (bst *UserSpaceSystemTable) Updater(ctx *sql.Context) sql.RowUpdater {
|
||||
return newBackedSystemTableWriter(bst)
|
||||
}
|
||||
|
||||
// Inserter returns an Inserter for this table. The Inserter will get one call to Insert() for each row to be
|
||||
// inserted, and will end with a call to Close() to finalize the insert operation.
|
||||
func (bst *UserSpaceSystemTable) Inserter(*sql.Context) sql.RowInserter {
|
||||
return newBackedSystemTableWriter(bst)
|
||||
}
|
||||
|
||||
// Deleter returns a RowDeleter for this table. The RowDeleter will get one call to Delete for each row to be deleted,
|
||||
// and will end with a call to Close() to finalize the delete operation.
|
||||
func (bst *UserSpaceSystemTable) Deleter(*sql.Context) sql.RowDeleter {
|
||||
return newBackedSystemTableWriter(bst)
|
||||
}
|
||||
|
||||
func (bst *UserSpaceSystemTable) LockedToRoot(ctx *sql.Context, root doltdb.RootValue) (sql.IndexAddressableTable, error) {
|
||||
if bst.backingTable == nil {
|
||||
return bst, nil
|
||||
}
|
||||
return bst.backingTable.LockedToRoot(ctx, root)
|
||||
}
|
||||
|
||||
// IndexedAccess implements IndexAddressableTable, but UserSpaceSystemTable has no indexes.
|
||||
// Thus, this should never be called.
|
||||
func (bst *UserSpaceSystemTable) IndexedAccess(ctx *sql.Context, lookup sql.IndexLookup) sql.IndexedTable {
|
||||
panic("Unreachable")
|
||||
}
|
||||
|
||||
// GetIndexes implements IndexAddressableTable, but IgnoreTables has no indexes.
|
||||
func (bst *UserSpaceSystemTable) GetIndexes(ctx *sql.Context) ([]sql.Index, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (bst *UserSpaceSystemTable) PreciseMatch() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var _ sql.RowReplacer = (*backedSystemTableWriter)(nil)
|
||||
var _ sql.RowUpdater = (*backedSystemTableWriter)(nil)
|
||||
var _ sql.RowInserter = (*backedSystemTableWriter)(nil)
|
||||
var _ sql.RowDeleter = (*backedSystemTableWriter)(nil)
|
||||
|
||||
type backedSystemTableWriter struct {
|
||||
bst *UserSpaceSystemTable
|
||||
errDuringStatementBegin error
|
||||
prevHash *hash.Hash
|
||||
tableWriter dsess.TableWriter
|
||||
}
|
||||
|
||||
func newBackedSystemTableWriter(bst *UserSpaceSystemTable) *backedSystemTableWriter {
|
||||
return &backedSystemTableWriter{bst, nil, nil, nil}
|
||||
}
|
||||
|
||||
// Insert inserts the row given, returning an error if it cannot. Insert will be called once for each row to process
|
||||
// for the insert operation, which may involve many rows. After all rows in an operation have been processed, Close
|
||||
// is called.
|
||||
func (bstw *backedSystemTableWriter) Insert(ctx *sql.Context, r sql.Row) error {
|
||||
if err := bstw.errDuringStatementBegin; err != nil {
|
||||
return err
|
||||
}
|
||||
return bstw.tableWriter.Insert(ctx, r)
|
||||
}
|
||||
|
||||
// Update the given row. Provides both the old and new rows.
|
||||
func (bstw *backedSystemTableWriter) Update(ctx *sql.Context, old sql.Row, new sql.Row) error {
|
||||
if err := bstw.errDuringStatementBegin; err != nil {
|
||||
return err
|
||||
}
|
||||
return bstw.tableWriter.Update(ctx, old, new)
|
||||
}
|
||||
|
||||
// Delete deletes the given row. Returns ErrDeleteRowNotFound if the row was not found. Delete will be called once for
|
||||
// each row to process for the delete operation, which may involve many rows. After all rows have been processed,
|
||||
// Close is called.
|
||||
func (bstw *backedSystemTableWriter) Delete(ctx *sql.Context, r sql.Row) error {
|
||||
if err := bstw.errDuringStatementBegin; err != nil {
|
||||
return err
|
||||
}
|
||||
return bstw.tableWriter.Delete(ctx, r)
|
||||
}
|
||||
|
||||
// StatementBegin is called before the first operation of a statement. Integrators should mark the state of the data
|
||||
// in some way that it may be returned to in the case of an error.
|
||||
func (bstw *backedSystemTableWriter) StatementBegin(ctx *sql.Context) {
|
||||
prevHash, tableWriter, err := createWriteableSystemTable(ctx, bstw.bst.tableName, bstw.bst.Schema())
|
||||
if err != nil {
|
||||
bstw.errDuringStatementBegin = err
|
||||
return
|
||||
}
|
||||
bstw.prevHash = prevHash
|
||||
bstw.tableWriter = tableWriter
|
||||
}
|
||||
|
||||
// DiscardChanges is called if a statement encounters an error, and all current changes since the statement beginning
|
||||
// should be discarded.
|
||||
func (bstw *backedSystemTableWriter) DiscardChanges(ctx *sql.Context, errorEncountered error) error {
|
||||
if bstw.tableWriter != nil {
|
||||
return bstw.tableWriter.DiscardChanges(ctx, errorEncountered)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StatementComplete is called after the last operation of the statement, indicating that it has successfully completed.
|
||||
// The mark set in StatementBegin may be removed, and a new one should be created on the next StatementBegin.
|
||||
func (bstw *backedSystemTableWriter) StatementComplete(ctx *sql.Context) error {
|
||||
if bstw.tableWriter != nil {
|
||||
return bstw.tableWriter.StatementComplete(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close finalizes the delete operation, persisting the result.
|
||||
func (bstw backedSystemTableWriter) Close(ctx *sql.Context) error {
|
||||
if bstw.tableWriter != nil {
|
||||
return bstw.tableWriter.Close(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateWriteableSystemTable is a helper function that creates a writeable system table (dolt_ignore, dolt_docs...) if it does not exist
|
||||
// Then returns the hash of the previous working root, and a TableWriter.
|
||||
func createWriteableSystemTable(ctx *sql.Context, tblName doltdb.TableName, tblSchema sql.Schema) (*hash.Hash, dsess.TableWriter, error) {
|
||||
dbName := ctx.GetCurrentDatabase()
|
||||
dSess := dsess.DSessFromSess(ctx.Session)
|
||||
|
||||
roots, _ := dSess.GetRoots(ctx, dbName)
|
||||
dbState, ok, err := dSess.LookupDbState(ctx, dbName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
}
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("no root value found in session")
|
||||
}
|
||||
|
||||
prevHash, err := roots.Working.HashOf()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
found, err := roots.Working.HasTable(ctx, tblName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if !found {
|
||||
sch := sql.NewPrimaryKeySchema(tblSchema)
|
||||
doltSch, err := sqlutil.ToDoltSchema(ctx, roots.Working, tblName, sch, roots.Head, sql.Collation_Default)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// underlying table doesn't exist. Record this, then create the table.
|
||||
newRootValue, err := doltdb.CreateEmptyTable(ctx, roots.Working, tblName, doltSch)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if dbState.WorkingSet() == nil {
|
||||
return nil, nil, doltdb.ErrOperationNotSupportedInDetachedHead
|
||||
}
|
||||
|
||||
// We use WriteSession.SetWorkingSet instead of DoltSession.SetWorkingRoot because we want to avoid modifying the root
|
||||
// until the end of the transaction, but we still want the WriteSession to be able to find the newly
|
||||
// created table.
|
||||
if ws := dbState.WriteSession(); ws != nil {
|
||||
err = ws.SetWorkingSet(ctx, dbState.WorkingSet().WithWorkingRoot(newRootValue))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("could not create %s table, database does not allow writing", tblName)
|
||||
}
|
||||
}
|
||||
|
||||
var tableWriter dsess.TableWriter
|
||||
if ws := dbState.WriteSession(); ws != nil {
|
||||
tableWriter, err = ws.GetTableWriter(ctx, tblName, dbName, dSess.SetWorkingRoot, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
tableWriter.StatementBegin(ctx)
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("could not create %s table, database does not allow writing", tblName)
|
||||
}
|
||||
|
||||
return &prevHash, tableWriter, nil
|
||||
}
|
||||
@@ -658,7 +658,7 @@ type WritableDoltTable struct {
|
||||
var _ doltTableInterface = (*WritableDoltTable)(nil)
|
||||
|
||||
// WritableDoltTableWrapper is an interface that allows a table to be returned as an sql.Table, but actually be a wrapped
|
||||
// fake table. Specifically, databases.getTableInsensitive will returns an sql.Table, and there are cases where we
|
||||
// fake table. Specifically, databases.getTableInsensitiveWithRoot will returns an sql.Table, and there are cases where we
|
||||
// want to return a table that hasn't been materialized yet.
|
||||
type WritableDoltTableWrapper interface {
|
||||
// Unwrap returns the underlying WritableDoltTable, nil returns are expected when the wrapped table hasn't been materialized
|
||||
|
||||
@@ -143,6 +143,7 @@ SKIP_SERVER_TESTS=$(cat <<-EOM
|
||||
~import-no-header-psv.bats~
|
||||
~admin-conjoin.bats~
|
||||
~admin-archive-inspect.bats~
|
||||
~nonlocal.bats~
|
||||
EOM
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,378 @@
|
||||
#!/usr/bin/env bats
|
||||
load $BATS_TEST_DIRNAME/helper/common.bash
|
||||
|
||||
setup() {
|
||||
setup_common
|
||||
}
|
||||
|
||||
teardown() {
|
||||
assert_feature_version
|
||||
teardown_common
|
||||
}
|
||||
|
||||
@test "nonlocal: basic case" {
|
||||
dolt checkout -b other
|
||||
dolt sql <<SQL
|
||||
CALL dolt_checkout('main');
|
||||
CREATE TABLE aliased_table (pk char(8) PRIMARY KEY);
|
||||
INSERT INTO aliased_table VALUES ("amzmapqt");
|
||||
|
||||
CALL dolt_checkout('other');
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, ref_table, options) VALUES
|
||||
("table_alias_branch", "main", "aliased_table", "immediate");
|
||||
SQL
|
||||
|
||||
run dolt sql -q "select * from table_alias_branch;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "amzmapqt" ]] || false
|
||||
|
||||
# Nonlocal tables appear in "show create", but the output matches the aliased table.
|
||||
run dolt sql -q "show create table table_alias_branch"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "aliased_table" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: branch name reflects the working set of the referenced branch" {
|
||||
dolt checkout -b other
|
||||
dolt sql <<SQL
|
||||
CALL dolt_checkout('main');
|
||||
CREATE TABLE aliased_table (pk char(8) PRIMARY KEY);
|
||||
INSERT INTO aliased_table VALUES ("amzmapqt");
|
||||
|
||||
CALL dolt_checkout('other');
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, ref_table, options) VALUES
|
||||
("table_alias_branch", "main", "aliased_table", "immediate");
|
||||
SQL
|
||||
|
||||
run dolt sql -q "select * from table_alias_branch;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "amzmapqt" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: branch ref reflects the committed version of the parent" {
|
||||
dolt checkout -b other
|
||||
dolt sql <<SQL
|
||||
CALL DOLT_CHECKOUT('main');
|
||||
CREATE TABLE aliased_table (pk char(8) PRIMARY KEY);
|
||||
INSERT INTO aliased_table VALUES ('amzmapqt');
|
||||
CALL DOLT_COMMIT('-Am', 'create table');
|
||||
INSERT INTO aliased_table VALUES ('eesekkgo');
|
||||
|
||||
CALL DOLT_CHECKOUT('other');
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, ref_table, options) VALUES
|
||||
("table_alias_branch_ref", "heads/main", "aliased_table", "immediate");
|
||||
SQL
|
||||
|
||||
run dolt sql -q "select * from table_alias_branch_ref;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "amzmapqt" ]] || false
|
||||
! [[ "$output" =~ "eesekkgo" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: tag and hash" {
|
||||
dolt checkout -b other
|
||||
dolt sql <<SQL
|
||||
CALL DOLT_CHECKOUT('main');
|
||||
CREATE TABLE aliased_table (pk char(8) PRIMARY KEY);
|
||||
INSERT INTO aliased_table VALUES ("amzmapqt");
|
||||
CALL DOLT_COMMIT('-Am', 'commit');
|
||||
CALL DOLT_TAG('v1.0');
|
||||
|
||||
CALL DOLT_CHECKOUT('other');
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, ref_table, options) VALUES
|
||||
("table_alias_tag", "v1.0", "aliased_table", "immediate"),
|
||||
("table_alias_tag_ref", "tags/v1.0", "aliased_table", "immediate"),
|
||||
("table_alias_hash", DOLT_HASHOF('v1.0'), "aliased_table", "immediate");
|
||||
SQL
|
||||
|
||||
run dolt sql -q "select * from table_alias_tag_ref;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "amzmapqt" ]] || false
|
||||
|
||||
run dolt sql -q "select * from table_alias_tag;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "amzmapqt" ]] || false
|
||||
|
||||
run dolt sql -q "select * from table_alias_hash;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "amzmapqt" ]] || false
|
||||
|
||||
# These nonlocal tables are read-only because they reference a read-only ref
|
||||
run dolt sql <<SQL
|
||||
INSERT INTO table_alias_tag_ref VALUES ("eesekkgo");
|
||||
SQL
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "table doesn't support INSERT INTO" ]] || false
|
||||
|
||||
run dolt sql <<SQL
|
||||
INSERT INTO table_alias_tag VALUES ("eesekkgo");
|
||||
SQL
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "table doesn't support INSERT INTO" ]] || false
|
||||
|
||||
run dolt sql <<SQL
|
||||
INSERT INTO table_alias_hash VALUES ("eesekkgo");
|
||||
SQL
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "table doesn't support INSERT INTO" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: remote ref" {
|
||||
mkdir child
|
||||
dolt checkout -b other
|
||||
dolt sql <<SQL
|
||||
CALL DOLT_CHECKOUT('main');
|
||||
CREATE TABLE aliased_table (pk char(8) PRIMARY KEY);
|
||||
INSERT INTO aliased_table VALUES ("amzmapqt");
|
||||
CALL DOLT_COMMIT('-Am', 'create table');
|
||||
CALL DOLT_REMOTE('add', 'remote_db', 'file://./remote');
|
||||
CALL DOLT_PUSH('remote_db', 'main');
|
||||
|
||||
-- drop table so it is only accessible from the remote ref
|
||||
DROP TABLE aliased_table;
|
||||
CALL DOLT_COMMIT('-am', 'drop table');
|
||||
|
||||
CALL DOLT_CHECKOUT('other');
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, ref_table, options) VALUES
|
||||
("table_alias_remote_branch", "remote_db/main", "aliased_table", "immediate"),
|
||||
("table_alias_remote_ref", "remotes/remote_db/main", "aliased_table", "immediate");
|
||||
SQL
|
||||
|
||||
run dolt sql -q "select * from table_alias_remote_branch;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "amzmapqt" ]] || false
|
||||
|
||||
run dolt sql -q "select * from table_alias_remote_ref;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "amzmapqt" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: default ref" {
|
||||
# If unspecified, the ref defaults to the current HEAD.
|
||||
# This allows one table to alias another table on the same branch.
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE aliased_table (pk char(8) PRIMARY KEY);
|
||||
INSERT INTO aliased_table VALUES ("amzmapqt");
|
||||
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, ref_table, options) VALUES
|
||||
("table_alias", "aliased_table", "immediate");
|
||||
SQL
|
||||
|
||||
run dolt sql -q "select * from table_alias;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "amzmapqt" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: default table_name" {
|
||||
# If unspecified, the parent table name defaults to the same table name as the child
|
||||
dolt checkout -b other
|
||||
dolt sql <<SQL
|
||||
CALL DOLT_CHECKOUT('main');
|
||||
CREATE TABLE table_alias (pk char(8) PRIMARY KEY);
|
||||
INSERT INTO table_alias VALUES ("amzmapqt");
|
||||
|
||||
CALL DOLT_CHECKOUT('other');
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, options) VALUES
|
||||
("table_alias", "main", "immediate");
|
||||
SQL
|
||||
|
||||
run dolt sql -q "select * from table_alias;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "amzmapqt" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: wildcard table_name" {
|
||||
# The wildcard syntax matches the wildcard syntax used in dolt_ignore
|
||||
dolt checkout -b other
|
||||
dolt sql <<SQL
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, options) VALUES
|
||||
("nonlocal_*", "main", "immediate");
|
||||
|
||||
CALL DOLT_CHECKOUT('main');
|
||||
|
||||
CREATE TABLE nonlocal_table1 (pk char(8) PRIMARY KEY);
|
||||
CREATE TABLE nonlocal_table2 (pk char(8) PRIMARY KEY);
|
||||
CREATE TABLE not_nonlocal_table (pk char(8) PRIMARY KEY);
|
||||
INSERT INTO nonlocal_table1 VALUES ("amzmapqt");
|
||||
INSERT INTO nonlocal_table2 VALUES ("eesekkgo");
|
||||
INSERT INTO not_nonlocal_table VALUES ("pzdxwmbd");
|
||||
|
||||
SQL
|
||||
|
||||
run dolt sql -q "select * from nonlocal_table1;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "amzmapqt" ]] || false
|
||||
|
||||
run dolt sql -q "select * from nonlocal_table2;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "eesekkgo" ]] || false
|
||||
|
||||
run dolt sql -q "select * from not_nonlocal_table;"
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "table not found" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: a transaction that tries to update multiple branches fails as expected" {
|
||||
run dolt sql <<SQL
|
||||
CREATE TABLE aliased_table (pk char(8) PRIMARY KEY);
|
||||
CALL DOLT_CHECKOUT('-b', 'other');
|
||||
CREATE TABLE local_table (pk char(8) PRIMARY KEY);
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, ref_table, options) VALUES
|
||||
("nonlocal_table", "main", "aliased_table", "immediate");
|
||||
set autocommit = 0;
|
||||
INSERT INTO local_table VALUES ("amzmapqt");
|
||||
INSERT INTO nonlocal_table VALUES ("eesekkgo");
|
||||
COMMIT;
|
||||
SQL
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "Cannot commit changes on more than one branch / database" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: test foreign keys" {
|
||||
# Currently, foreign keys cannot be added to nonlocal tables
|
||||
dolt checkout -b other
|
||||
run dolt sql <<SQL
|
||||
CALL DOLT_CHECKOUT('main');
|
||||
CREATE TABLE aliased_table (pk char(8) PRIMARY KEY);
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, ref_table, options) VALUES
|
||||
("nonlocal_table", "main", "aliased_table", "immediate");
|
||||
set autocommit = 0;
|
||||
INSERT INTO nonlocal_table VALUES ("eesekkgo");
|
||||
|
||||
SQL
|
||||
|
||||
run dolt sql <<SQL
|
||||
CREATE TABLE local_table (pk char(8) PRIMARY KEY, FOREIGN KEY (pk) REFERENCES nonlocal_table(pk));
|
||||
SQL
|
||||
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "table not found: nonlocal_table" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: trying to dolt_add a nonlocal table returns the appropriate warning" {
|
||||
dolt checkout -b other
|
||||
dolt sql <<SQL
|
||||
CALL DOLT_CHECKOUT('main');
|
||||
CREATE TABLE nonlocal_table (pk char(8) PRIMARY KEY);
|
||||
SQL
|
||||
|
||||
dolt sql <<SQL
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, options) VALUES
|
||||
("nonlocal_table", "main", "immediate");
|
||||
INSERT INTO nonlocal_table values ('ghdsgerg');
|
||||
SQL
|
||||
|
||||
run dolt add nonlocal_table
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "the table(s) nonlocal_table do not exist" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: dolt_add('.') doesn't add nonlocal tables" {
|
||||
dolt checkout -b other
|
||||
dolt sql <<SQL
|
||||
CALL DOLT_CHECKOUT('main');
|
||||
CREATE TABLE test_table (pk char(8) PRIMARY KEY);
|
||||
SQL
|
||||
|
||||
dolt sql <<SQL
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, options) VALUES
|
||||
("test_table", "main", "immediate");
|
||||
INSERT INTO test_table values ('ghdsgerg');
|
||||
|
||||
CALL DOLT_ADD('.');
|
||||
SQL
|
||||
|
||||
run dolt sql -q "SELECT * FROM dolt_status"
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output"
|
||||
! [[ "$output" =~ "test_table" ]] || false
|
||||
|
||||
run dolt sql -q "CALL DOLT_CHECKOUT('main'); SELECT * FROM dolt_status"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "test_table | false" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: self-referrential nonlocal tables in the same branch as their target are effectively ignored" {
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE nonlocal_table (pk char(8) PRIMARY KEY);
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, options) VALUES
|
||||
("nonlocal_table", "main", "immediate");
|
||||
|
||||
INSERT INTO nonlocal_table values ('ghdsgerg');
|
||||
SQL
|
||||
|
||||
dolt add nonlocal_table
|
||||
run dolt sql -q "select * from dolt_status"
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output"
|
||||
[[ "$output" =~ "nonlocal_table | true" ]] || false
|
||||
|
||||
# Unstage nonlocal_table but keep it in the working set
|
||||
dolt reset HEAD
|
||||
|
||||
dolt add .
|
||||
run dolt sql -q "select * from dolt_status"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "nonlocal_table | true" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: invalid options detected" {
|
||||
dolt sql <<SQL
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, options) VALUES
|
||||
("nonlocal_table", "main", "invalid");
|
||||
SQL
|
||||
|
||||
run dolt sql -q "select * from nonlocal_table;"
|
||||
[ "$status" -eq 1 ]
|
||||
echo "$output"
|
||||
[[ "$output" =~ "Invalid nonlocal table options" ]] || false
|
||||
}
|
||||
|
||||
# The below tests are convenience features but not necessary for the MVP
|
||||
|
||||
@test "nonlocal: nonlocal tables appear in show_tables" {
|
||||
skip
|
||||
dolt checkout -b other
|
||||
dolt sql <<SQL
|
||||
CALL dolt_checkout('main');
|
||||
CREATE TABLE aliased_table (pk char(8) PRIMARY KEY);
|
||||
INSERT INTO aliased_table VALUES ("amzmapqt");
|
||||
|
||||
CALL dolt_checkout('other');
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, ref_table, options) VALUES
|
||||
("table_alias_branch", "main", "aliased_table", "immediate");
|
||||
SQL
|
||||
|
||||
# Nonlocal tables should appear in "show tables"
|
||||
run dolt sql -q "show tables"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "table_alias_branch" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: creating a nonlocal table creates it on the appropriate branch" {
|
||||
skip
|
||||
dolt checkout -b other
|
||||
dolt sql <<SQL
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, options) VALUES
|
||||
("nonlocal_table", "main", "immediate");
|
||||
|
||||
CREATE TABLE nonlocal_table (pk char(8) PRIMARY KEY);
|
||||
INSERT INTO nonlocal_table VALUES ("amzmapqt");
|
||||
SQL
|
||||
|
||||
run dolt ls main
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "nonlocal_table" ]] || false
|
||||
}
|
||||
|
||||
@test "nonlocal: adding an existing table to nonlocal tables errors" {
|
||||
skip
|
||||
dolt checkout -b other
|
||||
run dolt sql <<SQL
|
||||
CREATE TABLE nonlocal_table (pk char(8) PRIMARY KEY);
|
||||
INSERT INTO dolt_nonlocal_tables(table_name, target_ref, options) VALUES
|
||||
("nonlocal_table", "main", "immediate");
|
||||
SQL
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "cannot make nonlocal table nonlocal_table, table already exists on branch other" ]] || false
|
||||
}
|
||||
Reference in New Issue
Block a user