@@ -5171,3 +5171,145 @@ func (og *operationGenerator) dropPolicy(ctx context.Context, tx pgx.Tx) (*opStm
51715171
51725172 return opStmt , nil
51735173}
5174+
5175+ func (og * operationGenerator ) alterPolicy (ctx context.Context , tx pgx.Tx ) (* opStmt , error ) {
5176+ // Try to find an existing policy to alter
5177+ policyWithInfo , policyExists , err := findExistingPolicy (ctx , tx , og )
5178+ if err != nil {
5179+ return nil , err
5180+ }
5181+
5182+ // Variable to track table existence
5183+ var tableExists bool = true
5184+
5185+ if ! policyExists {
5186+ // Fall back to random table if no tables with policies were found
5187+ randomTable , err := og .randTable (ctx , tx , og .pctExisting (true ), "" )
5188+ if err != nil {
5189+ return nil , err
5190+ }
5191+ policyWithInfo .table = * randomTable
5192+
5193+ // If we didn't get a real policy name, generate a random one
5194+ if policyWithInfo .policyName == "" {
5195+ policyWithInfo .policyName = fmt .Sprintf ("dummy_policy_%s" , og .newUniqueSeqNumSuffix ())
5196+ }
5197+
5198+ // Check if table exists to include appropriate expected error
5199+ tableExists , err = og .tableExists (ctx , tx , randomTable )
5200+ if err != nil {
5201+ return nil , err
5202+ }
5203+ }
5204+
5205+ // Determine which ALTER POLICY features to include
5206+ alterType := og .randIntn (4 ) // 0-3 for different types of alterations
5207+
5208+ var sqlStatement strings.Builder
5209+ sqlStatement .WriteString (fmt .Sprintf ("ALTER POLICY %s ON %s" , policyWithInfo .policyName , & policyWithInfo .table ))
5210+
5211+ usesDummyRole := false
5212+ includeUsing := false
5213+ includeWithCheck := false
5214+
5215+ var columns []string
5216+
5217+ switch alterType {
5218+ case 0 : // RENAME TO
5219+ newName := fmt .Sprintf ("policy_%s" , og .newUniqueSeqNumSuffix ())
5220+ sqlStatement .WriteString (fmt .Sprintf (" RENAME TO %s" , newName ))
5221+ case 1 : // TO roles
5222+ // Define roles to grant the policy to
5223+ var roles string
5224+ if og .randIntn (2 ) == 0 {
5225+ // 50% chance to a real username
5226+ roles , err = og .randUser (ctx , tx )
5227+ if err != nil {
5228+ return nil , err
5229+ }
5230+ } else if og .randIntn (4 ) == 0 {
5231+ // Fall back to two options: 25% chance for PUBLIC or a 75% for generated dummy role
5232+ roles = "PUBLIC"
5233+ } else {
5234+ // Generate a random role name that doesn't exist
5235+ roles = fmt .Sprintf ("dummy_role_%s" , og .newUniqueSeqNumSuffix ())
5236+ usesDummyRole = true
5237+ }
5238+
5239+ sqlStatement .WriteString (fmt .Sprintf (" TO %s" , roles ))
5240+ default : // USING and/or WITH CHECK expressions
5241+ // For case 2 and 3, we generate USING, WITH CHECK, or both
5242+ includeUsing = alterType == 2 || og .randIntn (2 ) == 0
5243+ includeWithCheck = alterType == 3 || og .randIntn (2 ) == 0
5244+
5245+ // If neither was selected, default to including USING
5246+ if ! includeUsing && ! includeWithCheck {
5247+ includeUsing = true
5248+ }
5249+
5250+ // Get columns for the table to reference in expressions
5251+ if tableExists {
5252+ columns , err = og .tableColumnsShuffled (ctx , tx , policyWithInfo .table .String ())
5253+ if err != nil {
5254+ return nil , err
5255+ }
5256+ }
5257+
5258+ // Generate expressions for USING and WITH CHECK
5259+ if includeUsing {
5260+ usingExpr := og .generatePolicyExpression (columns , - 1 ) // -1 means no preferred column
5261+ sqlStatement .WriteString (fmt .Sprintf (" USING (%s)" , usingExpr ))
5262+ }
5263+
5264+ if includeWithCheck {
5265+ // Try to use a different column for WITH CHECK if possible
5266+ preferredColIdx := - 1
5267+ if len (columns ) > 1 && includeUsing {
5268+ preferredColIdx = og .randIntn (len (columns ))
5269+ }
5270+
5271+ withCheckExpr := og .generatePolicyExpression (columns , preferredColIdx )
5272+ sqlStatement .WriteString (fmt .Sprintf (" WITH CHECK (%s)" , withCheckExpr ))
5273+ }
5274+ }
5275+
5276+ // Create the operation statement
5277+ opStmt := makeOpStmt (OpStmtDDL )
5278+ opStmt .sql = sqlStatement .String ()
5279+
5280+ opStmt .expectedExecErrors .addAll (codesWithConditions {
5281+ {code : pgcode .UndefinedObject , condition : ! policyExists },
5282+ {code : pgcode .UndefinedTable , condition : ! tableExists },
5283+ {code : pgcode .UndefinedObject , condition : usesDummyRole },
5284+ })
5285+
5286+ return opStmt , nil
5287+ }
5288+
5289+ // randUser returns a real username from the database.
5290+ // It returns an error if no user is found.
5291+ func (og * operationGenerator ) randUser (ctx context.Context , tx pgx.Tx ) (string , error ) {
5292+ query := "SELECT username FROM [SHOW USERS] ORDER BY random() LIMIT 1"
5293+ rows , err := tx .Query (ctx , query )
5294+ if rows .Err () != nil {
5295+ return "" , rows .Err ()
5296+ }
5297+
5298+ if err != nil {
5299+ return "" , err
5300+ }
5301+ defer rows .Close ()
5302+
5303+ var realUser string
5304+ if rows .Next () {
5305+ if err := rows .Scan (& realUser ); err != nil {
5306+ return "" , err
5307+ }
5308+ og .LogMessage (fmt .Sprintf ("Found real user: '%s'" , realUser ))
5309+ return realUser , nil
5310+ }
5311+
5312+ // This should never happen in a valid CockroachDB instance.
5313+ // There should always be at least one user.
5314+ return "" , errors .New ("no users found in the database" )
5315+ }
0 commit comments