@@ -486,6 +486,89 @@ def action_clear_results(self: ResultsMixinHost) -> None:
486486 self ._last_result_rows = []
487487 self ._last_result_row_count = 0
488488
489+ def action_delete_row (self : ResultsMixinHost ) -> None :
490+ """Generate a DELETE query for the selected row and enter insert mode."""
491+ table , columns , _rows , _stacked = self ._get_active_results_context ()
492+ if not table or table .row_count <= 0 :
493+ self .notify ("No results" , severity = "warning" )
494+ return
495+
496+ if not columns :
497+ self .notify ("No column info" , severity = "warning" )
498+ return
499+
500+ try :
501+ cursor_row , _cursor_col = table .cursor_coordinate
502+ row_values = table .get_row_at (cursor_row )
503+ except Exception :
504+ return
505+
506+ # Format value for SQL
507+ def sql_value (v : object ) -> str :
508+ if v is None :
509+ return "NULL"
510+ if isinstance (v , bool ):
511+ return "TRUE" if v else "FALSE"
512+ if isinstance (v , int | float ):
513+ return str (v )
514+ # String - escape single quotes
515+ return "'" + str (v ).replace ("'" , "''" ) + "'"
516+
517+ # Get table name and primary key columns
518+ table_name = "<table>"
519+ pk_column_names : set [str ] = set ()
520+
521+ if hasattr (self , "_last_query_table" ) and self ._last_query_table :
522+ table_info = self ._last_query_table
523+ table_name = table_info ["name" ]
524+ # Get PK columns from column info
525+ for col in table_info .get ("columns" , []):
526+ if col .is_primary_key :
527+ pk_column_names .add (col .name )
528+
529+ # Build WHERE clause - prefer PK columns, fall back to all columns
530+ where_parts = []
531+ for i , col in enumerate (columns ):
532+ if i < len (row_values ):
533+ # If we have PK info, only use PK columns; otherwise use all columns
534+ if pk_column_names and col not in pk_column_names :
535+ continue
536+ val = row_values [i ]
537+ if val is None :
538+ where_parts .append (f"{ col } IS NULL" )
539+ else :
540+ where_parts .append (f"{ col } = { sql_value (val )} " )
541+
542+ # If no where parts (no PKs matched result columns), fall back to all columns
543+ if not where_parts :
544+ for i , col in enumerate (columns ):
545+ if i < len (row_values ):
546+ val = row_values [i ]
547+ if val is None :
548+ where_parts .append (f"{ col } IS NULL" )
549+ else :
550+ where_parts .append (f"{ col } = { sql_value (val )} " )
551+
552+ if not where_parts :
553+ self .notify ("No row values" , severity = "warning" )
554+ return
555+
556+ where_clause = " AND " .join (where_parts )
557+
558+ # Generate DELETE query for the row
559+ query = f"DELETE FROM { table_name } WHERE { where_clause } ;"
560+
561+ # Set query and switch to insert mode
562+ self ._suppress_autocomplete_once = True
563+ self .query_input .text = query
564+ # Position cursor before the trailing semicolon
565+ cursor_pos = max (len (query ) - 1 , 0 )
566+ self .query_input .cursor_location = (0 , cursor_pos )
567+
568+ # Focus query editor but keep NORMAL mode (no INSERT for deletes)
569+ self .action_focus_query ()
570+ self ._update_footer_bindings ()
571+
489572 def action_edit_cell (self : ResultsMixinHost ) -> None :
490573 """Generate an UPDATE query for the selected cell and enter insert mode."""
491574 table , columns , _rows , _stacked = self ._get_active_results_context ()
@@ -571,6 +654,7 @@ def sql_value(v: object) -> str:
571654 cursor_pos = query .find (set_prefix ) + len (set_prefix )
572655
573656 # Set query and switch to insert mode
657+ self ._suppress_autocomplete_once = True
574658 self .query_input .text = query
575659 self .query_input .focus ()
576660
0 commit comments