From ceec17a000e1f031f4966ca3b52b8995e3b45c9f Mon Sep 17 00:00:00 2001 From: mihsan Date: Thu, 4 Jun 2026 09:58:17 +0300 Subject: [PATCH] feat: add hotkey for DROP TABLE query generation --- config/keymap.template.json | 1 + sqlit/core/input_context.py | 1 + sqlit/core/keymap.py | 1 + .../connections/providers/adapter_provider.py | 1 + .../connections/providers/adapters/base.py | 15 +++++++++ .../connections/providers/athena/adapter.py | 4 +++ .../connections/providers/bigquery/adapter.py | 5 +++ .../providers/clickhouse/adapter.py | 9 ++++++ .../connections/providers/d1/adapter.py | 4 +++ .../connections/providers/db2/adapter.py | 5 +++ .../connections/providers/duckdb/adapter.py | 4 +++ .../connections/providers/firebird/adapter.py | 4 +++ .../connections/providers/flight/adapter.py | 8 +++++ .../connections/providers/hana/adapter.py | 5 +++ .../connections/providers/impala/adapter.py | 3 ++ sqlit/domains/connections/providers/model.py | 4 +++ .../connections/providers/mssql/adapter.py | 5 +++ .../connections/providers/mysql/base.py | 6 ++++ .../connections/providers/oracle/adapter.py | 4 +++ .../connections/providers/osquery/adapter.py | 8 +++++ .../connections/providers/postgresql/base.py | 5 +++ .../connections/providers/presto/adapter.py | 8 +++++ .../connections/providers/redshift/adapter.py | 5 +++ .../providers/snowflake/adapter.py | 5 +++ .../connections/providers/spanner/adapter.py | 5 +++ .../connections/providers/sqlite/adapter.py | 4 +++ .../providers/surrealdb/adapter.py | 4 +++ .../connections/providers/teradata/adapter.py | 4 +++ .../connections/providers/trino/adapter.py | 9 ++++++ .../connections/providers/turso/adapter.py | 4 +++ sqlit/domains/explorer/state/tree_on_table.py | 15 +++++++++ sqlit/domains/explorer/ui/mixins/tree.py | 32 +++++++++++++++++++ sqlit/domains/shell/app/main.py | 3 ++ sqlit/domains/shell/state/machine.py | 1 + 34 files changed, 201 insertions(+) diff --git a/config/keymap.template.json b/config/keymap.template.json index 7b750141..09f31f4b 100644 --- a/config/keymap.template.json +++ b/config/keymap.template.json @@ -22,6 +22,7 @@ "enter_tree_visual_mode": "v", "clear_connection_selection": "escape", "select_table": "s", + "drop_table": "d", "refresh_tree": [ "f", "R" diff --git a/sqlit/core/input_context.py b/sqlit/core/input_context.py index 45f512b1..7230de3c 100644 --- a/sqlit/core/input_context.py +++ b/sqlit/core/input_context.py @@ -34,3 +34,4 @@ class InputContext: has_results: bool stacked_result_count: int = 0 count_buffer: str = "" + current_provider_supports_drop_table: bool = False diff --git a/sqlit/core/keymap.py b/sqlit/core/keymap.py index 7f320e49..608b4fe2 100644 --- a/sqlit/core/keymap.py +++ b/sqlit/core/keymap.py @@ -347,6 +347,7 @@ def _build_action_keys(self) -> list[ActionKeyDef]: ActionKeyDef("d", "delete_connection_folder", "tree"), ActionKeyDef("delete", "delete_connection_folder", "tree", primary=False), ActionKeyDef("d", "delete_connection", "tree"), + ActionKeyDef("d", "drop_table", "tree"), ActionKeyDef("delete", "delete_connection", "tree", primary=False), ActionKeyDef("D", "duplicate_connection", "tree"), ActionKeyDef("m", "move_connection_to_folder", "tree"), diff --git a/sqlit/domains/connections/providers/adapter_provider.py b/sqlit/domains/connections/providers/adapter_provider.py index f2400649..53b68ae7 100644 --- a/sqlit/domains/connections/providers/adapter_provider.py +++ b/sqlit/domains/connections/providers/adapter_provider.py @@ -90,6 +90,7 @@ def build_adapter_provider(spec: ProviderSpec, schema: ConnectionSchema, adapter supports_indexes=bool(getattr(adapter, "supports_indexes", False)), supports_triggers=bool(getattr(adapter, "supports_triggers", False)), supports_sequences=bool(getattr(adapter, "supports_sequences", False)), + support_drop_table=(bool(getattr(adapter, "supports_drop_table", False))), default_schema=str(getattr(adapter, "default_schema", "")), system_databases=frozenset(getattr(adapter, "system_databases", frozenset())), ) diff --git a/sqlit/domains/connections/providers/adapters/base.py b/sqlit/domains/connections/providers/adapters/base.py index 109ea1da..6b1e1838 100644 --- a/sqlit/domains/connections/providers/adapters/base.py +++ b/sqlit/domains/connections/providers/adapters/base.py @@ -199,6 +199,10 @@ def supports_process_worker(self) -> bool: """Whether this adapter supports running queries in a separate process.""" return True + @property + def supports_drop_table(self) -> bool: + return True + @property def test_query(self) -> str: """A simple query to test the connection. @@ -445,6 +449,17 @@ def build_select_query(self, table: str, limit: int, database: str | None = None """ pass + @abstractmethod + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build a SELECT query with limit. + + Args: + table: Table name. + database: Database name (if supported). + schema: Schema name (if supported). + """ + pass + @abstractmethod def execute_query(self, conn: Any, query: str, max_rows: int | None = None) -> tuple[list[str], list[tuple], bool]: """Execute a query and return (columns, rows, truncated). diff --git a/sqlit/domains/connections/providers/athena/adapter.py b/sqlit/domains/connections/providers/athena/adapter.py index 2f321925..b4246045 100644 --- a/sqlit/domains/connections/providers/athena/adapter.py +++ b/sqlit/domains/connections/providers/athena/adapter.py @@ -149,6 +149,10 @@ def build_select_query(self, table: str, limit: int, database: str | None = None target_db = database or schema or self.default_schema return f"SELECT * FROM {target_db}.{table} LIMIT {limit}" + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + return f'DROP TABLE IF EXISTS "{database or ""}.{table}"' + def get_procedures(self, conn: Any, database: str | None = None) -> list[str]: return [] diff --git a/sqlit/domains/connections/providers/bigquery/adapter.py b/sqlit/domains/connections/providers/bigquery/adapter.py index 4d427648..ca9fba29 100644 --- a/sqlit/domains/connections/providers/bigquery/adapter.py +++ b/sqlit/domains/connections/providers/bigquery/adapter.py @@ -375,3 +375,8 @@ def build_select_query( return f"SELECT * FROM `{dataset}.{table}` LIMIT {limit}" return f"SELECT * FROM `{dataset}`.`{table}` LIMIT {limit}" return f"SELECT * FROM `{table}` LIMIT {limit}" + + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + dataset = schema or database + return f"DROP TABLE IF EXISTS `{dataset}.{table}`" diff --git a/sqlit/domains/connections/providers/clickhouse/adapter.py b/sqlit/domains/connections/providers/clickhouse/adapter.py index 31b2e009..a7252fdc 100644 --- a/sqlit/domains/connections/providers/clickhouse/adapter.py +++ b/sqlit/domains/connections/providers/clickhouse/adapter.py @@ -296,6 +296,15 @@ def build_select_query( return f"SELECT * FROM {quoted_db}.{quoted_table} LIMIT {limit}" return f"SELECT * FROM {quoted_table} LIMIT {limit}" + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + db = database or schema + quoted_table = self.quote_identifier(table) + if db: + quoted_db = self.quote_identifier(db) + return f"DROP TABLE IF EXISTS {quoted_db}.{quoted_table}" + return f"DROP TABLE IF EXISTS {quoted_table}" + def execute_query( self, conn: Any, query: str, max_rows: int | None = None ) -> tuple[list[str], list[tuple], bool]: diff --git a/sqlit/domains/connections/providers/d1/adapter.py b/sqlit/domains/connections/providers/d1/adapter.py index 00d507fa..ed845cdb 100644 --- a/sqlit/domains/connections/providers/d1/adapter.py +++ b/sqlit/domains/connections/providers/d1/adapter.py @@ -269,6 +269,10 @@ def build_select_query(self, table: str, limit: int, database: str | None = None """Builds a standard SELECT ... LIMIT query.""" return f"SELECT * FROM {self.quote_identifier(table)} LIMIT {limit}" + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + return f"DROP TABLE IF EXISTS {self.quote_identifier(table)}" + def execute_query( self, conn: D1Connection, query: str, max_rows: int | None = None ) -> tuple[list[str], list[tuple], bool]: diff --git a/sqlit/domains/connections/providers/db2/adapter.py b/sqlit/domains/connections/providers/db2/adapter.py index 7aafbd82..1b6196cb 100644 --- a/sqlit/domains/connections/providers/db2/adapter.py +++ b/sqlit/domains/connections/providers/db2/adapter.py @@ -190,3 +190,8 @@ def build_select_query(self, table: str, limit: int, database: str | None = None if schema: return f'SELECT * FROM "{schema}"."{table}" FETCH FIRST {limit} ROWS ONLY' return f'SELECT * FROM "{table}" FETCH FIRST {limit} ROWS ONLY' + + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + schema = schema or "" + return f'DROP TABLE "{schema}"."{table}"' if schema else f'DROP TABLE "{table}"' diff --git a/sqlit/domains/connections/providers/duckdb/adapter.py b/sqlit/domains/connections/providers/duckdb/adapter.py index 0bc97462..1079f521 100644 --- a/sqlit/domains/connections/providers/duckdb/adapter.py +++ b/sqlit/domains/connections/providers/duckdb/adapter.py @@ -275,6 +275,10 @@ def build_select_query(self, table: str, limit: int, database: str | None = None schema = schema or "main" return f'SELECT * FROM "{schema}"."{table}" LIMIT {limit}' + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + schema = schema or "main" + return f'DROP TABLE IF EXISTS "{schema}"."{table}"' + def execute_query(self, conn: Any, query: str, max_rows: int | None = None) -> tuple[list[str], list[tuple], bool]: """Execute a query on DuckDB with optional row limit.""" result = conn.execute(query) diff --git a/sqlit/domains/connections/providers/firebird/adapter.py b/sqlit/domains/connections/providers/firebird/adapter.py index f424cb0b..6296ff38 100644 --- a/sqlit/domains/connections/providers/firebird/adapter.py +++ b/sqlit/domains/connections/providers/firebird/adapter.py @@ -347,6 +347,10 @@ def build_select_query( """Build SELECT LIMIT query.""" return f'SELECT * FROM "{table}" ROWS {limit}' + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + return f'DROP TABLE "{table}"' + def execute_non_query(self, conn: Any, query: str) -> int: # Firebird has no autocommit mode, so we need to guarantee it ourselves. try: diff --git a/sqlit/domains/connections/providers/flight/adapter.py b/sqlit/domains/connections/providers/flight/adapter.py index 0dce60d7..96c9f8eb 100644 --- a/sqlit/domains/connections/providers/flight/adapter.py +++ b/sqlit/domains/connections/providers/flight/adapter.py @@ -358,6 +358,14 @@ def build_select_query( return f"SELECT * FROM {quoted_schema}.{quoted_table} LIMIT {limit}" return f"SELECT * FROM {quoted_table} LIMIT {limit}" + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + quoted_table = self.quote_identifier(table) + if schema: + quoted_schema = self.quote_identifier(schema) + return f'DROP TABLE IF EXISTS {quoted_schema}.{quoted_table}' + return f'DROP TABLE IF EXISTS {quoted_table}' + def _arrow_table_to_tuples( self, table: Any ) -> tuple[list[str], list[tuple[Any, ...]]]: diff --git a/sqlit/domains/connections/providers/hana/adapter.py b/sqlit/domains/connections/providers/hana/adapter.py index 12afe5d3..a54d72a5 100644 --- a/sqlit/domains/connections/providers/hana/adapter.py +++ b/sqlit/domains/connections/providers/hana/adapter.py @@ -183,3 +183,8 @@ def quote_identifier(self, name: str) -> str: def build_select_query(self, table: str, limit: int, database: str | None = None, schema: str | None = None) -> str: schema = schema or self.default_schema return f'SELECT * FROM "{schema}"."{table}" LIMIT {limit}' + + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + schema = schema or self.default_schema + return f'DROP TABLE "{schema}"."{table}"' diff --git a/sqlit/domains/connections/providers/impala/adapter.py b/sqlit/domains/connections/providers/impala/adapter.py index f9a9f7a7..600ce302 100644 --- a/sqlit/domains/connections/providers/impala/adapter.py +++ b/sqlit/domains/connections/providers/impala/adapter.py @@ -174,3 +174,6 @@ def build_select_query( if database: return f"SELECT * FROM `{database}`.`{table}` LIMIT {limit}" return f"SELECT * FROM `{table}` LIMIT {limit}" + + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + return f"DROP TABLE IF EXISTS `{database}`.`{table}`" if database else f"DROP TABLE IF EXISTS `{table}`" diff --git a/sqlit/domains/connections/providers/model.py b/sqlit/domains/connections/providers/model.py index b104db3c..8fd2cfd8 100644 --- a/sqlit/domains/connections/providers/model.py +++ b/sqlit/domains/connections/providers/model.py @@ -36,6 +36,7 @@ class SchemaCapabilities: supports_indexes: bool supports_triggers: bool supports_sequences: bool + support_drop_table: bool default_schema: str system_databases: frozenset[str] @@ -60,6 +61,9 @@ def quote_identifier(self, name: str) -> str: ... def build_select_query(self, table: str, limit: int, database: str | None = None, schema: str | None = None) -> str: ... + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + ... + def format_table_name(self, schema: str | None, table: str) -> str: ... def qualified_name(self, database: str | None, schema: str | None, name: str) -> str: diff --git a/sqlit/domains/connections/providers/mssql/adapter.py b/sqlit/domains/connections/providers/mssql/adapter.py index 3ebfec42..ee3eb96f 100644 --- a/sqlit/domains/connections/providers/mssql/adapter.py +++ b/sqlit/domains/connections/providers/mssql/adapter.py @@ -545,6 +545,11 @@ def build_select_query(self, table: str, limit: int, database: str | None = None schema = schema or "dbo" return f"SELECT TOP {limit} * FROM [{schema}].[{table}]" + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + schema = schema or "dbo" + return f'DROP TABLE IF EXISTS [{schema}].[{table}]' + def execute_query(self, conn: Any, query: str, max_rows: int | None = None) -> tuple[list[str], list[tuple], bool]: """Execute a query on SQL Server with optional row limit.""" cursor = conn.cursor() diff --git a/sqlit/domains/connections/providers/mysql/base.py b/sqlit/domains/connections/providers/mysql/base.py index d65fcb90..a353625d 100644 --- a/sqlit/domains/connections/providers/mysql/base.py +++ b/sqlit/domains/connections/providers/mysql/base.py @@ -148,6 +148,12 @@ def build_select_query(self, table: str, limit: int, database: str | None = None return f"SELECT * FROM `{database}`.`{table}` LIMIT {limit}" return f"SELECT * FROM `{table}` LIMIT {limit}" + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + db = database or schema + return f"DROP TABLE IF EXISTS `{db}`.`{table}`" if db else f"DROP TABLE IF EXISTS `{table}`" + + def get_indexes(self, conn: Any, database: str | None = None) -> list[IndexInfo]: """Get indexes from MySQL/MariaDB.""" cursor = conn.cursor() diff --git a/sqlit/domains/connections/providers/oracle/adapter.py b/sqlit/domains/connections/providers/oracle/adapter.py index 76edc895..5d2021fa 100644 --- a/sqlit/domains/connections/providers/oracle/adapter.py +++ b/sqlit/domains/connections/providers/oracle/adapter.py @@ -335,6 +335,10 @@ def build_select_query(self, table: str, limit: int, database: str | None = None """Build SELECT query with FETCH FIRST for Oracle 12c+. Schema parameter is ignored.""" return f'SELECT * FROM "{table}" FETCH FIRST {limit} ROWS ONLY' + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + return f'DROP TABLE "{table}"' + def execute_query(self, conn: Any, query: str, max_rows: int | None = None) -> tuple[list[str], list[tuple], bool]: """Execute a query on Oracle with optional row limit.""" cursor = conn.cursor() diff --git a/sqlit/domains/connections/providers/osquery/adapter.py b/sqlit/domains/connections/providers/osquery/adapter.py index ba62fd4d..a3e650e1 100644 --- a/sqlit/domains/connections/providers/osquery/adapter.py +++ b/sqlit/domains/connections/providers/osquery/adapter.py @@ -93,6 +93,10 @@ def supports_process_worker(self) -> bool: # osquery spawned instances may not work well across process boundaries return False + @property + def supports_drop_table(self) -> bool: + return False + @property def default_schema(self) -> str: return "" @@ -199,6 +203,10 @@ def build_select_query( ) -> str: return f'SELECT * FROM "{table}" LIMIT {limit}' + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + return '' + def execute_query( self, conn: Any, query: str, max_rows: int | None = None ) -> tuple[list[str], list[tuple], bool]: diff --git a/sqlit/domains/connections/providers/postgresql/base.py b/sqlit/domains/connections/providers/postgresql/base.py index c5140eca..179a8fb9 100644 --- a/sqlit/domains/connections/providers/postgresql/base.py +++ b/sqlit/domains/connections/providers/postgresql/base.py @@ -124,6 +124,11 @@ def build_select_query(self, table: str, limit: int, database: str | None = None schema = schema or "public" return f'SELECT * FROM "{schema}"."{table}" LIMIT {limit}' + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + schema = schema or "public" + return f'DROP TABLE IF EXISTS "{schema}"."{table}"' + @property def supports_sequences(self) -> bool: """PostgreSQL supports sequences.""" diff --git a/sqlit/domains/connections/providers/presto/adapter.py b/sqlit/domains/connections/providers/presto/adapter.py index 7c3c84c2..9dbb286b 100644 --- a/sqlit/domains/connections/providers/presto/adapter.py +++ b/sqlit/domains/connections/providers/presto/adapter.py @@ -190,3 +190,11 @@ def build_select_query(self, table: str, limit: int, database: str | None = None if schema_name: return f'SELECT * FROM "{schema_name}"."{table}" LIMIT {limit}' return f'SELECT * FROM "{table}" LIMIT {limit}' + + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + schema_name = schema or self.default_schema + if database and schema_name: + return f'DROP TABLE IF EXISTS "{database}"."{schema_name}"."{table}"' + if db := database or schema: + return f'DROP TABLE IF EXISTS "{db}"."{table}"' + return f'DROP TABLE IF EXISTS "{table}"' diff --git a/sqlit/domains/connections/providers/redshift/adapter.py b/sqlit/domains/connections/providers/redshift/adapter.py index 6e29ef71..6cc2b60c 100644 --- a/sqlit/domains/connections/providers/redshift/adapter.py +++ b/sqlit/domains/connections/providers/redshift/adapter.py @@ -233,3 +233,8 @@ def build_select_query( """Build SELECT query with LIMIT.""" schema = schema or self.default_schema return f'SELECT * FROM "{schema}"."{table}" LIMIT {limit}' + + + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + schema = schema or self.default_schema + return f'DROP TABLE IF EXISTS "{schema}"."{table}"' diff --git a/sqlit/domains/connections/providers/snowflake/adapter.py b/sqlit/domains/connections/providers/snowflake/adapter.py index 2055eeb3..44f8399f 100644 --- a/sqlit/domains/connections/providers/snowflake/adapter.py +++ b/sqlit/domains/connections/providers/snowflake/adapter.py @@ -199,6 +199,11 @@ def build_select_query(self, table: str, limit: int, database: str | None = None schema = schema or "PUBLIC" return f'SELECT * FROM "{schema}"."{table}" LIMIT {limit}' + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + schema = schema or "PUBLIC" + return f'DROP TABLE IF EXISTS "{schema}"."{table}"' + def get_procedures(self, conn: Any, database: str | None = None) -> list[str]: """Get stored procedures.""" cursor = conn.cursor() diff --git a/sqlit/domains/connections/providers/spanner/adapter.py b/sqlit/domains/connections/providers/spanner/adapter.py index f5ed784b..35e290c4 100644 --- a/sqlit/domains/connections/providers/spanner/adapter.py +++ b/sqlit/domains/connections/providers/spanner/adapter.py @@ -376,3 +376,8 @@ def build_select_query_for_conn( """Build SELECT query with LIMIT using connection-aware quoting.""" quoted = self._quote_identifier_for_conn(conn, table) return f"SELECT * FROM {quoted} LIMIT {limit}" + + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + quoted_table = self._quote_identifier_for_dialect(DIALECT_GOOGLESQL, table) + return f'DROP TABLE IF EXISTS {quoted_table}' diff --git a/sqlit/domains/connections/providers/sqlite/adapter.py b/sqlit/domains/connections/providers/sqlite/adapter.py index f59dbdaf..b5f7f296 100644 --- a/sqlit/domains/connections/providers/sqlite/adapter.py +++ b/sqlit/domains/connections/providers/sqlite/adapter.py @@ -206,6 +206,10 @@ def build_select_query(self, table: str, limit: int, database: str | None = None """Build SELECT LIMIT query for SQLite. Schema parameter is ignored.""" return f'SELECT * FROM "{table}" LIMIT {limit}' + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + return f"DROP TABLE IF EXISTS {table}" + def execute_query(self, conn: Any, query: str, max_rows: int | None = None) -> tuple[list[str], list[tuple], bool]: """Execute a query on SQLite with optional row limit.""" cursor = conn.cursor() diff --git a/sqlit/domains/connections/providers/surrealdb/adapter.py b/sqlit/domains/connections/providers/surrealdb/adapter.py index a1397d67..b16900c9 100644 --- a/sqlit/domains/connections/providers/surrealdb/adapter.py +++ b/sqlit/domains/connections/providers/surrealdb/adapter.py @@ -245,6 +245,10 @@ def build_select_query( ) -> str: return f"SELECT * FROM {self.quote_identifier(table)} LIMIT {limit}" + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build REMOVE TABLE query.""" + return f"REMOVE TABLE {self.quote_identifier(table)}" + def execute_query( self, conn: Any, query: str, max_rows: int | None = None ) -> tuple[list[str], list[tuple], bool]: diff --git a/sqlit/domains/connections/providers/teradata/adapter.py b/sqlit/domains/connections/providers/teradata/adapter.py index aa8e0201..8542efa1 100644 --- a/sqlit/domains/connections/providers/teradata/adapter.py +++ b/sqlit/domains/connections/providers/teradata/adapter.py @@ -264,3 +264,7 @@ def build_select_query(self, table: str, limit: int, database: str | None = None if schema_name: return f'lock row for access select top {limit} * from "{schema_name}"."{table}"' return f'lock row for access select top {limit} * from "{table}"' + + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + db = database or schema + return f'DROP TABLE "{db}"."{table}"' if db else f'DROP TABLE "{table}"' diff --git a/sqlit/domains/connections/providers/trino/adapter.py b/sqlit/domains/connections/providers/trino/adapter.py index fe9a406b..8f87cdd7 100644 --- a/sqlit/domains/connections/providers/trino/adapter.py +++ b/sqlit/domains/connections/providers/trino/adapter.py @@ -190,3 +190,12 @@ def build_select_query(self, table: str, limit: int, database: str | None = None if schema_name: return f'SELECT * FROM "{schema_name}"."{table}" LIMIT {limit}' return f'SELECT * FROM "{table}" LIMIT {limit}' + + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + schema_name = schema or self.default_schema + if database and schema_name: + return f'DROP TABLE IF EXISTS "{database}"."{schema_name}"."{table}"' + if db := database or schema_name: + return f'DROP TABLE IF EXISTS "{db}"."{table}"' + return f'DROP TABLE IF EXISTS "{table}"' diff --git a/sqlit/domains/connections/providers/turso/adapter.py b/sqlit/domains/connections/providers/turso/adapter.py index 1e65d546..d8e59d91 100644 --- a/sqlit/domains/connections/providers/turso/adapter.py +++ b/sqlit/domains/connections/providers/turso/adapter.py @@ -176,6 +176,10 @@ def build_select_query(self, table: str, limit: int, database: str | None = None """Build SELECT LIMIT query for Turso. Schema parameter is ignored.""" return f'SELECT * FROM "{table}" LIMIT {limit}' + def build_drop_table_query(self, table: str, database: str | None = None, schema: str | None = None) -> str: + """Build DROP TABLE query.""" + return f'DROP TABLE IF EXISTS "{table}"' + def execute_query(self, conn: Any, query: str, max_rows: int | None = None) -> tuple[list[str], list[tuple], bool]: """Execute a query on Turso with optional row limit.""" cur = conn.cursor() diff --git a/sqlit/domains/explorer/state/tree_on_table.py b/sqlit/domains/explorer/state/tree_on_table.py index ee092789..45c17aa6 100644 --- a/sqlit/domains/explorer/state/tree_on_table.py +++ b/sqlit/domains/explorer/state/tree_on_table.py @@ -13,6 +13,12 @@ class TreeOnTableState(State): def _setup_actions(self) -> None: self.allows("select_table", label="Select TOP 100", help="Select TOP 100 (table/view)") + self.allows( + "drop_table", + guard=lambda app: app.tree_node_kind == "table" and app.current_provider_supports_drop_table, + label="Drop Table", + help="Drop selected Table", + ) def get_display_bindings(self, app: InputContext) -> tuple[list[DisplayBinding], list[DisplayBinding]]: left: list[DisplayBinding] = [] @@ -28,6 +34,15 @@ def get_display_bindings(self, app: InputContext) -> tuple[list[DisplayBinding], ) ) seen.add("select_table") + if app.tree_node_kind == "table" and app.current_provider_supports_drop_table: + left.append( + DisplayBinding( + key=resolve_display_key("drop_table") or "d", + label="Drop Table", + action="drop_table", + ) + ) + seen.add("drop_table") left.append( DisplayBinding( key=resolve_display_key("refresh_tree") or "f", diff --git a/sqlit/domains/explorer/ui/mixins/tree.py b/sqlit/domains/explorer/ui/mixins/tree.py index 26cf34b3..5ee29c18 100644 --- a/sqlit/domains/explorer/ui/mixins/tree.py +++ b/sqlit/domains/explorer/ui/mixins/tree.py @@ -357,6 +357,38 @@ def action_select_table(self: TreeMixinHost) -> None: tree_object_info.show_sequence_info(self, data) return + def action_drop_table(self: TreeMixinHost) -> None: + """Generate DROP TABLE query for selected table.""" + if not self.current_provider or not self._session: + return + + if not self.current_provider.capabilities.support_drop_table: + return + + node = self.object_tree.cursor_node + + if not node or not node.data: + return + + data = node.data + + if self._get_node_kind(node) == "table": + self._last_query_table = { + "database": data.database, + "schema": data.schema, + "name": data.name, + "columns": [], + } + + self.query_input.text = self.current_provider.dialect.build_drop_table_query( + data.name, + data.database, + data.schema, + ) + self._query_target_database = data.database + self.query_input.focus() + return + def action_use_database(self: TreeMixinHost) -> None: """Toggle the selected database as the default for the current connection.""" node = self.object_tree.cursor_node diff --git a/sqlit/domains/shell/app/main.py b/sqlit/domains/shell/app/main.py index e98c4c2c..baf3da41 100644 --- a/sqlit/domains/shell/app/main.py +++ b/sqlit/domains/shell/app/main.py @@ -319,6 +319,9 @@ def _get_input_context(self) -> InputContext: has_results=has_results, stacked_result_count=stacked_result_count, count_buffer=self._count_buffer, + current_provider_supports_drop_table=bool( + self.current_provider and self.current_provider.capabilities.support_drop_table + ), ) def _debug_screen_label(self, screen: Any | None) -> str: diff --git a/sqlit/domains/shell/state/machine.py b/sqlit/domains/shell/state/machine.py index dc146c35..e0bba30a 100644 --- a/sqlit/domains/shell/state/machine.py +++ b/sqlit/domains/shell/state/machine.py @@ -214,6 +214,7 @@ def lk(action: str, menu: str, fallback: str) -> str: s.binding("", "Expand node / Connect") s.binding(k("new_connection", "n"), "New connection") s.binding(k("select_table", "s"), "SELECT TOP 100 (on table/view)") + s.binding(k("drop_table", "d"), "DROP TABLE (if supported by the database)") s.binding(k("tree_filter", "/"), "Filter tree") s.binding(k("collapse_tree", "z"), "Collapse all nodes") s.binding(k("refresh_tree", "f"), "Refresh tree")