github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/dtables/branch_namespace_control.go (about)

     1  // Copyright 2022 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package dtables
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"math"
    21  	"strings"
    22  
    23  	"github.com/dolthub/go-mysql-server/sql"
    24  	"github.com/dolthub/go-mysql-server/sql/types"
    25  	"github.com/dolthub/vitess/go/sqltypes"
    26  
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/branch_control"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/index"
    29  )
    30  
    31  const (
    32  	NamespaceTableName = "dolt_branch_namespace_control"
    33  )
    34  
    35  // namespaceSchema is the schema for the "dolt_branch_namespace_control" table.
    36  var namespaceSchema = sql.Schema{
    37  	&sql.Column{
    38  		Name:       "database",
    39  		Type:       types.MustCreateString(sqltypes.VarChar, 16383, sql.Collation_utf8mb4_0900_ai_ci),
    40  		Source:     NamespaceTableName,
    41  		PrimaryKey: true,
    42  	},
    43  	&sql.Column{
    44  		Name:       "branch",
    45  		Type:       types.MustCreateString(sqltypes.VarChar, 16383, sql.Collation_utf8mb4_0900_ai_ci),
    46  		Source:     NamespaceTableName,
    47  		PrimaryKey: true,
    48  	},
    49  	&sql.Column{
    50  		Name:       "user",
    51  		Type:       types.MustCreateString(sqltypes.VarChar, 16383, sql.Collation_utf8mb4_0900_bin),
    52  		Source:     NamespaceTableName,
    53  		PrimaryKey: true,
    54  	},
    55  	&sql.Column{
    56  		Name:       "host",
    57  		Type:       types.MustCreateString(sqltypes.VarChar, 16383, sql.Collation_utf8mb4_0900_ai_ci),
    58  		Source:     NamespaceTableName,
    59  		PrimaryKey: true,
    60  	},
    61  }
    62  
    63  // BranchNamespaceControlTable provides a layer over the branch_control.Namespace structure, exposing it as a system
    64  // table.
    65  type BranchNamespaceControlTable struct {
    66  	*branch_control.Namespace
    67  }
    68  
    69  var _ sql.Table = BranchNamespaceControlTable{}
    70  var _ sql.InsertableTable = BranchNamespaceControlTable{}
    71  var _ sql.ReplaceableTable = BranchNamespaceControlTable{}
    72  var _ sql.UpdatableTable = BranchNamespaceControlTable{}
    73  var _ sql.DeletableTable = BranchNamespaceControlTable{}
    74  var _ sql.RowInserter = BranchNamespaceControlTable{}
    75  var _ sql.RowReplacer = BranchNamespaceControlTable{}
    76  var _ sql.RowUpdater = BranchNamespaceControlTable{}
    77  var _ sql.RowDeleter = BranchNamespaceControlTable{}
    78  
    79  // NewBranchNamespaceControlTable returns a new BranchNamespaceControlTable.
    80  func NewBranchNamespaceControlTable(namespace *branch_control.Namespace) BranchNamespaceControlTable {
    81  	return BranchNamespaceControlTable{namespace}
    82  }
    83  
    84  // Name implements the interface sql.Table.
    85  func (tbl BranchNamespaceControlTable) Name() string {
    86  	return NamespaceTableName
    87  }
    88  
    89  // String implements the interface sql.Table.
    90  func (tbl BranchNamespaceControlTable) String() string {
    91  	return NamespaceTableName
    92  }
    93  
    94  // Schema implements the interface sql.Table.
    95  func (tbl BranchNamespaceControlTable) Schema() sql.Schema {
    96  	return namespaceSchema
    97  }
    98  
    99  // Collation implements the interface sql.Table.
   100  func (tbl BranchNamespaceControlTable) Collation() sql.CollationID {
   101  	return sql.Collation_Default
   102  }
   103  
   104  // Partitions implements the interface sql.Table.
   105  func (tbl BranchNamespaceControlTable) Partitions(context *sql.Context) (sql.PartitionIter, error) {
   106  	return index.SinglePartitionIterFromNomsMap(nil), nil
   107  }
   108  
   109  // PartitionRows implements the interface sql.Table.
   110  func (tbl BranchNamespaceControlTable) PartitionRows(context *sql.Context, partition sql.Partition) (sql.RowIter, error) {
   111  	tbl.RWMutex.RLock()
   112  	defer tbl.RWMutex.RUnlock()
   113  
   114  	var rows []sql.Row
   115  	for _, value := range tbl.Values {
   116  		rows = append(rows, sql.Row{
   117  			value.Database,
   118  			value.Branch,
   119  			value.User,
   120  			value.Host,
   121  		})
   122  	}
   123  	return sql.RowsToRowIter(rows...), nil
   124  }
   125  
   126  // Inserter implements the interface sql.InsertableTable.
   127  func (tbl BranchNamespaceControlTable) Inserter(context *sql.Context) sql.RowInserter {
   128  	return tbl
   129  }
   130  
   131  // Replacer implements the interface sql.ReplaceableTable.
   132  func (tbl BranchNamespaceControlTable) Replacer(ctx *sql.Context) sql.RowReplacer {
   133  	return tbl
   134  }
   135  
   136  // Updater implements the interface sql.UpdatableTable.
   137  func (tbl BranchNamespaceControlTable) Updater(ctx *sql.Context) sql.RowUpdater {
   138  	return tbl
   139  }
   140  
   141  // Deleter implements the interface sql.DeletableTable.
   142  func (tbl BranchNamespaceControlTable) Deleter(context *sql.Context) sql.RowDeleter {
   143  	return tbl
   144  }
   145  
   146  // StatementBegin implements the interface sql.TableEditor.
   147  func (tbl BranchNamespaceControlTable) StatementBegin(ctx *sql.Context) {
   148  	//TODO: will use the binlog to implement
   149  }
   150  
   151  // DiscardChanges implements the interface sql.TableEditor.
   152  func (tbl BranchNamespaceControlTable) DiscardChanges(ctx *sql.Context, errorEncountered error) error {
   153  	//TODO: will use the binlog to implement
   154  	return nil
   155  }
   156  
   157  // StatementComplete implements the interface sql.TableEditor.
   158  func (tbl BranchNamespaceControlTable) StatementComplete(ctx *sql.Context) error {
   159  	//TODO: will use the binlog to implement
   160  	return nil
   161  }
   162  
   163  // Insert implements the interface sql.RowInserter.
   164  func (tbl BranchNamespaceControlTable) Insert(ctx *sql.Context, row sql.Row) error {
   165  	tbl.RWMutex.Lock()
   166  	defer tbl.RWMutex.Unlock()
   167  
   168  	// Database, Branch, and Host are case-insensitive, while user is case-sensitive
   169  	database := strings.ToLower(branch_control.FoldExpression(row[0].(string)))
   170  	branch := strings.ToLower(branch_control.FoldExpression(row[1].(string)))
   171  	user := branch_control.FoldExpression(row[2].(string))
   172  	host := strings.ToLower(branch_control.FoldExpression(row[3].(string)))
   173  
   174  	// Verify that the lengths of each expression fit within an uint16
   175  	if len(database) > math.MaxUint16 || len(branch) > math.MaxUint16 || len(user) > math.MaxUint16 || len(host) > math.MaxUint16 {
   176  		return branch_control.ErrExpressionsTooLong.New(database, branch, user, host)
   177  	}
   178  
   179  	// A nil session means we're not in the SQL context, so we allow the insertion in such a case
   180  	if branchAwareSession := branch_control.GetBranchAwareSession(ctx); branchAwareSession != nil &&
   181  		// Having the correct database privileges also allows the insertion
   182  		!branch_control.HasDatabasePrivileges(branchAwareSession, database) {
   183  
   184  		// tbl.Access() shares a lock with the namespace table. No need to acquire its lock.
   185  
   186  		insertUser := branchAwareSession.GetUser()
   187  		insertHost := branchAwareSession.GetHost()
   188  		// As we've folded the branch expression, we can use it directly as though it were a normal branch name to
   189  		// determine if the user attempting the insertion has permission to perform the insertion.
   190  		_, modPerms := tbl.Access().Match(database, branch, insertUser, insertHost)
   191  		if modPerms&branch_control.Permissions_Admin != branch_control.Permissions_Admin {
   192  			return branch_control.ErrInsertingNamespaceRow.New(insertUser, insertHost, database, branch, user, host)
   193  		}
   194  	}
   195  
   196  	return tbl.insert(ctx, database, branch, user, host)
   197  }
   198  
   199  // Update implements the interface sql.RowUpdater.
   200  func (tbl BranchNamespaceControlTable) Update(ctx *sql.Context, old sql.Row, new sql.Row) error {
   201  	tbl.RWMutex.Lock()
   202  	defer tbl.RWMutex.Unlock()
   203  
   204  	// Database, Branch, and Host are case-insensitive, while User is case-sensitive
   205  	oldDatabase := strings.ToLower(branch_control.FoldExpression(old[0].(string)))
   206  	oldBranch := strings.ToLower(branch_control.FoldExpression(old[1].(string)))
   207  	oldUser := branch_control.FoldExpression(old[2].(string))
   208  	oldHost := strings.ToLower(branch_control.FoldExpression(old[3].(string)))
   209  	newDatabase := strings.ToLower(branch_control.FoldExpression(new[0].(string)))
   210  	newBranch := strings.ToLower(branch_control.FoldExpression(new[1].(string)))
   211  	newUser := branch_control.FoldExpression(new[2].(string))
   212  	newHost := strings.ToLower(branch_control.FoldExpression(new[3].(string)))
   213  
   214  	// Verify that the lengths of each expression fit within an uint16
   215  	if len(newDatabase) > math.MaxUint16 || len(newBranch) > math.MaxUint16 || len(newUser) > math.MaxUint16 || len(newHost) > math.MaxUint16 {
   216  		return branch_control.ErrExpressionsTooLong.New(newDatabase, newBranch, newUser, newHost)
   217  	}
   218  
   219  	// If we're not updating the same row, then we pre-emptively check for a row violation
   220  	if oldDatabase != newDatabase || oldBranch != newBranch || oldUser != newUser || oldHost != newHost {
   221  		if tblIndex := tbl.GetIndex(newDatabase, newBranch, newUser, newHost); tblIndex != -1 {
   222  			return sql.NewUniqueKeyErr(
   223  				fmt.Sprintf(`[%q, %q, %q, %q]`, newDatabase, newBranch, newUser, newHost),
   224  				true,
   225  				sql.Row{newDatabase, newBranch, newUser, newHost})
   226  		}
   227  	}
   228  
   229  	// A nil session means we're not in the SQL context, so we'd allow the update in such a case
   230  	if branchAwareSession := branch_control.GetBranchAwareSession(ctx); branchAwareSession != nil {
   231  		// tbl.Access() shares a lock with the namespace table. No need to acquire its lock.
   232  
   233  		insertUser := branchAwareSession.GetUser()
   234  		insertHost := branchAwareSession.GetHost()
   235  		if !branch_control.HasDatabasePrivileges(branchAwareSession, oldDatabase) {
   236  			// As we've folded the branch expression, we can use it directly as though it were a normal branch name to
   237  			// determine if the user attempting the update has permission to perform the update on the old branch name.
   238  			_, modPerms := tbl.Access().Match(oldDatabase, oldBranch, insertUser, insertHost)
   239  			if modPerms&branch_control.Permissions_Admin != branch_control.Permissions_Admin {
   240  				return branch_control.ErrUpdatingRow.New(insertUser, insertHost, oldDatabase, oldBranch, oldUser, oldHost)
   241  			}
   242  		}
   243  		if !branch_control.HasDatabasePrivileges(branchAwareSession, newDatabase) {
   244  			// Similar to the block above, we check if the user has permission to use the new branch name
   245  			_, modPerms := tbl.Access().Match(newDatabase, newBranch, insertUser, insertHost)
   246  			if modPerms&branch_control.Permissions_Admin != branch_control.Permissions_Admin {
   247  				return branch_control.ErrUpdatingToRow.
   248  					New(insertUser, insertHost, oldDatabase, oldBranch, oldUser, oldHost, newDatabase, newBranch)
   249  			}
   250  		}
   251  	}
   252  
   253  	if tblIndex := tbl.GetIndex(oldDatabase, oldBranch, oldUser, oldHost); tblIndex != -1 {
   254  		if err := tbl.delete(ctx, oldDatabase, oldBranch, oldUser, oldHost); err != nil {
   255  			return err
   256  		}
   257  	}
   258  	return tbl.insert(ctx, newDatabase, newBranch, newUser, newHost)
   259  }
   260  
   261  // Delete implements the interface sql.RowDeleter.
   262  func (tbl BranchNamespaceControlTable) Delete(ctx *sql.Context, row sql.Row) error {
   263  	tbl.RWMutex.Lock()
   264  	defer tbl.RWMutex.Unlock()
   265  
   266  	// Database, Branch, and Host are case-insensitive, while User is case-sensitive
   267  	database := strings.ToLower(branch_control.FoldExpression(row[0].(string)))
   268  	branch := strings.ToLower(branch_control.FoldExpression(row[1].(string)))
   269  	user := branch_control.FoldExpression(row[2].(string))
   270  	host := strings.ToLower(branch_control.FoldExpression(row[3].(string)))
   271  
   272  	// A nil session means we're not in the SQL context, so we allow the deletion in such a case
   273  	if branchAwareSession := branch_control.GetBranchAwareSession(ctx); branchAwareSession != nil &&
   274  		// Having the correct database privileges also allows the deletion
   275  		!branch_control.HasDatabasePrivileges(branchAwareSession, database) {
   276  
   277  		// tbl.Access() shares a lock with the namespace table. No need to acquire its lock.
   278  
   279  		insertUser := branchAwareSession.GetUser()
   280  		insertHost := branchAwareSession.GetHost()
   281  		// As we've folded the branch expression, we can use it directly as though it were a normal branch name to
   282  		// determine if the user attempting the deletion has permission to perform the deletion.
   283  		_, modPerms := tbl.Access().Match(database, branch, insertUser, insertHost)
   284  		if modPerms&branch_control.Permissions_Admin != branch_control.Permissions_Admin {
   285  			return branch_control.ErrDeletingRow.New(insertUser, insertHost, database, branch, user, host)
   286  		}
   287  	}
   288  
   289  	return tbl.delete(ctx, database, branch, user, host)
   290  }
   291  
   292  // Close implements the interface sql.Closer.
   293  func (tbl BranchNamespaceControlTable) Close(context *sql.Context) error {
   294  	return branch_control.SaveData(context)
   295  }
   296  
   297  // insert adds the given branch, user, and host expression strings to the table. Assumes that the expressions have
   298  // already been folded.
   299  func (tbl BranchNamespaceControlTable) insert(ctx context.Context, database, branch, user, host string) error {
   300  	// If we already have this in the table, then we return a duplicate PK error
   301  	if tblIndex := tbl.GetIndex(database, branch, user, host); tblIndex != -1 {
   302  		return sql.NewUniqueKeyErr(
   303  			fmt.Sprintf(`[%q, %q, %q, %q]`, database, branch, user, host),
   304  			true,
   305  			sql.Row{database, branch, user, host})
   306  	}
   307  
   308  	// Add an entry to the binlog
   309  	tbl.GetBinlog().Insert(database, branch, user, host, 0)
   310  	// Add the expressions to their respective slices
   311  	databaseExpr := branch_control.ParseExpression(database, sql.Collation_utf8mb4_0900_ai_ci)
   312  	branchExpr := branch_control.ParseExpression(branch, sql.Collation_utf8mb4_0900_ai_ci)
   313  	userExpr := branch_control.ParseExpression(user, sql.Collation_utf8mb4_0900_bin)
   314  	hostExpr := branch_control.ParseExpression(host, sql.Collation_utf8mb4_0900_ai_ci)
   315  	nextIdx := uint32(len(tbl.Values))
   316  	tbl.Databases = append(tbl.Databases, branch_control.MatchExpression{CollectionIndex: nextIdx, SortOrders: databaseExpr})
   317  	tbl.Branches = append(tbl.Branches, branch_control.MatchExpression{CollectionIndex: nextIdx, SortOrders: branchExpr})
   318  	tbl.Users = append(tbl.Users, branch_control.MatchExpression{CollectionIndex: nextIdx, SortOrders: userExpr})
   319  	tbl.Hosts = append(tbl.Hosts, branch_control.MatchExpression{CollectionIndex: nextIdx, SortOrders: hostExpr})
   320  	tbl.Values = append(tbl.Values, branch_control.NamespaceValue{
   321  		Database: database,
   322  		Branch:   branch,
   323  		User:     user,
   324  		Host:     host,
   325  	})
   326  	return nil
   327  }
   328  
   329  // delete removes the given branch, user, and host expression strings from the table. Assumes that the expressions have
   330  // already been folded.
   331  func (tbl BranchNamespaceControlTable) delete(ctx context.Context, database, branch, user, host string) error {
   332  	// If we don't have this in the table, then we just return
   333  	tblIndex := tbl.GetIndex(database, branch, user, host)
   334  	if tblIndex == -1 {
   335  		return nil
   336  	}
   337  
   338  	endIndex := len(tbl.Values) - 1
   339  	// Add an entry to the binlog
   340  	tbl.GetBinlog().Delete(database, branch, user, host, 0)
   341  	// Remove the matching row from all slices by first swapping with the last element
   342  	tbl.Databases[tblIndex], tbl.Databases[endIndex] = tbl.Databases[endIndex], tbl.Databases[tblIndex]
   343  	tbl.Branches[tblIndex], tbl.Branches[endIndex] = tbl.Branches[endIndex], tbl.Branches[tblIndex]
   344  	tbl.Users[tblIndex], tbl.Users[endIndex] = tbl.Users[endIndex], tbl.Users[tblIndex]
   345  	tbl.Hosts[tblIndex], tbl.Hosts[endIndex] = tbl.Hosts[endIndex], tbl.Hosts[tblIndex]
   346  	tbl.Values[tblIndex], tbl.Values[endIndex] = tbl.Values[endIndex], tbl.Values[tblIndex]
   347  	// Then we remove the last element
   348  	tbl.Databases = tbl.Databases[:endIndex]
   349  	tbl.Branches = tbl.Branches[:endIndex]
   350  	tbl.Users = tbl.Users[:endIndex]
   351  	tbl.Hosts = tbl.Hosts[:endIndex]
   352  	tbl.Values = tbl.Values[:endIndex]
   353  	// Then we update the index for the match expressions
   354  	if tblIndex != endIndex {
   355  		tbl.Databases[tblIndex].CollectionIndex = uint32(tblIndex)
   356  		tbl.Branches[tblIndex].CollectionIndex = uint32(tblIndex)
   357  		tbl.Users[tblIndex].CollectionIndex = uint32(tblIndex)
   358  		tbl.Hosts[tblIndex].CollectionIndex = uint32(tblIndex)
   359  	}
   360  	return nil
   361  }