@@ -96,6 +96,7 @@ def parse_arguments():
9696 'query' , 'ping' ,
9797 'list-tables' , 'list-views' , 'list-materialized-views' ,
9898 'list-sequences' , 'list-temp-tables' , 'list-temp-views' ,
99+ 'list-columns' , 'list-indexes' ,
99100 ],
100101 help = 'Action to perform' ,
101102 )
@@ -254,6 +255,84 @@ def action_list_entities(engine, action, conn=None):
254255 ) from e
255256
256257
258+ def action_list_columns (engine , table_name , conn = None ):
259+ """List column details for a table via SQLAlchemy inspect."""
260+ try :
261+ from sqlalchemy import inspect as sa_inspect
262+ inspector = sa_inspect (conn if conn is not None else engine )
263+ columns = inspector .get_columns (table_name )
264+ rows = []
265+ for col in columns :
266+ rows .append ({
267+ 'column_name' : col ['name' ],
268+ 'type' : str (col ['type' ]),
269+ 'nullable' : col .get ('nullable' , True ),
270+ 'default' : (
271+ str (col ['default' ])
272+ if col .get ('default' ) is not None
273+ else None
274+ ),
275+ 'autoincrement' : col .get ('autoincrement' , False ),
276+ 'primary_key' : col .get ('primary_key' , False ),
277+ })
278+ return {
279+ "kind" : "result-set" ,
280+ "rows" : rows ,
281+ "columns" : [
282+ 'column_name' , 'type' , 'nullable' ,
283+ 'default' , 'autoincrement' , 'primary_key' ,
284+ ],
285+ "rowCount" : len (rows ),
286+ "message" : f"Found { len (rows )} column(s)" ,
287+ }
288+ except Exception as e :
289+ raise Exception (
290+ f"Failed to list columns: { e } "
291+ ) from e
292+
293+
294+ def action_list_indexes (engine , table_name , conn = None ):
295+ """List index details for a table via SQLAlchemy inspect."""
296+ try :
297+ from sqlalchemy import inspect as sa_inspect
298+ inspector = sa_inspect (conn if conn is not None else engine )
299+ # Get regular indexes
300+ indexes = inspector .get_indexes (table_name )
301+ # Get primary key constraint
302+ try :
303+ pk = inspector .get_pk_constraint (table_name )
304+ if pk and pk .get ('constrained_columns' ):
305+ indexes = [{
306+ 'name' : pk .get ('name' ) or 'PRIMARY' ,
307+ 'column_names' : pk ['constrained_columns' ],
308+ 'unique' : True ,
309+ 'primary_key' : True ,
310+ }] + indexes
311+ except Exception :
312+ pass
313+ rows = []
314+ for idx in indexes :
315+ rows .append ({
316+ 'index_name' : idx .get ('name' ) or '(unnamed)' ,
317+ 'columns' : ', ' .join (
318+ str (c ) for c in idx .get ('column_names' , []) if c
319+ ),
320+ 'unique' : idx .get ('unique' , False ),
321+ 'primary_key' : idx .get ('primary_key' , False ),
322+ })
323+ return {
324+ "kind" : "result-set" ,
325+ "rows" : rows ,
326+ "columns" : ['index_name' , 'columns' , 'unique' , 'primary_key' ],
327+ "rowCount" : len (rows ),
328+ "message" : f"Found { len (rows )} index(es)" ,
329+ }
330+ except Exception as e :
331+ raise Exception (
332+ f"Failed to list indexes: { e } "
333+ ) from e
334+
335+
257336def action_query (engine , sql_query , query_params , conn = None ):
258337 """Execute a SQL query and return results."""
259338 try :
@@ -320,6 +399,14 @@ def dispatch_action(engine, action, query='', params_raw='', conn=None):
320399 result = action_ping (engine )
321400 elif action in ENTITY_ACTIONS :
322401 result = action_list_entities (engine , action , conn )
402+ elif action == 'list-columns' :
403+ if not query or not query .strip ():
404+ raise ValueError ('Table name is required for list-columns.' )
405+ result = action_list_columns (engine , query .strip (), conn )
406+ elif action == 'list-indexes' :
407+ if not query or not query .strip ():
408+ raise ValueError ('Table name is required for list-indexes.' )
409+ result = action_list_indexes (engine , query .strip (), conn )
323410 elif action == 'query' :
324411 if not query or not query .strip ():
325412 raise ValueError ('Query is empty.' )
0 commit comments