@lix-js/sdk 0.6.0-preview.0 → 0.6.0-preview.2
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.
- package/README.md +9 -0
- package/SKILL.md +468 -0
- package/dist/engine-wasm/index.d.ts +15 -11
- package/dist/engine-wasm/index.js +105 -38
- package/dist/engine-wasm/wasm/lix_engine.d.ts +14 -2
- package/dist/engine-wasm/wasm/lix_engine.js +18 -17
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +2 -1
- package/dist/generated/builtin-schemas.d.ts +31 -41
- package/dist/generated/builtin-schemas.js +52 -56
- package/dist/open-lix.d.ts +141 -24
- package/dist/open-lix.js +199 -35
- package/dist/sqlite/index.js +99 -22
- package/dist-engine-src/README.md +18 -0
- package/dist-engine-src/src/backend/kv.rs +358 -0
- package/dist-engine-src/src/backend/mod.rs +12 -0
- package/dist-engine-src/src/backend/testing.rs +658 -0
- package/dist-engine-src/src/backend/types.rs +96 -0
- package/dist-engine-src/src/binary_cas/chunking.rs +31 -0
- package/dist-engine-src/src/binary_cas/codec.rs +346 -0
- package/dist-engine-src/src/binary_cas/context.rs +139 -0
- package/dist-engine-src/src/binary_cas/kv.rs +1063 -0
- package/dist-engine-src/src/binary_cas/mod.rs +11 -0
- package/dist-engine-src/src/binary_cas/types.rs +127 -0
- package/dist-engine-src/src/cel/context.rs +86 -0
- package/dist-engine-src/src/cel/error.rs +19 -0
- package/dist-engine-src/src/cel/mod.rs +8 -0
- package/dist-engine-src/src/cel/provider.rs +9 -0
- package/dist-engine-src/src/cel/runtime.rs +167 -0
- package/dist-engine-src/src/cel/value.rs +50 -0
- package/dist-engine-src/src/changelog/codec.rs +321 -0
- package/dist-engine-src/src/changelog/context.rs +92 -0
- package/dist-engine-src/src/changelog/materialization.rs +121 -0
- package/dist-engine-src/src/changelog/mod.rs +13 -0
- package/dist-engine-src/src/changelog/reader.rs +20 -0
- package/dist-engine-src/src/changelog/storage.rs +220 -0
- package/dist-engine-src/src/changelog/types.rs +38 -0
- package/dist-engine-src/src/commit_graph/context.rs +1588 -0
- package/dist-engine-src/src/commit_graph/mod.rs +12 -0
- package/dist-engine-src/src/commit_graph/types.rs +145 -0
- package/dist-engine-src/src/commit_graph/walker.rs +780 -0
- package/dist-engine-src/src/common/error.rs +313 -0
- package/dist-engine-src/src/common/fingerprint.rs +3 -0
- package/dist-engine-src/src/common/fs_path.rs +1336 -0
- package/dist-engine-src/src/common/identity.rs +135 -0
- package/dist-engine-src/src/common/metadata.rs +35 -0
- package/dist-engine-src/src/common/mod.rs +23 -0
- package/dist-engine-src/src/common/types.rs +105 -0
- package/dist-engine-src/src/common/wire.rs +222 -0
- package/dist-engine-src/src/engine.rs +239 -0
- package/dist-engine-src/src/entity_identity.rs +285 -0
- package/dist-engine-src/src/functions/context.rs +327 -0
- package/dist-engine-src/src/functions/deterministic.rs +113 -0
- package/dist-engine-src/src/functions/mod.rs +18 -0
- package/dist-engine-src/src/functions/provider.rs +130 -0
- package/dist-engine-src/src/functions/state.rs +363 -0
- package/dist-engine-src/src/functions/types.rs +37 -0
- package/dist-engine-src/src/init.rs +505 -0
- package/dist-engine-src/src/json_store/compression.rs +77 -0
- package/dist-engine-src/src/json_store/context.rs +129 -0
- package/dist-engine-src/src/json_store/encoded.rs +15 -0
- package/dist-engine-src/src/json_store/mod.rs +9 -0
- package/dist-engine-src/src/json_store/store.rs +236 -0
- package/dist-engine-src/src/json_store/types.rs +52 -0
- package/dist-engine-src/src/lib.rs +61 -0
- package/dist-engine-src/src/live_state/context.rs +2241 -0
- package/dist-engine-src/src/live_state/mod.rs +15 -0
- package/dist-engine-src/src/live_state/overlay.rs +75 -0
- package/dist-engine-src/src/live_state/reader.rs +23 -0
- package/dist-engine-src/src/live_state/types.rs +239 -0
- package/dist-engine-src/src/live_state/visibility.rs +218 -0
- package/dist-engine-src/src/plugin/archive.rs +441 -0
- package/dist-engine-src/src/plugin/component.rs +183 -0
- package/dist-engine-src/src/plugin/install.rs +637 -0
- package/dist-engine-src/src/plugin/manifest.rs +516 -0
- package/dist-engine-src/src/plugin/materializer.rs +477 -0
- package/dist-engine-src/src/plugin/mod.rs +33 -0
- package/dist-engine-src/src/plugin/plugin_manifest.json +119 -0
- package/dist-engine-src/src/plugin/storage.rs +74 -0
- package/dist-engine-src/src/schema/annotations/defaults.rs +280 -0
- package/dist-engine-src/src/schema/annotations/mod.rs +1 -0
- package/dist-engine-src/src/schema/builtin/lix_account.json +22 -0
- package/dist-engine-src/src/schema/builtin/lix_active_account.json +30 -0
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +30 -0
- package/dist-engine-src/src/schema/builtin/lix_change.json +62 -0
- package/dist-engine-src/src/schema/builtin/lix_change_author.json +46 -0
- package/dist-engine-src/src/schema/builtin/lix_change_set.json +18 -0
- package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +75 -0
- package/dist-engine-src/src/schema/builtin/lix_commit.json +62 -0
- package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +46 -0
- package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +53 -0
- package/dist-engine-src/src/schema/builtin/lix_entity_label.json +63 -0
- package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +53 -0
- package/dist-engine-src/src/schema/builtin/lix_key_value.json +41 -0
- package/dist-engine-src/src/schema/builtin/lix_label.json +22 -0
- package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +31 -0
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +35 -0
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +49 -0
- package/dist-engine-src/src/schema/builtin/mod.rs +271 -0
- package/dist-engine-src/src/schema/definition.json +157 -0
- package/dist-engine-src/src/schema/definition.rs +636 -0
- package/dist-engine-src/src/schema/key.rs +206 -0
- package/dist-engine-src/src/schema/mod.rs +20 -0
- package/dist-engine-src/src/schema/seed.rs +14 -0
- package/dist-engine-src/src/schema/tests.rs +739 -0
- package/dist-engine-src/src/schema_registry.rs +294 -0
- package/dist-engine-src/src/session/context.rs +366 -0
- package/dist-engine-src/src/session/create_version.rs +80 -0
- package/dist-engine-src/src/session/execute.rs +447 -0
- package/dist-engine-src/src/session/merge/analysis.rs +102 -0
- package/dist-engine-src/src/session/merge/apply.rs +23 -0
- package/dist-engine-src/src/session/merge/conflicts.rs +62 -0
- package/dist-engine-src/src/session/merge/mod.rs +11 -0
- package/dist-engine-src/src/session/merge/stats.rs +65 -0
- package/dist-engine-src/src/session/merge/version.rs +437 -0
- package/dist-engine-src/src/session/mod.rs +25 -0
- package/dist-engine-src/src/session/switch_version.rs +121 -0
- package/dist-engine-src/src/sql2/change_provider.rs +337 -0
- package/dist-engine-src/src/sql2/classify.rs +147 -0
- package/dist-engine-src/src/sql2/commit_derived_provider.rs +591 -0
- package/dist-engine-src/src/sql2/context.rs +307 -0
- package/dist-engine-src/src/sql2/directory_history_provider.rs +623 -0
- package/dist-engine-src/src/sql2/directory_provider.rs +2405 -0
- package/dist-engine-src/src/sql2/dml.rs +148 -0
- package/dist-engine-src/src/sql2/entity_history_provider.rs +444 -0
- package/dist-engine-src/src/sql2/entity_provider.rs +2700 -0
- package/dist-engine-src/src/sql2/error.rs +196 -0
- package/dist-engine-src/src/sql2/execute.rs +3379 -0
- package/dist-engine-src/src/sql2/file_history_provider.rs +902 -0
- package/dist-engine-src/src/sql2/file_provider.rs +3254 -0
- package/dist-engine-src/src/sql2/filesystem_planner.rs +1526 -0
- package/dist-engine-src/src/sql2/filesystem_predicates.rs +159 -0
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +369 -0
- package/dist-engine-src/src/sql2/history_projection.rs +80 -0
- package/dist-engine-src/src/sql2/history_provider.rs +418 -0
- package/dist-engine-src/src/sql2/history_route.rs +643 -0
- package/dist-engine-src/src/sql2/lix_state_provider.rs +2430 -0
- package/dist-engine-src/src/sql2/mod.rs +43 -0
- package/dist-engine-src/src/sql2/read_only.rs +65 -0
- package/dist-engine-src/src/sql2/record_batch.rs +17 -0
- package/dist-engine-src/src/sql2/result_metadata.rs +29 -0
- package/dist-engine-src/src/sql2/runtime.rs +60 -0
- package/dist-engine-src/src/sql2/session.rs +135 -0
- package/dist-engine-src/src/sql2/udfs/common.rs +295 -0
- package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +53 -0
- package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +47 -0
- package/dist-engine-src/src/sql2/udfs/lix_json.rs +100 -0
- package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +99 -0
- package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +99 -0
- package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +82 -0
- package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +85 -0
- package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +76 -0
- package/dist-engine-src/src/sql2/udfs/mod.rs +82 -0
- package/dist-engine-src/src/sql2/version_provider.rs +1187 -0
- package/dist-engine-src/src/sql2/version_scope.rs +394 -0
- package/dist-engine-src/src/sql2/write_normalization.rs +345 -0
- package/dist-engine-src/src/storage/context.rs +356 -0
- package/dist-engine-src/src/storage/mod.rs +14 -0
- package/dist-engine-src/src/storage/read_scope.rs +88 -0
- package/dist-engine-src/src/storage/types.rs +501 -0
- package/dist-engine-src/src/storage_bench.rs +3406 -0
- package/dist-engine-src/src/test_support.rs +81 -0
- package/dist-engine-src/src/tracked_state/by_file_index.rs +102 -0
- package/dist-engine-src/src/tracked_state/codec.rs +747 -0
- package/dist-engine-src/src/tracked_state/context.rs +983 -0
- package/dist-engine-src/src/tracked_state/diff.rs +494 -0
- package/dist-engine-src/src/tracked_state/materialization.rs +141 -0
- package/dist-engine-src/src/tracked_state/merge.rs +474 -0
- package/dist-engine-src/src/tracked_state/mod.rs +31 -0
- package/dist-engine-src/src/tracked_state/rebuild.rs +771 -0
- package/dist-engine-src/src/tracked_state/storage.rs +243 -0
- package/dist-engine-src/src/tracked_state/tree.rs +2744 -0
- package/dist-engine-src/src/tracked_state/tree_types.rs +176 -0
- package/dist-engine-src/src/tracked_state/types.rs +61 -0
- package/dist-engine-src/src/transaction/commit.rs +1224 -0
- package/dist-engine-src/src/transaction/context.rs +1307 -0
- package/dist-engine-src/src/transaction/live_state_overlay.rs +34 -0
- package/dist-engine-src/src/transaction/mod.rs +11 -0
- package/dist-engine-src/src/transaction/normalization.rs +1026 -0
- package/dist-engine-src/src/transaction/schema_resolver.rs +127 -0
- package/dist-engine-src/src/transaction/staging.rs +1436 -0
- package/dist-engine-src/src/transaction/types.rs +351 -0
- package/dist-engine-src/src/transaction/validation.rs +4811 -0
- package/dist-engine-src/src/untracked_state/codec.rs +363 -0
- package/dist-engine-src/src/untracked_state/context.rs +82 -0
- package/dist-engine-src/src/untracked_state/materialization.rs +157 -0
- package/dist-engine-src/src/untracked_state/mod.rs +17 -0
- package/dist-engine-src/src/untracked_state/storage.rs +348 -0
- package/dist-engine-src/src/untracked_state/types.rs +96 -0
- package/dist-engine-src/src/version/context.rs +52 -0
- package/dist-engine-src/src/version/mod.rs +12 -0
- package/dist-engine-src/src/version/refs.rs +421 -0
- package/dist-engine-src/src/version/stage_rows.rs +71 -0
- package/dist-engine-src/src/version/types.rs +21 -0
- package/dist-engine-src/src/wasm/mod.rs +60 -0
- package/package.json +68 -63
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
use std::any::Any;
|
|
2
|
+
|
|
3
|
+
use datafusion::arrow::datatypes::DataType;
|
|
4
|
+
use datafusion::common::{Result, ScalarValue};
|
|
5
|
+
use datafusion::logical_expr::{
|
|
6
|
+
ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
10
|
+
pub(super) struct LixEmptyBlob;
|
|
11
|
+
|
|
12
|
+
impl ScalarUDFImpl for LixEmptyBlob {
|
|
13
|
+
fn as_any(&self) -> &dyn Any {
|
|
14
|
+
self
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fn name(&self) -> &str {
|
|
18
|
+
"lix_empty_blob"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fn signature(&self) -> &Signature {
|
|
22
|
+
static SIGNATURE: std::sync::LazyLock<Signature> =
|
|
23
|
+
std::sync::LazyLock::new(|| Signature::nullary(Volatility::Immutable));
|
|
24
|
+
&SIGNATURE
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
|
|
28
|
+
Ok(DataType::Binary)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
|
|
32
|
+
Ok(ColumnarValue::Scalar(ScalarValue::Binary(Some(Vec::new()))))
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#[cfg(test)]
|
|
37
|
+
mod tests {
|
|
38
|
+
use super::super::test_support::single_binary;
|
|
39
|
+
|
|
40
|
+
#[tokio::test]
|
|
41
|
+
async fn returns_empty_binary_value() {
|
|
42
|
+
assert_eq!(
|
|
43
|
+
single_binary("SELECT lix_empty_blob()").await,
|
|
44
|
+
Some(Vec::new())
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
use std::any::Any;
|
|
2
|
+
use std::sync::Arc;
|
|
3
|
+
|
|
4
|
+
use datafusion::arrow::array::{Array, StringArray};
|
|
5
|
+
use datafusion::arrow::datatypes::{DataType, FieldRef};
|
|
6
|
+
use datafusion::common::{plan_err, DataFusionError, Result, ScalarValue};
|
|
7
|
+
use datafusion::logical_expr::{
|
|
8
|
+
ColumnarValue, ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility,
|
|
9
|
+
};
|
|
10
|
+
use serde_json::Value as JsonValue;
|
|
11
|
+
|
|
12
|
+
use crate::sql2::result_metadata::json_field;
|
|
13
|
+
|
|
14
|
+
use super::common::{scalar_inputs, text_like_value};
|
|
15
|
+
|
|
16
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
17
|
+
pub(super) struct LixJson;
|
|
18
|
+
|
|
19
|
+
impl ScalarUDFImpl for LixJson {
|
|
20
|
+
fn as_any(&self) -> &dyn Any {
|
|
21
|
+
self
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fn name(&self) -> &str {
|
|
25
|
+
"lix_json"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fn signature(&self) -> &Signature {
|
|
29
|
+
static SIGNATURE: std::sync::LazyLock<Signature> =
|
|
30
|
+
std::sync::LazyLock::new(|| Signature::any(1, Volatility::Immutable));
|
|
31
|
+
&SIGNATURE
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
|
|
35
|
+
Ok(DataType::Utf8)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fn return_field_from_args(&self, _args: ReturnFieldArgs) -> Result<FieldRef> {
|
|
39
|
+
Ok(Arc::new(json_field(self.name(), true)))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
|
|
43
|
+
if args.args.len() != 1 {
|
|
44
|
+
return plan_err!("lix_json requires exactly 1 argument");
|
|
45
|
+
}
|
|
46
|
+
let scalar_inputs = scalar_inputs(&args.args);
|
|
47
|
+
let arrays = ColumnarValue::values_to_arrays(&args.args)?;
|
|
48
|
+
let input = &arrays[0];
|
|
49
|
+
let len = input.len();
|
|
50
|
+
let mut values = Vec::with_capacity(len);
|
|
51
|
+
for row in 0..len {
|
|
52
|
+
values.push(json_value(input.as_ref(), row)?);
|
|
53
|
+
}
|
|
54
|
+
if scalar_inputs {
|
|
55
|
+
Ok(ColumnarValue::Scalar(ScalarValue::Utf8(
|
|
56
|
+
values.into_iter().next().flatten(),
|
|
57
|
+
)))
|
|
58
|
+
} else {
|
|
59
|
+
Ok(ColumnarValue::Array(Arc::new(StringArray::from(values))))
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fn json_value(array: &dyn Array, row: usize) -> Result<Option<String>> {
|
|
65
|
+
if matches!(array.data_type(), DataType::Null) {
|
|
66
|
+
return Ok(Some("null".to_string()));
|
|
67
|
+
}
|
|
68
|
+
let Some(raw) = text_like_value(array, row)? else {
|
|
69
|
+
return Ok(Some("null".to_string()));
|
|
70
|
+
};
|
|
71
|
+
let parsed = serde_json::from_str::<JsonValue>(&raw).map_err(|error| {
|
|
72
|
+
DataFusionError::Execution(format!(
|
|
73
|
+
"lix_json() expected valid JSON text, got error: {error}"
|
|
74
|
+
))
|
|
75
|
+
})?;
|
|
76
|
+
Ok(Some(serde_json::to_string(&parsed).map_err(|error| {
|
|
77
|
+
DataFusionError::Execution(format!("lix_json() could not render JSON: {error}"))
|
|
78
|
+
})?))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#[cfg(test)]
|
|
82
|
+
mod tests {
|
|
83
|
+
use super::super::test_support::single_text;
|
|
84
|
+
|
|
85
|
+
#[tokio::test]
|
|
86
|
+
async fn canonicalizes_json_text() {
|
|
87
|
+
assert_eq!(
|
|
88
|
+
single_text("SELECT lix_json('{ \"name\" : \"Ada\" }')").await,
|
|
89
|
+
Some("{\"name\":\"Ada\"}".to_string())
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#[tokio::test]
|
|
94
|
+
async fn null_input_returns_json_null() {
|
|
95
|
+
assert_eq!(
|
|
96
|
+
single_text("SELECT lix_json(NULL)").await,
|
|
97
|
+
Some("null".to_string())
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
use std::any::Any;
|
|
2
|
+
use std::sync::Arc;
|
|
3
|
+
|
|
4
|
+
use datafusion::arrow::array::StringArray;
|
|
5
|
+
use datafusion::arrow::datatypes::{DataType, FieldRef};
|
|
6
|
+
use datafusion::common::{plan_err, Result, ScalarValue};
|
|
7
|
+
use datafusion::logical_expr::{
|
|
8
|
+
ColumnarValue, ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility,
|
|
9
|
+
};
|
|
10
|
+
use serde_json::Value as JsonValue;
|
|
11
|
+
|
|
12
|
+
use crate::sql2::result_metadata::json_field;
|
|
13
|
+
|
|
14
|
+
use super::common::{extract_json_path, json_json_value, scalar_inputs};
|
|
15
|
+
|
|
16
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
17
|
+
pub(super) struct LixJsonGet {
|
|
18
|
+
signature: Signature,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl LixJsonGet {
|
|
22
|
+
pub(super) fn new() -> Self {
|
|
23
|
+
Self {
|
|
24
|
+
signature: Signature::variadic_any(Volatility::Immutable),
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl ScalarUDFImpl for LixJsonGet {
|
|
30
|
+
fn as_any(&self) -> &dyn Any {
|
|
31
|
+
self
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fn name(&self) -> &str {
|
|
35
|
+
"lix_json_get"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fn signature(&self) -> &Signature {
|
|
39
|
+
&self.signature
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
|
|
43
|
+
Ok(DataType::Utf8)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fn return_field_from_args(&self, _args: ReturnFieldArgs) -> Result<FieldRef> {
|
|
47
|
+
Ok(Arc::new(json_field(self.name(), true)))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
|
|
51
|
+
if args.args.len() < 2 {
|
|
52
|
+
return plan_err!("lix_json_get requires at least 2 arguments");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let scalar_inputs = scalar_inputs(&args.args);
|
|
56
|
+
let arrays = ColumnarValue::values_to_arrays(&args.args)?;
|
|
57
|
+
let len = arrays.first().map(|array| array.len()).unwrap_or(1);
|
|
58
|
+
|
|
59
|
+
let mut values = Vec::with_capacity(len);
|
|
60
|
+
for row in 0..len {
|
|
61
|
+
values.push(match extract_json_path(self.name(), &arrays, row)? {
|
|
62
|
+
None | Some(JsonValue::Null) => None,
|
|
63
|
+
Some(other) => Some(json_json_value(&other)?),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if scalar_inputs {
|
|
67
|
+
Ok(ColumnarValue::Scalar(ScalarValue::Utf8(
|
|
68
|
+
values.into_iter().next().flatten(),
|
|
69
|
+
)))
|
|
70
|
+
} else {
|
|
71
|
+
Ok(ColumnarValue::Array(Arc::new(StringArray::from(values))))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[cfg(test)]
|
|
77
|
+
mod tests {
|
|
78
|
+
use super::super::test_support::single_text;
|
|
79
|
+
|
|
80
|
+
#[tokio::test]
|
|
81
|
+
async fn returns_json_representation() {
|
|
82
|
+
assert_eq!(
|
|
83
|
+
single_text("SELECT lix_json_get('{\"name\":\"Ada\"}', 'name')").await,
|
|
84
|
+
Some("\"Ada\"".to_string())
|
|
85
|
+
);
|
|
86
|
+
assert_eq!(
|
|
87
|
+
single_text("SELECT lix_json_get('{\"tags\":[\"db\"]}', 'tags')").await,
|
|
88
|
+
Some("[\"db\"]".to_string())
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#[tokio::test]
|
|
93
|
+
async fn missing_path_returns_null() {
|
|
94
|
+
assert_eq!(
|
|
95
|
+
single_text("SELECT lix_json_get('{\"name\":\"Ada\"}', 'missing')").await,
|
|
96
|
+
None
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
use std::any::Any;
|
|
2
|
+
use std::sync::Arc;
|
|
3
|
+
|
|
4
|
+
use datafusion::arrow::array::StringArray;
|
|
5
|
+
use datafusion::arrow::datatypes::DataType;
|
|
6
|
+
use datafusion::common::{plan_err, Result, ScalarValue};
|
|
7
|
+
use datafusion::logical_expr::{
|
|
8
|
+
ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility,
|
|
9
|
+
};
|
|
10
|
+
use serde_json::Value as JsonValue;
|
|
11
|
+
|
|
12
|
+
use super::common::{extract_json_path, json_text_value, scalar_inputs};
|
|
13
|
+
|
|
14
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
15
|
+
pub(super) struct LixJsonGetText {
|
|
16
|
+
signature: Signature,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl LixJsonGetText {
|
|
20
|
+
pub(super) fn new() -> Self {
|
|
21
|
+
Self {
|
|
22
|
+
signature: Signature::variadic_any(Volatility::Immutable),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
impl ScalarUDFImpl for LixJsonGetText {
|
|
28
|
+
fn as_any(&self) -> &dyn Any {
|
|
29
|
+
self
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn name(&self) -> &str {
|
|
33
|
+
"lix_json_get_text"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fn signature(&self) -> &Signature {
|
|
37
|
+
&self.signature
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
|
|
41
|
+
Ok(DataType::Utf8)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
|
|
45
|
+
if args.args.len() < 2 {
|
|
46
|
+
return plan_err!("lix_json_get_text requires at least 2 arguments");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let scalar_inputs = scalar_inputs(&args.args);
|
|
50
|
+
let arrays = ColumnarValue::values_to_arrays(&args.args)?;
|
|
51
|
+
let len = arrays.first().map(|array| array.len()).unwrap_or(1);
|
|
52
|
+
|
|
53
|
+
let mut values = Vec::with_capacity(len);
|
|
54
|
+
for row in 0..len {
|
|
55
|
+
values.push(match extract_json_path(self.name(), &arrays, row)? {
|
|
56
|
+
None | Some(JsonValue::Null) => None,
|
|
57
|
+
Some(JsonValue::Bool(value)) => Some(if value {
|
|
58
|
+
"true".to_string()
|
|
59
|
+
} else {
|
|
60
|
+
"false".to_string()
|
|
61
|
+
}),
|
|
62
|
+
Some(JsonValue::String(value)) => Some(value),
|
|
63
|
+
Some(other) => Some(json_text_value(&other)?),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if scalar_inputs {
|
|
67
|
+
Ok(ColumnarValue::Scalar(ScalarValue::Utf8(
|
|
68
|
+
values.into_iter().next().flatten(),
|
|
69
|
+
)))
|
|
70
|
+
} else {
|
|
71
|
+
Ok(ColumnarValue::Array(Arc::new(StringArray::from(values))))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[cfg(test)]
|
|
77
|
+
mod tests {
|
|
78
|
+
use super::super::test_support::single_text;
|
|
79
|
+
|
|
80
|
+
#[tokio::test]
|
|
81
|
+
async fn returns_unwrapped_text() {
|
|
82
|
+
assert_eq!(
|
|
83
|
+
single_text("SELECT lix_json_get_text('{\"name\":\"Ada\"}', 'name')").await,
|
|
84
|
+
Some("Ada".to_string())
|
|
85
|
+
);
|
|
86
|
+
assert_eq!(
|
|
87
|
+
single_text("SELECT lix_json_get_text('{\"active\":true}', 'active')").await,
|
|
88
|
+
Some("true".to_string())
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#[tokio::test]
|
|
93
|
+
async fn missing_path_returns_null() {
|
|
94
|
+
assert_eq!(
|
|
95
|
+
single_text("SELECT lix_json_get_text('{\"name\":\"Ada\"}', 'missing')").await,
|
|
96
|
+
None
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
use std::any::Any;
|
|
2
|
+
use std::sync::Arc;
|
|
3
|
+
|
|
4
|
+
use datafusion::arrow::array::StringArray;
|
|
5
|
+
use datafusion::arrow::datatypes::DataType;
|
|
6
|
+
use datafusion::common::{plan_err, Result, ScalarValue};
|
|
7
|
+
use datafusion::logical_expr::{
|
|
8
|
+
ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
use super::common::{decode_utf8_value, scalar_inputs, validate_utf8_encoding_arg};
|
|
12
|
+
|
|
13
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
14
|
+
pub(super) struct LixTextDecode {
|
|
15
|
+
signature: Signature,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
impl LixTextDecode {
|
|
19
|
+
pub(super) fn new() -> Self {
|
|
20
|
+
Self {
|
|
21
|
+
signature: Signature::one_of(
|
|
22
|
+
vec![Signature::any(1, Volatility::Immutable).type_signature],
|
|
23
|
+
Volatility::Immutable,
|
|
24
|
+
),
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl ScalarUDFImpl for LixTextDecode {
|
|
30
|
+
fn as_any(&self) -> &dyn Any {
|
|
31
|
+
self
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fn name(&self) -> &str {
|
|
35
|
+
"lix_text_decode"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fn signature(&self) -> &Signature {
|
|
39
|
+
&self.signature
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
|
|
43
|
+
Ok(DataType::Utf8)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
|
|
47
|
+
if !(1..=2).contains(&args.args.len()) {
|
|
48
|
+
return plan_err!("lix_text_decode requires 1 or 2 arguments");
|
|
49
|
+
}
|
|
50
|
+
validate_utf8_encoding_arg(self.name(), args.args.get(1))?;
|
|
51
|
+
|
|
52
|
+
let scalar_inputs = scalar_inputs(&args.args);
|
|
53
|
+
let arrays = ColumnarValue::values_to_arrays(&args.args)?;
|
|
54
|
+
let input = &arrays[0];
|
|
55
|
+
let len = input.len();
|
|
56
|
+
|
|
57
|
+
let mut values = Vec::with_capacity(len);
|
|
58
|
+
for row in 0..len {
|
|
59
|
+
values.push(decode_utf8_value(input.as_ref(), row)?);
|
|
60
|
+
}
|
|
61
|
+
if scalar_inputs {
|
|
62
|
+
Ok(ColumnarValue::Scalar(ScalarValue::Utf8(
|
|
63
|
+
values.into_iter().next().flatten(),
|
|
64
|
+
)))
|
|
65
|
+
} else {
|
|
66
|
+
Ok(ColumnarValue::Array(Arc::new(StringArray::from(values))))
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[cfg(test)]
|
|
72
|
+
mod tests {
|
|
73
|
+
use super::super::test_support::single_text;
|
|
74
|
+
|
|
75
|
+
#[tokio::test]
|
|
76
|
+
async fn decodes_utf8_binary_to_text() {
|
|
77
|
+
assert_eq!(
|
|
78
|
+
single_text("SELECT lix_text_decode(X'416461')").await,
|
|
79
|
+
Some("Ada".to_string())
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
use std::any::Any;
|
|
2
|
+
|
|
3
|
+
use datafusion::arrow::datatypes::DataType;
|
|
4
|
+
use datafusion::common::{plan_err, Result, ScalarValue};
|
|
5
|
+
use datafusion::logical_expr::{
|
|
6
|
+
ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
use super::common::{
|
|
10
|
+
array_ref, binary_array_from_owned, encode_utf8_value, scalar_inputs,
|
|
11
|
+
validate_utf8_encoding_arg,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
15
|
+
pub(super) struct LixTextEncode {
|
|
16
|
+
signature: Signature,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl LixTextEncode {
|
|
20
|
+
pub(super) fn new() -> Self {
|
|
21
|
+
Self {
|
|
22
|
+
signature: Signature::one_of(
|
|
23
|
+
vec![Signature::any(1, Volatility::Immutable).type_signature],
|
|
24
|
+
Volatility::Immutable,
|
|
25
|
+
),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
impl ScalarUDFImpl for LixTextEncode {
|
|
31
|
+
fn as_any(&self) -> &dyn Any {
|
|
32
|
+
self
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn name(&self) -> &str {
|
|
36
|
+
"lix_text_encode"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn signature(&self) -> &Signature {
|
|
40
|
+
&self.signature
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
|
|
44
|
+
Ok(DataType::Binary)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
|
|
48
|
+
if !(1..=2).contains(&args.args.len()) {
|
|
49
|
+
return plan_err!("lix_text_encode requires 1 or 2 arguments");
|
|
50
|
+
}
|
|
51
|
+
validate_utf8_encoding_arg(self.name(), args.args.get(1))?;
|
|
52
|
+
|
|
53
|
+
let scalar_inputs = scalar_inputs(&args.args);
|
|
54
|
+
let arrays = ColumnarValue::values_to_arrays(&args.args)?;
|
|
55
|
+
let input = &arrays[0];
|
|
56
|
+
let len = input.len();
|
|
57
|
+
|
|
58
|
+
let mut values = Vec::with_capacity(len);
|
|
59
|
+
for row in 0..len {
|
|
60
|
+
values.push(encode_utf8_value(input.as_ref(), row)?);
|
|
61
|
+
}
|
|
62
|
+
if scalar_inputs {
|
|
63
|
+
Ok(ColumnarValue::Scalar(ScalarValue::Binary(
|
|
64
|
+
values.into_iter().next().flatten(),
|
|
65
|
+
)))
|
|
66
|
+
} else {
|
|
67
|
+
Ok(ColumnarValue::Array(array_ref(binary_array_from_owned(
|
|
68
|
+
&values,
|
|
69
|
+
))))
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#[cfg(test)]
|
|
75
|
+
mod tests {
|
|
76
|
+
use super::super::test_support::single_binary;
|
|
77
|
+
|
|
78
|
+
#[tokio::test]
|
|
79
|
+
async fn encodes_utf8_text_to_binary() {
|
|
80
|
+
assert_eq!(
|
|
81
|
+
single_binary("SELECT lix_text_encode('Ada')").await,
|
|
82
|
+
Some(b"Ada".to_vec())
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
use std::any::Any;
|
|
2
|
+
|
|
3
|
+
use datafusion::arrow::datatypes::DataType;
|
|
4
|
+
use datafusion::common::{plan_err, Result, ScalarValue};
|
|
5
|
+
use datafusion::logical_expr::{
|
|
6
|
+
ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
use crate::functions::FunctionProviderHandle;
|
|
10
|
+
|
|
11
|
+
#[derive(Clone)]
|
|
12
|
+
pub(super) struct LixUuidV7 {
|
|
13
|
+
pub(super) functions: FunctionProviderHandle,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
impl PartialEq for LixUuidV7 {
|
|
17
|
+
fn eq(&self, _other: &Self) -> bool {
|
|
18
|
+
true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl Eq for LixUuidV7 {}
|
|
23
|
+
|
|
24
|
+
impl std::hash::Hash for LixUuidV7 {
|
|
25
|
+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
26
|
+
self.name().hash(state);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
impl std::fmt::Debug for LixUuidV7 {
|
|
31
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
32
|
+
f.debug_struct("LixUuidV7").finish()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
impl ScalarUDFImpl for LixUuidV7 {
|
|
37
|
+
fn as_any(&self) -> &dyn Any {
|
|
38
|
+
self
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fn name(&self) -> &str {
|
|
42
|
+
"lix_uuid_v7"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fn signature(&self) -> &Signature {
|
|
46
|
+
static SIGNATURE: std::sync::LazyLock<Signature> =
|
|
47
|
+
std::sync::LazyLock::new(|| Signature::nullary(Volatility::Volatile));
|
|
48
|
+
&SIGNATURE
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
|
|
52
|
+
Ok(DataType::Utf8)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
|
|
56
|
+
if !args.args.is_empty() {
|
|
57
|
+
return plan_err!("lix_uuid_v7 requires no arguments");
|
|
58
|
+
}
|
|
59
|
+
Ok(ColumnarValue::Scalar(ScalarValue::Utf8(Some(
|
|
60
|
+
self.functions.call_uuid_v7(),
|
|
61
|
+
))))
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#[cfg(test)]
|
|
66
|
+
mod tests {
|
|
67
|
+
use super::super::test_support::single_text;
|
|
68
|
+
|
|
69
|
+
#[tokio::test]
|
|
70
|
+
async fn returns_uuid_text() {
|
|
71
|
+
let value = single_text("SELECT lix_uuid_v7()")
|
|
72
|
+
.await
|
|
73
|
+
.expect("uuid should not be null");
|
|
74
|
+
assert!(!value.is_empty());
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
mod common;
|
|
2
|
+
mod lix_active_version_commit_id;
|
|
3
|
+
mod lix_empty_blob;
|
|
4
|
+
mod lix_json;
|
|
5
|
+
mod lix_json_get;
|
|
6
|
+
mod lix_json_get_text;
|
|
7
|
+
mod lix_text_decode;
|
|
8
|
+
mod lix_text_encode;
|
|
9
|
+
mod lix_uuid_v7;
|
|
10
|
+
|
|
11
|
+
use datafusion::execution::context::SessionContext;
|
|
12
|
+
use datafusion::logical_expr::ScalarUDF;
|
|
13
|
+
|
|
14
|
+
use crate::functions::FunctionProviderHandle;
|
|
15
|
+
|
|
16
|
+
#[cfg(test)]
|
|
17
|
+
pub(crate) fn system_sql2_function_provider() -> FunctionProviderHandle {
|
|
18
|
+
use crate::functions::{FunctionProvider, SharedFunctionProvider, SystemFunctionProvider};
|
|
19
|
+
|
|
20
|
+
SharedFunctionProvider::new(Box::new(SystemFunctionProvider) as Box<dyn FunctionProvider + Send>)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub(crate) fn register_sql2_functions(
|
|
24
|
+
ctx: &SessionContext,
|
|
25
|
+
functions: FunctionProviderHandle,
|
|
26
|
+
active_version_commit_id: Option<String>,
|
|
27
|
+
) {
|
|
28
|
+
ctx.register_udf(ScalarUDF::from(
|
|
29
|
+
lix_active_version_commit_id::LixActiveVersionCommitId::new(active_version_commit_id),
|
|
30
|
+
));
|
|
31
|
+
ctx.register_udf(ScalarUDF::from(lix_json_get::LixJsonGet::new()));
|
|
32
|
+
ctx.register_udf(ScalarUDF::from(lix_json_get_text::LixJsonGetText::new()));
|
|
33
|
+
ctx.register_udf(ScalarUDF::from(lix_text_decode::LixTextDecode::new()));
|
|
34
|
+
ctx.register_udf(ScalarUDF::from(lix_text_encode::LixTextEncode::new()));
|
|
35
|
+
ctx.register_udf(ScalarUDF::from(lix_json::LixJson));
|
|
36
|
+
ctx.register_udf(ScalarUDF::from(lix_empty_blob::LixEmptyBlob));
|
|
37
|
+
ctx.register_udf(ScalarUDF::from(lix_uuid_v7::LixUuidV7 { functions }));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#[cfg(test)]
|
|
41
|
+
pub(super) mod test_support {
|
|
42
|
+
use datafusion::arrow::array::{Array, BinaryArray, StringArray};
|
|
43
|
+
use datafusion::prelude::SessionContext;
|
|
44
|
+
|
|
45
|
+
use super::{register_sql2_functions, system_sql2_function_provider};
|
|
46
|
+
|
|
47
|
+
pub(super) async fn single_text(sql: &str) -> Option<String> {
|
|
48
|
+
let ctx = SessionContext::new();
|
|
49
|
+
register_sql2_functions(&ctx, system_sql2_function_provider(), None);
|
|
50
|
+
let batches = ctx
|
|
51
|
+
.sql(sql)
|
|
52
|
+
.await
|
|
53
|
+
.expect("query should plan")
|
|
54
|
+
.collect()
|
|
55
|
+
.await
|
|
56
|
+
.expect("query should execute");
|
|
57
|
+
let array = batches[0]
|
|
58
|
+
.column(0)
|
|
59
|
+
.as_any()
|
|
60
|
+
.downcast_ref::<StringArray>()
|
|
61
|
+
.expect("first column should be utf8");
|
|
62
|
+
(!array.is_null(0)).then(|| array.value(0).to_string())
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
pub(super) async fn single_binary(sql: &str) -> Option<Vec<u8>> {
|
|
66
|
+
let ctx = SessionContext::new();
|
|
67
|
+
register_sql2_functions(&ctx, system_sql2_function_provider(), None);
|
|
68
|
+
let batches = ctx
|
|
69
|
+
.sql(sql)
|
|
70
|
+
.await
|
|
71
|
+
.expect("query should plan")
|
|
72
|
+
.collect()
|
|
73
|
+
.await
|
|
74
|
+
.expect("query should execute");
|
|
75
|
+
let array = batches[0]
|
|
76
|
+
.column(0)
|
|
77
|
+
.as_any()
|
|
78
|
+
.downcast_ref::<BinaryArray>()
|
|
79
|
+
.expect("first column should be binary");
|
|
80
|
+
(!array.is_null(0)).then(|| array.value(0).to_vec())
|
|
81
|
+
}
|
|
82
|
+
}
|