Skip to content

Commit 739d6f5

Browse files
authored
feat(datafusion): add BlobDescriptor SQL helper functions (#525)
1 parent 7ceb2f8 commit 739d6f5

6 files changed

Lines changed: 455 additions & 7 deletions

File tree

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use std::sync::Arc;
19+
20+
use datafusion::arrow::array::{
21+
Array, BinaryArray, BinaryBuilder, BinaryViewArray, LargeBinaryArray, LargeStringArray,
22+
StringArray, StringBuilder, StringViewArray,
23+
};
24+
use datafusion::arrow::datatypes::DataType as ArrowDataType;
25+
use datafusion::common::types::logical_binary;
26+
use datafusion::common::utils::take_function_args;
27+
use datafusion::common::{DataFusionError, Result as DFResult, ScalarValue};
28+
use datafusion::logical_expr::{
29+
Coercion, ColumnarValue, ScalarFunctionArgs, ScalarUDF, ScalarUDFImpl, Signature,
30+
TypeSignatureClass, Volatility,
31+
};
32+
use datafusion::prelude::SessionContext;
33+
use paimon::spec::BlobDescriptor;
34+
35+
use crate::error::to_datafusion_error;
36+
37+
const PATH_TO_DESCRIPTOR: &str = "path_to_descriptor";
38+
const DESCRIPTOR_TO_STRING: &str = "descriptor_to_string";
39+
40+
pub(crate) fn register_blob_descriptor_functions(ctx: &SessionContext) {
41+
ctx.register_udf(ScalarUDF::from(PathToDescriptorFunc::new()));
42+
ctx.register_udf(ScalarUDF::from(DescriptorToStringFunc::new()));
43+
}
44+
45+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
46+
struct PathToDescriptorFunc {
47+
signature: Signature,
48+
aliases: Vec<String>,
49+
}
50+
51+
impl PathToDescriptorFunc {
52+
fn new() -> Self {
53+
Self {
54+
signature: Signature::string(1, Volatility::Immutable),
55+
aliases: vec!["sys.path_to_descriptor".to_string()],
56+
}
57+
}
58+
}
59+
60+
impl ScalarUDFImpl for PathToDescriptorFunc {
61+
fn name(&self) -> &str {
62+
PATH_TO_DESCRIPTOR
63+
}
64+
65+
fn aliases(&self) -> &[String] {
66+
&self.aliases
67+
}
68+
69+
fn signature(&self) -> &Signature {
70+
&self.signature
71+
}
72+
73+
fn return_type(&self, _arg_types: &[ArrowDataType]) -> DFResult<ArrowDataType> {
74+
Ok(ArrowDataType::Binary)
75+
}
76+
77+
fn invoke_with_args(&self, args: ScalarFunctionArgs) -> DFResult<ColumnarValue> {
78+
let [input] = take_function_args(self.name(), args.args)?;
79+
match input {
80+
ColumnarValue::Scalar(value) => path_scalar(value),
81+
ColumnarValue::Array(array) => path_array(array.as_ref()),
82+
}
83+
}
84+
}
85+
86+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
87+
struct DescriptorToStringFunc {
88+
signature: Signature,
89+
aliases: Vec<String>,
90+
}
91+
92+
impl DescriptorToStringFunc {
93+
fn new() -> Self {
94+
Self {
95+
signature: Signature::coercible(
96+
vec![Coercion::new_exact(TypeSignatureClass::Native(
97+
logical_binary(),
98+
))],
99+
Volatility::Immutable,
100+
),
101+
aliases: vec!["sys.descriptor_to_string".to_string()],
102+
}
103+
}
104+
}
105+
106+
impl ScalarUDFImpl for DescriptorToStringFunc {
107+
fn name(&self) -> &str {
108+
DESCRIPTOR_TO_STRING
109+
}
110+
111+
fn aliases(&self) -> &[String] {
112+
&self.aliases
113+
}
114+
115+
fn signature(&self) -> &Signature {
116+
&self.signature
117+
}
118+
119+
fn return_type(&self, _arg_types: &[ArrowDataType]) -> DFResult<ArrowDataType> {
120+
Ok(ArrowDataType::Utf8)
121+
}
122+
123+
fn invoke_with_args(&self, args: ScalarFunctionArgs) -> DFResult<ColumnarValue> {
124+
let [input] = take_function_args(self.name(), args.args)?;
125+
match input {
126+
ColumnarValue::Scalar(value) => descriptor_scalar(value),
127+
ColumnarValue::Array(array) => descriptor_array(array.as_ref()),
128+
}
129+
}
130+
}
131+
132+
fn serialize_path(path: &str) -> Vec<u8> {
133+
BlobDescriptor::new(path.to_string(), 0, -1).serialize()
134+
}
135+
136+
fn descriptor_string(bytes: &[u8]) -> DFResult<String> {
137+
BlobDescriptor::deserialize(bytes)
138+
.map(|descriptor| descriptor.to_string())
139+
.map_err(to_datafusion_error)
140+
}
141+
142+
fn path_scalar(value: ScalarValue) -> DFResult<ColumnarValue> {
143+
let path = match value {
144+
ScalarValue::Utf8(path) | ScalarValue::LargeUtf8(path) | ScalarValue::Utf8View(path) => {
145+
path
146+
}
147+
ScalarValue::Null => None,
148+
other => return unexpected_type(PATH_TO_DESCRIPTOR, &other.data_type()),
149+
};
150+
Ok(ColumnarValue::Scalar(ScalarValue::Binary(
151+
path.as_deref().map(serialize_path),
152+
)))
153+
}
154+
155+
fn path_array(input: &dyn Array) -> DFResult<ColumnarValue> {
156+
if let Some(values) = input.as_any().downcast_ref::<StringArray>() {
157+
return Ok(descriptor_array_from_paths(values.iter()));
158+
}
159+
if let Some(values) = input.as_any().downcast_ref::<LargeStringArray>() {
160+
return Ok(descriptor_array_from_paths(values.iter()));
161+
}
162+
if let Some(values) = input.as_any().downcast_ref::<StringViewArray>() {
163+
return Ok(descriptor_array_from_paths(values.iter()));
164+
}
165+
unexpected_type(PATH_TO_DESCRIPTOR, input.data_type())
166+
}
167+
168+
fn descriptor_array_from_paths<'a>(paths: impl Iterator<Item = Option<&'a str>>) -> ColumnarValue {
169+
let mut builder = BinaryBuilder::new();
170+
for path in paths {
171+
match path {
172+
Some(path) => builder.append_value(serialize_path(path)),
173+
None => builder.append_null(),
174+
}
175+
}
176+
ColumnarValue::Array(Arc::new(builder.finish()))
177+
}
178+
179+
fn descriptor_scalar(value: ScalarValue) -> DFResult<ColumnarValue> {
180+
let bytes = match value {
181+
ScalarValue::Binary(bytes)
182+
| ScalarValue::LargeBinary(bytes)
183+
| ScalarValue::BinaryView(bytes) => bytes,
184+
ScalarValue::Null => None,
185+
other => return unexpected_type(DESCRIPTOR_TO_STRING, &other.data_type()),
186+
};
187+
Ok(ColumnarValue::Scalar(ScalarValue::Utf8(
188+
bytes.as_deref().map(descriptor_string).transpose()?,
189+
)))
190+
}
191+
192+
fn descriptor_array(input: &dyn Array) -> DFResult<ColumnarValue> {
193+
if let Some(values) = input.as_any().downcast_ref::<BinaryArray>() {
194+
return strings_from_descriptors(values.iter());
195+
}
196+
if let Some(values) = input.as_any().downcast_ref::<LargeBinaryArray>() {
197+
return strings_from_descriptors(values.iter());
198+
}
199+
if let Some(values) = input.as_any().downcast_ref::<BinaryViewArray>() {
200+
return strings_from_descriptors(values.iter());
201+
}
202+
unexpected_type(DESCRIPTOR_TO_STRING, input.data_type())
203+
}
204+
205+
fn strings_from_descriptors<'a>(
206+
descriptors: impl Iterator<Item = Option<&'a [u8]>>,
207+
) -> DFResult<ColumnarValue> {
208+
let mut builder = StringBuilder::new();
209+
for bytes in descriptors {
210+
match bytes {
211+
Some(bytes) => builder.append_value(descriptor_string(bytes)?),
212+
None => builder.append_null(),
213+
}
214+
}
215+
Ok(ColumnarValue::Array(Arc::new(builder.finish())))
216+
}
217+
218+
fn unexpected_type<T>(function: &str, data_type: &ArrowDataType) -> DFResult<T> {
219+
Err(DataFusionError::Execution(format!(
220+
"{function} received unexpected argument type {data_type}"
221+
)))
222+
}

crates/integrations/datafusion/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
//! This version supports partition predicate pushdown by extracting
3737
//! translatable partition-only conjuncts from DataFusion filters.
3838
39+
mod blob_descriptor_functions;
3940
mod blob_reader;
4041
mod blob_view;
4142
mod catalog;

crates/integrations/datafusion/src/sql_context.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ impl SQLContext {
117117
))
118118
.build();
119119
let ctx = SessionContext::new_with_state(state);
120+
crate::blob_descriptor_functions::register_blob_descriptor_functions(&ctx);
120121
crate::variant_functions::register_variant_functions(&ctx);
121122
Self {
122123
ctx,

0 commit comments

Comments
 (0)