use std::ffi::CString;
use crate::driver::dataflow::query_plan::QueryPlan;
use crate::query_plan_native::error::QueryPlanError;
use crate::query_plan_native::native::{
self as query_plan_native, GeospatialType, PartitionKeyRangesApiOptions, PartitionKind, WChar,
};
const INITIAL_BUFFER_SIZE: u32 = 8 * 1024;
pub struct QueryPlanProvider {
handle: query_plan_native::ServiceProviderHandle,
}
impl std::fmt::Debug for QueryPlanProvider {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("QueryPlanProvider")
.field("handle", &(!self.handle.is_null()))
.finish()
}
}
unsafe impl Send for QueryPlanProvider {}
unsafe impl Sync for QueryPlanProvider {}
impl QueryPlanProvider {
pub fn new(config_json: &str) -> Result<Self, QueryPlanError> {
let lib = query_plan_native::query_plan_native_lib()?;
let c_config = CString::new(config_json).map_err(|_| QueryPlanError::ConfigContainsNull)?;
let (hr, handle) = lib.create_service_provider(&c_config);
if query_plan_native::failed(hr) {
return Err(QueryPlanError::from_hresult(hr));
}
Ok(Self { handle })
}
pub fn update(&self, config_json: &str) -> Result<(), QueryPlanError> {
let lib = query_plan_native::query_plan_native_lib()?;
let c_config = CString::new(config_json).map_err(|_| QueryPlanError::ConfigContainsNull)?;
let hr = lib.update_service_provider(self.handle, &c_config);
if query_plan_native::failed(hr) {
return Err(QueryPlanError::from_hresult(hr));
}
Ok(())
}
pub fn get_partition_key_ranges(
&self,
query_spec_json: &str,
partition_key_paths: &[&str],
options: &QueryPlanOptions,
vector_embedding_policy_json: Option<&str>,
) -> Result<QueryPlan, QueryPlanError> {
let lib = query_plan_native::query_plan_native_lib()?;
let query_spec_native = to_native_string(query_spec_json);
let (flat_tokens, token_offsets, token_counts) =
tokenize_partition_key_paths(partition_key_paths);
let all_token_ptrs: Vec<*const WChar> = token_offsets
.iter()
.map(|&off| unsafe { flat_tokens.as_ptr().add(off) })
.collect();
let vector_policy = vector_embedding_policy_json.map(to_native_string);
let (vec_policy_ptr, vec_policy_len) = match &vector_policy {
Some(w) => (w.as_ptr(), w.len() as u32),
None => (std::ptr::null(), 0),
};
let native_options = PartitionKeyRangesApiOptions::from(options);
let mut buffer = vec![0u8; INITIAL_BUFFER_SIZE as usize];
let mut result_length: u32 = 0;
let pk_count = partition_key_paths.len() as u32;
let call_native = |buf: &mut Vec<u8>, out_len: &mut u32| unsafe {
lib.get_partition_key_ranges(
self.handle,
query_spec_native.as_ptr(),
native_options,
all_token_ptrs.as_ptr(),
token_counts.as_ptr(),
pk_count,
vec_policy_ptr,
vec_policy_len,
buf.as_mut_ptr(),
buf.len() as u32,
out_len,
)
};
let mut hr = call_native(&mut buffer, &mut result_length);
if hr == query_plan_native::DISP_E_BUFFERTOOSMALL && result_length as usize > buffer.len() {
buffer.resize(result_length as usize, 0);
hr = call_native(&mut buffer, &mut result_length);
}
if query_plan_native::failed(hr) {
let payload = std::str::from_utf8(&buffer[..result_length as usize]).unwrap_or("");
return if payload.is_empty() {
Err(QueryPlanError::from_hresult(hr))
} else {
Err(QueryPlanError::from_hresult_with_payload(
hr,
payload.to_string(),
))
};
}
let payload = std::str::from_utf8(&buffer[..result_length as usize]).map_err(|e| {
QueryPlanError::InvalidUtf8 {
message: e.to_string(),
}
})?;
let info: QueryPlan = serde_json::from_str(payload)?;
Ok(info)
}
}
#[derive(Debug, Clone)]
pub struct QueryPlanOptions {
pub require_formattable_order_by_query: bool,
pub is_continuation_expected: bool,
pub allow_non_value_aggregate_query: bool,
pub has_logical_partition_key: bool,
pub allow_dcount: bool,
pub use_system_prefix: bool,
pub partition_kind: PartitionKind,
pub geospatial_type: GeospatialType,
pub hybrid_search_skip_order_by_rewrite: bool,
}
impl Default for QueryPlanOptions {
fn default() -> Self {
Self {
require_formattable_order_by_query: false,
is_continuation_expected: true,
allow_non_value_aggregate_query: false,
has_logical_partition_key: false,
allow_dcount: false,
use_system_prefix: false,
partition_kind: PartitionKind::Hash,
geospatial_type: GeospatialType::Geography,
hybrid_search_skip_order_by_rewrite: false,
}
}
}
impl From<&QueryPlanOptions> for PartitionKeyRangesApiOptions {
fn from(opts: &QueryPlanOptions) -> Self {
Self {
require_formattable_order_by_query: opts.require_formattable_order_by_query as i32,
is_continuation_expected: opts.is_continuation_expected as i32,
allow_non_value_aggregate_query: opts.allow_non_value_aggregate_query as i32,
has_logical_partition_key: opts.has_logical_partition_key as i32,
allow_dcount: opts.allow_dcount as i32,
use_system_prefix: opts.use_system_prefix as i32,
partition_kind: opts.partition_kind,
geospatial_type: opts.geospatial_type,
hybrid_search_skip_order_by_rewrite: opts.hybrid_search_skip_order_by_rewrite as i32,
reserved: [0u8; 28],
}
}
}
#[cfg(target_os = "windows")]
fn to_native_string(s: &str) -> Vec<WChar> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
#[cfg(not(target_os = "windows"))]
fn to_native_string(s: &str) -> Vec<WChar> {
s.chars()
.map(|c| c as WChar)
.chain(std::iter::once(0))
.collect()
}
fn tokenize_partition_key_paths(paths: &[&str]) -> (Vec<WChar>, Vec<usize>, Vec<u32>) {
let mut flat_buf: Vec<WChar> = Vec::new();
let mut offsets: Vec<usize> = Vec::new();
let mut counts: Vec<u32> = Vec::with_capacity(paths.len());
for path in paths {
let segments: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
counts.push(segments.len() as u32);
for seg in &segments {
offsets.push(flat_buf.len());
flat_buf.extend_from_slice(&to_native_string(seg));
}
}
(flat_buf, offsets, counts)
}