@@ -4432,6 +4432,7 @@ FROM
4432
4432
})
4433
4433
opStmt .potentialExecErrors .addAll (codesWithConditions {
4434
4434
{pgcode .InvalidFunctionDefinition , hasFuncRefs },
4435
+ {pgcode .FeatureNotSupported , true },
4435
4436
})
4436
4437
4437
4438
return opStmt , nil
@@ -5338,3 +5339,160 @@ func (og *operationGenerator) randUser(ctx context.Context, tx pgx.Tx) (string,
5338
5339
// There should always be at least one user.
5339
5340
return "" , errors .New ("no users found in the database" )
5340
5341
}
5342
+
5343
+ // createTrigger generates a CREATE TRIGGER statement.
5344
+ func (og * operationGenerator ) createTrigger (ctx context.Context , tx pgx.Tx ) (* opStmt , error ) {
5345
+ tableName , err := og .randTable (ctx , tx , og .pctExisting (true ), "" )
5346
+ if err != nil {
5347
+ return nil , err
5348
+ }
5349
+
5350
+ tableExists , err := og .tableExists (ctx , tx , tableName )
5351
+ if err != nil {
5352
+ return nil , err
5353
+ }
5354
+
5355
+ // First, ensure the trigger_log table exists
5356
+ _ , err = tx .Exec (ctx , `
5357
+ CREATE TABLE IF NOT EXISTS trigger_log (
5358
+ changed_at TIMESTAMP DEFAULT current_timestamp
5359
+ )` )
5360
+ if err != nil {
5361
+ return nil , err
5362
+ }
5363
+
5364
+ // Create TRIGGER statement components
5365
+ triggerActionTime := "BEFORE"
5366
+ if og .randIntn (2 ) == 1 {
5367
+ triggerActionTime = "AFTER"
5368
+ }
5369
+
5370
+ eventTypes := []string {"INSERT" , "UPDATE" , "DELETE" }
5371
+ numEvents := og .randIntn (3 ) + 1 // 1-3 events
5372
+ events := make ([]string , 0 , numEvents )
5373
+ eventsSet := make (map [string ]bool )
5374
+
5375
+ for i := 0 ; i < numEvents ; i ++ {
5376
+ eventIndex := og .randIntn (len (eventTypes ))
5377
+ event := eventTypes [eventIndex ]
5378
+ if ! eventsSet [event ] {
5379
+ events = append (events , event )
5380
+ eventsSet [event ] = true
5381
+ }
5382
+ }
5383
+
5384
+ // Join events with OR
5385
+ eventClause := strings .Join (events , " OR " )
5386
+
5387
+ triggerName := fmt .Sprintf ("trigger_%s" , og .newUniqueSeqNumSuffix ())
5388
+
5389
+ // Build the SQL statement
5390
+ var sqlStatement strings.Builder
5391
+ sqlStatement .WriteString (fmt .Sprintf ("CREATE TRIGGER %s %s %s ON %s FOR EACH ROW EXECUTE FUNCTION log_change_timestamp()" ,
5392
+ triggerName , triggerActionTime , eventClause , tableName ))
5393
+
5394
+ og .LogMessage (fmt .Sprintf ("createTrigger: %s" , sqlStatement .String ()))
5395
+
5396
+ opStmt := makeOpStmt (OpStmtDDL )
5397
+ opStmt .sql = sqlStatement .String ()
5398
+
5399
+ opStmt .expectedExecErrors .addAll (codesWithConditions {
5400
+ {code : pgcode .FeatureNotSupported , condition : ! og .useDeclarativeSchemaChanger },
5401
+ {code : pgcode .UndefinedTable , condition : ! tableExists },
5402
+ })
5403
+
5404
+ opStmt .potentialExecErrors .addAll (codesWithConditions {
5405
+ {code : pgcode .UndefinedFunction , condition : true },
5406
+ })
5407
+
5408
+ return opStmt , nil
5409
+ }
5410
+
5411
+ // dropTrigger generates a DROP TRIGGER statement.
5412
+ func (og * operationGenerator ) dropTrigger (ctx context.Context , tx pgx.Tx ) (* opStmt , error ) {
5413
+ // Find an existing trigger
5414
+ triggerWithInfo , triggerExists , err := findExistingTrigger (ctx , tx , og )
5415
+ if err != nil {
5416
+ return nil , err
5417
+ }
5418
+ if ! triggerExists {
5419
+
5420
+ // Build the SQL statement
5421
+ var sqlStatement strings.Builder
5422
+ sqlStatement .WriteString (`DROP TRIGGER dummy_trigger ON dummy_table` )
5423
+
5424
+ // Set up expected errors since we know this trigger doesn't exist
5425
+ opStmt := makeOpStmt (OpStmtDDL )
5426
+ opStmt .sql = sqlStatement .String ()
5427
+
5428
+ opStmt .expectedExecErrors .add (pgcode .UndefinedTable )
5429
+
5430
+ og .LogMessage (fmt .Sprintf ("dropTrigger (non-existent): %s" , sqlStatement .String ()))
5431
+
5432
+ return opStmt , nil
5433
+ }
5434
+
5435
+ // Build the SQL statement
5436
+ var sqlStatement strings.Builder
5437
+ sqlStatement .WriteString (fmt .Sprintf ("DROP TRIGGER %s ON %s" , triggerWithInfo .triggerName , & triggerWithInfo .table ))
5438
+
5439
+ // Construct DROP TRIGGER statement
5440
+ og .LogMessage (fmt .Sprintf ("dropTrigger: %s" , sqlStatement .String ()))
5441
+
5442
+ opStmt := makeOpStmt (OpStmtDDL )
5443
+ opStmt .sql = sqlStatement .String ()
5444
+
5445
+ return opStmt , nil
5446
+ }
5447
+
5448
+ // triggerInfo contains information about a trigger.
5449
+ type triggerInfo struct {
5450
+ table tree.TableName
5451
+ triggerName string
5452
+ }
5453
+
5454
+ // findExistingTrigger returns a triggerInfo struct with the qualified table name and trigger name.
5455
+ // It also returns a boolean indicating whether a trigger was found.
5456
+ func findExistingTrigger (
5457
+ ctx context.Context , tx pgx.Tx , og * operationGenerator ,
5458
+ ) (* triggerInfo , bool , error ) {
5459
+ var triggerWithInfo triggerInfo
5460
+ triggerExists := false
5461
+
5462
+ // Query to find all triggers in the database using system tables
5463
+ triggerQuery := `
5464
+ SELECT
5465
+ ns.name AS schema_name,
5466
+ crdb_internal.pb_to_json('descriptor', d.descriptor) -> 'table' ->> 'name' AS table_name,
5467
+ trigger->>'name' AS trigger_name
5468
+ FROM system.descriptor AS d
5469
+ JOIN system.namespace AS ns
5470
+ ON (crdb_internal.pb_to_json('descriptor', d.descriptor) -> 'table' ->> 'unexposedParentSchemaId')::INT = ns.id
5471
+ JOIN LATERAL json_array_elements(
5472
+ crdb_internal.pb_to_json('descriptor', d.descriptor) -> 'table' -> 'triggers'
5473
+ ) AS trigger ON TRUE
5474
+ WHERE (crdb_internal.pb_to_json('descriptor', d.descriptor) -> 'table' -> 'triggers') IS NOT NULL
5475
+ ORDER BY random()
5476
+ LIMIT 1
5477
+ `
5478
+
5479
+ var schemaName , tableName , triggerName string
5480
+ err := tx .QueryRow (ctx , triggerQuery ).Scan (& schemaName , & tableName , & triggerName )
5481
+ if err != nil {
5482
+ if errors .Is (err , pgx .ErrNoRows ) {
5483
+ return nil , false , nil
5484
+ }
5485
+ return nil , false , err
5486
+ }
5487
+
5488
+ triggerWithInfo = triggerInfo {
5489
+ table : tree .MakeTableNameFromPrefix (tree.ObjectNamePrefix {
5490
+ SchemaName : tree .Name (schemaName ),
5491
+ ExplicitSchema : true ,
5492
+ }, tree .Name (tableName )),
5493
+ triggerName : triggerName ,
5494
+ }
5495
+ triggerExists = true
5496
+
5497
+ return & triggerWithInfo , triggerExists , nil
5498
+ }
0 commit comments