1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
use std::sync::Arc;
use super::{
ConcatByNameOptions, DataFrame, DataFrameConditionalFullJoinSizeData, DataFrameCrossJoinOptions,
DataFrameFilteredJoin, DataFrameFilteredJoinOptions, DataFrameHashJoin, DataFrameHashJoinOptions,
DataFrameJoinIndices, DataFrameJoinOptions, DataFrameJoinPairIndices, DataFrameJoinSides,
DataFrameMixedJoinOptions, DataFrameMixedJoinSizeData, DataFrameMixedSingleSideJoinSizeData,
DataFramePredicateJoinOptions, DataFramePreparedSort, DropDuplicatesOptions, DropNaOptions,
HashPartitionedDataFrame, WithColumnExpr,
};
use crate::{
CudfType, DataType, Expr, JoinType, Result,
core::{
column::{Column, position_sequence_in_domain},
table::Table,
},
frame::{
factorization::{Codebook, Factorized},
series::Series,
},
ops::{
ast::AstExpression,
join::{MergeOptions, SizedConditionalPairJoinType, SizedMixedJoinType},
sorting::SortOptions,
},
runtime::Exec,
};
macro_rules! dataframe_scope_forward {
($(#[$meta:meta])* fn $method:ident($($param:ident : $pty:ty),*) -> $ret:ty => $target:ident;) => {
$(#[$meta])*
pub fn $method(&self, $($param: $pty),*) -> $ret {
self.df.$target($($param,)* self.ctx)
}
};
($( $(#[$meta:meta])* fn $method:ident($($param:ident : $pty:ty),*) -> $ret:ty => $target:ident; )*) => {
$(
dataframe_scope_forward!(
$(#[$meta])*
fn $method($($param : $pty),*) -> $ret => $target;
);
)*
};
}
/// Execution scope for `DataFrame` GPU operations bound to an [`Exec`] context.
#[must_use]
pub struct FrameOps<'a> {
pub(super) df: &'a DataFrame,
pub(super) ctx: Exec<'a>,
}
impl DataFrame {
/// The GPU operations of this frame, run where `exec` selects.
pub fn on<'a>(&'a self, exec: Exec<'a>) -> FrameOps<'a> {
FrameOps { df: self, ctx: exec }
}
}
impl FrameOps<'_> {
/// Concatenate `other` row-wise after aligning columns by name.
pub fn concat_by_name(&self, other: &DataFrame, options: ConcatByNameOptions) -> Result<DataFrame> {
self.concat_many_by_name(&[other], options)
}
/// Concatenate `others` row-wise after aligning columns by name;
/// [`ConcatByNameOptions`] selects how columns missing from a frame are
/// handled.
pub fn concat_many_by_name(&self, others: &[&DataFrame], options: ConcatByNameOptions) -> Result<DataFrame> {
self.df.concat_many_by_name_in_context(others, options, self.ctx)
}
/// Build a reusable codebook from a named column's distinct values.
///
/// The returned [`Codebook`] owns both the distinct key series and an
/// explicit positional code series, so it can be reused safely across
/// later `encode` / `decode` calls.
///
/// # Errors
///
/// Returns an error if `column` is not in the schema.
pub fn codebook(&self, column: &str) -> Result<Codebook> {
let operation = "dataframe::codebook";
let series = self.df.series(column).map_err(|e| e.reanchor(operation))?;
series.on(self.ctx).codebook().map_err(|e| e.reanchor(operation).with_column(column.to_string()))
}
/// Factorize a named column into row-aligned encoded indices plus a reusable codebook.
///
/// This is equivalent to `pandas.factorize()`. The result is a typed
/// [`Factorized`] value, and no raw `(codes, uniques)` tuple is returned.
///
/// # Errors
///
/// Returns an error if `column` is not in the schema.
pub fn factorize(&self, column: &str) -> Result<Factorized> {
let operation = "dataframe::factorize";
let series = self.df.series(column).map_err(|e| e.reanchor(operation))?;
series.on(self.ctx).factorize().map_err(|e| e.reanchor(operation).with_column(column.to_string()))
}
/// Build an owned [`Table`] by deep-copying each column.
///
/// This is useful when calling APIs that operate on `Table` directly.
///
/// # Cost
///
/// This allocates on the GPU. It deep-copies every column to a new `Table`.
/// Each column gets its own fresh device allocation.
pub fn to_table_copy(&self) -> Result<Table> {
let operation = "dataframe::to_table_copy";
let output_domain = self.ctx.resolve_output_domain(&self.df.domain)?;
let columns = self
.df
.columns
.iter()
.map(|(name, column)| {
column.copy_into_domain(Arc::clone(&output_domain), operation).map_err(|e| e.with_column(name.clone()))
})
.collect::<Result<Vec<Column>>>()?;
if columns.is_empty() {
Table::from_columns_in_domain_with_num_rows(columns, self.df.num_rows(), output_domain, operation)
} else {
Table::from_columns(columns).map_err(|e| e.reanchor(operation))
}
}
/// Evaluate expressions against this DataFrame and add/replace the
/// resulting columns.
///
/// Use this for declarative computed columns. For pre-computed Series,
/// use [`with_column`](Self::with_column) (single) or
/// [`DataFrame::assign`] (batch).
///
/// Expressions evaluate against the input state of `self` regardless of
/// order, allowing expressions to reference columns overwritten in the same
/// call.
///
/// Each item must resolve to an output name, supplied as:
///
/// - A bare expression with an inferable [`Expr::output_name()`](crate::ops::expr::Expr::output_name).
/// - An explicit `(name, expr)` tuple.
///
/// Bare expressions infer names from aliases ([`.alias()`](crate::ops::expr::Expr::alias)),
/// column references ([`col()`](crate::ops::expr::col)), or single-input
/// transforms that inherit column identity (`cast`, `is_null`, `fillna`,
/// cumulative scans, and string unary operations). Broadcast reductions do
/// not inherit output names; they require an alias or explicit tuple name.
/// Items without resolvable names fail validation before GPU kernels launch.
///
/// # Execution and Cost
///
/// Expression intermediates and outputs use the selected stream and
/// memory resource. Unchanged columns are shared with `Arc::clone` when
/// the output provenance matches the frame's, as with [`Exec::RETAINED`],
/// and device-copied otherwise so the result keeps one provenance.
///
/// # Errors
///
/// Returns an error if any item lacks an output name, or if
/// evaluation of any expression fails.
pub fn with_columns<I, T>(&self, exprs: I) -> Result<DataFrame>
where
I: IntoIterator<Item = T>,
T: Into<WithColumnExpr>,
{
self.df.with_columns_in_context(exprs, self.ctx, "dataframe::with_columns")
}
/// Add or replace one precomputed column.
///
/// `series` must already have the output provenance, for example the
/// output of another operation on the same `Exec`. Unchanged columns of
/// another provenance are copied into it. [`DataFrame::with_column`]
/// adds a column of the frame's own provenance without GPU work.
pub fn with_column(&self, name: &str, series: Series) -> Result<DataFrame> {
let operation = "dataframe::with_column";
self.ctx
.resolve_output_domain(self.df.execution_domain())
.and_then(|output_domain| self.df.with_column_in_domain(name, series, output_domain, operation))
.map_err(|error| error.reanchor_if_needed(operation))
}
/// Return a new DataFrame with the named column cast to a different type.
///
/// All other columns are preserved unchanged.
///
/// # Errors
///
/// Returns an error if `column` is not in the schema, or if the cast
/// is unsupported.
pub fn cast_column(&self, column: &str, target_type: impl Into<DataType>) -> Result<DataFrame> {
self.df.cast_column_in_context(column, target_type.into(), self.ctx, "dataframe::cast_column")
}
/// Add a zero-based Int32 row-position column.
pub fn with_position_column(&self, output_name: &str) -> Result<DataFrame> {
let operation = "dataframe::with_position_column";
let output_domain = self.ctx.resolve_output_domain(self.df.execution_domain())?;
let row_ids = Series::from_column(position_sequence_in_domain(self.df.num_rows(), &output_domain, operation)?)
.map_err(|e| e.reanchor(operation).with_column(output_name.to_string()))?;
self.df.with_column_in_domain(output_name, row_ids, output_domain, operation).map_err(|e| e.reanchor(operation))
}
/// Add a fixed-width arithmetic sequence column.
pub fn with_sequence_column<T: CudfType>(&self, output_name: &str, start: T, step: T) -> Result<DataFrame> {
let operation = "dataframe::with_sequence_column";
let output_domain = self.ctx.resolve_output_domain(self.df.execution_domain())?;
let values = Series::from_column(Column::from_sequence_in_domain(
start,
step,
self.df.num_rows(),
Arc::clone(&output_domain),
operation,
)?)
.map_err(|error| error.reanchor(operation).with_column(output_name.to_string()))?;
self.df
.with_column_in_domain(output_name, values, output_domain, operation)
.map_err(|error| error.reanchor(operation))
}
/// Add an encoded integer-code column using a reusable codebook.
pub fn with_encoded_column(&self, output_name: &str, input_column: &str, codebook: &Codebook) -> Result<DataFrame> {
let encoded = codebook
.encode(&self.df.series(input_column).map_err(|e| e.reanchor("dataframe::with_encoded_column"))?, self.ctx)
.map_err(|e| e.reanchor("dataframe::with_encoded_column").with_column(input_column.to_string()))?;
let output_domain = Arc::clone(encoded.as_series().as_column().execution_domain());
self.df
.with_column_in_domain(output_name, encoded, output_domain, "dataframe::with_encoded_column")
.map_err(|e| e.reanchor("dataframe::with_encoded_column"))
}
/// Add a decoded value column from integer codes.
pub fn with_decoded_column(&self, output_name: &str, input_column: &str, codebook: &Codebook) -> Result<DataFrame> {
let decoded = codebook
.decode_series(
&self.df.series(input_column).map_err(|e| e.reanchor("dataframe::with_decoded_column"))?,
self.ctx,
)
.map_err(|e| e.reanchor("dataframe::with_decoded_column").with_column(input_column.to_string()))?;
let output_domain = Arc::clone(decoded.as_column().execution_domain());
self.df
.with_column_in_domain(output_name, decoded, output_domain, "dataframe::with_decoded_column")
.map_err(|e| e.reanchor("dataframe::with_decoded_column"))
}
/// Sort.
pub fn sort_values<I, S>(&self, by: I, options: SortOptions) -> Result<DataFrame>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.df.sort_values_in_context(by, options, self.ctx)
}
/// Prepare a retained sorted-order gather map for a later DataFrame sort.
///
/// This resolves and validates keys/options with the same user-facing
/// rules as [`sort_values`](Self::sort_values), then retains only the INT32
/// sorted-order row-index column. Use
/// [`DataFramePreparedSort::materialized_output_reservation_size_bytes`]
/// to reserve the exact gathered output bytes before calling
/// [`DataFramePreparedSort::materialize`].
///
/// This contract covers the retained sorted-order bytes and the
/// materialized gather output sizing. It does not cover the libcudf sort
/// temporary workspace used while producing the sorted order. That workspace
/// contract remains unresolved.
pub fn prepare_sort_values<I, S>(&self, by: I, options: SortOptions) -> Result<DataFramePreparedSort>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.df.prepare_sort_values_in_context(by, options, self.ctx)
}
/// Hash-partition rows.
pub fn hash_partition_by<I, S>(&self, by: I, num_partitions: usize) -> Result<HashPartitionedDataFrame>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.df.hash_partition_by_in_context(by, num_partitions, self.ctx)
}
/// Filter rows using a validated fused predicate expression.
///
/// This is the preferred high-level API for multi-condition row
/// filtering. It avoids chained intermediate `DataFrame`
/// materializations by lowering a supported predicate subset to a
/// dedicated row-predicate execution path and applying one final table
/// filter.
///
/// V1 supported predicate syntax is intentionally narrower than the full
/// [`Expr`] DSL:
///
/// - column references and literals
/// - comparisons: `eq`, `ne`, `lt`, `le`, `gt`, `ge`
/// - null checks: `is_null`, `is_not_null`
/// - boolean composition: `!`, `&`, `|`
/// - alias wrappers around a supported inner predicate
///
/// Unsupported predicate nodes are evaluated through the general
/// expression engine and applied as a boolean mask until the fused
/// row-predicate path supports those expressions directly.
///
/// The predicate must evaluate to a row-aligned `Bool8`. Rows where the
/// predicate is `false` or null are dropped.
pub fn filter_expr(&self, predicate: Expr) -> Result<DataFrame> {
self.df.filter_expr_in_context(predicate, self.ctx)
}
/// Filter rows where all predicates are true.
///
/// This is the preferred ergonomic wrapper for common multi-condition
/// conjunctions. It is equivalent to combining the predicates with `&`
/// and passing the result to [`FrameOps::filter_expr`](crate::FrameOps::filter_expr).
///
/// Empty input is the logical identity and returns `self.clone()`.
pub fn filter_all<I>(&self, predicates: I) -> Result<DataFrame>
where I: IntoIterator<Item = Expr> {
self.df.filter_all_in_context(predicates, self.ctx)
}
/// Filter rows where any predicate is true.
///
/// This is the preferred ergonomic wrapper for common multi-condition
/// disjunctions. It is equivalent to combining the predicates with `|`
/// and passing the result to [`FrameOps::filter_expr`](crate::FrameOps::filter_expr).
///
/// Empty input returns an empty DataFrame with the same schema.
pub fn filter_any<I>(&self, predicates: I) -> Result<DataFrame>
where I: IntoIterator<Item = Expr> {
self.df.filter_any_in_context(predicates, self.ctx)
}
/// Materialize selected columns by gathering rows with join indices.
///
/// This performs a GPU gather of the selected columns and therefore copies
/// device buffers for those output columns. It is not a zero-copy schema
/// projection. The row count of the output is the length of `indices`.
pub fn materialize_join_indices<I, S>(&self, indices: &DataFrameJoinIndices, columns: I) -> Result<DataFrame>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.df.materialize_join_indices_in_context(indices, columns, self.ctx, "dataframe::materialize_join_indices")
}
/// Return the exact retained device allocation size of
/// [`materialize_join_indices`](Self::materialize_join_indices) for the
/// selected payload columns.
///
/// This is a lower-level reservation contract for bounded engines. It is
/// implemented in the cuDF layer by materializing non-empty projected
/// gather results, observing the bytes the table allocates, and releasing
/// the temporary result before returning. Empty gather maps return zero
/// without executing the gather. Callers should reserve the returned byte
/// count before performing the durable materialization.
///
/// Unlike
/// [`hash_join_pre_build_reservation_size_bytes`](Self::hash_join_pre_build_reservation_size_bytes),
/// this contract executes the non-empty gather end-to-end and discards the
/// result. Its cost is comparable to
/// [`materialize_join_indices`](Self::materialize_join_indices) itself, so
/// reserve-then-materialize roughly doubles the gather work.
///
/// This helper is deprecated in documentation only. It is retained for legacy
/// callers that need an exact post-gather allocation measurement. It is an
/// executable preflight. It does not compute a closed-form bound, and it can
/// allocate the full gather working set before the caller has reserved the
/// returned bytes. New bounded execution paths should prefer an explicit
/// closed-form reservation contract and then reconcile the observed retained
/// size after materialization.
pub fn materialize_join_indices_reservation_size_bytes<I, S>(
&self,
indices: &DataFrameJoinIndices,
columns: I,
) -> Result<usize>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.df.materialize_join_indices_reservation_size_bytes_in_context(
indices,
columns,
self.ctx,
"dataframe::materialize_join_indices_reservation_size_bytes",
)
}
/// Build a reusable hash-join artifact.
pub fn build_hash_join<I, S>(&self, build_on: I, options: DataFrameHashJoinOptions) -> Result<DataFrameHashJoin>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.df.build_hash_join_in_context(build_on, options, self.ctx)
}
/// Return exact device bytes that must be reserved before building a
/// reusable hash-join artifact from this DataFrame's named key columns.
///
/// This is a planning contract for bounded engines. It returns the exact
/// retained bytes for the reusable hash table and build-key device views,
/// without building or allocating retained payload. libcudf includes retained
/// null-payload sanitization for flat nullable string keys; nested build
/// keys remain unsupported.
pub fn hash_join_pre_build_reservation_size_bytes<I, S>(
&self,
build_on: I,
options: DataFrameHashJoinOptions,
) -> Result<usize>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.df.hash_join_pre_build_reservation_size_bytes_in_context(build_on, options, self.ctx)
}
/// Build a reusable filtered-join artifact.
pub fn build_filtered_join<I, S>(
&self,
build_on: I,
options: DataFrameFilteredJoinOptions,
) -> Result<DataFrameFilteredJoin>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.df.build_filtered_join_in_context(build_on, options, self.ctx)
}
/// Return exact pre-build reservation bytes for a reusable filtered-join
/// artifact.
pub fn filtered_join_pre_build_reservation_size_bytes<I, S>(
&self,
build_on: I,
options: DataFrameFilteredJoinOptions,
) -> Result<usize>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.df.filtered_join_pre_build_reservation_size_bytes_in_context(build_on, options, self.ctx)
}
/// Produce conditional inner join row-index pairs.
pub fn conditional_inner_join_indices(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFramePredicateJoinOptions,
) -> Result<DataFrameJoinPairIndices> {
self.df.conditional_join_indices_in_context(right, predicate, options, JoinType::Inner, self.ctx)
}
/// Return the exact conditional inner join row count.
pub fn conditional_inner_join_size(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFramePredicateJoinOptions,
) -> Result<usize> {
self.df.conditional_join_size_in_context(
right,
predicate,
options,
SizedConditionalPairJoinType::Inner,
self.ctx,
)
}
/// Produce conditional inner join row-index pairs using an exact caller-supplied output row count.
pub fn conditional_inner_join_indices_with_output_size(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFramePredicateJoinOptions,
output_size: usize,
) -> Result<DataFrameJoinPairIndices> {
self.df.conditional_join_indices_with_output_size_in_context(
right,
predicate,
options,
SizedConditionalPairJoinType::Inner,
output_size,
self.ctx,
)
}
/// Produce conditional left join row-index pairs.
pub fn conditional_left_join_indices(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFramePredicateJoinOptions,
) -> Result<DataFrameJoinPairIndices> {
self.df.conditional_join_indices_in_context(right, predicate, options, JoinType::Left, self.ctx)
}
/// Return the exact conditional left join row count.
pub fn conditional_left_join_size(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFramePredicateJoinOptions,
) -> Result<usize> {
self.df.conditional_join_size_in_context(
right,
predicate,
options,
SizedConditionalPairJoinType::Left,
self.ctx,
)
}
/// Produce conditional left join row-index pairs using an exact caller-supplied output row count.
pub fn conditional_left_join_indices_with_output_size(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFramePredicateJoinOptions,
output_size: usize,
) -> Result<DataFrameJoinPairIndices> {
self.df.conditional_join_indices_with_output_size_in_context(
right,
predicate,
options,
SizedConditionalPairJoinType::Left,
output_size,
self.ctx,
)
}
/// Produce conditional full join row-index pairs.
pub fn conditional_full_join_indices(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFramePredicateJoinOptions,
) -> Result<DataFrameJoinPairIndices> {
self.df.conditional_join_indices_in_context(right, predicate, options, JoinType::Full, self.ctx)
}
/// Begin retained size-data computation for a conditional full join.
pub fn conditional_full_join_size_data<'a>(
&self,
right: &DataFrame,
predicate: &'a AstExpression,
options: DataFramePredicateJoinOptions,
) -> Result<DataFrameConditionalFullJoinSizeData<'a>> {
self.df.conditional_full_join_size_data_in_context(right, predicate, options, self.ctx)
}
/// Produce conditional left semi join indices.
pub fn conditional_left_semi_join_indices(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFramePredicateJoinOptions,
) -> Result<DataFrameJoinIndices> {
self.df.conditional_single_side_join_indices_in_context(right, predicate, options, true, self.ctx)
}
/// Return the exact conditional left semi join row count.
pub fn conditional_left_semi_join_size(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFramePredicateJoinOptions,
) -> Result<usize> {
self.df.conditional_single_side_join_size_in_context(right, predicate, options, true, self.ctx)
}
/// Produce conditional left semi join indices using an exact caller-supplied output row count.
pub fn conditional_left_semi_join_indices_with_output_size(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFramePredicateJoinOptions,
output_size: usize,
) -> Result<DataFrameJoinIndices> {
self.df.conditional_single_side_join_indices_with_output_size_in_context(
right,
predicate,
options,
true,
output_size,
self.ctx,
)
}
/// Produce conditional left anti join indices.
pub fn conditional_left_anti_join_indices(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFramePredicateJoinOptions,
) -> Result<DataFrameJoinIndices> {
self.df.conditional_single_side_join_indices_in_context(right, predicate, options, false, self.ctx)
}
/// Return the exact conditional left anti join row count.
pub fn conditional_left_anti_join_size(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFramePredicateJoinOptions,
) -> Result<usize> {
self.df.conditional_single_side_join_size_in_context(right, predicate, options, false, self.ctx)
}
/// Produce conditional left anti join indices using an exact caller-supplied output row count.
pub fn conditional_left_anti_join_indices_with_output_size(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFramePredicateJoinOptions,
output_size: usize,
) -> Result<DataFrameJoinIndices> {
self.df.conditional_single_side_join_indices_with_output_size_in_context(
right,
predicate,
options,
false,
output_size,
self.ctx,
)
}
/// Produce mixed inner join row-index pairs.
pub fn mixed_inner_join_indices(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFrameMixedJoinOptions,
) -> Result<DataFrameJoinPairIndices> {
self.df.mixed_join_indices_in_context(right, predicate, options, JoinType::Inner, self.ctx)
}
/// Produce mixed left join row-index pairs.
pub fn mixed_left_join_indices(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFrameMixedJoinOptions,
) -> Result<DataFrameJoinPairIndices> {
self.df.mixed_join_indices_in_context(right, predicate, options, JoinType::Left, self.ctx)
}
/// Begin retained size-data computation for a mixed inner join.
pub fn mixed_inner_join_size_data<'a>(
&self,
right: &DataFrame,
predicate: &'a AstExpression,
options: DataFrameMixedJoinOptions,
) -> Result<DataFrameMixedJoinSizeData<'a>> {
self.df.mixed_join_size_data_in_context(right, predicate, options, SizedMixedJoinType::Inner, self.ctx)
}
/// Begin retained size-data computation for a mixed left join.
pub fn mixed_left_join_size_data<'a>(
&self,
right: &DataFrame,
predicate: &'a AstExpression,
options: DataFrameMixedJoinOptions,
) -> Result<DataFrameMixedJoinSizeData<'a>> {
self.df.mixed_join_size_data_in_context(right, predicate, options, SizedMixedJoinType::Left, self.ctx)
}
/// Begin retained size-data computation for a mixed full join.
pub fn mixed_full_join_size_data<'a>(
&self,
right: &DataFrame,
predicate: &'a AstExpression,
options: DataFrameMixedJoinOptions,
) -> Result<DataFrameMixedJoinSizeData<'a>> {
self.df.mixed_join_size_data_in_context(right, predicate, options, SizedMixedJoinType::Full, self.ctx)
}
/// Produce mixed full join row-index pairs.
pub fn mixed_full_join_indices(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFrameMixedJoinOptions,
) -> Result<DataFrameJoinPairIndices> {
self.df.mixed_join_indices_in_context(right, predicate, options, JoinType::Full, self.ctx)
}
/// Produce mixed left semi join indices.
pub fn mixed_left_semi_join_indices(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFrameMixedJoinOptions,
) -> Result<DataFrameJoinIndices> {
self.df.mixed_single_side_join_indices_in_context(right, predicate, options, true, self.ctx)
}
/// Begin retained size-data computation for a mixed left semi join.
pub fn mixed_left_semi_join_size_data<'a>(
&self,
right: &DataFrame,
predicate: &'a AstExpression,
options: DataFrameMixedJoinOptions,
) -> Result<DataFrameMixedSingleSideJoinSizeData<'a>> {
self.df.mixed_single_side_join_size_data_in_context(right, predicate, options, true, self.ctx)
}
/// Produce mixed left anti join indices.
pub fn mixed_left_anti_join_indices(
&self,
right: &DataFrame,
predicate: &AstExpression,
options: DataFrameMixedJoinOptions,
) -> Result<DataFrameJoinIndices> {
self.df.mixed_single_side_join_indices_in_context(right, predicate, options, false, self.ctx)
}
/// Begin retained size-data computation for a mixed left anti join.
pub fn mixed_left_anti_join_size_data<'a>(
&self,
right: &DataFrame,
predicate: &'a AstExpression,
options: DataFrameMixedJoinOptions,
) -> Result<DataFrameMixedSingleSideJoinSizeData<'a>> {
self.df.mixed_single_side_join_size_data_in_context(right, predicate, options, false, self.ctx)
}
/// Filter existing join-index pairs with a predicate.
pub fn filter_join_indices(
&self,
right: &DataFrame,
left_indices: &DataFrameJoinIndices,
right_indices: &DataFrameJoinIndices,
predicate: &AstExpression,
options: DataFramePredicateJoinOptions,
join_type: JoinType,
) -> Result<DataFrameJoinPairIndices> {
self.df.filter_join_indices_in_context(
right,
left_indices,
right_indices,
predicate,
options,
join_type,
self.ctx,
)
}
dataframe_scope_forward!(
/// Return the first `n` rows.
fn head(n: usize) -> Result<DataFrame> => head_in_context;
/// Return the last `n` rows.
fn tail(n: usize) -> Result<DataFrame> => tail_in_context;
/// Return `length` rows starting at `offset`.
fn slice(offset: usize, length: usize) -> Result<DataFrame> => slice_in_context;
/// Gather rows.
fn gather(indices: &Series) -> Result<DataFrame> => gather_in_context;
/// Filter rows.
fn filter(mask: &Series) -> Result<DataFrame> => filter_in_context;
/// Filter rows to those where `column` values appear in a `Series`.
fn filter_isin(column: &str, values: &Series) -> Result<DataFrame> => filter_isin_in_context;
/// Concatenate two DataFrames row-wise.
///
/// Both DataFrames must have the same column names in the same order
/// and matching column types.
///
/// # Errors
///
/// Returns an error if the schemas do not match (different names, count,
/// order, or types).
fn concat(other: &DataFrame) -> Result<DataFrame> => concat_in_context;
/// Concatenate multiple DataFrames row-wise.
///
/// All DataFrames must have the same column names in the same order
/// and matching column types. An empty `others` slice returns a clone
/// of `self`.
///
/// This is more efficient than chaining multiple [`concat`](Self::concat)
/// calls because it passes all table views to a single FFI call.
///
/// # Errors
///
/// Returns an error if any DataFrame's schema does not match `self`
/// (different names, count, order, or types).
fn concat_many(others: &[&DataFrame]) -> Result<DataFrame> => concat_many_in_context;
/// Merge (join) two DataFrames with cuDF-style options.
///
/// This is the unified high-level entry point for joins. Configure the merge
/// via [`MergeOptions`](crate::MergeOptions).
///
/// # Key modes
///
/// - `on(keys)`: shared key column names present in both sides. Output
/// contains one shared key column per key.
/// - `left_on(keys)` + `right_on(keys)`: asymmetric key names. Both key
/// columns are preserved in the output.
///
/// # Suffix handling
///
/// When `on()` is used: if a left payload and right payload column share
/// the same name, both are suffixed with the configured suffixes (default
/// `"_left"` / `"_right"`).
///
/// When `left_on()` / `right_on()` is used: both key columns are preserved,
/// and conflicting payload names get the same suffix treatment.
///
/// # Errors
///
/// Returns an error if:
/// - No keys are specified (`on` or `left_on`+`right_on`)
/// - `left_on` is set without `right_on` or vice versa
/// - `left_on` and `right_on` have different lengths
/// - A key column is not found in its respective DataFrame
fn merge(right: &DataFrame, options: MergeOptions) -> Result<DataFrame> => merge_in_context;
/// Merge (join) two DataFrames, returning split left/right results.
///
/// Like [`merge`](Self::merge) but returns a [`DataFrameJoinSides`] with
/// separate left and right `DataFrame`s. The columns are not merged into
/// one frame. Each side contains `[key_columns..., payload_columns...]`.
///
/// # Errors
///
/// Same error conditions as [`merge`](Self::merge).
fn merge_split(right: &DataFrame, options: MergeOptions) -> Result<DataFrameJoinSides> => merge_split_in_context;
/// Return device-side gather index pairs for an inner equality join.
///
/// This is an index-producing query-planning helper. It allocates only the
/// left and right `INT32` device gather maps and does not copy payload
/// columns. Duplicate key matches produce one row pair per match.
fn inner_join_indices(right: &DataFrame, options: DataFrameJoinOptions) -> Result<DataFrameJoinPairIndices> => inner_join_indices_in_context;
/// Return device-side gather index pairs for a left equality join.
///
/// Misses on the right side contain libcudf's no-match sentinel. Use
/// [`materialize_join_indices`](Self::materialize_join_indices) to gather
/// selected right-side columns with nullification for those misses.
fn left_join_indices(right: &DataFrame, options: DataFrameJoinOptions) -> Result<DataFrameJoinPairIndices> => left_join_indices_in_context;
/// Return device-side gather index pairs for a full equality join.
///
/// Misses on either side contain libcudf's no-match sentinel. Use
/// [`materialize_join_indices`](Self::materialize_join_indices) to gather
/// selected columns with nullification for unmatched rows.
fn full_join_indices(right: &DataFrame, options: DataFrameJoinOptions) -> Result<DataFrameJoinPairIndices> => full_join_indices_in_context;
/// Cross join two DataFrames and materialize only the requested output columns.
///
/// Source columns are selected before calling libcudf. Output names are
/// taken from [`DataFrameCrossJoinOptions`], allowing same-named source
/// columns from each side to be projected with distinct result names.
fn cross_join_projected(right: &DataFrame, options: DataFrameCrossJoinOptions) -> Result<DataFrame> => cross_join_projected_in_context;
/// Return device-side gather indices for the left rows that have a match in `right`.
///
/// This is an index-producing query-planning helper. It allocates only an
/// `INT32` device gather map and does not copy payload columns. Duplicate
/// matches on the right side do not duplicate left rows.
fn left_semi_join_indices(right: &DataFrame, options: DataFrameJoinOptions) -> Result<DataFrameJoinIndices> => left_semi_join_indices_in_context;
/// Return device-side gather indices for the left rows that have no match in `right`.
///
/// This is an index-producing query-planning helper. It allocates only an
/// `INT32` device gather map and does not copy payload columns.
fn left_anti_join_indices(right: &DataFrame, options: DataFrameJoinOptions) -> Result<DataFrameJoinIndices> => left_anti_join_indices_in_context;
/// Left semi join, returning only left-side columns.
///
/// This helper first computes left-side join indices and then gathers the
/// left frame's columns. The gather copies selected device rows into new
/// columns. Right-side payload columns are never included, and duplicate
/// right matches do not duplicate left rows.
fn left_semi_join(right: &DataFrame, options: DataFrameJoinOptions) -> Result<DataFrame> => left_semi_join_in_context;
/// Left anti join, returning only left-side columns.
///
/// This helper first computes left-side join indices and then gathers the
/// left frame's columns. The gather copies selected device rows into new
/// columns. Right-side payload columns are never included.
fn left_anti_join(right: &DataFrame, options: DataFrameJoinOptions) -> Result<DataFrame> => left_anti_join_in_context;
/// Drop rows containing null values.
///
/// Equivalent to cuDF Python's `DataFrame.dropna()`.
fn dropna(opts: DropNaOptions) -> Result<DataFrame> => dropna_in_context;
/// Remove duplicate rows.
///
/// Equivalent to cuDF Python's `DataFrame.drop_duplicates()`.
fn drop_duplicates(opts: DropDuplicatesOptions) -> Result<DataFrame> => drop_duplicates_in_context;
/// Remove consecutive duplicate rows.
///
/// Rows that compare equal on the selected columns must already be
/// adjacent. Use [`drop_duplicates`](Self::drop_duplicates) otherwise.
fn drop_consecutive_duplicates(opts: DropDuplicatesOptions) -> Result<DataFrame> => drop_consecutive_duplicates_in_context;
);
}