1use api::v1::SemanticType;
16use arrow_schema::extension::ExtensionType;
17use common_function::scalars::json::json_get::{JsonGetWithType, json_object_path};
18use datafusion_expr::Expr;
19use datatypes::arrow::array::{Array, ArrayRef, StructArray};
20use datatypes::arrow::record_batch::RecordBatch;
21use datatypes::data_type::{ConcreteDataType, DataType};
22use datatypes::extension::json::Json2ExtensionType;
23use datatypes::json::JsonTypeHint;
24use datatypes::schema::FulltextOptions;
25use datatypes::vectors::{Helper, VectorRef};
26use index::target::IndexTarget;
27use snafu::{OptionExt, ResultExt};
28use store_api::metadata::RegionMetadata;
29use store_api::storage::consts::PRIMARY_KEY_COLUMN_NAME;
30
31use crate::error::{ConvertVectorSnafu, IndexOptionsSnafu, InvalidRecordBatchSnafu, Result};
32use crate::sst::parquet::format::PrimaryKeyArray;
33
34pub(crate) fn extract_json_type_hint_values(root: &ArrayRef, path: &[String]) -> Result<VectorRef> {
46 let mut array = root.clone();
47 for segment in path {
48 let object = array
49 .as_any()
50 .downcast_ref::<StructArray>()
51 .with_context(|| InvalidRecordBatchSnafu {
52 reason: format!("JSON index path {path:?} traverses a non-object"),
53 })?;
54 array = object
55 .column_by_name(segment)
56 .with_context(|| InvalidRecordBatchSnafu {
57 reason: format!("JSON index path {path:?} has no materialized field {segment}"),
58 })?
59 .clone();
60 }
61 Helper::try_into_vector(array).context(ConvertVectorSnafu)
62}
63
64pub(crate) fn column_index_rows(
70 batch: &RecordBatch,
71 semantic_type: SemanticType,
72) -> impl Iterator<Item = (usize, usize)> + '_ {
73 let keys = (semantic_type == SemanticType::Tag)
74 .then(|| batch.column_by_name(PRIMARY_KEY_COLUMN_NAME))
75 .flatten()
76 .and_then(|array| array.as_any().downcast_ref::<PrimaryKeyArray>())
77 .filter(|array| array.null_count() == 0 && array.values().null_count() == 0)
78 .map(|array| array.keys().values());
79 let mut row = 0;
80 std::iter::from_fn(move || {
81 if row == batch.num_rows() {
82 return None;
83 }
84 let start = row;
85 row += 1;
86 if let Some(keys) = keys {
87 while row < keys.len() && keys[row] == keys[start] {
88 row += 1;
89 }
90 }
91 Some((start, row - start))
92 })
93}
94
95pub(crate) fn json_index_target(
97 metadata: &RegionMetadata,
98 expr: &Expr,
99 enabled: impl Fn(&JsonTypeHint) -> bool,
100) -> Result<Option<(IndexTarget, ConcreteDataType)>> {
101 let Expr::ScalarFunction(func) = expr else {
102 return Ok(None);
103 };
104
105 if func.func.name() != JsonGetWithType::NAME || !(2..=3).contains(&func.args.len()) {
106 return Ok(None);
107 }
108
109 let Expr::Column(column) = &func.args[0] else {
110 return Ok(None);
111 };
112
113 let Some(col) = metadata.column_by_name(&column.name) else {
114 return Ok(None);
115 };
116
117 if !col.column_schema.data_type.is_json2() {
118 return Ok(None);
119 }
120
121 let Some(path_literal) = func.args[1]
122 .as_literal()
123 .and_then(|v| v.try_as_str())
124 .flatten()
125 else {
126 return Ok(None);
127 };
128
129 let Some(path) = json_object_path(path_literal).ok().flatten() else {
130 return Ok(None);
131 };
132
133 let Ok(Some(extension)) = col.column_schema.extension_type::<Json2ExtensionType>() else {
134 return Ok(None);
135 };
136
137 let Some(hint) = extension
138 .metadata()
139 .json_settings()
140 .type_hints()
141 .iter()
142 .find(|hint| hint.path == path && enabled(hint))
143 else {
144 return Ok(None);
145 };
146
147 if let Some(type_arg) = func.args.get(2) {
148 let Expr::Literal(value, _) = type_arg else {
149 return Ok(None);
150 };
151 if !value.is_null() {
152 return Ok(None);
153 }
154 let result_type = value.data_type();
156 if result_type != hint.data_type.as_arrow_type()
157 && !(result_type.is_string() && hint.data_type.is_string())
158 {
159 return Ok(None);
160 }
161 }
162
163 if !(hint.data_type.is_numeric() || hint.data_type.is_boolean() || hint.data_type.is_string()) {
164 return Ok(None);
165 }
166 Ok(IndexTarget::new_json_path(col.column_id, path)
167 .ok()
168 .map(|target| (target, hint.data_type.clone())))
169}
170
171pub(crate) fn skipping_target_data_type(
173 metadata: &RegionMetadata,
174 target: &IndexTarget,
175) -> Option<ConcreteDataType> {
176 let path = match target {
177 IndexTarget::ColumnId(id) => {
178 return metadata
179 .column_by_id(*id)
180 .map(|col| col.column_schema.data_type.clone());
181 }
182 IndexTarget::JsonPath(path) => path,
183 };
184 let column = metadata.column_by_id(path.column_id())?;
185 let extension = column
186 .column_schema
187 .extension_type::<Json2ExtensionType>()
188 .ok()??;
189 extension
190 .metadata()
191 .json_settings()
192 .type_hints()
193 .iter()
194 .find(|hint| hint.path == path.path() && hint.skipping_index.is_some())
195 .map(|hint| hint.data_type.clone())
196}
197
198pub(crate) fn fulltext_target_options(
200 metadata: &RegionMetadata,
201 target: &IndexTarget,
202) -> Result<Option<FulltextOptions>> {
203 let path = match target {
204 IndexTarget::ColumnId(id) => {
205 let Some(column) = metadata.column_by_id(*id) else {
206 return Ok(None);
207 };
208 if !column.column_schema.data_type.is_string() {
209 return Ok(None);
210 }
211 return column
212 .column_schema
213 .fulltext_options()
214 .with_context(|_| IndexOptionsSnafu {
215 column_name: &column.column_schema.name,
216 })
217 .map(|options| options.filter(|options| options.enable));
218 }
219 IndexTarget::JsonPath(path) => path,
220 };
221 let Some(column) = metadata.column_by_id(path.column_id()) else {
222 return Ok(None);
223 };
224 if !column.column_schema.data_type.is_json2() {
225 return Ok(None);
226 }
227 let Some(extension) = column
228 .column_schema
229 .extension_type::<Json2ExtensionType>()
230 .with_context(|_| IndexOptionsSnafu {
231 column_name: &column.column_schema.name,
232 })?
233 else {
234 return Ok(None);
235 };
236 Ok(extension
237 .metadata()
238 .json_settings()
239 .type_hints()
240 .iter()
241 .find(|hint| hint.path == path.path() && hint.data_type.is_string())
242 .and_then(|hint| hint.fulltext_index.clone())
243 .filter(|options| options.enable))
244}
245
246#[cfg(test)]
247mod tests {
248 use std::sync::Arc;
249
250 use datatypes::arrow::array::{Int64Array, NullArray};
251 use datatypes::arrow::buffer::NullBuffer;
252 use datatypes::arrow::datatypes::Field;
253 use datatypes::schema::{ColumnSchema, FulltextOptions};
254 use datatypes::value::Value;
255 use store_api::metadata::{ColumnMetadata, RegionMetadataBuilder};
256 use store_api::storage::RegionId;
257
258 use super::*;
259
260 #[test]
261 fn test_fulltext_target_options_requires_string_column() {
262 let options = FulltextOptions {
263 enable: true,
264 ..Default::default()
265 };
266 for data_type in [
267 ConcreteDataType::string_datatype(),
268 ConcreteDataType::int64_datatype(),
269 ] {
270 let expected = data_type.is_string().then_some(options.clone());
271 let mut builder = RegionMetadataBuilder::new(RegionId::new(1, 1));
272 builder.push_column_metadata(ColumnMetadata {
273 column_schema: ColumnSchema::new("value", data_type, true)
274 .with_fulltext_options(options.clone())
275 .unwrap(),
276 column_id: 1,
277 semantic_type: SemanticType::Field,
278 });
279 builder.push_column_metadata(ColumnMetadata {
280 column_schema: ColumnSchema::new(
281 "ts",
282 ConcreteDataType::timestamp_millisecond_datatype(),
283 false,
284 ),
285 column_id: 2,
286 semantic_type: SemanticType::Timestamp,
287 });
288 let metadata = builder.build().unwrap();
289 assert_eq!(
290 fulltext_target_options(&metadata, &IndexTarget::ColumnId(1)).unwrap(),
291 expected
292 );
293 }
294 }
295
296 #[test]
297 fn test_extract_json_type_hint_values_null_type() {
298 let nulls: ArrayRef = Arc::new(NullArray::new(2));
299 let root: ArrayRef = Arc::new(StructArray::new(
300 vec![Field::new("a", nulls.data_type().clone(), true)].into(),
301 vec![nulls],
302 None,
303 ));
304 assert!(extract_json_type_hint_values(&root, &["a".into(), "b".into()]).is_err());
306 let leaf = extract_json_type_hint_values(&root, &["a".into()]).unwrap();
307 assert_eq!(leaf.len(), 2);
308 assert_eq!(leaf.get(0), Value::Null);
309 assert_eq!(leaf.get(1), Value::Null);
310
311 let values: ArrayRef = Arc::new(Int64Array::from(vec![None, None]));
312 let root: ArrayRef = Arc::new(StructArray::new(
313 vec![Field::new("a", values.data_type().clone(), true)].into(),
314 vec![values],
315 None,
316 ));
317 let leaf = extract_json_type_hint_values(&root, &["a".into()]).unwrap();
318 assert_eq!(leaf.len(), 2);
319 assert_eq!(leaf.get(0), Value::Null);
320 assert_eq!(leaf.get(1), Value::Null);
321 }
322
323 #[test]
324 fn test_extract_json_type_hint_values_nulls_and_slices() {
325 let child = Arc::new(Int64Array::from(vec![Some(11), None, None, None, Some(55)]));
326 let nested = StructArray::new(
327 vec![Field::new("a.b", child.data_type().clone(), true)].into(),
328 vec![child],
329 Some(NullBuffer::from(vec![true, false, false, true, true])),
330 );
331 let root: ArrayRef = Arc::new(StructArray::new(
332 vec![Field::new("nested", nested.data_type().clone(), true)].into(),
333 vec![Arc::new(nested)],
334 Some(NullBuffer::from(vec![true, true, false, true, true])),
335 ));
336 let path = vec!["nested".into(), "a.b".into()];
337 let leaf = extract_json_type_hint_values(&root, &path).unwrap();
338 assert_eq!(
339 (0..leaf.len()).map(|i| leaf.get(i)).collect::<Vec<_>>(),
340 vec![
341 Value::Int64(11),
342 Value::Null,
343 Value::Null,
344 Value::Null,
345 Value::Int64(55)
346 ]
347 );
348 let slice = extract_json_type_hint_values(&root.slice(1, 4), &path).unwrap();
349 assert_eq!(
350 (0..slice.len()).map(|i| slice.get(i)).collect::<Vec<_>>(),
351 vec![Value::Null, Value::Null, Value::Null, Value::Int64(55)]
352 );
353 assert!(extract_json_type_hint_values(&root, &["missing".into()]).is_err());
354 assert!(
355 extract_json_type_hint_values(&root, &["nested".into(), "a.b".into(), "child".into()])
356 .is_err()
357 );
358 }
359}