// TableDetails is an endpoint that returns columns, indices, and other // relevant details for the specified table. func (s *adminServer) TableDetails( ctx context.Context, req *serverpb.TableDetailsRequest, ) (*serverpb.TableDetailsResponse, error) { args := sql.SessionArgs{User: s.getUser(req)} session := s.NewSessionForRPC(ctx, args) defer session.Finish(s.server.sqlExecutor) escDBName := parser.Name(req.Database).String() if err := s.assertNotVirtualSchema(escDBName); err != nil { return nil, err } // TODO(cdo): Use real placeholders for the table and database names when we've extended our SQL // grammar to allow that. escTableName := parser.Name(req.Table).String() escQualTable := fmt.Sprintf("%s.%s", escDBName, escTableName) query := fmt.Sprintf("SHOW COLUMNS FROM %s; SHOW INDEX FROM %s; SHOW GRANTS ON TABLE %s; SHOW CREATE TABLE %s;", escQualTable, escQualTable, escQualTable, escQualTable) r := s.server.sqlExecutor.ExecuteStatements(session, query, nil) defer r.Close() if err := s.firstNotFoundError(r.ResultList); err != nil { return nil, grpc.Errorf(codes.NotFound, "%s", err) } if err := s.checkQueryResults(r.ResultList, 4); err != nil { return nil, err } var resp serverpb.TableDetailsResponse // Marshal SHOW COLUMNS result. // // TODO(cdo): protobuf v3's default behavior for fields with zero values (e.g. empty strings) // is to suppress them. So, if protobuf field "foo" is an empty string, "foo" won't show // up in the marshalled JSON. I feel that this is counterintuitive, and this should be fixed // for our API. { const ( fieldCol = "Field" // column name typeCol = "Type" nullCol = "Null" defaultCol = "Default" ) scanner := makeResultScanner(r.ResultList[0].Columns) for i, nRows := 0, r.ResultList[0].Rows.Len(); i < nRows; i++ { row := r.ResultList[0].Rows.At(i) var col serverpb.TableDetailsResponse_Column if err := scanner.Scan(row, fieldCol, &col.Name); err != nil { return nil, err } if err := scanner.Scan(row, typeCol, &col.Type); err != nil { return nil, err } if err := scanner.Scan(row, nullCol, &col.Nullable); err != nil { return nil, err } isDefaultNull, err := scanner.IsNull(row, defaultCol) if err != nil { return nil, err } if !isDefaultNull { if err := scanner.Scan(row, defaultCol, &col.DefaultValue); err != nil { return nil, err } } resp.Columns = append(resp.Columns, col) } } // Marshal SHOW INDEX result. { const ( nameCol = "Name" uniqueCol = "Unique" seqCol = "Seq" columnCol = "Column" directionCol = "Direction" storingCol = "Storing" implicitCol = "Implicit" ) scanner := makeResultScanner(r.ResultList[1].Columns) for i, nRows := 0, r.ResultList[1].Rows.Len(); i < nRows; i++ { row := r.ResultList[1].Rows.At(i) // Marshal grant, splitting comma-separated privileges into a proper slice. var index serverpb.TableDetailsResponse_Index if err := scanner.Scan(row, nameCol, &index.Name); err != nil { return nil, err } if err := scanner.Scan(row, uniqueCol, &index.Unique); err != nil { return nil, err } if err := scanner.Scan(row, seqCol, &index.Seq); err != nil { return nil, err } if err := scanner.Scan(row, columnCol, &index.Column); err != nil { return nil, err } if err := scanner.Scan(row, directionCol, &index.Direction); err != nil { return nil, err } if err := scanner.Scan(row, storingCol, &index.Storing); err != nil { return nil, err } if err := scanner.Scan(row, implicitCol, &index.Implicit); err != nil { return nil, err } resp.Indexes = append(resp.Indexes, index) } } // Marshal SHOW GRANTS result. { const ( userCol = "User" privilegesCol = "Privileges" ) scanner := makeResultScanner(r.ResultList[2].Columns) for i, nRows := 0, r.ResultList[2].Rows.Len(); i < nRows; i++ { row := r.ResultList[2].Rows.At(i) // Marshal grant, splitting comma-separated privileges into a proper slice. var grant serverpb.TableDetailsResponse_Grant var privileges string if err := scanner.Scan(row, userCol, &grant.User); err != nil { return nil, err } if err := scanner.Scan(row, privilegesCol, &privileges); err != nil { return nil, err } grant.Privileges = strings.Split(privileges, ",") resp.Grants = append(resp.Grants, grant) } } // Marshal SHOW CREATE TABLE result. { const createTableCol = "CreateTable" showResult := r.ResultList[3] if showResult.Rows.Len() != 1 { return nil, s.serverErrorf("CreateTable response not available.") } scanner := makeResultScanner(showResult.Columns) var createStmt string if err := scanner.Scan(showResult.Rows.At(0), createTableCol, &createStmt); err != nil { return nil, err } resp.CreateTableStatement = createStmt } // Get the number of ranges in the table. We get the key span for the table // data. Then, we count the number of ranges that make up that key span. { iexecutor := sql.InternalExecutor{LeaseManager: s.server.leaseMgr} var tableSpan roachpb.Span if err := s.server.db.Txn(ctx, func(txn *client.Txn) error { var err error tableSpan, err = iexecutor.GetTableSpan( s.getUser(req), txn, req.Database, req.Table, ) return err }); err != nil { return nil, s.serverError(err) } tableRSpan := roachpb.RSpan{} var err error tableRSpan.Key, err = keys.Addr(tableSpan.Key) if err != nil { return nil, s.serverError(err) } tableRSpan.EndKey, err = keys.Addr(tableSpan.EndKey) if err != nil { return nil, s.serverError(err) } rangeCount, err := s.server.distSender.CountRanges(ctx, tableRSpan) if err != nil { return nil, s.serverError(err) } resp.RangeCount = rangeCount } // Query the descriptor ID and zone configuration for this table. { path, err := s.queryDescriptorIDPath(session, []string{req.Database, req.Table}) if err != nil { return nil, s.serverError(err) } resp.DescriptorID = int64(path[2]) id, zone, zoneExists, err := s.queryZonePath(session, path) if err != nil { return nil, s.serverError(err) } if !zoneExists { zone = config.DefaultZoneConfig() } resp.ZoneConfig = zone switch id { case path[1]: resp.ZoneConfigLevel = serverpb.ZoneConfigurationLevel_DATABASE case path[2]: resp.ZoneConfigLevel = serverpb.ZoneConfigurationLevel_TABLE default: resp.ZoneConfigLevel = serverpb.ZoneConfigurationLevel_CLUSTER } } return &resp, nil }
// TableStats is an endpoint that returns columns, indices, and other // relevant details for the specified table. func (s *adminServer) TableStats( ctx context.Context, req *serverpb.TableStatsRequest, ) (*serverpb.TableStatsResponse, error) { escDBName := parser.Name(req.Database).String() if err := s.assertNotVirtualSchema(escDBName); err != nil { return nil, err } // Get table span. var tableSpan roachpb.Span iexecutor := sql.InternalExecutor{LeaseManager: s.server.leaseMgr} if err := s.server.db.Txn(ctx, func(txn *client.Txn) error { var err error tableSpan, err = iexecutor.GetTableSpan(s.getUser(req), txn, req.Database, req.Table) return err }); err != nil { return nil, s.serverError(err) } startKey, err := keys.Addr(tableSpan.Key) if err != nil { return nil, s.serverError(err) } endKey, err := keys.Addr(tableSpan.EndKey) if err != nil { return nil, s.serverError(err) } // Get current range descriptors for table. This is done by scanning over // meta2 keys for the range. rangeDescKVs, err := s.server.db.Scan(ctx, keys.RangeMetaKey(startKey), keys.RangeMetaKey(endKey), 0) if err != nil { return nil, s.serverError(err) } // Extract a list of node IDs from the response. nodeIDs := make(map[roachpb.NodeID]struct{}) for _, kv := range rangeDescKVs { var rng roachpb.RangeDescriptor if err := kv.Value.GetProto(&rng); err != nil { return nil, s.serverError(err) } for _, repl := range rng.Replicas { nodeIDs[repl.NodeID] = struct{}{} } } // Construct TableStatsResponse by sending an RPC to every node involved. tableStatResponse := serverpb.TableStatsResponse{ NodeCount: int64(len(nodeIDs)), // TODO(mrtracy): The "RangeCount" returned by TableStats is more // accurate than the "RangeCount" returned by TableDetails, because this // method always consistently queries the meta2 key range for the table; // in contrast, TableDetails uses a method on the DistSender, which // queries using a range metadata cache and thus may return stale data // for tables that are rapidly splitting. However, one potential // *advantage* of using the DistSender is that it will populate the // DistSender's range metadata cache in the case where meta2 information // for this table is not already present; the query used by TableStats // does not populate the DistSender cache. We should consider plumbing // TableStats' meta2 query through the DistSender so that it will share // the advantage of populating the cache (without the disadvantage of // potentially returning stale data). // See Github #5435 for some discussion. RangeCount: int64(len(rangeDescKVs)), } type nodeResponse struct { nodeID roachpb.NodeID resp *serverpb.SpanStatsResponse err error } // Send a SpanStats query to each node. Set a timeout on the context for // these queries. responses := make(chan nodeResponse) nodeCtx, cancel := context.WithTimeout(ctx, base.NetworkTimeout) defer cancel() for nodeID := range nodeIDs { nodeID := nodeID if err := s.server.stopper.RunAsyncTask(nodeCtx, func(ctx context.Context) { var spanResponse *serverpb.SpanStatsResponse client, err := s.server.status.dialNode(nodeID) if err == nil { req := serverpb.SpanStatsRequest{ StartKey: startKey, EndKey: endKey, NodeID: nodeID.String(), } spanResponse, err = client.SpanStats(ctx, &req) } response := nodeResponse{ nodeID: nodeID, resp: spanResponse, err: err, } select { case responses <- response: // Response processed. case <-ctx.Done(): // Context completed, response no longer needed. } }); err != nil { return nil, err } } for remainingResponses := len(nodeIDs); remainingResponses > 0; remainingResponses-- { select { case resp := <-responses: // For nodes which returned an error, note that the node's data // is missing. For successful calls, aggregate statistics. if resp.err != nil { tableStatResponse.MissingNodes = append( tableStatResponse.MissingNodes, serverpb.TableStatsResponse_MissingNode{ NodeID: resp.nodeID.String(), ErrorMessage: resp.err.Error(), }, ) } else { tableStatResponse.Stats.Add(resp.resp.TotalStats) tableStatResponse.ReplicaCount += int64(resp.resp.RangeCount) } case <-ctx.Done(): return nil, ctx.Err() } } return &tableStatResponse, nil }
// TableStats is an endpoint that returns columns, indices, and other // relevant details for the specified table. func (s *adminServer) TableStats( ctx context.Context, req *serverpb.TableStatsRequest, ) (*serverpb.TableStatsResponse, error) { // Get table span. var tableSpan roachpb.Span iexecutor := sql.InternalExecutor{LeaseManager: s.server.leaseMgr} if err := s.server.db.Txn(ctx, func(txn *client.Txn) error { var err error tableSpan, err = iexecutor.GetTableSpan(s.getUser(req), txn, req.Database, req.Table) return err }); err != nil { return nil, s.serverError(err) } startKey, err := keys.Addr(tableSpan.Key) if err != nil { return nil, s.serverError(err) } endKey, err := keys.Addr(tableSpan.EndKey) if err != nil { return nil, s.serverError(err) } // Get current range descriptors for table. This is done by scanning over // meta2 keys for the range. rangeDescKVs, err := s.server.db.Scan(ctx, keys.RangeMetaKey(startKey), keys.RangeMetaKey(endKey), 0) if err != nil { return nil, s.serverError(err) } // Extract a list of node IDs from the response. nodeIDs := make(map[roachpb.NodeID]struct{}) for _, kv := range rangeDescKVs { var rng roachpb.RangeDescriptor if err := kv.Value.GetProto(&rng); err != nil { return nil, s.serverError(err) } for _, repl := range rng.Replicas { nodeIDs[repl.NodeID] = struct{}{} } } // Construct TableStatsResponse by sending an RPC to every node involved. tableStatResponse := serverpb.TableStatsResponse{ NodeCount: int64(len(nodeIDs)), RangeCount: int64(len(rangeDescKVs)), } type nodeResponse struct { nodeID roachpb.NodeID resp *serverpb.SpanStatsResponse err error } // Send a SpanStats query to each node. Set a timeout on the context for // these queries. responses := make(chan nodeResponse) nodeCtx, cancel := context.WithTimeout(ctx, base.NetworkTimeout) defer cancel() for nodeID := range nodeIDs { nodeID := nodeID if err := s.server.stopper.RunAsyncTask(nodeCtx, func(ctx context.Context) { var spanResponse *serverpb.SpanStatsResponse client, err := s.server.status.dialNode(nodeID) if err == nil { req := serverpb.SpanStatsRequest{ StartKey: startKey, EndKey: endKey, NodeID: nodeID.String(), } spanResponse, err = client.SpanStats(ctx, &req) } response := nodeResponse{ nodeID: nodeID, resp: spanResponse, err: err, } select { case responses <- response: // Response processed. case <-ctx.Done(): // Context completed, response no longer needed. } }); err != nil { return nil, err } } for remainingResponses := len(nodeIDs); remainingResponses > 0; remainingResponses-- { select { case resp := <-responses: // For nodes which returned an error, note that the node's data // is missing. For successful calls, aggregate statistics. if resp.err != nil { tableStatResponse.MissingNodes = append( tableStatResponse.MissingNodes, serverpb.TableStatsResponse_MissingNode{ NodeID: resp.nodeID.String(), ErrorMessage: resp.err.Error(), }, ) } else { tableStatResponse.Stats.Add(resp.resp.TotalStats) tableStatResponse.ReplicaCount += int64(resp.resp.RangeCount) } case <-ctx.Done(): return nil, ctx.Err() } } return &tableStatResponse, nil }