rypipe 2.1.0__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rypipe-2.1.0 → rypipe-2.2.0}/Cargo.lock +6 -6
- {rypipe-2.1.0 → rypipe-2.2.0}/Cargo.toml +1 -1
- {rypipe-2.1.0 → rypipe-2.2.0}/PKG-INFO +1 -1
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/Cargo.toml +1 -1
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/columnar.rs +103 -60
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/dict.rs +17 -11
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/parallel.rs +36 -24
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/tests/data_integrity_test.rs +528 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-python/Cargo.toml +1 -1
- {rypipe-2.1.0 → rypipe-2.2.0}/pyproject.toml +1 -1
- {rypipe-2.1.0 → rypipe-2.2.0}/LICENSE +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/README.md +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/_rypipe/__init__.py +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/README.md +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/build.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/examples/bench_blockmasks.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/examples/bench_push_tier.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/examples/bench_scanner_single.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/examples/bench_throughput.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/alloc_stats.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/arrow_export.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/bench/mod.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/block_masks.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/bounded.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/consumer.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/decoder.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/engine/locate.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/engine/mod.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/engine/predicate.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/engine/table_builder.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/error.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/input.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/lib.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/merge.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/parallel_stream.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/pipeline.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/plan.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/scan/mod.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/schema.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/streaming.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/src/value.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/tests/adapter_contract_test.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/tests/bounded_executor_test.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/tests/filter_tree_bytes_test.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/tests/merge_error_test.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/tests/pushdown_test.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-core/tests/schema_unify_test.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-python/build.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-python/src/export.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-python/src/lib.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-python/src/plan_kwargs.rs +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-python/tests/conftest.py +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-python/tests/test_rypipe_api.py +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/crates/rypipe-python/tests/test_source_pipeline.py +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/rypipe/__init__.py +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/rypipe/batchpipe.py +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/rypipe/fusion.py +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/rypipe/pipeline.py +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/rypipe/sinks.py +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/rypipe/source.py +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/rypipe/stages/__init__.py +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/rypipe/stages/cast.py +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/rypipe/stages/drop.py +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/rypipe/stages/filter.py +0 -0
- {rypipe-2.1.0 → rypipe-2.2.0}/rypipe/stages/rename.py +0 -0
|
@@ -417,9 +417,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
|
|
417
417
|
|
|
418
418
|
[[package]]
|
|
419
419
|
name = "cc"
|
|
420
|
-
version = "1.4.
|
|
420
|
+
version = "1.4.5"
|
|
421
421
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
422
|
-
checksum = "
|
|
422
|
+
checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
|
|
423
423
|
dependencies = [
|
|
424
424
|
"find-msvc-tools",
|
|
425
425
|
"jobserver",
|
|
@@ -518,9 +518,9 @@ checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
|
|
518
518
|
|
|
519
519
|
[[package]]
|
|
520
520
|
name = "find-msvc-tools"
|
|
521
|
-
version = "0.1.
|
|
521
|
+
version = "0.1.12"
|
|
522
522
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
523
|
-
checksum = "
|
|
523
|
+
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
|
|
524
524
|
|
|
525
525
|
[[package]]
|
|
526
526
|
name = "flate2"
|
|
@@ -1058,7 +1058,7 @@ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
|
|
1058
1058
|
|
|
1059
1059
|
[[package]]
|
|
1060
1060
|
name = "rypipe-core"
|
|
1061
|
-
version = "2.
|
|
1061
|
+
version = "2.2.0"
|
|
1062
1062
|
dependencies = [
|
|
1063
1063
|
"arrow 55.2.0",
|
|
1064
1064
|
"chrono",
|
|
@@ -1078,7 +1078,7 @@ dependencies = [
|
|
|
1078
1078
|
|
|
1079
1079
|
[[package]]
|
|
1080
1080
|
name = "rypipe-python"
|
|
1081
|
-
version = "2.
|
|
1081
|
+
version = "2.2.0"
|
|
1082
1082
|
dependencies = [
|
|
1083
1083
|
"arrow 59.3.0",
|
|
1084
1084
|
"mimalloc",
|
|
@@ -454,20 +454,31 @@ pub(crate) enum ColumnBuilder {
|
|
|
454
454
|
Timestamp(TimeUnit, PrimColumn<i64>),
|
|
455
455
|
Dictionary {
|
|
456
456
|
codes: NullableColumn<i32>,
|
|
457
|
-
|
|
457
|
+
/// Contiguous byte buffer for dictionary values (like StrColumn).
|
|
458
|
+
data: Vec<u8>,
|
|
459
|
+
/// Byte offsets into `data` for each dictionary entry.
|
|
460
|
+
offsets: Vec<i32>,
|
|
458
461
|
/// value → code side-index.
|
|
459
|
-
index:
|
|
462
|
+
index: rustc_hash::FxHashMap<Box<str>, i32>,
|
|
460
463
|
},
|
|
461
464
|
}
|
|
462
465
|
|
|
463
466
|
/// Look up `v` in the dictionary index, inserting a new code if absent.
|
|
464
|
-
|
|
465
|
-
|
|
467
|
+
/// Uses a contiguous byte buffer for values instead of Vec<String>.
|
|
468
|
+
fn dict_code(
|
|
469
|
+
data: &mut Vec<u8>,
|
|
470
|
+
offsets: &mut Vec<i32>,
|
|
471
|
+
index: &mut rustc_hash::FxHashMap<Box<str>, i32>,
|
|
472
|
+
v: &str,
|
|
473
|
+
) -> i32 {
|
|
474
|
+
let key: Box<str> = v.into();
|
|
475
|
+
if let Some(&code) = index.get(&key) {
|
|
466
476
|
return code;
|
|
467
477
|
}
|
|
468
|
-
let code =
|
|
469
|
-
|
|
470
|
-
|
|
478
|
+
let code = offsets.len() as i32 - 1;
|
|
479
|
+
data.extend_from_slice(v.as_bytes());
|
|
480
|
+
offsets.push(data.len() as i32);
|
|
481
|
+
index.insert(key, code);
|
|
471
482
|
code
|
|
472
483
|
}
|
|
473
484
|
|
|
@@ -547,8 +558,9 @@ impl ColumnBuilder {
|
|
|
547
558
|
}
|
|
548
559
|
FieldType::Dictionary => ColumnBuilder::Dictionary {
|
|
549
560
|
codes: NullableColumn::with_capacity(cap),
|
|
550
|
-
|
|
551
|
-
|
|
561
|
+
data: Vec::new(),
|
|
562
|
+
offsets: vec![0],
|
|
563
|
+
index: rustc_hash::FxHashMap::default(),
|
|
552
564
|
},
|
|
553
565
|
}
|
|
554
566
|
}
|
|
@@ -567,8 +579,8 @@ impl ColumnBuilder {
|
|
|
567
579
|
ColumnBuilder::Int64(v) => v.push(Some(i)),
|
|
568
580
|
ColumnBuilder::Float64(v) => v.push(Some(i as f64)),
|
|
569
581
|
ColumnBuilder::String(col) => col.push(Some(&i.to_string())),
|
|
570
|
-
ColumnBuilder::Dictionary { codes,
|
|
571
|
-
let code = dict_code(
|
|
582
|
+
ColumnBuilder::Dictionary { codes, data, offsets, index } => {
|
|
583
|
+
let code = dict_code(data, offsets, index, &i.to_string());
|
|
572
584
|
codes.push(Some(code));
|
|
573
585
|
}
|
|
574
586
|
_ => self.push(None),
|
|
@@ -577,8 +589,8 @@ impl ColumnBuilder {
|
|
|
577
589
|
ColumnBuilder::Float64(v) => v.push(Some(f)),
|
|
578
590
|
ColumnBuilder::Int64(v) => v.push(Some(f as i64)),
|
|
579
591
|
ColumnBuilder::String(col) => col.push(Some(&f.to_string())),
|
|
580
|
-
ColumnBuilder::Dictionary { codes,
|
|
581
|
-
let code = dict_code(
|
|
592
|
+
ColumnBuilder::Dictionary { codes, data, offsets, index } => {
|
|
593
|
+
let code = dict_code(data, offsets, index, &f.to_string());
|
|
582
594
|
codes.push(Some(code));
|
|
583
595
|
}
|
|
584
596
|
_ => self.push(None),
|
|
@@ -586,8 +598,8 @@ impl ColumnBuilder {
|
|
|
586
598
|
Value::Bool(b) => match self {
|
|
587
599
|
ColumnBuilder::Boolean(v) => v.push(Some(b)),
|
|
588
600
|
ColumnBuilder::String(col) => col.push(Some(&b.to_string())),
|
|
589
|
-
ColumnBuilder::Dictionary { codes,
|
|
590
|
-
let code = dict_code(
|
|
601
|
+
ColumnBuilder::Dictionary { codes, data, offsets, index } => {
|
|
602
|
+
let code = dict_code(data, offsets, index, &b.to_string());
|
|
591
603
|
codes.push(Some(code));
|
|
592
604
|
}
|
|
593
605
|
_ => self.push(None),
|
|
@@ -597,8 +609,8 @@ impl ColumnBuilder {
|
|
|
597
609
|
ColumnBuilder::Int64(v) => v.push(Some(d as i64)),
|
|
598
610
|
ColumnBuilder::Float64(v) => v.push(Some(d as f64)),
|
|
599
611
|
ColumnBuilder::String(col) => col.push(Some(&format_date32(d))),
|
|
600
|
-
ColumnBuilder::Dictionary { codes,
|
|
601
|
-
let code = dict_code(
|
|
612
|
+
ColumnBuilder::Dictionary { codes, data, offsets, index } => {
|
|
613
|
+
let code = dict_code(data, offsets, index, &format_date32(d));
|
|
602
614
|
codes.push(Some(code));
|
|
603
615
|
}
|
|
604
616
|
_ => self.push(None),
|
|
@@ -614,12 +626,12 @@ impl ColumnBuilder {
|
|
|
614
626
|
Some(unit) => col.push(Some(&format_timestamp(ts, unit))),
|
|
615
627
|
None => col.push(Some(&ts.to_string())),
|
|
616
628
|
},
|
|
617
|
-
ColumnBuilder::Dictionary { codes,
|
|
629
|
+
ColumnBuilder::Dictionary { codes, data, offsets, index } => {
|
|
618
630
|
let text = match unit {
|
|
619
631
|
Some(unit) => format_timestamp(ts, unit),
|
|
620
632
|
None => ts.to_string(),
|
|
621
633
|
};
|
|
622
|
-
let code = dict_code(
|
|
634
|
+
let code = dict_code(data, offsets, index, &text);
|
|
623
635
|
codes.push(Some(code));
|
|
624
636
|
}
|
|
625
637
|
_ => self.push(None),
|
|
@@ -697,16 +709,17 @@ impl ColumnBuilder {
|
|
|
697
709
|
"cannot promote column to unified variant '{target}'"
|
|
698
710
|
)));
|
|
699
711
|
}
|
|
700
|
-
let mut
|
|
701
|
-
let mut
|
|
712
|
+
let mut data: Vec<u8> = Vec::new();
|
|
713
|
+
let mut offsets: Vec<i32> = vec![0];
|
|
714
|
+
let mut index: rustc_hash::FxHashMap<Box<str>, i32> = rustc_hash::FxHashMap::default();
|
|
702
715
|
let mut codes = NullableColumn::with_capacity(old.len());
|
|
703
716
|
for val in old.iter() {
|
|
704
717
|
match val {
|
|
705
|
-
Some(s) => codes.push(Some(dict_code(&mut
|
|
718
|
+
Some(s) => codes.push(Some(dict_code(&mut data, &mut offsets, &mut index, s))),
|
|
706
719
|
None => codes.push(None),
|
|
707
720
|
}
|
|
708
721
|
}
|
|
709
|
-
*self = ColumnBuilder::Dictionary { codes,
|
|
722
|
+
*self = ColumnBuilder::Dictionary { codes, data, offsets, index };
|
|
710
723
|
return Ok(());
|
|
711
724
|
}
|
|
712
725
|
|
|
@@ -735,9 +748,9 @@ impl ColumnBuilder {
|
|
|
735
748
|
ColumnBuilder::Timestamp(unit, v) => {
|
|
736
749
|
v.push(value.and_then(|s| parse_timestamp(&s, *unit)));
|
|
737
750
|
}
|
|
738
|
-
ColumnBuilder::Dictionary { codes,
|
|
751
|
+
ColumnBuilder::Dictionary { codes, data, offsets, index } => match value {
|
|
739
752
|
Some(v) => {
|
|
740
|
-
let idx = dict_code(
|
|
753
|
+
let idx = dict_code(data, offsets, index, &v);
|
|
741
754
|
codes.push(Some(idx));
|
|
742
755
|
}
|
|
743
756
|
None => codes.push(None),
|
|
@@ -765,9 +778,9 @@ impl ColumnBuilder {
|
|
|
765
778
|
ColumnBuilder::Timestamp(unit, v) => {
|
|
766
779
|
v.push(value.and_then(|s| parse_timestamp(s, *unit)));
|
|
767
780
|
}
|
|
768
|
-
ColumnBuilder::Dictionary { codes,
|
|
781
|
+
ColumnBuilder::Dictionary { codes, data, offsets, index } => match value {
|
|
769
782
|
Some(v) => {
|
|
770
|
-
let idx = dict_code(
|
|
783
|
+
let idx = dict_code(data, offsets, index, v);
|
|
771
784
|
codes.push(Some(idx));
|
|
772
785
|
}
|
|
773
786
|
None => codes.push(None),
|
|
@@ -807,7 +820,7 @@ impl ColumnBuilder {
|
|
|
807
820
|
ColumnBuilder::Boolean(v) => v.bytes_used(),
|
|
808
821
|
ColumnBuilder::Date32(v) => v.bytes_used(),
|
|
809
822
|
ColumnBuilder::Timestamp(_, v) => v.bytes_used(),
|
|
810
|
-
ColumnBuilder::Dictionary { codes,
|
|
823
|
+
ColumnBuilder::Dictionary { codes, data, offsets, .. } => codes.len() * 4 + data.len() + offsets.len() * 4,
|
|
811
824
|
}
|
|
812
825
|
}
|
|
813
826
|
|
|
@@ -820,8 +833,8 @@ impl ColumnBuilder {
|
|
|
820
833
|
ColumnBuilder::Boolean(v) => v.capacity_bytes(),
|
|
821
834
|
ColumnBuilder::Date32(v) => v.capacity_bytes(),
|
|
822
835
|
ColumnBuilder::Timestamp(_, v) => v.capacity_bytes(),
|
|
823
|
-
ColumnBuilder::Dictionary { codes,
|
|
824
|
-
codes.capacity_bytes() +
|
|
836
|
+
ColumnBuilder::Dictionary { codes, data, offsets, .. } => {
|
|
837
|
+
codes.capacity_bytes() + data.capacity() + offsets.capacity() * 4
|
|
825
838
|
}
|
|
826
839
|
}
|
|
827
840
|
}
|
|
@@ -835,11 +848,12 @@ impl ColumnBuilder {
|
|
|
835
848
|
ColumnBuilder::Boolean(v) => ColumnBuilder::Boolean(v.split_off(n)),
|
|
836
849
|
ColumnBuilder::Date32(v) => ColumnBuilder::Date32(v.split_off(n)),
|
|
837
850
|
ColumnBuilder::Timestamp(unit, v) => ColumnBuilder::Timestamp(*unit, v.split_off(n)),
|
|
838
|
-
ColumnBuilder::Dictionary { codes,
|
|
851
|
+
ColumnBuilder::Dictionary { codes, data, offsets, index } => {
|
|
839
852
|
let other_codes = codes.split_off(n);
|
|
840
853
|
ColumnBuilder::Dictionary {
|
|
841
854
|
codes: other_codes,
|
|
842
|
-
|
|
855
|
+
data: data.clone(),
|
|
856
|
+
offsets: offsets.clone(),
|
|
843
857
|
index: index.clone(),
|
|
844
858
|
}
|
|
845
859
|
}
|
|
@@ -852,8 +866,12 @@ impl ColumnBuilder {
|
|
|
852
866
|
pub(crate) fn get_filter_view(&self, index: usize) -> Option<&str> {
|
|
853
867
|
match self {
|
|
854
868
|
ColumnBuilder::String(v) => v.get(index),
|
|
855
|
-
ColumnBuilder::Dictionary { codes,
|
|
856
|
-
codes.get(index).map(|code|
|
|
869
|
+
ColumnBuilder::Dictionary { codes, data, offsets, .. } => {
|
|
870
|
+
codes.get(index).map(|code| {
|
|
871
|
+
let start = offsets[*code as usize] as usize;
|
|
872
|
+
let end = offsets[*code as usize + 1] as usize;
|
|
873
|
+
std::str::from_utf8(&data[start..end]).unwrap_or("")
|
|
874
|
+
})
|
|
857
875
|
}
|
|
858
876
|
_ => None,
|
|
859
877
|
}
|
|
@@ -872,8 +890,12 @@ impl ColumnBuilder {
|
|
|
872
890
|
let unit = *unit;
|
|
873
891
|
v.get(index).map(|ts| format_timestamp(ts, unit))
|
|
874
892
|
}
|
|
875
|
-
ColumnBuilder::Dictionary { codes,
|
|
876
|
-
codes.get(index).map(|code|
|
|
893
|
+
ColumnBuilder::Dictionary { codes, data, offsets, .. } => {
|
|
894
|
+
codes.get(index).map(|code| {
|
|
895
|
+
let start = offsets[*code as usize] as usize;
|
|
896
|
+
let end = offsets[*code as usize + 1] as usize;
|
|
897
|
+
String::from_utf8_lossy(&data[start..end]).into_owned()
|
|
898
|
+
})
|
|
877
899
|
}
|
|
878
900
|
}
|
|
879
901
|
}
|
|
@@ -888,9 +910,13 @@ impl ColumnBuilder {
|
|
|
888
910
|
ColumnBuilder::Boolean(v) => v.get(index).map(TypedValue::Bool),
|
|
889
911
|
ColumnBuilder::Date32(v) => v.get(index).map(TypedValue::Date32),
|
|
890
912
|
ColumnBuilder::Timestamp(_, v) => v.get(index).map(TypedValue::Timestamp),
|
|
891
|
-
ColumnBuilder::Dictionary { codes,
|
|
913
|
+
ColumnBuilder::Dictionary { codes, data, offsets, .. } => codes
|
|
892
914
|
.get(index)
|
|
893
|
-
.map(|&idx|
|
|
915
|
+
.map(|&idx| {
|
|
916
|
+
let start = offsets[idx as usize] as usize;
|
|
917
|
+
let end = offsets[idx as usize + 1] as usize;
|
|
918
|
+
TypedValue::Str(std::str::from_utf8(&data[start..end]).unwrap_or(""))
|
|
919
|
+
}),
|
|
894
920
|
}
|
|
895
921
|
}
|
|
896
922
|
|
|
@@ -925,20 +951,25 @@ impl ColumnBuilder {
|
|
|
925
951
|
(
|
|
926
952
|
ColumnBuilder::Dictionary {
|
|
927
953
|
codes: a_codes,
|
|
928
|
-
|
|
954
|
+
data: a_data,
|
|
955
|
+
offsets: a_offsets,
|
|
929
956
|
index: a_index,
|
|
930
957
|
},
|
|
931
958
|
ColumnBuilder::Dictionary {
|
|
932
959
|
codes: b_codes,
|
|
933
|
-
|
|
960
|
+
data: b_data,
|
|
961
|
+
offsets: b_offsets,
|
|
934
962
|
..
|
|
935
963
|
},
|
|
936
964
|
) => {
|
|
937
965
|
// Remap b's dictionary into a's once, then translate codes.
|
|
938
|
-
let remap: Vec<i32> =
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
966
|
+
let mut remap: Vec<i32> = Vec::with_capacity(b_offsets.len() - 1);
|
|
967
|
+
for i in 0..(b_offsets.len() - 1) {
|
|
968
|
+
let start = b_offsets[i] as usize;
|
|
969
|
+
let end = b_offsets[i + 1] as usize;
|
|
970
|
+
let val = std::str::from_utf8(&b_data[start..end]).unwrap_or("");
|
|
971
|
+
remap.push(dict_code(a_data, a_offsets, a_index, val));
|
|
972
|
+
}
|
|
942
973
|
for c in b_codes.iter() {
|
|
943
974
|
a_codes.push(c.map(|code| remap[*code as usize]));
|
|
944
975
|
}
|
|
@@ -981,19 +1012,20 @@ impl ColumnBuilder {
|
|
|
981
1012
|
return;
|
|
982
1013
|
}
|
|
983
1014
|
// Upgrade: build dictionary + codes.
|
|
984
|
-
let mut
|
|
985
|
-
let mut
|
|
1015
|
+
let mut data: Vec<u8> = Vec::new();
|
|
1016
|
+
let mut offsets: Vec<i32> = vec![0];
|
|
1017
|
+
let mut index: rustc_hash::FxHashMap<Box<str>, i32> = rustc_hash::FxHashMap::default();
|
|
986
1018
|
let mut codes = NullableColumn::with_capacity(old.len());
|
|
987
1019
|
for v in old.iter() {
|
|
988
1020
|
match v {
|
|
989
1021
|
Some(s) => {
|
|
990
|
-
let idx = dict_code(&mut
|
|
1022
|
+
let idx = dict_code(&mut data, &mut offsets, &mut index, s);
|
|
991
1023
|
codes.push(Some(idx));
|
|
992
1024
|
}
|
|
993
1025
|
None => codes.push(None),
|
|
994
1026
|
}
|
|
995
1027
|
}
|
|
996
|
-
*self = ColumnBuilder::Dictionary { codes,
|
|
1028
|
+
*self = ColumnBuilder::Dictionary { codes, data, offsets, index };
|
|
997
1029
|
}
|
|
998
1030
|
|
|
999
1031
|
/// Mutable access to dictionary codes for in-place remap.
|
|
@@ -1009,11 +1041,13 @@ impl ColumnBuilder {
|
|
|
1009
1041
|
/// No-op if not a Dictionary variant.
|
|
1010
1042
|
pub(crate) fn replace_dict(
|
|
1011
1043
|
&mut self,
|
|
1012
|
-
|
|
1013
|
-
|
|
1044
|
+
new_data: Vec<u8>,
|
|
1045
|
+
new_offsets: Vec<i32>,
|
|
1046
|
+
new_index: rustc_hash::FxHashMap<Box<str>, i32>,
|
|
1014
1047
|
) {
|
|
1015
|
-
if let ColumnBuilder::Dictionary {
|
|
1016
|
-
*
|
|
1048
|
+
if let ColumnBuilder::Dictionary { data, offsets, index, .. } = self {
|
|
1049
|
+
*data = new_data;
|
|
1050
|
+
*offsets = new_offsets;
|
|
1017
1051
|
*index = new_index;
|
|
1018
1052
|
}
|
|
1019
1053
|
}
|
|
@@ -1100,13 +1134,14 @@ impl ColumnBuilder {
|
|
|
1100
1134
|
TimeUnit::Microsecond => v.to_arrow::<TimestampMicrosecondType>()?,
|
|
1101
1135
|
TimeUnit::Nanosecond => v.to_arrow::<TimestampNanosecondType>()?,
|
|
1102
1136
|
},
|
|
1103
|
-
ColumnBuilder::Dictionary { codes,
|
|
1137
|
+
ColumnBuilder::Dictionary { codes, data, offsets, .. } => {
|
|
1138
|
+
use arrow::buffer::{Buffer, OffsetBuffer, ScalarBuffer};
|
|
1104
1139
|
let codes_arr: Int32Array = std::mem::take(codes).into_options().collect();
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
);
|
|
1140
|
+
// Zero-copy: wrap contiguous data+offsets directly into StringArray.
|
|
1141
|
+
// Dictionary values are always non-null, so validity is None.
|
|
1142
|
+
let offsets_buf = OffsetBuffer::new(ScalarBuffer::from(std::mem::take(offsets)));
|
|
1143
|
+
let data_buf = Buffer::from_vec(std::mem::take(data));
|
|
1144
|
+
let values: ArrayRef = Arc::new(StringArray::try_new(offsets_buf, data_buf, None)?);
|
|
1110
1145
|
let arr = DictionaryArray::<Int32Type>::try_new(codes_arr, values)?;
|
|
1111
1146
|
Arc::new(arr)
|
|
1112
1147
|
}
|
|
@@ -1277,8 +1312,16 @@ mod tests {
|
|
|
1277
1312
|
d.push_value(Value::Float64(7.0)); // same string as Int64(7)
|
|
1278
1313
|
d.push_value(Value::Bool(true));
|
|
1279
1314
|
d.push_value(Value::Null);
|
|
1280
|
-
if let ColumnBuilder::Dictionary { codes,
|
|
1281
|
-
|
|
1315
|
+
if let ColumnBuilder::Dictionary { codes, data, offsets, .. } = &d {
|
|
1316
|
+
// Verify dictionary values via the contiguous buffer
|
|
1317
|
+
let vals: Vec<&str> = (0..offsets.len() - 1)
|
|
1318
|
+
.map(|i| {
|
|
1319
|
+
let start = offsets[i] as usize;
|
|
1320
|
+
let end = offsets[i + 1] as usize;
|
|
1321
|
+
std::str::from_utf8(&data[start..end]).unwrap()
|
|
1322
|
+
})
|
|
1323
|
+
.collect();
|
|
1324
|
+
assert_eq!(vals, vec!["7", "true"]);
|
|
1282
1325
|
assert_eq!(
|
|
1283
1326
|
codes.iter().map(|o| o.copied()).collect::<Vec<_>>(),
|
|
1284
1327
|
vec![Some(0), Some(0), Some(1), None]
|
|
@@ -74,7 +74,7 @@ pub struct DictValues {
|
|
|
74
74
|
|
|
75
75
|
/// Unify per-chunk dictionaries with the seed.
|
|
76
76
|
///
|
|
77
|
-
/// Works with `ColumnBuilder::Dictionary { codes,
|
|
77
|
+
/// Works with `ColumnBuilder::Dictionary { codes, data, offsets, index }`.
|
|
78
78
|
/// Seed codes are `0..seed.len()`, overflow are `seed.len()..`.
|
|
79
79
|
pub(crate) fn unify_dictionaries(
|
|
80
80
|
seed: &SeedDict,
|
|
@@ -91,12 +91,15 @@ pub(crate) fn unify_dictionaries(
|
|
|
91
91
|
|
|
92
92
|
// Collect overflow values not in seed
|
|
93
93
|
for col in locals {
|
|
94
|
-
if let ColumnBuilder::Dictionary {
|
|
95
|
-
for
|
|
96
|
-
let
|
|
94
|
+
if let ColumnBuilder::Dictionary { data, offsets, .. } = col {
|
|
95
|
+
for i in seed.len()..(offsets.len() - 1) {
|
|
96
|
+
let start = offsets[i] as usize;
|
|
97
|
+
let end = offsets[i + 1] as usize;
|
|
98
|
+
let val = std::str::from_utf8(&data[start..end]).unwrap_or("");
|
|
99
|
+
let k: Box<str> = val.into();
|
|
97
100
|
if !global_index.contains_key(&k) {
|
|
98
101
|
global_index.insert(k.clone(), next_code);
|
|
99
|
-
global_data.extend_from_slice(
|
|
102
|
+
global_data.extend_from_slice(val.as_bytes());
|
|
100
103
|
global_offsets.push(global_data.len() as i32);
|
|
101
104
|
next_code += 1;
|
|
102
105
|
}
|
|
@@ -112,12 +115,15 @@ pub(crate) fn unify_dictionaries(
|
|
|
112
115
|
// Build remap tables: local code -> global code
|
|
113
116
|
let mut remaps = Vec::with_capacity(locals.len());
|
|
114
117
|
for col in locals {
|
|
115
|
-
if let ColumnBuilder::Dictionary {
|
|
116
|
-
let local_len =
|
|
118
|
+
if let ColumnBuilder::Dictionary { data, offsets, index, .. } = col {
|
|
119
|
+
let local_len = offsets.len() - 1;
|
|
117
120
|
let mut map = Vec::with_capacity(local_len);
|
|
118
121
|
let mut is_identity = true;
|
|
119
|
-
for
|
|
120
|
-
let
|
|
122
|
+
for i in 0..local_len {
|
|
123
|
+
let start = offsets[i] as usize;
|
|
124
|
+
let end = offsets[i + 1] as usize;
|
|
125
|
+
let val = std::str::from_utf8(&data[start..end]).unwrap_or("");
|
|
126
|
+
let k: Box<str> = val.into();
|
|
121
127
|
let g = *global_index.get(&k).unwrap();
|
|
122
128
|
let local = *index.get(k.as_ref()).unwrap();
|
|
123
129
|
if g != local {
|
|
@@ -126,9 +132,9 @@ pub(crate) fn unify_dictionaries(
|
|
|
126
132
|
map.push(g);
|
|
127
133
|
}
|
|
128
134
|
// If no overflow, it's essentially identity if dict == seed
|
|
129
|
-
if
|
|
135
|
+
if local_len == seed.len() && is_identity {
|
|
130
136
|
// All seed, identity
|
|
131
|
-
} else if
|
|
137
|
+
} else if local_len > seed.len() {
|
|
132
138
|
is_identity = false;
|
|
133
139
|
}
|
|
134
140
|
remaps.push(RemapTable { map, is_identity });
|
|
@@ -153,21 +153,21 @@ impl ParallelExecutor {
|
|
|
153
153
|
let first_idx = engines[0].field_index.get(col_name).copied();
|
|
154
154
|
if let Some(idx) = first_idx {
|
|
155
155
|
if engines[0].columns[idx].variant_key() == "dictionary" {
|
|
156
|
-
let
|
|
157
|
-
if let crate::columnar::ColumnBuilder::Dictionary {
|
|
156
|
+
let first_data =
|
|
157
|
+
if let crate::columnar::ColumnBuilder::Dictionary { data, offsets, .. } =
|
|
158
158
|
&engines[0].columns[idx]
|
|
159
159
|
{
|
|
160
|
-
|
|
160
|
+
(data, offsets)
|
|
161
161
|
} else {
|
|
162
162
|
continue;
|
|
163
163
|
};
|
|
164
164
|
for e in &engines[1..] {
|
|
165
165
|
if let Some(&j) = e.field_index.get(col_name) {
|
|
166
166
|
if let crate::columnar::ColumnBuilder::Dictionary {
|
|
167
|
-
|
|
167
|
+
data, offsets, ..
|
|
168
168
|
} = &e.columns[j]
|
|
169
169
|
{
|
|
170
|
-
if
|
|
170
|
+
if data != first_data.0 || offsets != first_data.1 {
|
|
171
171
|
unify_col = Some(col_name.clone());
|
|
172
172
|
break;
|
|
173
173
|
}
|
|
@@ -185,42 +185,54 @@ impl ParallelExecutor {
|
|
|
185
185
|
}
|
|
186
186
|
let col_name = unify_col.unwrap();
|
|
187
187
|
// Build seed from first chunk, unify, remap.
|
|
188
|
-
let
|
|
189
|
-
if let crate::columnar::ColumnBuilder::Dictionary {
|
|
188
|
+
let first_data =
|
|
189
|
+
if let crate::columnar::ColumnBuilder::Dictionary { data, offsets, .. } =
|
|
190
190
|
&engines[0].columns[engines[0].field_index[&col_name]]
|
|
191
191
|
{
|
|
192
|
-
|
|
192
|
+
(data.clone(), offsets.clone())
|
|
193
193
|
} else {
|
|
194
194
|
return engines_to_record_batches(engines, &plan);
|
|
195
195
|
};
|
|
196
|
-
|
|
196
|
+
// Build SeedDict from the contiguous buffer
|
|
197
|
+
let mut seed_values = Vec::new();
|
|
198
|
+
let mut seed_offsets = Vec::with_capacity(first_data.1.len());
|
|
199
|
+
seed_offsets.push(0);
|
|
200
|
+
for i in 0..(first_data.1.len() - 1) {
|
|
201
|
+
let start = first_data.1[i] as usize;
|
|
202
|
+
let end = first_data.1[i + 1] as usize;
|
|
203
|
+
seed_values.extend_from_slice(&first_data.0[start..end]);
|
|
204
|
+
seed_offsets.push(seed_values.len() as i32);
|
|
205
|
+
}
|
|
206
|
+
let seed = crate::dict::SeedDict {
|
|
207
|
+
values: seed_values,
|
|
208
|
+
offsets: seed_offsets,
|
|
209
|
+
index: rustc_hash::FxHashMap::default(), // Will be populated by unify
|
|
210
|
+
};
|
|
197
211
|
let col_refs: Vec<&crate::columnar::ColumnBuilder> = engines
|
|
198
212
|
.iter()
|
|
199
213
|
.filter_map(|e| e.field_index.get(&col_name).map(|&idx| &e.columns[idx]))
|
|
200
214
|
.collect();
|
|
201
215
|
let (unified_dict, remaps) = crate::dict::unify_dictionaries(&seed, &col_refs);
|
|
202
|
-
// Build unified
|
|
203
|
-
let mut
|
|
204
|
-
for
|
|
205
|
-
let start =
|
|
206
|
-
let end =
|
|
207
|
-
let s = std::str::from_utf8(&unified_dict.data[start..end])
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
new_dict.push(s);
|
|
216
|
+
// Build unified data+offsets+index for replace_dict.
|
|
217
|
+
let mut new_index: rustc_hash::FxHashMap<Box<str>, i32> = rustc_hash::FxHashMap::default();
|
|
218
|
+
for i in 0..(unified_dict.offsets.len() - 1) {
|
|
219
|
+
let start = unified_dict.offsets[i] as usize;
|
|
220
|
+
let end = unified_dict.offsets[i + 1] as usize;
|
|
221
|
+
let s = std::str::from_utf8(&unified_dict.data[start..end]).unwrap_or("");
|
|
222
|
+
let k: Box<str> = s.into();
|
|
223
|
+
new_index.insert(k, i as i32);
|
|
211
224
|
}
|
|
212
|
-
let new_index: HashMap<String, i32> = new_dict
|
|
213
|
-
.iter()
|
|
214
|
-
.enumerate()
|
|
215
|
-
.map(|(i, s)| (s.clone(), i as i32))
|
|
216
|
-
.collect();
|
|
217
225
|
// Apply remap to each chunk, then replace dict.
|
|
218
226
|
for (e, remap) in engines.iter_mut().zip(remaps.iter()) {
|
|
219
227
|
if let Some(idx) = e.field_index.get(&col_name).copied() {
|
|
220
228
|
if let Some(codes) = e.columns[idx].dict_codes_mut() {
|
|
221
229
|
codes.remap_codes(&remap.map);
|
|
222
230
|
}
|
|
223
|
-
e.columns[idx].replace_dict(
|
|
231
|
+
e.columns[idx].replace_dict(
|
|
232
|
+
unified_dict.data.clone(),
|
|
233
|
+
unified_dict.offsets.clone(),
|
|
234
|
+
new_index.clone(),
|
|
235
|
+
);
|
|
224
236
|
}
|
|
225
237
|
}
|
|
226
238
|
return engines_to_record_batches(engines, &plan);
|