Skip to content

Commit 58b5d75

Browse files
authored
Merge branch 'main' into jascha/age-311-tiger-cli-investigate-interactive-testing-via-go-expect
2 parents e6e4235 + 4ed6666 commit 58b5d75

2 files changed

Lines changed: 47 additions & 43 deletions

File tree

internal/tiger/mcp/db_tools.go

Lines changed: 46 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ type DBExecuteQueryInput struct {
2929
func (DBExecuteQueryInput) Schema() *jsonschema.Schema {
3030
schema := util.Must(jsonschema.For[DBExecuteQueryInput](nil))
3131

32-
schema.Properties["service_id"].Description = "The unique identifier of the service (10-character alphanumeric string). Use service_list to find service IDs."
32+
schema.Properties["service_id"].Description = "Unique identifier of the service (10-character alphanumeric string). Use service_list to find service IDs."
3333
schema.Properties["service_id"].Examples = []any{"e6ue9697jf", "u8me885b93"}
3434
schema.Properties["service_id"].Pattern = "^[a-z0-9]{10}$"
3535

3636
schema.Properties["query"].Description = "PostgreSQL query to execute"
3737

38-
schema.Properties["parameters"].Description = "Query parameters for parameterized queries. Values are substituted for $1, $2, etc. placeholders in the query."
38+
schema.Properties["parameters"].Description = "Query parameters. Values are substituted for $1, $2, etc. placeholders in the query."
3939
schema.Properties["parameters"].Examples = []any{[]string{"1", "alice"}, []string{"2024-01-01", "100"}}
4040

4141
schema.Properties["timeout_seconds"].Description = "Query timeout in seconds"
@@ -47,7 +47,7 @@ func (DBExecuteQueryInput) Schema() *jsonschema.Schema {
4747
schema.Properties["role"].Default = util.Must(json.Marshal("tsdbadmin"))
4848
schema.Properties["role"].Examples = []any{"tsdbadmin", "readonly", "postgres"}
4949

50-
schema.Properties["pooled"].Description = "Use connection pooling (if available for the service)"
50+
schema.Properties["pooled"].Description = "Use connection pooling (if available)"
5151
schema.Properties["pooled"].Default = util.Must(json.Marshal(false))
5252
schema.Properties["pooled"].Examples = []any{false, true}
5353

@@ -60,31 +60,45 @@ type DBExecuteQueryColumn struct {
6060
Type string `json:"type"`
6161
}
6262

63+
// ResultSet represents a single query result set
64+
type ResultSet struct {
65+
CommandTag string `json:"command_tag"`
66+
Columns []DBExecuteQueryColumn `json:"columns,omitempty"`
67+
Rows *[][]any `json:"rows,omitempty"`
68+
RowsAffected int64 `json:"rows_affected"`
69+
}
70+
6371
// DBExecuteQueryOutput represents output for db_execute_query
6472
type DBExecuteQueryOutput struct {
65-
Columns []DBExecuteQueryColumn `json:"columns,omitempty"`
66-
Rows *[][]any `json:"rows,omitempty"`
67-
RowsAffected int64 `json:"rows_affected"`
68-
ExecutionTime string `json:"execution_time"`
73+
ResultSets []ResultSet `json:"result_sets"`
74+
ExecutionTime string `json:"execution_time"`
6975
}
7076

7177
func (DBExecuteQueryOutput) Schema() *jsonschema.Schema {
7278
schema := util.Must(jsonschema.For[DBExecuteQueryOutput](nil))
7379

74-
schema.Properties["columns"].Description = "Column metadata from the query result including name and PostgreSQL type. Omitted for commands that don't return rows (INSERT, UPDATE, DELETE, etc.)"
75-
schema.Properties["columns"].Examples = []any{[]DBExecuteQueryColumn{
80+
schema.Properties["result_sets"].Description = "Array of result sets returned. For single-statement queries, this array will contain one element. For multi-statement queries, this array will contain one element per statement."
81+
82+
// Add descriptions for nested ResultSet fields
83+
resultSetSchema := schema.Properties["result_sets"].Items
84+
85+
resultSetSchema.Properties["command_tag"].Description = "Identifies the type of command executed."
86+
resultSetSchema.Properties["command_tag"].Examples = []any{"SELECT 2", "INSERT 0 2"}
87+
88+
resultSetSchema.Properties["columns"].Description = "Column metadata including name and PostgreSQL type. Omitted for commands that don't return rows (INSERT, UPDATE, DELETE, etc.)"
89+
resultSetSchema.Properties["columns"].Examples = []any{[]DBExecuteQueryColumn{
7690
{Name: "id", Type: "int4"},
7791
{Name: "name", Type: "text"},
7892
{Name: "created_at", Type: "timestamptz"},
7993
}}
8094

81-
schema.Properties["rows"].Description = "Result rows as arrays of values. Omitted for commands that don't return rows (INSERT, UPDATE, DELETE, etc.)"
82-
schema.Properties["rows"].Examples = []any{[][]any{{1, "alice", "2024-01-01"}, {2, "bob", "2024-01-02"}}}
95+
resultSetSchema.Properties["rows"].Description = "Result rows as arrays of values. Omitted for commands that don't return rows (INSERT, UPDATE, DELETE, etc.)"
96+
resultSetSchema.Properties["rows"].Examples = []any{[][]any{{1, "alice", "2024-01-01"}, {2, "bob", "2024-01-02"}}}
8397

84-
schema.Properties["rows_affected"].Description = "Number of rows affected by the query. For SELECT, this is the number of rows returned. For INSERT/UPDATE/DELETE, this is the number of rows modified. Returns 0 for statements that don't return or modify rows (e.g. CREATE TABLE)."
85-
schema.Properties["rows_affected"].Examples = []any{5, 42, 1000}
98+
resultSetSchema.Properties["rows_affected"].Description = "Number of rows affected. For SELECT, this is the number of rows returned. For INSERT/UPDATE/DELETE, this is the number of rows modified. Returns 0 for statements that don't return or modify rows (e.g. CREATE TABLE)."
99+
resultSetSchema.Properties["rows_affected"].Examples = []any{5, 42, 1000}
86100

87-
schema.Properties["execution_time"].Description = "Query execution time as a human-readable duration string"
101+
schema.Properties["execution_time"].Description = "Execution time as a human-readable duration string"
88102
schema.Properties["execution_time"].Examples = []any{"123ms", "1.5s", "45.2µs"}
89103

90104
return schema
@@ -97,11 +111,11 @@ func (s *Server) registerDatabaseTools() {
97111
Title: "Execute SQL Query",
98112
Description: `Execute SQL queries against a service database.
99113
100-
This tool connects to a PostgreSQL database service in Tiger Cloud and executes the provided SQL query, returning the results with column names, row data, and execution metadata.
114+
Connects to a PostgreSQL database service in Tiger Cloud and executes the provided SQL query, returning the results with column information, row data, and execution metadata.
101115
102-
Multi-statement queries are supported when no parameters are provided. When executing multiple statements separated by semicolons, all statements are executed in a single transaction, and only the results from the final statement are returned. Multi-statement queries with parameters are not supported and will return an error.
116+
Multi-statement queries (semicolon-separated) are supported when no parameters are provided. All result sets are returned. By default, statements execute in an implicit transaction that automatically commits on success or rolls back on error. Explicit transactions (opened with BEGIN) must be explicitly committed with COMMIT, or they roll back when the connection closes.
103117
104-
WARNING: Use with caution - this tool can execute any SQL statement including INSERT, UPDATE, DELETE, and DDL commands. Always review queries before execution.`,
118+
WARNING: Can execute any SQL statement including INSERT, UPDATE, DELETE, and DDL commands. Always review queries before execution.`,
105119
InputSchema: DBExecuteQueryInput{}.Schema(),
106120
OutputSchema: DBExecuteQueryOutput{}.Schema(),
107121
Annotations: &mcp.ToolAnnotations{
@@ -203,8 +217,8 @@ func (s *Server) handleDBExecuteQuery(ctx context.Context, req *mcp.CallToolRequ
203217
br := conn.SendBatch(queryCtx, batch)
204218
defer br.Close()
205219

206-
// Process all result sets, keeping only the final one
207-
var finalResult resultSet
220+
// Process all result sets, collecting them all
221+
resultSets := make([]ResultSet, 0)
208222
for {
209223
rows, err := br.Query()
210224
if err != nil {
@@ -225,34 +239,25 @@ func (s *Server) handleDBExecuteQuery(ctx context.Context, req *mcp.CallToolRequ
225239
return nil, DBExecuteQueryOutput{}, err
226240
}
227241

228-
// Save this result set as the current "final" one
229-
finalResult = result
242+
// Collect this result set
243+
resultSets = append(resultSets, result)
230244
}
231245

232246
if err := br.Close(); err != nil {
233247
return nil, DBExecuteQueryOutput{}, err
234248
}
235249

236-
// Build output from the final result set
250+
// Build output from all result sets
237251
output := DBExecuteQueryOutput{
238-
Columns: finalResult.columns,
239-
Rows: finalResult.rows,
240-
RowsAffected: finalResult.rowsAffected,
252+
ResultSets: resultSets,
241253
ExecutionTime: time.Since(startTime).String(),
242254
}
243255

244256
return nil, output, nil
245257
}
246258

247-
// resultSet holds the columns, rows, and metadata from a single query result set
248-
type resultSet struct {
249-
columns []DBExecuteQueryColumn
250-
rows *[][]any
251-
rowsAffected int64
252-
}
253-
254259
// processResultSet reads all data from a pgx.Rows result set
255-
func processResultSet(conn *pgx.Conn, rows pgx.Rows) (resultSet, error) {
260+
func processResultSet(conn *pgx.Conn, rows pgx.Rows) (ResultSet, error) {
256261
defer rows.Close()
257262

258263
// Get column metadata from field descriptions
@@ -286,22 +291,21 @@ func processResultSet(conn *pgx.Conn, rows pgx.Rows) (resultSet, error) {
286291
// Scan values into generic interface slice
287292
values, err := rows.Values()
288293
if err != nil {
289-
return resultSet{}, err
294+
return ResultSet{}, err
290295
}
291296
resultRows = append(resultRows, values)
292297
}
293298

294299
// Check for errors during iteration
295300
if err := rows.Err(); err != nil {
296-
return resultSet{}, err
301+
return ResultSet{}, err
297302
}
298303

299-
// Get rows affected
300-
rowsAffected := rows.CommandTag().RowsAffected()
301-
302-
return resultSet{
303-
columns: columns,
304-
rows: util.PtrIfNonNil(resultRows),
305-
rowsAffected: rowsAffected,
304+
commandTag := rows.CommandTag()
305+
return ResultSet{
306+
CommandTag: commandTag.String(),
307+
Columns: columns,
308+
Rows: util.PtrIfNonNil(resultRows),
309+
RowsAffected: commandTag.RowsAffected(),
306310
}, nil
307311
}

internal/tiger/mcp/service_tools.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ type ResourceInfo struct {
7777

7878
// setServiceIDSchemaProperties sets common service_id schema properties
7979
func setServiceIDSchemaProperties(schema *jsonschema.Schema) {
80-
schema.Properties["service_id"].Description = "The unique identifier of the service (10-character alphanumeric string). Use service_list to find service IDs."
80+
schema.Properties["service_id"].Description = "Unique identifier of the service (10-character alphanumeric string). Use service_list to find service IDs."
8181
schema.Properties["service_id"].Examples = []any{"e6ue9697jf", "u8me885b93"}
8282
schema.Properties["service_id"].Pattern = "^[a-z0-9]{10}$"
8383
}

0 commit comments

Comments
 (0)