github.com/segmentio/kafka-go@v0.4.48-0.20240318174348-3f6244eb34fd/offsetfetch.go (about)

     1  package kafka
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"net"
     8  	"time"
     9  
    10  	"github.com/segmentio/kafka-go/protocol/offsetfetch"
    11  )
    12  
    13  // OffsetFetchRequest represents a request sent to a kafka broker to read the
    14  // currently committed offsets of topic partitions.
    15  type OffsetFetchRequest struct {
    16  	// Address of the kafka broker to send the request to.
    17  	Addr net.Addr
    18  
    19  	// ID of the consumer group to retrieve the offsets for.
    20  	GroupID string
    21  
    22  	// Set of topic partitions to retrieve the offsets for.
    23  	Topics map[string][]int
    24  }
    25  
    26  // OffsetFetchResponse represents a response from a kafka broker to an offset
    27  // fetch request.
    28  type OffsetFetchResponse struct {
    29  	// The amount of time that the broker throttled the request.
    30  	Throttle time.Duration
    31  
    32  	// Set of topic partitions that the kafka broker has returned offsets for.
    33  	Topics map[string][]OffsetFetchPartition
    34  
    35  	// An error that may have occurred while attempting to retrieve consumer
    36  	// group offsets.
    37  	//
    38  	// The error contains both the kafka error code, and an error message
    39  	// returned by the kafka broker. Programs may use the standard errors.Is
    40  	// function to test the error against kafka error codes.
    41  	Error error
    42  }
    43  
    44  // OffsetFetchPartition represents the state of a single partition in a consumer
    45  // group.
    46  type OffsetFetchPartition struct {
    47  	// ID of the partition.
    48  	Partition int
    49  
    50  	// Last committed offsets on the partition when the request was served by
    51  	// the kafka broker.
    52  	CommittedOffset int64
    53  
    54  	// Consumer group metadata for this partition.
    55  	Metadata string
    56  
    57  	// An error that may have occurred while attempting to retrieve consumer
    58  	// group offsets for this partition.
    59  	//
    60  	// The error contains both the kafka error code, and an error message
    61  	// returned by the kafka broker. Programs may use the standard errors.Is
    62  	// function to test the error against kafka error codes.
    63  	Error error
    64  }
    65  
    66  // OffsetFetch sends an offset fetch request to a kafka broker and returns the
    67  // response.
    68  func (c *Client) OffsetFetch(ctx context.Context, req *OffsetFetchRequest) (*OffsetFetchResponse, error) {
    69  
    70  	// Kafka version 0.10.2.x and above allow null Topics map for OffsetFetch API
    71  	// which will return the result for all topics with the desired consumer group:
    72  	// https://kafka.apache.org/0102/protocol.html#The_Messages_OffsetFetch
    73  	// For Kafka version below 0.10.2.x this call will result in an error
    74  	var topics []offsetfetch.RequestTopic
    75  
    76  	if len(req.Topics) > 0 {
    77  		topics = make([]offsetfetch.RequestTopic, 0, len(req.Topics))
    78  
    79  		for topicName, partitions := range req.Topics {
    80  			indexes := make([]int32, len(partitions))
    81  
    82  			for i, p := range partitions {
    83  				indexes[i] = int32(p)
    84  			}
    85  
    86  			topics = append(topics, offsetfetch.RequestTopic{
    87  				Name:             topicName,
    88  				PartitionIndexes: indexes,
    89  			})
    90  		}
    91  	}
    92  
    93  	m, err := c.roundTrip(ctx, req.Addr, &offsetfetch.Request{
    94  		GroupID: req.GroupID,
    95  		Topics:  topics,
    96  	})
    97  
    98  	if err != nil {
    99  		return nil, fmt.Errorf("kafka.(*Client).OffsetFetch: %w", err)
   100  	}
   101  
   102  	res := m.(*offsetfetch.Response)
   103  	ret := &OffsetFetchResponse{
   104  		Throttle: makeDuration(res.ThrottleTimeMs),
   105  		Topics:   make(map[string][]OffsetFetchPartition, len(res.Topics)),
   106  		Error:    makeError(res.ErrorCode, ""),
   107  	}
   108  
   109  	for _, t := range res.Topics {
   110  		partitions := make([]OffsetFetchPartition, len(t.Partitions))
   111  
   112  		for i, p := range t.Partitions {
   113  			partitions[i] = OffsetFetchPartition{
   114  				Partition:       int(p.PartitionIndex),
   115  				CommittedOffset: p.CommittedOffset,
   116  				Metadata:        p.Metadata,
   117  				Error:           makeError(p.ErrorCode, ""),
   118  			}
   119  		}
   120  
   121  		ret.Topics[t.Name] = partitions
   122  	}
   123  
   124  	return ret, nil
   125  }
   126  
   127  type offsetFetchRequestV1Topic struct {
   128  	// Topic name
   129  	Topic string
   130  
   131  	// Partitions to fetch offsets
   132  	Partitions []int32
   133  }
   134  
   135  func (t offsetFetchRequestV1Topic) size() int32 {
   136  	return sizeofString(t.Topic) +
   137  		sizeofInt32Array(t.Partitions)
   138  }
   139  
   140  func (t offsetFetchRequestV1Topic) writeTo(wb *writeBuffer) {
   141  	wb.writeString(t.Topic)
   142  	wb.writeInt32Array(t.Partitions)
   143  }
   144  
   145  type offsetFetchRequestV1 struct {
   146  	// GroupID holds the unique group identifier
   147  	GroupID string
   148  
   149  	// Topics to fetch offsets.
   150  	Topics []offsetFetchRequestV1Topic
   151  }
   152  
   153  func (t offsetFetchRequestV1) size() int32 {
   154  	return sizeofString(t.GroupID) +
   155  		sizeofArray(len(t.Topics), func(i int) int32 { return t.Topics[i].size() })
   156  }
   157  
   158  func (t offsetFetchRequestV1) writeTo(wb *writeBuffer) {
   159  	wb.writeString(t.GroupID)
   160  	wb.writeArray(len(t.Topics), func(i int) { t.Topics[i].writeTo(wb) })
   161  }
   162  
   163  type offsetFetchResponseV1PartitionResponse struct {
   164  	// Partition ID
   165  	Partition int32
   166  
   167  	// Offset of last committed message
   168  	Offset int64
   169  
   170  	// Metadata client wants to keep
   171  	Metadata string
   172  
   173  	// ErrorCode holds response error code
   174  	ErrorCode int16
   175  }
   176  
   177  func (t offsetFetchResponseV1PartitionResponse) size() int32 {
   178  	return sizeofInt32(t.Partition) +
   179  		sizeofInt64(t.Offset) +
   180  		sizeofString(t.Metadata) +
   181  		sizeofInt16(t.ErrorCode)
   182  }
   183  
   184  func (t offsetFetchResponseV1PartitionResponse) writeTo(wb *writeBuffer) {
   185  	wb.writeInt32(t.Partition)
   186  	wb.writeInt64(t.Offset)
   187  	wb.writeString(t.Metadata)
   188  	wb.writeInt16(t.ErrorCode)
   189  }
   190  
   191  func (t *offsetFetchResponseV1PartitionResponse) readFrom(r *bufio.Reader, size int) (remain int, err error) {
   192  	if remain, err = readInt32(r, size, &t.Partition); err != nil {
   193  		return
   194  	}
   195  	if remain, err = readInt64(r, remain, &t.Offset); err != nil {
   196  		return
   197  	}
   198  	if remain, err = readString(r, remain, &t.Metadata); err != nil {
   199  		return
   200  	}
   201  	if remain, err = readInt16(r, remain, &t.ErrorCode); err != nil {
   202  		return
   203  	}
   204  	return
   205  }
   206  
   207  type offsetFetchResponseV1Response struct {
   208  	// Topic name
   209  	Topic string
   210  
   211  	// PartitionResponses holds offsets by partition
   212  	PartitionResponses []offsetFetchResponseV1PartitionResponse
   213  }
   214  
   215  func (t offsetFetchResponseV1Response) size() int32 {
   216  	return sizeofString(t.Topic) +
   217  		sizeofArray(len(t.PartitionResponses), func(i int) int32 { return t.PartitionResponses[i].size() })
   218  }
   219  
   220  func (t offsetFetchResponseV1Response) writeTo(wb *writeBuffer) {
   221  	wb.writeString(t.Topic)
   222  	wb.writeArray(len(t.PartitionResponses), func(i int) { t.PartitionResponses[i].writeTo(wb) })
   223  }
   224  
   225  func (t *offsetFetchResponseV1Response) readFrom(r *bufio.Reader, size int) (remain int, err error) {
   226  	if remain, err = readString(r, size, &t.Topic); err != nil {
   227  		return
   228  	}
   229  
   230  	fn := func(r *bufio.Reader, size int) (fnRemain int, fnErr error) {
   231  		item := offsetFetchResponseV1PartitionResponse{}
   232  		if fnRemain, fnErr = (&item).readFrom(r, size); err != nil {
   233  			return
   234  		}
   235  		t.PartitionResponses = append(t.PartitionResponses, item)
   236  		return
   237  	}
   238  	if remain, err = readArrayWith(r, remain, fn); err != nil {
   239  		return
   240  	}
   241  
   242  	return
   243  }
   244  
   245  type offsetFetchResponseV1 struct {
   246  	// Responses holds topic partition offsets
   247  	Responses []offsetFetchResponseV1Response
   248  }
   249  
   250  func (t offsetFetchResponseV1) size() int32 {
   251  	return sizeofArray(len(t.Responses), func(i int) int32 { return t.Responses[i].size() })
   252  }
   253  
   254  func (t offsetFetchResponseV1) writeTo(wb *writeBuffer) {
   255  	wb.writeArray(len(t.Responses), func(i int) { t.Responses[i].writeTo(wb) })
   256  }
   257  
   258  func (t *offsetFetchResponseV1) readFrom(r *bufio.Reader, size int) (remain int, err error) {
   259  	fn := func(r *bufio.Reader, withSize int) (fnRemain int, fnErr error) {
   260  		item := offsetFetchResponseV1Response{}
   261  		if fnRemain, fnErr = (&item).readFrom(r, withSize); fnErr != nil {
   262  			return
   263  		}
   264  		t.Responses = append(t.Responses, item)
   265  		return
   266  	}
   267  	if remain, err = readArrayWith(r, size, fn); err != nil {
   268  		return
   269  	}
   270  
   271  	return
   272  }