github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/internal/topic/topicwriterinternal/message.go (about) 1 package topicwriterinternal 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "time" 9 10 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopiccommon" 11 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopicwriter" 12 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 13 ) 14 15 var errNoRawContent = xerrors.Wrap(errors.New("ydb: internal state error - no raw message content")) 16 17 type PublicMessage struct { 18 SeqNo int64 19 CreatedAt time.Time 20 Data io.Reader 21 Metadata map[string][]byte 22 23 // partitioning at level message available by protocol, but doesn't available by current server implementation 24 // the field hidden from public access for prevent runtime errors. 25 // it will be published after implementation on server side. 26 futurePartitioning PublicFuturePartitioning 27 } 28 29 // PublicFuturePartitioning will be published in feature, after server implementation completed. 30 type PublicFuturePartitioning struct { 31 messageGroupID string 32 partitionID int64 33 hasPartitionID bool 34 } 35 36 func (p PublicFuturePartitioning) ToRaw() rawtopicwriter.Partitioning { 37 if p.hasPartitionID { 38 return rawtopicwriter.NewPartitioningPartitionID(p.partitionID) 39 } 40 41 return rawtopicwriter.NewPartitioningMessageGroup(p.messageGroupID) 42 } 43 44 func NewPartitioningWithMessageGroupID(id string) PublicFuturePartitioning { 45 return PublicFuturePartitioning{ 46 messageGroupID: id, 47 } 48 } 49 50 func NewPartitioningWithPartitionID(id int64) PublicFuturePartitioning { 51 return PublicFuturePartitioning{ 52 partitionID: id, 53 hasPartitionID: true, 54 } 55 } 56 57 type messageWithDataContent struct { 58 PublicMessage 59 60 dataWasRead bool 61 hasRawContent bool 62 hasEncodedContent bool 63 metadataCached bool 64 bufCodec rawtopiccommon.Codec 65 bufEncoded bytes.Buffer 66 rawBuf bytes.Buffer 67 encoders *EncoderMap 68 BufUncompressedSize int 69 } 70 71 func (m *messageWithDataContent) GetEncodedBytes(codec rawtopiccommon.Codec) ([]byte, error) { 72 if codec == rawtopiccommon.CodecRaw { 73 return m.getRawBytes() 74 } 75 76 return m.getEncodedBytes(codec) 77 } 78 79 func (m *messageWithDataContent) cacheMetadata() { 80 if m.metadataCached { 81 return 82 } 83 84 // ensure message metadata can't be changed by external code 85 if len(m.Metadata) > 0 { 86 ownCopy := make(map[string][]byte, len(m.Metadata)) 87 for key, val := range m.Metadata { 88 ownCopy[key] = bytes.Clone(val) 89 } 90 m.Metadata = ownCopy 91 } else { 92 m.Metadata = nil 93 } 94 m.metadataCached = true 95 } 96 97 func (m *messageWithDataContent) CacheMessageData(codec rawtopiccommon.Codec) error { 98 m.cacheMetadata() 99 _, err := m.GetEncodedBytes(codec) 100 101 return err 102 } 103 104 func (m *messageWithDataContent) encodeRawContent(codec rawtopiccommon.Codec) ([]byte, error) { 105 if !m.hasRawContent { 106 return nil, xerrors.WithStackTrace(errNoRawContent) 107 } 108 109 m.bufEncoded.Reset() 110 111 writer, err := m.encoders.CreateLazyEncodeWriter(codec, &m.bufEncoded) 112 if err != nil { 113 return nil, xerrors.WithStackTrace(xerrors.Wrap(fmt.Errorf( 114 "ydb: failed create encoder for message, codec '%v': %w", 115 codec, 116 err, 117 ))) 118 } 119 _, err = writer.Write(m.rawBuf.Bytes()) 120 if err == nil { 121 err = writer.Close() 122 } 123 if err != nil { 124 return nil, xerrors.WithStackTrace(xerrors.Wrap(fmt.Errorf( 125 "ydb: failed to compress message, codec '%v': %w", 126 codec, 127 err, 128 ))) 129 } 130 131 m.bufCodec = codec 132 133 return m.bufEncoded.Bytes(), nil 134 } 135 136 func (m *messageWithDataContent) readDataToRawBuf() error { 137 m.rawBuf.Reset() 138 m.hasRawContent = true 139 if m.Data != nil { 140 writtenBytes, err := io.Copy(&m.rawBuf, m.Data) 141 if err != nil { 142 return xerrors.WithStackTrace(err) 143 } 144 m.BufUncompressedSize = int(writtenBytes) 145 m.Data = nil 146 } 147 148 return nil 149 } 150 151 func (m *messageWithDataContent) readDataToTargetCodec(codec rawtopiccommon.Codec) error { 152 m.dataWasRead = true 153 m.hasEncodedContent = true 154 m.bufCodec = codec 155 m.bufEncoded.Reset() 156 157 encoder, err := m.encoders.CreateLazyEncodeWriter(codec, &m.bufEncoded) 158 if err != nil { 159 return err 160 } 161 162 reader := m.Data 163 if reader == nil { 164 reader = &bytes.Reader{} 165 } 166 bytesCount, err := io.Copy(encoder, reader) 167 if err == nil { 168 err = encoder.Close() 169 } 170 if err != nil { 171 return xerrors.WithStackTrace(xerrors.Wrap(fmt.Errorf( 172 "ydb: failed compress message with codec '%v': %w", 173 codec, 174 err, 175 ))) 176 } 177 m.BufUncompressedSize = int(bytesCount) 178 m.Data = nil 179 180 return nil 181 } 182 183 func (m *messageWithDataContent) getRawBytes() ([]byte, error) { 184 if m.hasRawContent { 185 return m.rawBuf.Bytes(), nil 186 } 187 if m.dataWasRead { 188 return nil, xerrors.WithStackTrace(errNoRawContent) 189 } 190 191 err := m.readDataToRawBuf() 192 if err != nil { 193 return nil, err 194 } 195 196 return m.rawBuf.Bytes(), nil 197 } 198 199 func (m *messageWithDataContent) getEncodedBytes(codec rawtopiccommon.Codec) ([]byte, error) { 200 switch { 201 case m.hasEncodedContent && m.bufCodec == codec: 202 return m.bufEncoded.Bytes(), nil 203 case m.hasRawContent: 204 return m.encodeRawContent(codec) 205 case m.dataWasRead: 206 return nil, errNoRawContent 207 default: 208 err := m.readDataToTargetCodec(codec) 209 if err != nil { 210 return nil, err 211 } 212 213 return m.bufEncoded.Bytes(), nil 214 } 215 } 216 217 func newMessageDataWithContent( 218 message PublicMessage, //nolint:gocritic 219 encoders *EncoderMap, 220 ) messageWithDataContent { 221 return messageWithDataContent{ 222 PublicMessage: message, 223 encoders: encoders, 224 } 225 }