risingwave_connector/parser/protobuf/
parser.rs1use std::collections::HashSet;
16
17use anyhow::Context;
18use prost_reflect::{DescriptorPool, DynamicMessage, FileDescriptor, MessageDescriptor};
19use risingwave_common::catalog::Field;
20use risingwave_common::{bail, try_match_expand};
21use risingwave_connector_codec::decoder::protobuf::ProtobufAccess;
22pub use risingwave_connector_codec::decoder::protobuf::parser::{PROTOBUF_MESSAGES_AS_JSONB, *};
23
24use crate::error::ConnectorResult;
25use crate::parser::unified::AccessImpl;
26use crate::parser::utils::bytes_from_url;
27use crate::parser::{AccessBuilder, EncodingProperties, SchemaLocation};
28use crate::schema::schema_registry::{Client, WireFormatError, extract_schema_id, handle_sr_list};
29use crate::schema::{
30 ConfluentSchemaLoader, InvalidOptionError, SchemaLoader, bail_invalid_option_error,
31};
32
33#[derive(Debug)]
34pub struct ProtobufAccessBuilder {
35 wire_type: WireType,
36 message_descriptor: MessageDescriptor,
37
38 messages_as_jsonb: HashSet<String>,
41}
42
43impl AccessBuilder for ProtobufAccessBuilder {
44 async fn generate_accessor(
45 &mut self,
46 payload: Vec<u8>,
47 _: &crate::source::SourceMeta,
48 ) -> ConnectorResult<AccessImpl<'_>> {
49 let payload = match self.wire_type {
50 WireType::Confluent => resolve_pb_header(&payload)?,
51 WireType::None => &payload,
52 };
53
54 let message = DynamicMessage::decode(self.message_descriptor.clone(), payload)
55 .context("failed to parse message")?;
56
57 Ok(AccessImpl::Protobuf(ProtobufAccess::new(
58 message,
59 &self.messages_as_jsonb,
60 )))
61 }
62}
63
64impl ProtobufAccessBuilder {
65 pub fn new(config: ProtobufParserConfig) -> ConnectorResult<Self> {
66 let ProtobufParserConfig {
67 wire_type,
68 message_descriptor,
69 messages_as_jsonb,
70 } = config;
71
72 Ok(Self {
73 wire_type,
74 message_descriptor,
75 messages_as_jsonb,
76 })
77 }
78}
79
80#[derive(Debug, Clone)]
81enum WireType {
82 None,
83 Confluent,
84 }
87
88impl TryFrom<&SchemaLocation> for WireType {
89 type Error = InvalidOptionError;
90
91 fn try_from(value: &SchemaLocation) -> Result<Self, Self::Error> {
92 match value {
93 SchemaLocation::File { .. } => Ok(Self::None),
94 SchemaLocation::Confluent { .. } => Ok(Self::Confluent),
95 SchemaLocation::Glue { .. } => bail_invalid_option_error!(
96 "encode protobuf from aws glue schema registry not supported yet"
97 ),
98 SchemaLocation::Pulsar { .. } => bail_invalid_option_error!(
99 "encode protobuf from Pulsar schema registry not supported yet"
100 ),
101 }
102 }
103}
104
105#[derive(Debug, Clone)]
106pub struct ProtobufParserConfig {
107 wire_type: WireType,
108 pub(crate) message_descriptor: MessageDescriptor,
109 messages_as_jsonb: HashSet<String>,
110}
111
112impl ProtobufParserConfig {
113 pub async fn new(encoding_properties: EncodingProperties) -> ConnectorResult<Self> {
114 let protobuf_config = try_match_expand!(encoding_properties, EncodingProperties::Protobuf)?;
115 let message_name = &protobuf_config.message_name;
116
117 let wire_type = (&protobuf_config.schema_location).try_into()?;
118 if protobuf_config.key_message_name.is_some() {
119 bail!("protobuf key is not supported");
121 }
122 let pool = match protobuf_config.schema_location {
123 SchemaLocation::Confluent {
124 urls,
125 client_config,
126 name_strategy,
127 topic,
128 } => {
129 let url = handle_sr_list(urls.as_str())?;
130 let client = Client::new(url, &client_config)?;
131 let loader = SchemaLoader::Confluent(ConfluentSchemaLoader {
132 client,
133 name_strategy,
134 topic,
135 key_record_name: None,
136 val_record_name: Some(message_name.clone()),
137 });
138 let (_schema_id, root_file_descriptor) = loader
139 .load_val_schema::<FileDescriptor>()
140 .await
141 .context("load schema failed")?;
142 root_file_descriptor.parent_pool().clone()
143 }
144 SchemaLocation::File {
145 url,
146 aws_auth_props,
147 } => {
148 let url = handle_sr_list(url.as_str())?;
149 let url = url.first().unwrap();
150 let schema_bytes = bytes_from_url(url, aws_auth_props.as_ref()).await?;
151 DescriptorPool::decode(schema_bytes.as_slice())
152 .with_context(|| format!("cannot build descriptor pool from schema `{url}`"))?
153 }
154 SchemaLocation::Glue { .. } => bail_invalid_option_error!(
155 "encode protobuf from aws glue schema registry not supported yet"
156 ),
157 SchemaLocation::Pulsar { .. } => bail_invalid_option_error!(
158 "encode protobuf from Pulsar schema registry not supported yet"
159 ),
160 };
161
162 let message_descriptor = pool
163 .get_message_by_name(message_name)
164 .with_context(|| format!("cannot find message `{message_name}` in schema"))?;
165
166 Ok(Self {
167 message_descriptor,
168 wire_type,
169 messages_as_jsonb: protobuf_config.messages_as_jsonb,
170 })
171 }
172
173 pub fn map_to_columns(&self) -> ConnectorResult<Vec<Field>> {
175 pb_schema_to_fields(&self.message_descriptor, &self.messages_as_jsonb).map_err(|e| e.into())
176 }
177}
178
179fn decode_varint_zigzag(buffer: &[u8]) -> ConnectorResult<(i32, usize)> {
182 let mut value = 0u32;
184 let mut shift = 0;
185 let mut len = 0usize;
186
187 for &byte in buffer {
188 len += 1;
189 if len > 5 {
191 break;
192 }
193 let byte_ext = byte as u32;
195 value |= (byte_ext & 0x7F) << shift;
198 if byte_ext & 0x80 == 0 {
199 return Ok((((value >> 1) as i32) ^ -((value & 1) as i32), len));
200 }
201
202 shift += 7;
203 }
204
205 Err(WireFormatError::ParseMessageIndexes.into())
206}
207
208pub(crate) fn resolve_pb_header(payload: &[u8]) -> ConnectorResult<&[u8]> {
213 let (_, remained) = extract_schema_id(payload)?;
216 match remained.first() {
220 Some(0) => Ok(&remained[1..]),
221 Some(_) => {
222 let (index_len, mut offset) = decode_varint_zigzag(remained)?;
223 for _ in 0..index_len {
224 offset += decode_varint_zigzag(&remained[offset..])?.1;
225 }
226 Ok(&remained[offset..])
227 }
228 None => bail!("The proto payload is empty"),
229 }
230}
231
232#[cfg(test)]
233mod test {
234 use super::*;
235
236 #[test]
237 fn test_decode_varint_zigzag() {
238 let buffer = vec![0x02];
240 let (value, len) = decode_varint_zigzag(&buffer).unwrap();
241 assert_eq!(value, 1);
242 assert_eq!(len, 1);
243
244 let buffer = vec![0x01];
246 let (value, len) = decode_varint_zigzag(&buffer).unwrap();
247 assert_eq!(value, -1);
248 assert_eq!(len, 1);
249
250 let buffer = vec![0x9E, 0x03];
252 let (value, len) = decode_varint_zigzag(&buffer).unwrap();
253 assert_eq!(value, 207);
254 assert_eq!(len, 2);
255
256 let buffer = vec![0xBF, 0x07];
258 let (value, len) = decode_varint_zigzag(&buffer).unwrap();
259 assert_eq!(value, -480);
260 assert_eq!(len, 2);
261
262 let buffer = vec![0xFE, 0xFF, 0xFF, 0xFF, 0x0F];
264 let (value, len) = decode_varint_zigzag(&buffer).unwrap();
265 assert_eq!(value, i32::MAX);
266 assert_eq!(len, 5);
267
268 let buffer = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x0F];
270 let (value, len) = decode_varint_zigzag(&buffer).unwrap();
271 assert_eq!(value, i32::MIN);
272 assert_eq!(len, 5);
273
274 let buffer = vec![0xFF, 0xFF, 0xFF, 0xFF, 0x7F];
276 let (value, len) = decode_varint_zigzag(&buffer).unwrap();
277 assert_eq!(value, i32::MIN);
278 assert_eq!(len, 5);
279
280 let buffer = vec![0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
282 let result = decode_varint_zigzag(&buffer);
283 assert!(result.is_err());
284 }
285}