1use std::collections::BTreeMap;
8
9use super::types::{
10 ArraySchema, Compression, DataType, Dimension, TileDBConfig, TileDBError, TileOrder,
11};
12
13#[derive(Debug)]
19pub struct TileDBArray {
20 schema: ArraySchema,
22 config: TileDBConfig,
24 dense_data: Vec<f64>,
27 sparse_data: BTreeMap<Vec<i64>, f64>,
30 initialized: bool,
32}
33
34impl TileDBArray {
35 pub fn create(schema: ArraySchema, config: TileDBConfig) -> Result<Self, TileDBError> {
37 match &schema {
38 ArraySchema::Dense {
39 dimensions,
40 attributes,
41 } => {
42 if dimensions.is_empty() {
43 return Err(TileDBError::SchemaError(
44 "dense array must have at least one dimension".to_string(),
45 ));
46 }
47 if attributes.is_empty() {
48 return Err(TileDBError::SchemaError(
49 "array must have at least one attribute".to_string(),
50 ));
51 }
52 let total_cells: usize = dimensions.iter().map(|d| d.num_cells()).product();
54 let dense_data = vec![f64::NAN; total_cells];
55 Ok(Self {
56 schema,
57 config,
58 dense_data,
59 sparse_data: BTreeMap::new(),
60 initialized: false,
61 })
62 }
63 ArraySchema::Sparse {
64 dimensions,
65 attributes,
66 } => {
67 if dimensions.is_empty() {
68 return Err(TileDBError::SchemaError(
69 "sparse array must have at least one dimension".to_string(),
70 ));
71 }
72 if attributes.is_empty() {
73 return Err(TileDBError::SchemaError(
74 "array must have at least one attribute".to_string(),
75 ));
76 }
77 Ok(Self {
78 schema,
79 config,
80 dense_data: Vec::new(),
81 sparse_data: BTreeMap::new(),
82 initialized: false,
83 })
84 }
85 }
86 }
87
88 pub fn schema(&self) -> &ArraySchema {
90 &self.schema
91 }
92
93 pub fn config(&self) -> &TileDBConfig {
95 &self.config
96 }
97
98 pub fn write_dense(
103 &mut self,
104 data: &[f64],
105 subarray: &[(usize, usize)],
106 ) -> Result<(), TileDBError> {
107 if !self.schema.is_dense() {
108 return Err(TileDBError::SchemaError(
109 "write_dense called on sparse array".to_string(),
110 ));
111 }
112
113 let dims = self.schema.dimensions();
114 if subarray.len() != dims.len() {
115 return Err(TileDBError::InvalidQuery(format!(
116 "subarray has {} ranges but array has {} dimensions",
117 subarray.len(),
118 dims.len()
119 )));
120 }
121
122 for (i, (start, end)) in subarray.iter().enumerate() {
124 let dim = &dims[i];
125 let dim_start = dim.domain.0 as usize;
126 let dim_end = dim.domain.1 as usize;
127 if *start < dim_start || *end > dim_end || *start > *end {
128 return Err(TileDBError::OutOfBounds(format!(
129 "subarray range ({start}, {end}) out of bounds for dimension '{}' [{dim_start}, {dim_end}]",
130 dim.name
131 )));
132 }
133 }
134
135 let expected_len: usize = subarray.iter().map(|(s, e)| e - s + 1).product();
137 if data.len() != expected_len {
138 return Err(TileDBError::InvalidQuery(format!(
139 "data length {} does not match subarray size {expected_len}",
140 data.len()
141 )));
142 }
143
144 let dim_sizes: Vec<usize> = dims.iter().map(|d| d.num_cells()).collect();
146 let dim_offsets: Vec<usize> = dims.iter().map(|d| d.domain.0 as usize).collect();
147
148 let sub_sizes: Vec<usize> = subarray.iter().map(|(s, e)| e - s + 1).collect();
149
150 for flat_idx in 0..expected_len {
151 let sub_coords = flat_to_nd(flat_idx, &sub_sizes);
153
154 let global_coords: Vec<usize> = sub_coords
156 .iter()
157 .enumerate()
158 .map(|(i, &c)| subarray[i].0 + c)
159 .collect();
160
161 let rel_coords: Vec<usize> = global_coords
163 .iter()
164 .enumerate()
165 .map(|(i, &c)| c - dim_offsets[i])
166 .collect();
167
168 let storage_idx = nd_to_flat(&rel_coords, &dim_sizes, &self.config.cell_order);
169
170 if storage_idx < self.dense_data.len() {
171 self.dense_data[storage_idx] = data[flat_idx];
172 }
173 }
174
175 self.initialized = true;
176 Ok(())
177 }
178
179 pub fn read_dense(&self, subarray: &[(usize, usize)]) -> Result<Vec<f64>, TileDBError> {
184 if !self.schema.is_dense() {
185 return Err(TileDBError::SchemaError(
186 "read_dense called on sparse array".to_string(),
187 ));
188 }
189
190 let dims = self.schema.dimensions();
191 if subarray.len() != dims.len() {
192 return Err(TileDBError::InvalidQuery(format!(
193 "subarray has {} ranges but array has {} dimensions",
194 subarray.len(),
195 dims.len()
196 )));
197 }
198
199 for (i, (start, end)) in subarray.iter().enumerate() {
201 let dim = &dims[i];
202 let dim_start = dim.domain.0 as usize;
203 let dim_end = dim.domain.1 as usize;
204 if *start < dim_start || *end > dim_end || *start > *end {
205 return Err(TileDBError::OutOfBounds(format!(
206 "subarray range ({start}, {end}) out of bounds for dim '{}' [{dim_start}, {dim_end}]",
207 dim.name
208 )));
209 }
210 }
211
212 let dim_sizes: Vec<usize> = dims.iter().map(|d| d.num_cells()).collect();
213 let dim_offsets: Vec<usize> = dims.iter().map(|d| d.domain.0 as usize).collect();
214 let sub_sizes: Vec<usize> = subarray.iter().map(|(s, e)| e - s + 1).collect();
215 let total: usize = sub_sizes.iter().product();
216
217 let mut result = Vec::with_capacity(total);
218
219 for flat_idx in 0..total {
220 let sub_coords = flat_to_nd(flat_idx, &sub_sizes);
221 let global_coords: Vec<usize> = sub_coords
222 .iter()
223 .enumerate()
224 .map(|(i, &c)| subarray[i].0 + c)
225 .collect();
226 let rel_coords: Vec<usize> = global_coords
227 .iter()
228 .enumerate()
229 .map(|(i, &c)| c - dim_offsets[i])
230 .collect();
231
232 let storage_idx = nd_to_flat(&rel_coords, &dim_sizes, &self.config.cell_order);
233
234 if storage_idx < self.dense_data.len() {
235 result.push(self.dense_data[storage_idx]);
236 } else {
237 result.push(f64::NAN);
238 }
239 }
240
241 Ok(result)
242 }
243
244 pub fn write_sparse(&mut self, coords: &[Vec<f64>], values: &[f64]) -> Result<(), TileDBError> {
249 if self.schema.is_dense() {
250 return Err(TileDBError::SchemaError(
251 "write_sparse called on dense array".to_string(),
252 ));
253 }
254
255 let dims = self.schema.dimensions();
256 if coords.len() != dims.len() {
257 return Err(TileDBError::InvalidQuery(format!(
258 "coords has {} dimension vectors but array has {} dimensions",
259 coords.len(),
260 dims.len()
261 )));
262 }
263
264 let num_points = values.len();
265 for (i, coord_vec) in coords.iter().enumerate() {
266 if coord_vec.len() != num_points {
267 return Err(TileDBError::InvalidQuery(format!(
268 "dimension {} has {} coords but {} values provided",
269 i,
270 coord_vec.len(),
271 num_points
272 )));
273 }
274 }
275
276 for point_idx in 0..num_points {
278 for (dim_idx, dim) in dims.iter().enumerate() {
279 let c = coords[dim_idx][point_idx];
280 if c < dim.domain.0 || c > dim.domain.1 {
281 return Err(TileDBError::OutOfBounds(format!(
282 "coordinate {c} out of bounds for dimension '{}' [{}, {}]",
283 dim.name, dim.domain.0, dim.domain.1
284 )));
285 }
286 }
287 }
288
289 for point_idx in 0..num_points {
291 let key: Vec<i64> = coords.iter().map(|cv| cv[point_idx] as i64).collect();
292 self.sparse_data.insert(key, values[point_idx]);
293 }
294
295 self.initialized = true;
296 Ok(())
297 }
298
299 pub fn read_sparse(
304 &self,
305 query_range: &[(f64, f64)],
306 ) -> Result<(Vec<Vec<f64>>, Vec<f64>), TileDBError> {
307 if self.schema.is_dense() {
308 return Err(TileDBError::SchemaError(
309 "read_sparse called on dense array".to_string(),
310 ));
311 }
312
313 let dims = self.schema.dimensions();
314 if query_range.len() != dims.len() {
315 return Err(TileDBError::InvalidQuery(format!(
316 "query has {} ranges but array has {} dimensions",
317 query_range.len(),
318 dims.len()
319 )));
320 }
321
322 let num_dims = dims.len();
323 let mut result_coords: Vec<Vec<f64>> = (0..num_dims).map(|_| Vec::new()).collect();
324 let mut result_values: Vec<f64> = Vec::new();
325
326 for (key, &value) in &self.sparse_data {
327 let in_range = key.iter().enumerate().all(|(i, &c)| {
328 let cf = c as f64;
329 cf >= query_range[i].0 && cf <= query_range[i].1
330 });
331
332 if in_range {
333 for (i, &c) in key.iter().enumerate() {
334 result_coords[i].push(c as f64);
335 }
336 result_values.push(value);
337 }
338 }
339
340 Ok((result_coords, result_values))
341 }
342
343 pub fn num_cells(&self) -> usize {
345 if self.schema.is_dense() {
346 self.dense_data.iter().filter(|v| !v.is_nan()).count()
347 } else {
348 self.sparse_data.len()
349 }
350 }
351
352 pub fn total_capacity(&self) -> usize {
354 self.dense_data.len()
355 }
356
357 pub fn tile_info(&self) -> Vec<(String, usize, usize)> {
359 self.schema
360 .dimensions()
361 .iter()
362 .map(|d| (d.name.clone(), d.num_cells(), d.num_tiles()))
363 .collect()
364 }
365}
366
367fn flat_to_nd(flat_idx: usize, sizes: &[usize]) -> Vec<usize> {
371 let ndim = sizes.len();
372 let mut coords = vec![0usize; ndim];
373 let mut remaining = flat_idx;
374
375 for i in (0..ndim).rev() {
376 if sizes[i] > 0 {
377 coords[i] = remaining % sizes[i];
378 remaining /= sizes[i];
379 }
380 }
381
382 coords
383}
384
385fn nd_to_flat(coords: &[usize], sizes: &[usize], order: &TileOrder) -> usize {
387 let ndim = coords.len();
388 if ndim == 0 {
389 return 0;
390 }
391
392 match order {
393 TileOrder::RowMajor | TileOrder::Hilbert => {
394 let mut idx = 0usize;
396 let mut stride = 1usize;
397 for i in (0..ndim).rev() {
398 idx += coords[i] * stride;
399 stride *= sizes[i];
400 }
401 idx
402 }
403 TileOrder::ColMajor => {
404 let mut idx = 0usize;
406 let mut stride = 1usize;
407 for i in 0..ndim {
408 idx += coords[i] * stride;
409 stride *= sizes[i];
410 }
411 idx
412 }
413 }
414}