Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/swift-sdk-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,9 @@ jobs:
rm -rf packages/swift-sdk/SwiftExampleApp/.build || true

- name: Build and test Swift SDK
env:
# The self-hosted runner persists Cargo's target cache between jobs.
# Keep only one Apple architecture's intermediates at a time.
PRUNE_CARGO_TARGETS: "1"
run: |
bash packages/swift-sdk/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1322,7 +1322,7 @@ mod tests {

/// Flat-summed range cross-check: `Aggregate + range` on a PCPS
/// index resolves to `DocumentSumMode::RangeNoProof` with
/// `return_distinct_sums_in_range = false`. The joint executor
/// `walk_mode = RangeSumWalkMode::Aggregate`. The joint executor
/// folds visited PCPS elements via `count_sum_value_or_default()`
/// in Rust (no engine-side combined accumulator exists). Pin parity
/// vs. the independent count + sum aggregate dispatch — this is the
Expand Down Expand Up @@ -1409,7 +1409,7 @@ mod tests {

/// Compound-summed range cross-check: `GroupByIn + In + range` on
/// a PCPS index resolves to `DocumentSumMode::RangeNoProof` with
/// `return_distinct_sums_in_range = false`. The joint executor's
/// `walk_mode = RangeSumWalkMode::Aggregate`. The joint executor's
/// distinct path query expresses the multi-In outer walk as a
/// single grovedb call (atomicity inherent) and folds each
/// In-branch's PCPS elements into one `(count, sum)` pair via
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
use crate::drive::Drive;
use crate::error::Error;
use crate::query::drive_document_sum_query::{
DocumentSumMode, DocumentSumRequest, DocumentSumResponse, RangeSumOptions, SumMode,
DocumentSumMode, DocumentSumRequest, DocumentSumResponse, RangeSumOptions, RangeSumWalkMode,
SumMode,
};
use crate::query::{OrderClause, WhereClause};
use dpp::data_contract::accessors::v0::DataContractV0Getters;
Expand All @@ -22,6 +23,27 @@ use dpp::platform_value::Value;
use dpp::version::PlatformVersion;
use grovedb::TransactionArg;

fn effective_no_proof_distinct_limit(
requested_limit: Option<u32>,
drive_config: &crate::config::DriveConfig,
) -> Result<u16, Error> {
let effective_limit = requested_limit
.unwrap_or(drive_config.default_query_limit as u32)
.min(drive_config.max_query_limit as u32);

if effective_limit == 0 {
return Err(Error::Query(
crate::error::query::QuerySyntaxError::InvalidLimit(
"effective distinct SUM limit must be greater than zero".to_string(),
),
));
}

// Both configuration limits are u16, and the `min` above bounds every
// caller-supplied value to `max_query_limit` before this conversion.
Ok(effective_limit as u16)
}
Comment thread
QuantumExplorer marked this conversation as resolved.

#[cfg(feature = "server")]
impl Drive {
/// Server-side entry point for the sum surface. Routes a
Expand Down Expand Up @@ -65,7 +87,7 @@ impl Drive {
}
DocumentSumMode::PerInValue => {
let options = RangeSumOptions {
return_distinct_sums_in_range: false,
walk_mode: RangeSumWalkMode::Aggregate,
carrier_outer_limit: None,
left_to_right: order_by_ascending,
};
Expand All @@ -87,8 +109,16 @@ impl Drive {
request.mode,
SumMode::GroupByRange | SumMode::GroupByCompound
);
let walk_mode = if return_distinct {
RangeSumWalkMode::Distinct(effective_no_proof_distinct_limit(
request.limit,
request.drive_config,
)?)
} else {
RangeSumWalkMode::Aggregate
};
let options = RangeSumOptions {
return_distinct_sums_in_range: return_distinct,
walk_mode,
carrier_outer_limit: None,
left_to_right: order_by_ascending,
};
Expand Down Expand Up @@ -252,3 +282,37 @@ pub fn where_clauses_from_value(value: &Value) -> Result<Vec<WhereClause>, Error
pub fn order_clauses_from_value(value: &Value) -> Result<Vec<OrderClause>, Error> {
crate::query::drive_document_count_query::drive_dispatcher::order_clauses_from_value(value)
}

#[cfg(test)]
mod tests {
use super::effective_no_proof_distinct_limit;
use crate::config::DriveConfig;

#[test]
fn no_proof_distinct_limit_uses_the_default_and_clamps_to_the_maximum() {
let config = DriveConfig {
default_query_limit: 25,
max_query_limit: 100,
..DriveConfig::default()
};

assert_eq!(
effective_no_proof_distinct_limit(None, &config).unwrap(),
25
);
assert_eq!(
effective_no_proof_distinct_limit(Some(7), &config).unwrap(),
7
);
assert_eq!(
effective_no_proof_distinct_limit(Some(10_000), &config).unwrap(),
100
);

let disabled = DriveConfig {
max_query_limit: 0,
..config
};
assert!(effective_no_proof_distinct_limit(None, &disabled).is_err());
}
}
Comment thread
QuantumExplorer marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//! regular range proof against the `ProvableSumTree`, returning
//! per-key `KVSum` ops bound to the merk root.

use super::{DriveDocumentSumQuery, RangeSumOptions, SumEntry};
use super::{DriveDocumentSumQuery, RangeSumOptions, RangeSumWalkMode, SumEntry};
use crate::drive::Drive;
use crate::error::query::QuerySyntaxError;
use crate::error::Error;
Expand Down Expand Up @@ -50,7 +50,7 @@ impl DriveDocumentSumQuery<'_> {
.iter()
.any(|wc| wc.operator == WhereOperator::In);

if !options.return_distinct_sums_in_range {
if matches!(options.walk_mode, RangeSumWalkMode::Aggregate) {
if has_in_on_prefix {
// Enforce exactly one `In` clause. Without this, a request
// with multiple In filters would silently use only the
Expand Down Expand Up @@ -148,13 +148,14 @@ impl DriveDocumentSumQuery<'_> {
}]);
}

// Distinct mode. Mirror count's analog; currently relies on
// `distinct_sum_path_query` which is stubbed (pending port).
// Defer to the same builder so the error surfaces cleanly when
// distinct mode is requested before the builder body lands.
let (path_query_limit, left_to_right) = (None::<u16>, options.left_to_right);
let path_query =
self.distinct_sum_path_query(path_query_limit, left_to_right, platform_version)?;
let RangeSumWalkMode::Distinct(distinct_limit) = options.walk_mode else {
unreachable!("aggregate range sums return before the distinct storage walk")
};
let path_query = self.distinct_sum_path_query(
Some(distinct_limit),
options.left_to_right,
platform_version,
)?;
let base_path_len = path_query.path.len();

let mut drive_operations = vec![];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use grovedb::TransactionArg;
impl Drive {
/// Range-sum walk against a `rangeSummable: true` index. Returns
/// a summed entry or per-distinct-value entries depending on
/// `options.return_distinct_sums_in_range`.
/// `options.walk_mode`.
#[allow(clippy::too_many_arguments)]
pub fn execute_document_sum_range_no_proof(
&self,
Expand Down
16 changes: 13 additions & 3 deletions packages/rs-drive/src/query/drive_document_sum_query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,14 +261,24 @@ pub struct DriveDocumentSumQuery<'a> {
pub sum_property: String,
}

/// Storage-walk shape for a server-side range sum.
#[cfg(feature = "server")]
#[derive(Clone, Copy, Debug, Default)]
pub enum RangeSumWalkMode {
/// Return one aggregate sum for the range.
#[default]
Aggregate,
/// Return distinct sums, bounded by the supplied storage-walk limit.
Distinct(u16),
}

/// Server-side range-sum executor options, parallels
/// [`crate::query::drive_document_count_query::RangeCountOptions`].
#[cfg(feature = "server")]
#[derive(Clone, Debug, Default)]
pub struct RangeSumOptions {
/// When `true`, emit one `SumEntry` per distinct in-range value
/// rather than a single `Aggregate(i64)`.
pub return_distinct_sums_in_range: bool,
/// Select aggregate execution or a compile-time bounded distinct walk.
pub walk_mode: RangeSumWalkMode,
/// `Some(n)` caps the carrier walk for compound `(In, range)`
/// shapes at n entries. `None` accepts the platform-wide
/// `MAX_CARRIER_AGGREGATE_OUTER_RANGE_LIMIT`.
Expand Down
63 changes: 63 additions & 0 deletions packages/rs-drive/src/query/drive_document_sum_query/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,4 +557,67 @@ mod limit_policy_regression {
"error must name the rejected limit; got: {msg}"
);
}

#[test]
fn range_distinct_sum_no_proof_applies_default_explicit_and_max_limits() {
let drive = setup_drive_with_initial_state_structure(None);
let platform_version = PlatformVersion::latest();
let data_contract = build_widget_contract();
drive
.apply_contract(
&data_contract,
BlockInfo::default(),
true,
StorageFlags::optional_default_as_cow(),
None,
platform_version,
)
.expect("apply contract");

for (i, (color, amount)) in [("blue", 2u64), ("green", 3), ("red", 5), ("yellow", 7)]
.iter()
.enumerate()
{
insert_widget(&drive, &data_contract, i, color, *amount);
}

let document_type = data_contract
.document_type_for_name("widget")
.expect("widget");
let drive_config = DriveConfig {
default_query_limit: 2,
max_query_limit: 3,
..Default::default()
};
let make_request = |limit| DocumentSumRequest {
contract: &data_contract,
document_type,
sum_property: "amount".to_string(),
where_clauses: vec![WhereClause {
field: "color".to_string(),
operator: WhereOperator::GreaterThan,
value: Value::Text("blue".to_string()),
}],
order_clauses: Vec::new(),
mode: SumMode::GroupByRange,
limit,
prove: false,
drive_config: &drive_config,
};

for (requested, expected) in [(None, 2), (Some(1), 1), (Some(10_000), 3)] {
let response = drive
.execute_document_sum_request(make_request(requested), None, platform_version)
.expect("bounded no-proof distinct SUM should succeed");
let entries = match response {
DocumentSumResponse::Entries(entries) => entries,
other => panic!("expected Entries response, got {other:?}"),
};
assert_eq!(
entries.len(),
expected,
"unexpected entry count for requested limit {requested:?}"
);
}
}
}
6 changes: 4 additions & 2 deletions packages/rs-drive/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ pub use drive_document_count_query::{
DocumentCountRequest, DocumentCountResponse, RangeCountOptions, MAX_LIMIT_AS_FAILSAFE,
};

// `DocumentSumRequest` / `DocumentSumResponse` / `RangeSumOptions` are
// `DocumentSumRequest` / `DocumentSumResponse` / range-sum options are
// the server-side executor inputs and stay `server`-only (parallels
// the count-side `DocumentCountRequest` etc. above).
#[cfg(feature = "server")]
pub use drive_document_sum_query::{DocumentSumRequest, DocumentSumResponse, RangeSumOptions};
pub use drive_document_sum_query::{
DocumentSumRequest, DocumentSumResponse, RangeSumOptions, RangeSumWalkMode,
};

// `DocumentAverageRequest` / `DocumentAverageResponse` are the
// server-side executor inputs for the average surface and stay
Expand Down
55 changes: 55 additions & 0 deletions packages/swift-sdk/build_ios.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ TARGET_DIR="$ROOT_DIR/target"
PACKAGE="rs-unified-sdk-ffi"
XCFRAMEWORK="$SCRIPT_DIR/DashSDKFFI.xcframework"
PROFILE="dev"
PRUNE_CARGO_TARGETS="${PRUNE_CARGO_TARGETS:-0}"
STAGING_DIR=""

# Crates whose cbindgen-generated headers ship in the unified framework.
# Order matters: earlier headers define types referenced by later ones.
Expand All @@ -46,6 +48,31 @@ CLEAN=false
log_info() { echo -e "${GREEN}$1${NC}"; }
log_error() { echo -e "${RED}$1${NC}"; }

cleanup_staging_dir() {
if [ -n "$STAGING_DIR" ]; then
rm -rf "$STAGING_DIR"
fi
}

stage_target_artifacts() {
local target="$1"
local library="$2"
local headers="$3"
local target_staging_dir="$STAGING_DIR/$target"

mkdir -p "$target_staging_dir"
cp "$library" "$target_staging_dir/"
cp -R "$headers" "$target_staging_dir/include"

STAGED_LIB="$target_staging_dir/$(basename "$library")"
STAGED_HEADERS="$target_staging_dir/include"

# The final static library and generated headers are all xcodebuild needs.
# Release the much larger per-architecture dependency tree before building
# the next target so persistent CI runners cannot exhaust their disk.
rm -rf "$TARGET_DIR/$target"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

# -------------------------------
# Help
# -------------------------------
Expand Down Expand Up @@ -125,6 +152,19 @@ OUTPUT_DIR="$PROFILE"
log_info "Package: $PACKAGE"
log_info "Profile: $PROFILE"

if [ "$PRUNE_CARGO_TARGETS" = "1" ]; then
STAGING_DIR="$(mktemp -d "${TMPDIR:-/tmp}/dash-sdk-ffi.XXXXXX")"
trap cleanup_staging_dir EXIT

# Persistent self-hosted runners may contain incomplete or obsolete builds
# from an earlier job. Start the bounded build with only Cargo's shared host
# cache, then prune each Apple target after staging its final artifacts.
rm -rf \
"$TARGET_DIR/aarch64-apple-ios" \
"$TARGET_DIR/aarch64-apple-ios-sim" \
"$TARGET_DIR/aarch64-apple-darwin"
fi

# -------------------------------
# Build commands
# -------------------------------
Expand Down Expand Up @@ -195,6 +235,11 @@ if $BUILD_IOS; then
IOS_LIB="$TARGET_DIR/$IOS_TARGET/$OUTPUT_DIR/librs_unified_sdk_ffi.a"
IOS_HEADERS="$TARGET_DIR/$IOS_TARGET/$OUTPUT_DIR/include"
inject_modulemap "$IOS_HEADERS"
if [ "$PRUNE_CARGO_TARGETS" = "1" ]; then
stage_target_artifacts "$IOS_TARGET" "$IOS_LIB" "$IOS_HEADERS"
IOS_LIB="$STAGED_LIB"
IOS_HEADERS="$STAGED_HEADERS"
fi
fi

# iOS simulator
Expand All @@ -205,6 +250,11 @@ if $BUILD_SIM; then
SIM_LIB="$TARGET_DIR/$SIM_TARGET/$OUTPUT_DIR/librs_unified_sdk_ffi.a"
SIM_HEADERS="$TARGET_DIR/$SIM_TARGET/$OUTPUT_DIR/include"
inject_modulemap "$SIM_HEADERS"
if [ "$PRUNE_CARGO_TARGETS" = "1" ]; then
stage_target_artifacts "$SIM_TARGET" "$SIM_LIB" "$SIM_HEADERS"
SIM_LIB="$STAGED_LIB"
SIM_HEADERS="$STAGED_HEADERS"
fi
fi

# macOS
Expand All @@ -215,6 +265,11 @@ if $BUILD_MAC; then
MAC_LIB="$TARGET_DIR/$MAC_TARGET/$OUTPUT_DIR/librs_unified_sdk_ffi.a"
MAC_HEADERS="$TARGET_DIR/$MAC_TARGET/$OUTPUT_DIR/include"
inject_modulemap "$MAC_HEADERS"
if [ "$PRUNE_CARGO_TARGETS" = "1" ]; then
stage_target_artifacts "$MAC_TARGET" "$MAC_LIB" "$MAC_HEADERS"
MAC_LIB="$STAGED_LIB"
MAC_HEADERS="$STAGED_HEADERS"
fi
fi

# -------------------------------
Expand Down
Loading