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