You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iggy.apache.org by pi...@apache.org on 2025/09/04 20:02:52 UTC

(iggy) branch master updated: feat(go): add DeleteConsumerOffset function. (#2147)

This is an automated email from the ASF dual-hosted git repository.

piotr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git


The following commit(s) were added to refs/heads/master by this push:
     new 8f59d91a feat(go): add DeleteConsumerOffset function. (#2147)
8f59d91a is described below

commit 8f59d91aa7551800131c7f0739046ceb497f52ba
Author: Chengxi <ch...@gmail.com>
AuthorDate: Thu Sep 4 16:02:47 2025 -0400

    feat(go): add DeleteConsumerOffset function. (#2147)
---
 bdd/go/tests/tcp_test/offset_feature_delete.go     | 155 +++++++++++++++++++++
 .../binary_request_serializer.go                   |  17 +++
 .../consumer_serializer.go}                        |  27 ++--
 foreign/go/contracts/command_codes.go              |   1 +
 foreign/go/contracts/consumer.go                   |   2 +-
 foreign/go/contracts/offets.go                     |   7 +
 foreign/go/errors/constants.go                     |   4 +
 foreign/go/errors/errors.go                        |   2 +
 foreign/go/iggycli/client.go                       |   9 ++
 foreign/go/tcp/tcp_offset_managament.go            |  11 ++
 10 files changed, 215 insertions(+), 20 deletions(-)

diff --git a/bdd/go/tests/tcp_test/offset_feature_delete.go b/bdd/go/tests/tcp_test/offset_feature_delete.go
new file mode 100644
index 00000000..2b775e98
--- /dev/null
+++ b/bdd/go/tests/tcp_test/offset_feature_delete.go
@@ -0,0 +1,155 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package tcp_test
+
+import (
+	iggcon "github.com/apache/iggy/foreign/go/contracts"
+	ierror "github.com/apache/iggy/foreign/go/errors"
+	"github.com/onsi/ginkgo/v2"
+)
+
+var _ = ginkgo.Describe("DELETE CONSUMER OFFSET:", func() {
+	prefix := "DeleteConsumerOffset"
+	ginkgo.When("User is logged in", func() {
+		ginkgo.Context("and deletes an existing consumer offset after storing it", func() {
+			client := createAuthorizedConnection()
+			streamId, _ := successfullyCreateStream(prefix, client)
+			defer deleteStreamAfterTests(streamId, client)
+			topicId, _ := successfullyCreateTopic(streamId, client)
+			groupId, _ := successfullyCreateConsumer(streamId, topicId, client)
+
+			streamIdentifier, _ := iggcon.NewIdentifier(streamId)
+			topicIdentifier, _ := iggcon.NewIdentifier(topicId)
+			groupIdentifier, _ := iggcon.NewIdentifier(groupId)
+
+			partitionId := uint32(1)
+			testOffset := uint64(1)
+			consumer := iggcon.NewSingleConsumer(groupIdentifier)
+
+			// send test messages
+			messages := createDefaultMessages()
+			err := client.SendMessages(
+				streamIdentifier,
+				topicIdentifier,
+				iggcon.PartitionId(partitionId),
+				messages,
+			)
+			itShouldNotReturnError(err)
+
+			// store consumer offset
+			err = client.StoreConsumerOffset(
+				consumer,
+				streamIdentifier,
+				topicIdentifier,
+				testOffset,
+				&partitionId,
+			)
+			itShouldNotReturnError(err)
+
+			// verify offset was stored
+			storedOffset, err := client.GetConsumerOffset(
+				consumer, streamIdentifier, topicIdentifier, &partitionId,
+			)
+			itShouldNotReturnError(err)
+			itShouldReturnStoredConsumerOffset(storedOffset, partitionId, testOffset)
+
+			// delete the offset
+			err = client.DeleteConsumerOffset(
+				consumer, streamIdentifier, topicIdentifier, &partitionId,
+			)
+			itShouldNotReturnError(err)
+
+			// verify offset was deleted
+			storedOffset, err = client.GetConsumerOffset(
+				consumer, streamIdentifier, topicIdentifier, &partitionId,
+			)
+			itShouldNotReturnError(err)
+			itShouldReturnNilOffsetForNewConsumerGroup(storedOffset)
+		})
+
+		ginkgo.Context("and attempts to delete a non-existing consumer offset", func() {
+			client := createAuthorizedConnection()
+			streamId, _ := successfullyCreateStream(prefix, client)
+			defer deleteStreamAfterTests(streamId, client)
+			topicId, _ := successfullyCreateTopic(streamId, client)
+			groupId, _ := successfullyCreateConsumer(streamId, topicId, client)
+
+			streamIdentifier, _ := iggcon.NewIdentifier(streamId)
+			topicIdentifier, _ := iggcon.NewIdentifier(topicId)
+			groupIdentifier, _ := iggcon.NewIdentifier(groupId)
+
+			partitionId := uint32(1)
+			consumer := iggcon.NewSingleConsumer(groupIdentifier)
+
+			err := client.DeleteConsumerOffset(
+				consumer,
+				streamIdentifier,
+				topicIdentifier,
+				&partitionId,
+			)
+			itShouldReturnSpecificIggyError(err, ierror.ConsumerOffsetNotFound)
+		})
+
+		ginkgo.Context("and attempts to delete an offset from a non-existing consumer group", func() {
+			client := createAuthorizedConnection()
+			streamId, _ := successfullyCreateStream(prefix, client)
+			defer deleteStreamAfterTests(streamId, client)
+			topicId, _ := successfullyCreateTopic(streamId, client)
+
+			streamIdentifier, _ := iggcon.NewIdentifier(streamId)
+			topicIdentifier, _ := iggcon.NewIdentifier(topicId)
+			consumer := iggcon.NewGroupConsumer(randomU32Identifier())
+			partitionId := uint32(1)
+
+			err := client.DeleteConsumerOffset(
+				consumer,
+				streamIdentifier,
+				topicIdentifier,
+				&partitionId,
+			)
+
+			itShouldReturnSpecificIggyError(err, ierror.ConsumerGroupIdNotFound)
+		})
+
+		ginkgo.Context("and attempts to delete an offset from a non-existing stream", func() {
+			client := createAuthorizedConnection()
+			consumer := iggcon.NewGroupConsumer(randomU32Identifier())
+			partitionId := uint32(1)
+
+			err := client.DeleteConsumerOffset(
+				consumer,
+				randomU32Identifier(),
+				randomU32Identifier(),
+				&partitionId)
+			itShouldReturnSpecificIggyError(err, ierror.StreamIdNotFound)
+		})
+	})
+
+	ginkgo.When("User is not logged in", func() {
+		ginkgo.Context("and attempts to delete a consumer offset", func() {
+			client := createClient()
+			consumer := iggcon.NewGroupConsumer(randomU32Identifier())
+			partitionId := uint32(1)
+
+			err := client.DeleteConsumerOffset(
+				consumer, randomU32Identifier(), randomU32Identifier(), &partitionId,
+			)
+			itShouldReturnUnauthenticatedError(err)
+		})
+	})
+})
diff --git a/foreign/go/binary_serialization/binary_request_serializer.go b/foreign/go/binary_serialization/binary_request_serializer.go
index d69e3b13..7ec4d2c0 100644
--- a/foreign/go/binary_serialization/binary_request_serializer.go
+++ b/foreign/go/binary_serialization/binary_request_serializer.go
@@ -62,6 +62,23 @@ func GetOffset(request iggcon.GetConsumerOffsetRequest) []byte {
 	return bytes
 }
 
+func DeleteOffset(request iggcon.DeleteConsumerOffsetRequest) []byte {
+	consumerBytes := SerializeConsumer(request.Consumer)
+	streamIdBytes := SerializeIdentifier(request.StreamId)
+	topicIdBytes := SerializeIdentifier(request.TopicId)
+	bytes := make([]byte, 0, 12+len(consumerBytes)+len(streamIdBytes)+len(topicIdBytes))
+	bytes = append(bytes, consumerBytes...)
+	bytes = append(bytes, streamIdBytes...)
+	bytes = append(bytes, topicIdBytes...)
+	if request.PartitionId != nil {
+		bytes = binary.LittleEndian.AppendUint32(bytes, *request.PartitionId)
+	} else {
+		bytes = binary.LittleEndian.AppendUint32(bytes, 0)
+	}
+
+	return bytes
+}
+
 func CreatePartitions(request iggcon.CreatePartitionsRequest) []byte {
 	bytes := make([]byte, 8+request.StreamId.Length+request.TopicId.Length)
 	position := 4 + request.StreamId.Length + request.TopicId.Length
diff --git a/foreign/go/contracts/offets.go b/foreign/go/binary_serialization/consumer_serializer.go
similarity index 55%
copy from foreign/go/contracts/offets.go
copy to foreign/go/binary_serialization/consumer_serializer.go
index 7c4a1213..a359ed51 100644
--- a/foreign/go/contracts/offets.go
+++ b/foreign/go/binary_serialization/consumer_serializer.go
@@ -15,25 +15,14 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package iggcon
+package binaryserialization
 
-type StoreConsumerOffsetRequest struct {
-	StreamId    Identifier `json:"streamId"`
-	TopicId     Identifier `json:"topicId"`
-	Consumer    Consumer   `json:"consumer"`
-	PartitionId *uint32    `json:"partitionId"`
-	Offset      uint64     `json:"offset"`
-}
-
-type GetConsumerOffsetRequest struct {
-	StreamId    Identifier `json:"streamId"`
-	TopicId     Identifier `json:"topicId"`
-	Consumer    Consumer   `json:"consumer"`
-	PartitionId *uint32    `json:"partitionId"`
-}
+import iggcon "github.com/apache/iggy/foreign/go/contracts"
 
-type ConsumerOffsetInfo struct {
-	PartitionId   uint32 `json:"partitionId"`
-	CurrentOffset uint64 `json:"currentOffset"`
-	StoredOffset  uint64 `json:"storedOffset"`
+func SerializeConsumer(consumer iggcon.Consumer) []byte {
+	idBytes := SerializeIdentifier(consumer.Id)
+	bytes := make([]byte, 0, 1+len(idBytes))
+	bytes = append(bytes, uint8(consumer.Kind))
+	bytes = append(bytes, idBytes...)
+	return bytes
 }
diff --git a/foreign/go/contracts/command_codes.go b/foreign/go/contracts/command_codes.go
index 53001c08..f385d826 100644
--- a/foreign/go/contracts/command_codes.go
+++ b/foreign/go/contracts/command_codes.go
@@ -42,6 +42,7 @@ const (
 	SendMessagesCode         CommandCode = 101
 	GetOffsetCode            CommandCode = 120
 	StoreOffsetCode          CommandCode = 121
+	DeleteConsumerOffsetCode CommandCode = 122
 	GetStreamCode            CommandCode = 200
 	GetStreamsCode           CommandCode = 201
 	CreateStreamCode         CommandCode = 202
diff --git a/foreign/go/contracts/consumer.go b/foreign/go/contracts/consumer.go
index 72b204ec..9ca9ea12 100644
--- a/foreign/go/contracts/consumer.go
+++ b/foreign/go/contracts/consumer.go
@@ -17,7 +17,7 @@
 
 package iggcon
 
-type ConsumerKind int
+type ConsumerKind uint8
 
 const (
 	ConsumerKindSingle ConsumerKind = 1
diff --git a/foreign/go/contracts/offets.go b/foreign/go/contracts/offets.go
index 7c4a1213..d85c25f4 100644
--- a/foreign/go/contracts/offets.go
+++ b/foreign/go/contracts/offets.go
@@ -37,3 +37,10 @@ type ConsumerOffsetInfo struct {
 	CurrentOffset uint64 `json:"currentOffset"`
 	StoredOffset  uint64 `json:"storedOffset"`
 }
+
+type DeleteConsumerOffsetRequest struct {
+	Consumer    Consumer
+	StreamId    Identifier
+	TopicId     Identifier
+	PartitionId *uint32
+}
diff --git a/foreign/go/errors/constants.go b/foreign/go/errors/constants.go
index ad5d9ebf..c0294cc9 100644
--- a/foreign/go/errors/constants.go
+++ b/foreign/go/errors/constants.go
@@ -38,6 +38,10 @@ var (
 		Code:    2010,
 		Message: "topic_id_not_found",
 	}
+	ConsumerOffsetNotFound = &IggyError{
+		Code:    3021,
+		Message: "consumer_offset_not_found",
+	}
 	InvalidMessagesCount = &IggyError{
 		Code:    4009,
 		Message: "invalid_messages_count",
diff --git a/foreign/go/errors/errors.go b/foreign/go/errors/errors.go
index 0c45beaf..737f76f9 100644
--- a/foreign/go/errors/errors.go
+++ b/foreign/go/errors/errors.go
@@ -219,6 +219,8 @@ func TranslateErrorCode(code int) string {
 		return "partition_not_found"
 	case 3008:
 		return "no_partitions"
+	case 3021:
+		return "consumer_offset_not_found"
 	case 4000:
 		return "segment_not_found"
 	case 4001:
diff --git a/foreign/go/iggycli/client.go b/foreign/go/iggycli/client.go
index cc0cc191..a5c2720e 100644
--- a/foreign/go/iggycli/client.go
+++ b/foreign/go/iggycli/client.go
@@ -123,6 +123,15 @@ type Client interface {
 	// Authentication is required, and the permission to read the streams or topics.
 	GetConsumerGroups(streamId iggcon.Identifier, topicId iggcon.Identifier) ([]iggcon.ConsumerGroup, error)
 
+	// DeleteConsumerOffset delete the consumer offset for a specific consumer or consumer group for the given stream and topic by unique IDs or names.
+	// Authentication is required, and the permission to poll the messages.
+	DeleteConsumerOffset(
+		consumer iggcon.Consumer,
+		streamId iggcon.Identifier,
+		topicId iggcon.Identifier,
+		partitionId *uint32,
+	) error
+
 	// GetConsumerGroup get the info about a specific consumer group by unique ID or name for the given stream and topic by unique IDs or names.
 	// Authentication is required, and the permission to read the streams or topics.
 	GetConsumerGroup(
diff --git a/foreign/go/tcp/tcp_offset_managament.go b/foreign/go/tcp/tcp_offset_managament.go
index 071dd32f..689e5a60 100644
--- a/foreign/go/tcp/tcp_offset_managament.go
+++ b/foreign/go/tcp/tcp_offset_managament.go
@@ -48,3 +48,14 @@ func (tms *IggyTcpClient) StoreConsumerOffset(consumer iggcon.Consumer, streamId
 	_, err := tms.sendAndFetchResponse(message, iggcon.StoreOffsetCode)
 	return err
 }
+
+func (tms *IggyTcpClient) DeleteConsumerOffset(consumer iggcon.Consumer, streamId iggcon.Identifier, topicId iggcon.Identifier, partitionId *uint32) error {
+	message := binaryserialization.DeleteOffset(iggcon.DeleteConsumerOffsetRequest{
+		Consumer:    consumer,
+		StreamId:    streamId,
+		TopicId:     topicId,
+		PartitionId: partitionId,
+	})
+	_, err := tms.sendAndFetchResponse(message, iggcon.DeleteConsumerOffsetCode)
+	return err
+}