risingwave_connector/sink/elasticsearch_opensearch/
opensearch.rs1use anyhow::anyhow;
16use risingwave_common::catalog::Schema;
17use risingwave_common::session_config::sink_decouple::SinkDecouple;
18use tonic::async_trait;
19
20use super::super::writer::{AsyncTruncateLogSinkerOf, AsyncTruncateSinkWriterExt};
21use super::super::{Sink, SinkError, SinkParam, SinkWriterParam};
22use super::elasticsearch_opensearch_client::ElasticSearchOpenSearchSinkWriter;
23use super::elasticsearch_opensearch_config::{ElasticSearchOpenSearchConfig, OpenSearchConfig};
24use crate::enforce_secret::EnforceSecret;
25use crate::sink::Result;
26
27pub const OPENSEARCH_SINK: &str = "opensearch";
28
29#[derive(Debug)]
30pub struct OpenSearchSink {
31 config: ElasticSearchOpenSearchConfig,
32 schema: Schema,
33 pk_indices: Vec<usize>,
34 is_append_only: bool,
35}
36
37impl EnforceSecret for OpenSearchSink {
38 fn enforce_secret<'a>(
39 prop_iter: impl Iterator<Item = &'a str>,
40 ) -> crate::error::ConnectorResult<()> {
41 for prop in prop_iter {
42 ElasticSearchOpenSearchConfig::enforce_one(prop)?;
43 }
44 Ok(())
45 }
46}
47#[async_trait]
48impl TryFrom<SinkParam> for OpenSearchSink {
49 type Error = SinkError;
50
51 fn try_from(param: SinkParam) -> std::result::Result<Self, Self::Error> {
52 let schema = param.schema();
53 let pk_indices = param.downstream_pk_or_empty();
54 let config = OpenSearchConfig::from_btreemap(param.properties)?.inner;
55 Ok(Self {
56 config,
57 schema,
58 pk_indices,
59 is_append_only: param.sink_type.is_append_only(),
60 })
61 }
62}
63
64impl Sink for OpenSearchSink {
65 type LogSinker = AsyncTruncateLogSinkerOf<ElasticSearchOpenSearchSinkWriter>;
66
67 const SINK_NAME: &'static str = OPENSEARCH_SINK;
68
69 fn validate_unknown_fields(&self) -> Result<()> {
70 crate::sink::validate_sink_unknown_fields(&self.config)
71 }
72
73 async fn validate(&self) -> Result<()> {
74 risingwave_common::license::Feature::OpenSearchSink
75 .check_available()
76 .map_err(|e| anyhow::anyhow!(e))?;
77 self.config.validate_config(&self.schema)?;
78 let client = self.config.build_client(Self::SINK_NAME)?;
79 client.ping().await?;
80 Ok(())
81 }
82
83 async fn new_log_sinker(&self, _writer_param: SinkWriterParam) -> Result<Self::LogSinker> {
84 Ok(ElasticSearchOpenSearchSinkWriter::new(
85 self.config.clone(),
86 self.schema.clone(),
87 self.pk_indices.clone(),
88 Self::SINK_NAME,
89 self.is_append_only,
90 )?
91 .into_log_sinker(self.config.concurrent_requests))
92 }
93
94 fn set_default_commit_checkpoint_interval(
95 desc: &mut crate::sink::catalog::desc::SinkDesc,
96 user_specified: &risingwave_common::session_config::sink_decouple::SinkDecouple,
97 ) -> Result<()> {
98 if crate::sink::is_sink_support_commit_checkpoint_interval(Self::SINK_NAME) {
99 match desc
100 .properties
101 .get(crate::sink::decouple_checkpoint_log_sink::COMMIT_CHECKPOINT_INTERVAL)
102 {
103 Some(commit_checkpoint_interval) => {
104 let commit_checkpoint_interval = commit_checkpoint_interval
105 .parse::<u64>()
106 .map_err(|e| SinkError::Config(anyhow!(e)))?;
107 if std::matches!(user_specified, SinkDecouple::Disable)
108 && commit_checkpoint_interval > 1
109 {
110 return Err(SinkError::Config(anyhow!(
111 "config conflict: `commit_checkpoint_interval` larger than 1 means that sink decouple must be enabled, but session config sink_decouple is disabled"
112 )));
113 }
114 }
115 None => match user_specified {
116 risingwave_common::session_config::sink_decouple::SinkDecouple::Default
117 | risingwave_common::session_config::sink_decouple::SinkDecouple::Enable => {
118 desc.properties.insert(
119 crate::sink::decouple_checkpoint_log_sink::COMMIT_CHECKPOINT_INTERVAL.to_owned(),
120 crate::sink::decouple_checkpoint_log_sink::DEFAULT_COMMIT_CHECKPOINT_INTERVAL_WITH_SINK_DECOUPLE.to_string(),
121 );
122 }
123 risingwave_common::session_config::sink_decouple::SinkDecouple::Disable => {
124 desc.properties.insert(
125 crate::sink::decouple_checkpoint_log_sink::COMMIT_CHECKPOINT_INTERVAL.to_owned(),
126 crate::sink::decouple_checkpoint_log_sink::DEFAULT_COMMIT_CHECKPOINT_INTERVAL_WITHOUT_SINK_DECOUPLE.to_string(),
127 );
128 }
129 },
130 }
131 }
132 Ok(())
133 }
134
135 fn is_sink_decouple(
136 user_specified: &risingwave_common::session_config::sink_decouple::SinkDecouple,
137 ) -> Result<bool> {
138 match user_specified {
139 risingwave_common::session_config::sink_decouple::SinkDecouple::Default
140 | risingwave_common::session_config::sink_decouple::SinkDecouple::Enable => Ok(true),
141 risingwave_common::session_config::sink_decouple::SinkDecouple::Disable => Ok(false),
142 }
143 }
144}