1#![allow(unsafe_op_in_unsafe_fn)]
2use std::fmt::{Debug, Formatter};
3
4use polars_error::{PolarsResult, polars_ensure};
5
6use crate::nulls::IsNull;
7
8#[cfg(not(feature = "bigidx"))]
9pub type IdxSize = u32;
10#[cfg(feature = "bigidx")]
11pub type IdxSize = u64;
12
13#[inline]
15pub const fn idxsize_to_u64(
16 #[cfg(feature = "bigidx")] val: u64,
17 #[cfg(not(feature = "bigidx"))] val: u32,
18) -> u64 {
19 #[cfg(feature = "bigidx")]
20 {
21 val
22 }
23 #[cfg(not(feature = "bigidx"))]
24 {
25 val as u64
26 }
27}
28
29#[inline(always)]
31pub fn idxsize_try_from<T>(x: T) -> Result<IdxSize, <IdxSize as TryFrom<T>>::Error>
32where
33 IdxSize: TryFrom<T>,
34{
35 IdxSize::try_from(x)
36}
37
38#[cfg(not(feature = "bigidx"))]
39pub type NonZeroIdxSize = std::num::NonZeroU32;
40#[cfg(feature = "bigidx")]
41pub type NonZeroIdxSize = std::num::NonZeroU64;
42
43#[cfg(not(feature = "bigidx"))]
44pub type AtomicIdxSize = std::sync::atomic::AtomicU32;
45#[cfg(feature = "bigidx")]
46pub type AtomicIdxSize = std::sync::atomic::AtomicU64;
47
48#[derive(Clone, Copy)]
49#[repr(transparent)]
50pub struct NullableIdxSize {
51 pub inner: IdxSize,
52}
53
54impl PartialEq<Self> for NullableIdxSize {
55 fn eq(&self, other: &Self) -> bool {
56 self.inner == other.inner
57 }
58}
59
60impl Eq for NullableIdxSize {}
61
62unsafe impl bytemuck::Zeroable for NullableIdxSize {}
63unsafe impl bytemuck::AnyBitPattern for NullableIdxSize {}
64unsafe impl bytemuck::NoUninit for NullableIdxSize {}
65
66impl Debug for NullableIdxSize {
67 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
68 write!(f, "{:?}", self.inner)
69 }
70}
71
72impl NullableIdxSize {
73 #[inline(always)]
74 pub fn is_null_idx(&self) -> bool {
75 self.inner == IdxSize::MAX
77 }
78
79 #[inline(always)]
80 pub const fn null() -> Self {
81 Self {
82 inner: IdxSize::MAX,
83 }
84 }
85
86 #[inline(always)]
87 pub fn idx(&self) -> IdxSize {
88 self.inner
89 }
90
91 #[inline(always)]
92 pub fn to_opt(&self) -> Option<IdxSize> {
93 if self.is_null_idx() {
94 None
95 } else {
96 Some(self.idx())
97 }
98 }
99}
100
101impl From<IdxSize> for NullableIdxSize {
102 #[inline(always)]
103 fn from(value: IdxSize) -> Self {
104 Self { inner: value }
105 }
106}
107
108pub trait Bounded {
109 fn len(&self) -> usize;
110
111 fn is_empty(&self) -> bool {
112 self.len() == 0
113 }
114}
115
116pub trait NullCount {
117 fn null_count(&self) -> usize {
118 0
119 }
120}
121
122impl<T: NullCount> NullCount for &T {
123 fn null_count(&self) -> usize {
124 (*self).null_count()
125 }
126}
127
128impl<T> Bounded for &[T] {
129 fn len(&self) -> usize {
130 <[T]>::len(self)
131 }
132}
133
134impl<T> NullCount for &[T] {
135 fn null_count(&self) -> usize {
136 0
137 }
138}
139
140pub trait Indexable {
141 type Item: IsNull;
142
143 fn get(&self, i: usize) -> Self::Item;
144
145 unsafe fn get_unchecked(&self, i: usize) -> Self::Item;
148}
149
150impl<T: Copy + IsNull> Indexable for &[T] {
151 type Item = T;
152
153 fn get(&self, i: usize) -> Self::Item {
154 self[i]
155 }
156
157 unsafe fn get_unchecked(&self, i: usize) -> Self::Item {
160 *<[T]>::get_unchecked(self, i)
161 }
162}
163
164pub fn check_bounds(idx: &[IdxSize], len: IdxSize) -> PolarsResult<()> {
165 let Some(max_idx) = idx.iter().copied().max() else {
167 return Ok(());
168 };
169
170 polars_ensure!(max_idx < len, OutOfBounds: "indices are out of bounds");
171 Ok(())
172}
173
174const DEFAULT_CHUNK_BITS: u64 = 24;
177
178#[derive(Clone, Copy)]
179#[repr(transparent)]
180pub struct ChunkId<const CHUNK_BITS: u64 = DEFAULT_CHUNK_BITS> {
181 swizzled: u64,
182}
183
184impl<const CHUNK_BITS: u64> Debug for ChunkId<CHUNK_BITS> {
185 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
186 if self.is_null() {
187 write!(f, "NULL")
188 } else {
189 let (chunk, row) = self.extract();
190 write!(f, "({chunk}, {row})")
191 }
192 }
193}
194
195impl<const CHUNK_BITS: u64> ChunkId<CHUNK_BITS> {
196 #[inline(always)]
197 pub const fn null() -> Self {
198 Self { swizzled: u64::MAX }
199 }
200
201 #[inline(always)]
202 pub fn is_null(&self) -> bool {
203 self.swizzled == u64::MAX
204 }
205
206 #[inline(always)]
207 #[allow(clippy::unnecessary_cast)]
208 pub fn store(chunk: IdxSize, row: IdxSize) -> Self {
209 debug_assert!(chunk < !(u64::MAX << CHUNK_BITS) as IdxSize);
210 let swizzled = ((row as u64) << CHUNK_BITS) | chunk as u64;
211
212 Self { swizzled }
213 }
214
215 #[inline(always)]
216 #[allow(clippy::unnecessary_cast)]
217 pub fn extract(self) -> (IdxSize, IdxSize) {
218 let row = (self.swizzled >> CHUNK_BITS) as IdxSize;
219 let mask = (1u64 << CHUNK_BITS) - 1;
220 let chunk = (self.swizzled & mask) as IdxSize;
221 (chunk, row)
222 }
223
224 #[inline(always)]
225 pub fn inner_mut(&mut self) -> &mut u64 {
226 &mut self.swizzled
227 }
228
229 pub fn from_inner(inner: u64) -> Self {
230 Self { swizzled: inner }
231 }
232
233 pub fn into_inner(self) -> u64 {
234 self.swizzled
235 }
236}
237
238#[cfg(test)]
239mod test {
240 use super::*;
241
242 #[test]
243 fn test_chunk_idx() {
244 let chunk = 213908;
245 let row = 813457;
246
247 let ci: ChunkId = ChunkId::store(chunk, row);
248 let (c, r) = ci.extract();
249
250 assert_eq!(c, chunk);
251 assert_eq!(r, row);
252 }
253}