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 }