iceberg 0.11.2 → 0.12.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Cargo.lock +799 -270
- data/Cargo.toml +7 -0
- data/NOTICE.txt +2 -2
- data/README.md +32 -12
- data/ext/iceberg/Cargo.toml +16 -13
- data/ext/iceberg/src/arrow.rs +22 -7
- data/ext/iceberg/src/batch.rs +174 -0
- data/ext/iceberg/src/capsule.rs +24 -0
- data/ext/iceberg/src/catalog.rs +160 -33
- data/ext/iceberg/src/encryption.rs +32 -0
- data/ext/iceberg/src/error.rs +50 -23
- data/ext/iceberg/src/lib.rs +264 -41
- data/ext/iceberg/src/partitioning.rs +102 -0
- data/ext/iceberg/src/result.rs +201 -0
- data/ext/iceberg/src/runtime.rs +5 -1
- data/ext/iceberg/src/scan.rs +106 -26
- data/ext/iceberg/src/schema.rs +305 -0
- data/ext/iceberg/src/snapshot.rs +85 -0
- data/ext/iceberg/src/sorting.rs +122 -0
- data/ext/iceberg/src/statistics.rs +71 -0
- data/ext/iceberg/src/table.rs +213 -265
- data/ext/iceberg/src/utils.rs +222 -164
- data/lib/iceberg/catalog.rb +54 -35
- data/lib/iceberg/glue_catalog.rb +5 -2
- data/lib/iceberg/memory_catalog.rb +5 -2
- data/lib/iceberg/rest_catalog.rb +5 -2
- data/lib/iceberg/result.rb +22 -0
- data/lib/iceberg/s3_tables_catalog.rb +5 -2
- data/lib/iceberg/sql_catalog.rb +5 -2
- data/lib/iceberg/table.rb +87 -21
- data/lib/iceberg/table_definition.rb +49 -7
- data/lib/iceberg/table_scan.rb +14 -0
- data/lib/iceberg/transforms.rb +64 -0
- data/lib/iceberg/types.rb +137 -0
- data/lib/iceberg/version.rb +1 -1
- data/lib/iceberg.rb +10 -5
- metadata +14 -3
- data/lib/iceberg/schema.rb +0 -10
data/ext/iceberg/src/catalog.rs
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
use std::sync::{Arc, RwLock};
|
|
3
|
+
|
|
1
4
|
#[cfg(feature = "datafusion")]
|
|
2
|
-
use datafusion::
|
|
3
|
-
|
|
5
|
+
use datafusion::common::ScalarValue;
|
|
6
|
+
#[cfg(feature = "datafusion")]
|
|
7
|
+
use datafusion::execution::context::{SessionConfig, SessionContext};
|
|
4
8
|
use iceberg::memory::{MEMORY_CATALOG_WAREHOUSE, MemoryCatalogBuilder};
|
|
5
|
-
use iceberg::spec::
|
|
6
|
-
use iceberg::{
|
|
9
|
+
use iceberg::spec::FormatVersion;
|
|
10
|
+
use iceberg::{
|
|
11
|
+
Catalog, CatalogBuilder, MemoryCatalog, NamespaceIdent, Runtime, TableCreation, TableIdent,
|
|
12
|
+
};
|
|
7
13
|
#[cfg(feature = "glue")]
|
|
8
14
|
use iceberg_catalog_glue::{GLUE_CATALOG_PROP_WAREHOUSE, GlueCatalog, GlueCatalogBuilder};
|
|
9
15
|
#[cfg(feature = "rest")]
|
|
@@ -21,13 +27,23 @@ use iceberg_catalog_sql::{
|
|
|
21
27
|
};
|
|
22
28
|
#[cfg(feature = "datafusion")]
|
|
23
29
|
use iceberg_datafusion::IcebergCatalogProvider;
|
|
24
|
-
use
|
|
25
|
-
use
|
|
30
|
+
use iceberg_storage_opendal::OpenDalStorageFactory;
|
|
31
|
+
use magnus::{Error as RbErr, Ruby};
|
|
32
|
+
#[cfg(feature = "datafusion")]
|
|
33
|
+
use magnus::{
|
|
34
|
+
Float, Integer, RArray, RString, Value, value::Qfalse, value::Qtrue, value::ReprValue,
|
|
35
|
+
};
|
|
26
36
|
|
|
27
37
|
use crate::error::to_rb_err;
|
|
28
|
-
|
|
38
|
+
#[cfg(feature = "datafusion")]
|
|
39
|
+
use crate::error::{datafusion_error, todo_error};
|
|
40
|
+
#[cfg(feature = "datafusion")]
|
|
41
|
+
use crate::result::collect_batches;
|
|
42
|
+
use crate::runtime::{runtime, tokio_runtime};
|
|
29
43
|
use crate::utils::Wrap;
|
|
30
|
-
|
|
44
|
+
#[cfg(feature = "datafusion")]
|
|
45
|
+
use crate::utils::date_to_i32;
|
|
46
|
+
use crate::{RbPartitionSpec, RbResult, RbSchema, RbSortOrder, RbTable};
|
|
31
47
|
|
|
32
48
|
pub enum RbCatalogType {
|
|
33
49
|
#[cfg(feature = "glue")]
|
|
@@ -82,7 +98,11 @@ impl RbCatalog {
|
|
|
82
98
|
pub fn new_glue(warehouse: String) -> RbResult<Self> {
|
|
83
99
|
let props = HashMap::from([(GLUE_CATALOG_PROP_WAREHOUSE.to_string(), warehouse)]);
|
|
84
100
|
let catalog = runtime()
|
|
85
|
-
.block_on(
|
|
101
|
+
.block_on(
|
|
102
|
+
GlueCatalogBuilder::default()
|
|
103
|
+
.with_runtime(Runtime::new(tokio_runtime()))
|
|
104
|
+
.load("glue", props),
|
|
105
|
+
)
|
|
86
106
|
.map_err(to_rb_err)?;
|
|
87
107
|
Ok(Self {
|
|
88
108
|
catalog: RbCatalogType::Glue(catalog.into()).into(),
|
|
@@ -91,13 +111,14 @@ impl RbCatalog {
|
|
|
91
111
|
|
|
92
112
|
pub fn new_memory(warehouse: Option<String>) -> RbResult<Self> {
|
|
93
113
|
let mut props = HashMap::new();
|
|
94
|
-
if let Some(v) = warehouse {
|
|
95
|
-
props.insert(MEMORY_CATALOG_WAREHOUSE.to_string(), v);
|
|
114
|
+
if let Some(ref v) = warehouse {
|
|
115
|
+
props.insert(MEMORY_CATALOG_WAREHOUSE.to_string(), v.clone());
|
|
96
116
|
}
|
|
97
117
|
let catalog = runtime()
|
|
98
118
|
.block_on(
|
|
99
119
|
MemoryCatalogBuilder::default()
|
|
100
|
-
.
|
|
120
|
+
.with_runtime(Runtime::new(tokio_runtime()))
|
|
121
|
+
.with_storage_factory(Arc::new(storage_factory(warehouse)))
|
|
101
122
|
.load("memory", props),
|
|
102
123
|
)
|
|
103
124
|
.map_err(to_rb_err)?;
|
|
@@ -114,13 +135,14 @@ impl RbCatalog {
|
|
|
114
135
|
) -> RbResult<Self> {
|
|
115
136
|
let mut props = props;
|
|
116
137
|
props.insert(REST_CATALOG_PROP_URI.to_string(), uri);
|
|
117
|
-
if let Some(v) = warehouse {
|
|
118
|
-
props.insert(REST_CATALOG_PROP_WAREHOUSE.to_string(), v);
|
|
138
|
+
if let Some(ref v) = warehouse {
|
|
139
|
+
props.insert(REST_CATALOG_PROP_WAREHOUSE.to_string(), v.clone());
|
|
119
140
|
}
|
|
120
141
|
let catalog = runtime()
|
|
121
142
|
.block_on(
|
|
122
143
|
RestCatalogBuilder::default()
|
|
123
|
-
.
|
|
144
|
+
.with_runtime(Runtime::new(tokio_runtime()))
|
|
145
|
+
.with_storage_factory(Arc::new(storage_factory(warehouse)))
|
|
124
146
|
.load("rest", props),
|
|
125
147
|
)
|
|
126
148
|
.map_err(to_rb_err)?;
|
|
@@ -134,7 +156,11 @@ impl RbCatalog {
|
|
|
134
156
|
let mut props = HashMap::new();
|
|
135
157
|
props.insert(S3TABLES_CATALOG_PROP_TABLE_BUCKET_ARN.to_string(), arn);
|
|
136
158
|
let catalog = runtime()
|
|
137
|
-
.block_on(
|
|
159
|
+
.block_on(
|
|
160
|
+
S3TablesCatalogBuilder::default()
|
|
161
|
+
.with_runtime(Runtime::new(tokio_runtime()))
|
|
162
|
+
.load("s3tables", props),
|
|
163
|
+
)
|
|
138
164
|
.map_err(to_rb_err)?;
|
|
139
165
|
Ok(Self {
|
|
140
166
|
catalog: RbCatalogType::S3Tables(catalog.into()).into(),
|
|
@@ -150,7 +176,7 @@ impl RbCatalog {
|
|
|
150
176
|
) -> RbResult<Self> {
|
|
151
177
|
let mut props = props;
|
|
152
178
|
props.insert(SQL_CATALOG_PROP_URI.to_string(), uri);
|
|
153
|
-
props.insert(SQL_CATALOG_PROP_WAREHOUSE.to_string(), warehouse);
|
|
179
|
+
props.insert(SQL_CATALOG_PROP_WAREHOUSE.to_string(), warehouse.clone());
|
|
154
180
|
props.insert(
|
|
155
181
|
SQL_CATALOG_PROP_BIND_STYLE.to_string(),
|
|
156
182
|
SqlBindStyle::DollarNumeric.to_string(),
|
|
@@ -158,7 +184,8 @@ impl RbCatalog {
|
|
|
158
184
|
let catalog = runtime()
|
|
159
185
|
.block_on(
|
|
160
186
|
SqlCatalogBuilder::default()
|
|
161
|
-
.
|
|
187
|
+
.with_runtime(Runtime::new(tokio_runtime()))
|
|
188
|
+
.with_storage_factory(Arc::new(storage_factory(Some(warehouse))))
|
|
162
189
|
.load(name, props),
|
|
163
190
|
)
|
|
164
191
|
.map_err(to_rb_err)?;
|
|
@@ -279,20 +306,45 @@ impl RbCatalog {
|
|
|
279
306
|
.collect())
|
|
280
307
|
}
|
|
281
308
|
|
|
309
|
+
#[allow(clippy::too_many_arguments)]
|
|
282
310
|
pub fn create_table(
|
|
283
|
-
&
|
|
311
|
+
ruby: &Ruby,
|
|
312
|
+
rb_self: &Self,
|
|
284
313
|
name: Wrap<TableIdent>,
|
|
285
|
-
schema:
|
|
314
|
+
schema: &RbSchema,
|
|
286
315
|
location: Option<String>,
|
|
316
|
+
partition_spec: Option<&RbPartitionSpec>,
|
|
317
|
+
sort_order: Option<&RbSortOrder>,
|
|
318
|
+
properties: HashMap<String, String>,
|
|
287
319
|
) -> RbResult<RbTable> {
|
|
320
|
+
let format_version = if let Some(v) = properties.get("format-version") {
|
|
321
|
+
match v.as_str() {
|
|
322
|
+
"1" => FormatVersion::V1,
|
|
323
|
+
"2" => FormatVersion::V2,
|
|
324
|
+
"3" => FormatVersion::V3,
|
|
325
|
+
_ => {
|
|
326
|
+
return Err(RbErr::new(
|
|
327
|
+
ruby.exception_arg_error(),
|
|
328
|
+
"Unsupported format version",
|
|
329
|
+
));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
FormatVersion::V2
|
|
334
|
+
};
|
|
288
335
|
let creation = TableCreation::builder()
|
|
289
336
|
.name(name.0.name)
|
|
290
|
-
.schema(schema.
|
|
337
|
+
.schema(schema.schema.clone())
|
|
291
338
|
.location_opt(location)
|
|
339
|
+
.partition_spec_opt(partition_spec.map(|v| v.spec.clone()))
|
|
340
|
+
.sort_order_opt(sort_order.map(|v| v.order.clone()))
|
|
341
|
+
.properties(properties)
|
|
342
|
+
.format_version(format_version)
|
|
292
343
|
.build();
|
|
293
344
|
let table = runtime()
|
|
294
345
|
.block_on(
|
|
295
|
-
|
|
346
|
+
rb_self
|
|
347
|
+
.catalog
|
|
296
348
|
.read()
|
|
297
349
|
.unwrap()
|
|
298
350
|
.as_catalog()
|
|
@@ -332,6 +384,19 @@ impl RbCatalog {
|
|
|
332
384
|
Ok(())
|
|
333
385
|
}
|
|
334
386
|
|
|
387
|
+
pub fn purge_table(&self, name: Wrap<TableIdent>) -> RbResult<()> {
|
|
388
|
+
runtime()
|
|
389
|
+
.block_on(
|
|
390
|
+
self.catalog
|
|
391
|
+
.read()
|
|
392
|
+
.unwrap()
|
|
393
|
+
.as_catalog()
|
|
394
|
+
.purge_table(&name.0),
|
|
395
|
+
)
|
|
396
|
+
.map_err(to_rb_err)?;
|
|
397
|
+
Ok(())
|
|
398
|
+
}
|
|
399
|
+
|
|
335
400
|
pub fn table_exists(&self, name: Wrap<TableIdent>) -> RbResult<bool> {
|
|
336
401
|
let exists = runtime()
|
|
337
402
|
.block_on(
|
|
@@ -376,22 +441,84 @@ impl RbCatalog {
|
|
|
376
441
|
}
|
|
377
442
|
|
|
378
443
|
#[cfg(feature = "datafusion")]
|
|
379
|
-
pub fn
|
|
380
|
-
let runtime = runtime();
|
|
381
|
-
|
|
382
|
-
// TODO only create context once
|
|
444
|
+
pub fn session_context(&self, default_namespace: Option<String>) -> RbResult<RbSessionContext> {
|
|
383
445
|
let catalog = self.catalog.read().unwrap().as_arc();
|
|
384
|
-
let provider = runtime
|
|
446
|
+
let provider = runtime()
|
|
385
447
|
.block_on(IcebergCatalogProvider::try_new(catalog))
|
|
386
|
-
.
|
|
387
|
-
let
|
|
448
|
+
.map_err(to_rb_err)?;
|
|
449
|
+
let mut config = SessionConfig::new();
|
|
450
|
+
if let Some(namespace) = default_namespace {
|
|
451
|
+
config = config.with_default_catalog_and_schema("datafusion", namespace);
|
|
452
|
+
}
|
|
453
|
+
let ctx = SessionContext::new_with_config(config);
|
|
388
454
|
ctx.register_catalog("datafusion", Arc::new(provider));
|
|
455
|
+
Ok(RbSessionContext { ctx })
|
|
456
|
+
}
|
|
457
|
+
}
|
|
389
458
|
|
|
390
|
-
|
|
391
|
-
|
|
459
|
+
#[cfg(feature = "datafusion")]
|
|
460
|
+
#[magnus::wrap(class = "Iceberg::SessionContext")]
|
|
461
|
+
pub struct RbSessionContext {
|
|
462
|
+
pub(crate) ctx: SessionContext,
|
|
463
|
+
}
|
|
392
464
|
|
|
393
|
-
|
|
465
|
+
#[cfg(feature = "datafusion")]
|
|
466
|
+
impl RbSessionContext {
|
|
467
|
+
pub fn sql(ruby: &Ruby, rb_self: &Self, sql: String, rb_params: RArray) -> RbResult<Value> {
|
|
468
|
+
let mut params = Vec::new();
|
|
469
|
+
for param in rb_params.into_iter() {
|
|
470
|
+
if param.is_nil() {
|
|
471
|
+
params.push(ScalarValue::Null);
|
|
472
|
+
} else if Qtrue::from_value(param).is_some() {
|
|
473
|
+
params.push(ScalarValue::from(true));
|
|
474
|
+
} else if Qfalse::from_value(param).is_some() {
|
|
475
|
+
params.push(ScalarValue::from(false));
|
|
476
|
+
} else if let Some(v) = Integer::from_value(param) {
|
|
477
|
+
params.push(ScalarValue::from(v.to_i64()?));
|
|
478
|
+
} else if let Some(v) = Float::from_value(param) {
|
|
479
|
+
params.push(ScalarValue::from(v.to_f64()));
|
|
480
|
+
} else if let Some(v) = RString::from_value(param) {
|
|
481
|
+
// TODO support binary strings
|
|
482
|
+
params.push(ScalarValue::from(v.to_string()?));
|
|
483
|
+
} else if unsafe { param.classname() } == "Date" {
|
|
484
|
+
params.push(ScalarValue::Date32(Some(date_to_i32(param)?)));
|
|
485
|
+
} else if unsafe { param.classname() } == "Time" {
|
|
486
|
+
let sec: i64 = param.funcall("to_i", ())?;
|
|
487
|
+
let nsec: i64 = param.funcall("nsec", ())?;
|
|
488
|
+
params.push(ScalarValue::TimestampNanosecond(
|
|
489
|
+
Some(sec * 1_000_000_000 + nsec),
|
|
490
|
+
None,
|
|
491
|
+
));
|
|
492
|
+
} else {
|
|
493
|
+
return Err(todo_error(param));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
394
496
|
|
|
395
|
-
|
|
497
|
+
let runtime = runtime();
|
|
498
|
+
let stream = runtime
|
|
499
|
+
.block_on(rb_self.ctx.sql(&sql))
|
|
500
|
+
.map_err(datafusion_error)?
|
|
501
|
+
.with_param_values(params)
|
|
502
|
+
.map_err(datafusion_error)?;
|
|
503
|
+
let batches = runtime
|
|
504
|
+
.block_on(stream.collect())
|
|
505
|
+
.map_err(datafusion_error)?;
|
|
506
|
+
collect_batches(ruby, batches)
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
fn storage_factory(warehouse: Option<String>) -> OpenDalStorageFactory {
|
|
511
|
+
match warehouse {
|
|
512
|
+
Some(ref v) => {
|
|
513
|
+
if v.starts_with("s3://") {
|
|
514
|
+
OpenDalStorageFactory::S3 {
|
|
515
|
+
customized_credential_load: None,
|
|
516
|
+
}
|
|
517
|
+
} else {
|
|
518
|
+
OpenDalStorageFactory::Fs
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
// TODO consider OpenDalStorageFactory::Memory
|
|
522
|
+
None => OpenDalStorageFactory::Fs,
|
|
396
523
|
}
|
|
397
524
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
|
|
3
|
+
use iceberg::spec::EncryptedKey;
|
|
4
|
+
use magnus::{IntoValue, Ruby, value::ReprValue};
|
|
5
|
+
|
|
6
|
+
#[magnus::wrap(class = "Iceberg::EncryptedKey")]
|
|
7
|
+
pub struct RbEncryptedKey {
|
|
8
|
+
pub(crate) key: EncryptedKey,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
impl RbEncryptedKey {
|
|
12
|
+
pub fn key_id(&self) -> &str {
|
|
13
|
+
self.key.key_id()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub fn encrypted_by_id(&self) -> Option<&str> {
|
|
17
|
+
self.key.encrypted_by_id()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub fn properties(&self) -> HashMap<String, String> {
|
|
21
|
+
self.key.properties().clone()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub fn inspect(ruby: &Ruby, rb_self: &Self) -> String {
|
|
25
|
+
format!(
|
|
26
|
+
"#<Iceberg::EncryptedKey key_id={}, encrypted_by_id={}, properties={}>",
|
|
27
|
+
rb_self.key_id().into_value_with(ruby).inspect(),
|
|
28
|
+
rb_self.encrypted_by_id().into_value_with(ruby).inspect(),
|
|
29
|
+
rb_self.properties().into_value_with(ruby).inspect(),
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
}
|
data/ext/iceberg/src/error.rs
CHANGED
|
@@ -1,17 +1,36 @@
|
|
|
1
|
+
use iceberg::{Error, ErrorKind};
|
|
1
2
|
use magnus::{Error as RbErr, RModule, Ruby, prelude::*};
|
|
2
3
|
|
|
3
|
-
pub fn to_rb_err(err:
|
|
4
|
-
let class_name = match err.kind() {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
pub fn to_rb_err(err: Error) -> RbErr {
|
|
5
|
+
let mut class_name = match err.kind() {
|
|
6
|
+
ErrorKind::NamespaceAlreadyExists => "NamespaceAlreadyExistsError",
|
|
7
|
+
ErrorKind::NamespaceNotFound => "NoSuchNamespaceError",
|
|
8
|
+
ErrorKind::TableAlreadyExists => "TableAlreadyExistsError",
|
|
9
|
+
ErrorKind::TableNotFound => "NoSuchTableError",
|
|
10
|
+
ErrorKind::FeatureUnsupported => "UnsupportedFeatureError",
|
|
11
|
+
ErrorKind::DataInvalid => "InvalidDataError",
|
|
11
12
|
_ => "Error",
|
|
12
13
|
};
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
// no way to get context separately
|
|
16
|
+
// https://github.com/apache/iceberg-rust/issues/1071
|
|
17
|
+
let message = err.message().to_string();
|
|
18
|
+
|
|
19
|
+
if class_name == "Error" {
|
|
20
|
+
let s = err.to_string();
|
|
21
|
+
// for Glue
|
|
22
|
+
if s.contains("EntityNotFoundException") {
|
|
23
|
+
class_name = "NoSuchTableError";
|
|
24
|
+
|
|
25
|
+
// for Glue and S3 Tables
|
|
26
|
+
} else if s.contains("AlreadyExistsException")
|
|
27
|
+
|| s.contains("A table with an identical name already exists in the namespace.")
|
|
28
|
+
{
|
|
29
|
+
class_name = "TableAlreadyExistsError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let class = Ruby::get()
|
|
15
34
|
.unwrap()
|
|
16
35
|
.class_object()
|
|
17
36
|
.const_get::<_, RModule>("Iceberg")
|
|
@@ -19,20 +38,28 @@ pub fn to_rb_err(err: iceberg::Error) -> RbErr {
|
|
|
19
38
|
.const_get(class_name)
|
|
20
39
|
.unwrap();
|
|
21
40
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
let message = err.to_string();
|
|
25
|
-
|
|
26
|
-
// TODO remove in 0.12.0
|
|
27
|
-
if message.contains("target schema is not superset of current schema") {
|
|
28
|
-
class = Ruby::get().unwrap().exception_arg_error();
|
|
29
|
-
}
|
|
41
|
+
RbErr::new(class, message)
|
|
42
|
+
}
|
|
30
43
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.
|
|
34
|
-
.
|
|
35
|
-
.
|
|
44
|
+
pub fn todo_error<T: std::fmt::Debug>(message: T) -> RbErr {
|
|
45
|
+
let class = Ruby::get()
|
|
46
|
+
.unwrap()
|
|
47
|
+
.class_object()
|
|
48
|
+
.const_get::<_, RModule>("Iceberg")
|
|
49
|
+
.unwrap()
|
|
50
|
+
.const_get("Todo")
|
|
51
|
+
.unwrap();
|
|
52
|
+
RbErr::new(class, format!("not implemented yet: {:?}", message))
|
|
53
|
+
}
|
|
36
54
|
|
|
37
|
-
|
|
55
|
+
#[cfg(feature = "datafusion")]
|
|
56
|
+
pub fn datafusion_error(err: datafusion::common::DataFusionError) -> RbErr {
|
|
57
|
+
let class = Ruby::get()
|
|
58
|
+
.unwrap()
|
|
59
|
+
.class_object()
|
|
60
|
+
.const_get::<_, RModule>("Iceberg")
|
|
61
|
+
.unwrap()
|
|
62
|
+
.const_get("Error")
|
|
63
|
+
.unwrap();
|
|
64
|
+
RbErr::new(class, err.to_string())
|
|
38
65
|
}
|