Respond to PR feedback.

This commit is contained in:
Nick Tobey
2025-10-06 20:14:45 -07:00
parent ff3c6cb490
commit 66992d0a9c
9 changed files with 155 additions and 137 deletions

View File

@@ -17,10 +17,6 @@ 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"
)

View File

@@ -18,7 +18,6 @@ import (
"context"
"fmt"
"io"
"regexp"
"strings"
"github.com/dolthub/go-mysql-server/sql"
@@ -205,87 +204,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)
}
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
}
func resolveConflictingPatterns(trueMatches, falseMatches []string, tableName TableName) (IgnoreResult, error) {
trueMatchesToRemove := map[string]struct{}{}
falseMatchesToRemove := map[string]struct{}{}

View File

@@ -0,0 +1,102 @@
// 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 (
"github.com/dolthub/go-mysql-server/sql"
"regexp"
"strings"
)
// 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
}

View File

@@ -62,6 +62,13 @@ var ErrReservedDiffTableName = errors.NewKind("Invalid table name %s. Table name
var ErrSystemTableAlter = errors.NewKind("Cannot alter table %s: system tables cannot be dropped or altered")
var ErrInvalidGlobalsTableOptions = errors.NewKind("Invalid global table options %s: only valid value is 'immediate'.")
type readGlobalTablesFlag bool
const (
readGlobalTables readGlobalTablesFlag = true
dontReadGlobalTables readGlobalTablesFlag = false
)
// Database implements sql.Database for a dolt DB.
type Database struct {
rsr env.RepoStateReader[*sql.Context]
@@ -270,10 +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, true)
return db.getTableInsensitive(ctx, tblName, readGlobalTables)
}
func (db Database) getTableInsensitive(ctx *sql.Context, tblName string, readGlobalTables bool) (sql.Table, bool, error) {
func (db Database) getTableInsensitive(ctx *sql.Context, tblName string, readGlobalTables readGlobalTablesFlag) (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)
@@ -294,7 +301,7 @@ func (db Database) GetTableInsensitiveAsOf(ctx *sql.Context, tableName string, a
}
// GetTableInsensitiveAsOf implements sql.VersionedDatabase
func (db Database) getTableInsensitiveAsOf(ctx *sql.Context, tableName string, asOf interface{}, readGlobalTables bool) (sql.Table, bool, error) {
func (db Database) getTableInsensitiveAsOf(ctx *sql.Context, tableName string, asOf interface{}, readGlobalTables readGlobalTablesFlag) (sql.Table, bool, error) {
if asOf == nil {
return db.GetTableInsensitive(ctx, tableName)
}
@@ -340,7 +347,7 @@ func (db Database) getTableInsensitiveAsOf(ctx *sql.Context, tableName string, a
}
}
func (db Database) getTableInsensitiveWithRoot(ctx *sql.Context, head *doltdb.Commit, ds *dsess.DoltSession, root doltdb.RootValue, tblName string, asOf interface{}, readGlobalTables bool) (sql.Table, bool, error) {
func (db Database) getTableInsensitiveWithRoot(ctx *sql.Context, head *doltdb.Commit, ds *dsess.DoltSession, root doltdb.RootValue, tblName string, asOf interface{}, readGlobalTables readGlobalTablesFlag) (sql.Table, bool, error) {
lwrName := strings.ToLower(tblName)
if readGlobalTables {
@@ -496,7 +503,7 @@ func (db Database) getTableInsensitiveWithRoot(ctx *sql.Context, head *doltdb.Co
}
}
srcTable, ok, err := db.getTableInsensitiveWithRoot(ctx, head, ds, root, tname.Name, asOf, true)
srcTable, ok, err := db.getTableInsensitiveWithRoot(ctx, head, ds, root, tname.Name, asOf, readGlobalTables)
if err != nil {
return nil, false, err
} else if !ok {
@@ -938,12 +945,7 @@ func (db Database) getTableInsensitiveWithRoot(ctx *sql.Context, head *doltdb.Co
// getGlobalTable checks whether the table name maps onto a table in another root via the dolt_global_tables system table
func (db Database) getGlobalTable(ctx *sql.Context, root doltdb.RootValue, lwrName string) (sql.Table, bool, error) {
globalTablesTableName := doltdb.TableName{
Name: doltdb.GetGlobalTablesTableName(),
Schema: db.schemaName,
}
globalsTable, globalsTableExists, err := root.GetTable(ctx, globalTablesTableName)
_, globalsTable, globalsTableExists, err := db.resolveUserTable(ctx, root, doltdb.GetGlobalTablesTableName())
if err != nil {
return nil, false, err
}
@@ -1013,7 +1015,7 @@ func (db Database) getGlobalTable(ctx *sql.Context, root doltdb.RootValue, lwrNa
if err != nil {
return nil, false, err
}
return referencedBranch.(Database).getTableInsensitive(ctx, globalTablesEntry.NewTableName, false)
return referencedBranch.(Database).getTableInsensitive(ctx, globalTablesEntry.NewTableName, dontReadGlobalTables)
} 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'

View File

@@ -32,13 +32,13 @@ var _ sql.IndexAddressableTable = (*DocsTable)(nil)
// DocsTable is the system table that stores Dolt docs, such as LICENSE and README.
type DocsTable struct {
BackedSystemTable
UserSpaceSystemTable
}
// NewDocsTable creates a DocsTable
func NewDocsTable(_ *sql.Context, backingTable VersionableTable) sql.Table {
return &DocsTable{
BackedSystemTable: BackedSystemTable{
UserSpaceSystemTable: UserSpaceSystemTable{
backingTable: backingTable,
tableName: getDoltDocsTableName(),
schema: GetDocsSchema(),
@@ -49,7 +49,7 @@ func NewDocsTable(_ *sql.Context, backingTable VersionableTable) sql.Table {
// NewEmptyDocsTable creates a DocsTable
func NewEmptyDocsTable(_ *sql.Context) sql.Table {
return &DocsTable{
BackedSystemTable: BackedSystemTable{
UserSpaceSystemTable: UserSpaceSystemTable{
tableName: getDoltDocsTableName(),
schema: GetDocsSchema(),
},
@@ -81,7 +81,7 @@ func (dt *DocsTable) LockedToRoot(ctx *sql.Context, root doltdb.RootValue) (sql.
}
func (dt *DocsTable) PartitionRows(ctx *sql.Context, partition sql.Partition) (sql.RowIter, error) {
underlyingIter, err := dt.BackedSystemTable.PartitionRows(ctx, partition)
underlyingIter, err := dt.UserSpaceSystemTable.PartitionRows(ctx, partition)
if err != nil {
return nil, err
}

View File

@@ -33,18 +33,18 @@ func doltGlobalTablesSchema() sql.Schema {
var GetDoltGlobalTablesSchema = doltGlobalTablesSchema
// NewGlobalTablesTable creates a
// NewGlobalTablesTable creates a new dolt_table_aliases table
func NewGlobalTablesTable(_ *sql.Context, backingTable VersionableTable) sql.Table {
return &BackedSystemTable{
return &UserSpaceSystemTable{
backingTable: backingTable,
tableName: getDoltGlobalTablesName(),
schema: GetDoltGlobalTablesSchema(),
}
}
// NewEmptyGlobalTablesTable creates an empty
// NewEmptyGlobalTablesTable creates an empty dolt_table_aliases table
func NewEmptyGlobalTablesTable(_ *sql.Context) sql.Table {
return &BackedSystemTable{
return &UserSpaceSystemTable{
tableName: getDoltGlobalTablesName(),
schema: GetDoltGlobalTablesSchema(),
}

View File

@@ -32,9 +32,9 @@ func doltIgnoreSchema() sql.Schema {
// by Doltgres to update the dolt_ignore schema using Doltgres types.
var GetDoltIgnoreSchema = doltIgnoreSchema
// NewIgnoreTable creates an IgnoreTable
// NewIgnoreTable creates a dolt_ignore table
func NewIgnoreTable(_ *sql.Context, backingTable VersionableTable, schemaName string) sql.Table {
return &BackedSystemTable{
return &UserSpaceSystemTable{
backingTable: backingTable,
tableName: doltdb.TableName{
Name: doltdb.IgnoreTableName,
@@ -44,9 +44,9 @@ func NewIgnoreTable(_ *sql.Context, backingTable VersionableTable, schemaName st
}
}
// NewEmptyIgnoreTable creates an IgnoreTable
// NewEmptyIgnoreTable creates an empty dolt_ignore table
func NewEmptyIgnoreTable(_ *sql.Context, schemaName string) sql.Table {
return &BackedSystemTable{
return &UserSpaceSystemTable{
tableName: doltdb.TableName{
Name: doltdb.IgnoreTableName,
Schema: schemaName,

View File

@@ -36,7 +36,7 @@ var GetDoltQueryCatalogSchema = doltQueryCatalogSchema
// NewQueryCatalogTable creates a QueryCatalogTable
func NewQueryCatalogTable(_ *sql.Context, backingTable VersionableTable) sql.Table {
return &BackedSystemTable{
return &UserSpaceSystemTable{
backingTable: backingTable,
tableName: getDoltQueryCatalogTableName(),
schema: GetDoltQueryCatalogSchema(),
@@ -45,7 +45,7 @@ func NewQueryCatalogTable(_ *sql.Context, backingTable VersionableTable) sql.Tab
// NewEmptyQueryCatalogTable creates an empty QueryCatalogTable
func NewEmptyQueryCatalogTable(_ *sql.Context) sql.Table {
return &BackedSystemTable{
return &UserSpaceSystemTable{
tableName: getDoltQueryCatalogTableName(),
schema: GetDoltQueryCatalogSchema(),
}

View File

@@ -26,40 +26,40 @@ import (
"github.com/dolthub/dolt/go/store/hash"
)
var _ sql.Table = (*BackedSystemTable)(nil)
var _ sql.UpdatableTable = (*BackedSystemTable)(nil)
var _ sql.DeletableTable = (*BackedSystemTable)(nil)
var _ sql.InsertableTable = (*BackedSystemTable)(nil)
var _ sql.ReplaceableTable = (*BackedSystemTable)(nil)
var _ sql.IndexAddressableTable = (*BackedSystemTable)(nil)
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 BackedSystemTable is a system table backed by a normal table in storage.
// 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 BackedSystemTable struct {
type UserSpaceSystemTable struct {
backingTable VersionableTable
tableName doltdb.TableName
schema sql.Schema
}
func (bst *BackedSystemTable) Name() string {
func (bst *UserSpaceSystemTable) Name() string {
return bst.tableName.Name
}
func (bst *BackedSystemTable) String() string {
func (bst *UserSpaceSystemTable) String() string {
return bst.tableName.Name
}
func (bst *BackedSystemTable) Schema() sql.Schema {
func (bst *UserSpaceSystemTable) Schema() sql.Schema {
return bst.schema
}
func (bst *BackedSystemTable) Collation() sql.CollationID {
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 *BackedSystemTable) Partitions(context *sql.Context) (sql.PartitionIter, error) {
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
@@ -67,7 +67,7 @@ func (bst *BackedSystemTable) Partitions(context *sql.Context) (sql.PartitionIte
return bst.backingTable.Partitions(context)
}
func (bst *BackedSystemTable) PartitionRows(context *sql.Context, partition sql.Partition) (sql.RowIter, error) {
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
@@ -78,47 +78,47 @@ func (bst *BackedSystemTable) PartitionRows(context *sql.Context, partition sql.
// 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 *BackedSystemTable) Replacer(ctx *sql.Context) sql.RowReplacer {
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 *BackedSystemTable) Updater(ctx *sql.Context) sql.RowUpdater {
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 *BackedSystemTable) Inserter(*sql.Context) sql.RowInserter {
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 *BackedSystemTable) Deleter(*sql.Context) sql.RowDeleter {
func (bst *UserSpaceSystemTable) Deleter(*sql.Context) sql.RowDeleter {
return newBackedSystemTableWriter(bst)
}
func (bst *BackedSystemTable) LockedToRoot(ctx *sql.Context, root doltdb.RootValue) (sql.IndexAddressableTable, error) {
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 BackedSystemTable has no indexes.
// IndexedAccess implements IndexAddressableTable, but UserSpaceSystemTable has no indexes.
// Thus, this should never be called.
func (bst *BackedSystemTable) IndexedAccess(ctx *sql.Context, lookup sql.IndexLookup) sql.IndexedTable {
func (bst *UserSpaceSystemTable) IndexedAccess(ctx *sql.Context, lookup sql.IndexLookup) sql.IndexedTable {
panic("Unreachable")
}
// GetIndexes implements IndexAddressableTable, but IgnoreTables has no indexes.
func (bst *BackedSystemTable) GetIndexes(ctx *sql.Context) ([]sql.Index, error) {
func (bst *UserSpaceSystemTable) GetIndexes(ctx *sql.Context) ([]sql.Index, error) {
return nil, nil
}
func (bst *BackedSystemTable) PreciseMatch() bool {
func (bst *UserSpaceSystemTable) PreciseMatch() bool {
return true
}
@@ -128,13 +128,13 @@ var _ sql.RowInserter = (*backedSystemTableWriter)(nil)
var _ sql.RowDeleter = (*backedSystemTableWriter)(nil)
type backedSystemTableWriter struct {
bst *BackedSystemTable
bst *UserSpaceSystemTable
errDuringStatementBegin error
prevHash *hash.Hash
tableWriter dsess.TableWriter
}
func newBackedSystemTableWriter(bst *BackedSystemTable) *backedSystemTableWriter {
func newBackedSystemTableWriter(bst *UserSpaceSystemTable) *backedSystemTableWriter {
return &backedSystemTableWriter{bst, nil, nil, nil}
}