Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add external/iceberg and construct_struct expression
Browse files Browse the repository at this point in the history
ZENOTME committed Jan 10, 2024
1 parent f3b0650 commit 6d55b5c
Showing 13 changed files with 702 additions and 171 deletions.
292 changes: 131 additions & 161 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -120,7 +120,7 @@ tonic = { package = "madsim-tonic", version = "0.4.1" }
tonic-build = { package = "madsim-tonic-build", version = "0.4.2" }
otlp-embedded = { git = "https://github.com/risingwavelabs/otlp-embedded", rev = "58c1f003484449d7c6dd693b348bf19dd44889cb" }
prost = { version = "0.12" }
icelake = { git = "https://github.com/icelake-io/icelake", rev = "3f7b53ba5b563524212c25810345d1314678e7fc", features = [
icelake = { git = "https://github.com/icelake-io/icelake", rev = "cc27aa20d34b1d252bd7b8aaadee64c34140e687", features = [
"prometheus",
] }
arrow-array = "49"
10 changes: 10 additions & 0 deletions proto/expr.proto
Original file line number Diff line number Diff line change
@@ -259,6 +259,8 @@ message ExprNode {
JSONB_PATH_QUERY_ARRAY = 622;
JSONB_PATH_QUERY_FIRST = 623;

CONSTRUCT_STRUCT = 624;

// Non-pure functions below (> 1000)
// ------------------------
// Internal functions
@@ -275,6 +277,14 @@ message ExprNode {
PG_GET_INDEXDEF = 2400;
COL_DESCRIPTION = 2401;
PG_GET_VIEWDEF = 2402;

// EXTERNAL
ICEBERG_BUCKET = 2201;
ICEBERG_TRUNCATE = 2202;
ICEBERG_YEAR = 2203;
ICEBERG_MONTH = 2204;
ICEBERG_DAY = 2205;
ICEBERG_HOUR = 2206;
}
Type function_type = 1;
data.DataType return_type = 3;
1 change: 1 addition & 0 deletions src/expr/core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@ either = "1"
enum-as-inner = "0.6"
futures-async-stream = { workspace = true }
futures-util = "0.3"
icelake = { workspace = true }
itertools = "0.12"
num-traits = "0.2"
parse-display = "0.8"
16 changes: 14 additions & 2 deletions src/expr/core/src/expr/build.rs
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ use super::wrapper::non_strict::NonStrict;
use super::wrapper::EvalErrorReport;
use super::NonStrictExpression;
use crate::expr::{
BoxedExpression, Expression, ExpressionBoxExt, InputRefExpression, LiteralExpression,
external, BoxedExpression, Expression, ExpressionBoxExt, InputRefExpression, LiteralExpression,
};
use crate::sig::FUNCTION_REGISTRY;
use crate::{bail, ExprError, Result};
@@ -110,6 +110,16 @@ where
// Dedicated types
E::All | E::Some => SomeAllExpression::build_boxed(prost, build_child),

// Iceberg partition transform functions
// # TODO
// Move to general types
E::IcebergBucket => external::iceberg::Bucket::build_boxed(prost, build_child),
E::IcebergTruncate => external::iceberg::Truncate::build_boxed(prost, build_child),
E::IcebergYear => external::iceberg::Year::build_boxed(prost, build_child),
E::IcebergMonth => external::iceberg::Month::build_boxed(prost, build_child),
E::IcebergDay => external::iceberg::Day::build_boxed(prost, build_child),
E::IcebergHour => external::iceberg::Hour::build_boxed(prost, build_child),

// General types, lookup in the function signature map
_ => FuncCallBuilder::build_boxed(prost, build_child),
},
@@ -216,7 +226,9 @@ pub fn build_func_non_strict(
Ok(wrapped)
}

pub(super) fn get_children_and_return_type(prost: &ExprNode) -> Result<(&[ExprNode], DataType)> {
pub fn get_children_and_return_type_for_func_call(
prost: &ExprNode,
) -> Result<(&[ExprNode], DataType)> {
let ret_type = DataType::from(prost.get_return_type().unwrap());
if let RexNode::FuncCall(func_call) = prost.get_rex_node().unwrap() {
Ok((func_call.get_children(), ret_type))
10 changes: 6 additions & 4 deletions src/expr/core/src/expr/expr_some_all.rs
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ use risingwave_common::{bail, ensure};
use risingwave_pb::expr::expr_node::{RexNode, Type};
use risingwave_pb::expr::{ExprNode, FunctionCall};

use super::build::get_children_and_return_type;
use super::build::get_children_and_return_type_for_func_call;
use super::{BoxedExpression, Build, Expression};
use crate::Result;

@@ -211,17 +211,19 @@ impl Build for SomeAllExpression {
build_child: impl Fn(&ExprNode) -> Result<BoxedExpression>,
) -> Result<Self> {
let outer_expr_type = prost.get_function_type().unwrap();
let (outer_children, outer_return_type) = get_children_and_return_type(prost)?;
let (outer_children, outer_return_type) =
get_children_and_return_type_for_func_call(prost)?;
ensure!(matches!(outer_return_type, DataType::Boolean));

let mut inner_expr_type = outer_children[0].get_function_type().unwrap();
let (mut inner_children, mut inner_return_type) =
get_children_and_return_type(&outer_children[0])?;
get_children_and_return_type_for_func_call(&outer_children[0])?;
let mut stack = vec![];
while inner_children.len() != 2 {
stack.push((inner_expr_type, inner_return_type));
inner_expr_type = inner_children[0].get_function_type().unwrap();
(inner_children, inner_return_type) = get_children_and_return_type(&inner_children[0])?;
(inner_children, inner_return_type) =
get_children_and_return_type_for_func_call(&inner_children[0])?;
}

let left_expr = build_child(&inner_children[0])?;
399 changes: 399 additions & 0 deletions src/expr/core/src/expr/external/iceberg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,399 @@
// Copyright 2024 RisingWave Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fmt::{Debug, Formatter};
use std::sync::Arc;

use icelake::types::{
Bucket as BucketTransform, Day as DayTransform, Hour as HourTransform, Month as MonthTransform,
TransformFunction, Truncate as TruncateTransform, Year as YearTransform,
};
use risingwave_common::array::{ArrayRef, DataChunk};
use risingwave_common::ensure;
use risingwave_common::row::OwnedRow;
use risingwave_common::types::{DataType, Datum};
use risingwave_expr::expr::{get_children_and_return_type_for_func_call, BoxedExpression, Build};
use risingwave_expr::Result;
use risingwave_pb::expr::ExprNode;

/// This module contains the iceberg expression for computing the partition value.
/// spec ref: <https://iceberg.apache.org/spec/#partition-transforms>
pub struct Bucket {
child: BoxedExpression,
n: i32,
transform: BucketTransform,
}

impl Debug for Bucket {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Iceberg_Bucket({})", self.n)
}
}

#[async_trait::async_trait]
impl risingwave_expr::expr::Expression for Bucket {
fn return_type(&self) -> DataType {
DataType::Int32
}

async fn eval(&self, data_chunk: &DataChunk) -> Result<ArrayRef> {
// Get the child array
let array = self.child.eval(data_chunk).await?;
// Convert to arrow array
let arrow_array = array.as_ref().try_into().unwrap();
// Transform
let res_array = self.transform.transform(arrow_array).unwrap();
// Convert back to array ref and return it
Ok(Arc::new((&res_array).try_into().unwrap()))
}

async fn eval_row(&self, _row: &OwnedRow) -> Result<Datum> {
unimplemented!()
}
}

impl Build for Bucket {
fn build(
prost: &ExprNode,
build_child: impl Fn(&ExprNode) -> Result<BoxedExpression>,
) -> Result<Self> {
let (children, res_type) = get_children_and_return_type_for_func_call(prost)?;

// Check expression
ensure!(children.len() == 2);
ensure!(matches!(
children[0].get_return_type().unwrap().into(),
DataType::Int32
| DataType::Int64
| DataType::Decimal
| DataType::Date
| DataType::Time
| DataType::Timestamp
| DataType::Timestamptz
| DataType::Varchar
| DataType::Bytea
));
ensure!(DataType::Int32 == children[1].get_return_type().unwrap().into());
ensure!(res_type == DataType::Int32);

// Get the second child as const param
let literal = build_child(&children[1])?;
let n = *literal.eval_const()?.unwrap().as_int32();

// Build the child
let child = build_child(&children[0])?;
Ok(Bucket {
child,
n,
transform: BucketTransform::new(n),
})
}
}

pub struct Truncate {
child: BoxedExpression,
w: i32,
transform: TruncateTransform,
}

impl Debug for Truncate {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Iceberg_Truncate({})", self.w)
}
}

#[async_trait::async_trait]
impl risingwave_expr::expr::Expression for Truncate {
fn return_type(&self) -> DataType {
self.child.return_type()
}

async fn eval(&self, data_chunk: &DataChunk) -> Result<ArrayRef> {
// Get the child array
let array = self.child.eval(data_chunk).await?;
// Convert to arrow array
let arrow_array = array.as_ref().try_into().unwrap();
// Transform
let res_array = self.transform.transform(arrow_array).unwrap();
// Convert back to array ref and return it
Ok(Arc::new((&res_array).try_into().unwrap()))
}

async fn eval_row(&self, _row: &OwnedRow) -> Result<Datum> {
unimplemented!()
}
}

impl Build for Truncate {
fn build(
prost: &ExprNode,
build_child: impl Fn(&ExprNode) -> Result<BoxedExpression>,
) -> Result<Self> {
let (children, res_type) = get_children_and_return_type_for_func_call(prost)?;

// Check expression
ensure!(children.len() == 2);
ensure!(matches!(
children[0].get_return_type().unwrap().into(),
DataType::Int32 | DataType::Int64 | DataType::Decimal | DataType::Varchar
));
ensure!(DataType::Int32 == children[1].get_return_type().unwrap().into());
ensure!(res_type == children[0].get_return_type().unwrap().into());

// Get the second child as const param
let literal = build_child(&children[1])?;
let w = *literal.eval_const()?.unwrap().as_int32();

// Build the child
let child = build_child(&children[0])?;
Ok(Truncate {
child,
w,
transform: TruncateTransform::new(w),
})
}
}

// Year
pub struct Year {
child: BoxedExpression,
transform: YearTransform,
}

impl Debug for Year {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Iceberg_Year")
}
}

#[async_trait::async_trait]
impl risingwave_expr::expr::Expression for Year {
fn return_type(&self) -> DataType {
DataType::Int32
}

async fn eval(&self, data_chunk: &DataChunk) -> Result<ArrayRef> {
// Get the child array
let array = self.child.eval(data_chunk).await?;
// Convert to arrow array
let arrow_array = array.as_ref().try_into().unwrap();
// Transform
let res_array = self.transform.transform(arrow_array).unwrap();
// Convert back to array ref and return it
Ok(Arc::new((&res_array).try_into().unwrap()))
}

async fn eval_row(&self, _row: &OwnedRow) -> Result<Datum> {
unimplemented!()
}
}

impl Build for Year {
fn build(
prost: &ExprNode,
build_child: impl Fn(&ExprNode) -> Result<BoxedExpression>,
) -> Result<Self> {
let (children, res_type) = get_children_and_return_type_for_func_call(prost)?;

// Check expression
ensure!(children.len() == 1);
ensure!(matches!(
children[0].get_return_type().unwrap().into(),
DataType::Date | DataType::Timestamp | DataType::Timestamptz
));
ensure!(res_type == DataType::Int32);

// Build the child
let child = build_child(&children[0])?;
Ok(Year {
child,
transform: YearTransform {},
})
}
}

// Month
pub struct Month {
child: BoxedExpression,
transform: MonthTransform,
}

impl Debug for Month {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Iceberg_Month")
}
}

#[async_trait::async_trait]
impl risingwave_expr::expr::Expression for Month {
fn return_type(&self) -> DataType {
DataType::Int32
}

async fn eval(&self, data_chunk: &DataChunk) -> Result<ArrayRef> {
// Get the child array
let array = self.child.eval(data_chunk).await?;
// Convert to arrow array
let arrow_array = array.as_ref().try_into().unwrap();
// Transform
let res_array = self.transform.transform(arrow_array).unwrap();
// Convert back to array ref and return it
Ok(Arc::new((&res_array).try_into().unwrap()))
}

async fn eval_row(&self, _row: &OwnedRow) -> Result<Datum> {
unimplemented!()
}
}

impl Build for Month {
fn build(
prost: &ExprNode,
build_child: impl Fn(&ExprNode) -> Result<BoxedExpression>,
) -> Result<Self> {
let (children, res_type) = get_children_and_return_type_for_func_call(prost)?;

// Check expression
ensure!(children.len() == 1);
ensure!(matches!(
children[0].get_return_type().unwrap().into(),
DataType::Date | DataType::Timestamp | DataType::Timestamptz
));
ensure!(res_type == DataType::Int32);

// Build the child
let child = build_child(&children[0])?;
Ok(Month {
child,
transform: MonthTransform {},
})
}
}

// Day
pub struct Day {
child: BoxedExpression,
transform: DayTransform,
}

impl Debug for Day {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Iceberg_Day")
}
}

#[async_trait::async_trait]
impl risingwave_expr::expr::Expression for Day {
fn return_type(&self) -> DataType {
DataType::Int32
}

async fn eval(&self, data_chunk: &DataChunk) -> Result<ArrayRef> {
// Get the child array
let array = self.child.eval(data_chunk).await?;
// Convert to arrow array
let arrow_array = array.as_ref().try_into().unwrap();
// Transform
let res_array = self.transform.transform(arrow_array).unwrap();
// Convert back to array ref and return it
Ok(Arc::new((&res_array).try_into().unwrap()))
}

async fn eval_row(&self, _row: &OwnedRow) -> Result<Datum> {
unimplemented!()
}
}

impl Build for Day {
fn build(
prost: &ExprNode,
build_child: impl Fn(&ExprNode) -> Result<BoxedExpression>,
) -> Result<Self> {
let (children, res_type) = get_children_and_return_type_for_func_call(prost)?;

// Check expression
ensure!(children.len() == 1);
ensure!(matches!(
children[0].get_return_type().unwrap().into(),
DataType::Date | DataType::Timestamp | DataType::Timestamptz
));
ensure!(res_type == DataType::Int32);

// Build the child
let child = build_child(&children[0])?;
Ok(Day {
child,
transform: DayTransform {},
})
}
}

// Hour
pub struct Hour {
child: BoxedExpression,
transform: HourTransform,
}

impl Debug for Hour {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Iceberg_Hour")
}
}

#[async_trait::async_trait]
impl risingwave_expr::expr::Expression for Hour {
fn return_type(&self) -> DataType {
DataType::Int32
}

async fn eval(&self, data_chunk: &DataChunk) -> Result<ArrayRef> {
// Get the child array
let array = self.child.eval(data_chunk).await?;
// Convert to arrow array
let arrow_array = array.as_ref().try_into().unwrap();
// Transform
let res_array = self.transform.transform(arrow_array).unwrap();
// Convert back to array ref and return it
Ok(Arc::new((&res_array).try_into().unwrap()))
}

async fn eval_row(&self, _row: &OwnedRow) -> Result<Datum> {
unimplemented!()
}
}

impl Build for Hour {
fn build(
prost: &ExprNode,
build_child: impl Fn(&ExprNode) -> Result<BoxedExpression>,
) -> Result<Self> {
let (children, res_type) = get_children_and_return_type_for_func_call(prost)?;

// Check expression
ensure!(children.len() == 1);
ensure!(matches!(
children[0].get_return_type().unwrap().into(),
DataType::Timestamp | DataType::Timestamptz
));
ensure!(res_type == DataType::Int32);

// Build the child
let child = build_child(&children[0])?;
Ok(Hour {
child,
transform: HourTransform {},
})
}
}
15 changes: 15 additions & 0 deletions src/expr/core/src/expr/external/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2024 RisingWave Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

pub mod iceberg;
1 change: 1 addition & 0 deletions src/expr/core/src/expr/mod.rs
Original file line number Diff line number Diff line change
@@ -40,6 +40,7 @@ pub(crate) mod expr_udf;
pub(crate) mod wrapper;

mod build;
mod external;
pub mod test_utils;
mod value;

109 changes: 109 additions & 0 deletions src/expr/impl/src/scalar/construct_struct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright 2024 RisingWave Labs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::sync::Arc;

use risingwave_common::array::{ArrayImpl, ArrayRef, DataChunk, StructArray};
use risingwave_common::row::OwnedRow;
use risingwave_common::types::{DataType, Datum, ScalarImpl, StructValue};
use risingwave_common::util::iter_util::ZipEqFast;
use risingwave_expr::expr::{BoxedExpression, Expression};
use risingwave_expr::{build_function, Result};

#[derive(Debug)]
pub struct ConstructStructExpression {
return_type: DataType,
children: Vec<BoxedExpression>,
}

#[async_trait::async_trait]
impl Expression for ConstructStructExpression {
fn return_type(&self) -> DataType {
self.return_type.clone()
}

async fn eval(&self, input: &DataChunk) -> Result<ArrayRef> {
let mut struct_cols = Vec::with_capacity(self.children.len());
for child in &self.children {
let res = child.eval(input).await?;
struct_cols.push(res);
}
Ok(Arc::new(ArrayImpl::Struct(StructArray::new(
self.return_type.as_struct().clone(),
struct_cols,
input.visibility().clone(),
))))
}

async fn eval_row(&self, input: &OwnedRow) -> Result<Datum> {
let mut datums = Vec::with_capacity(self.children.len());
for child in &self.children {
let res = child.eval_row(input).await?;
datums.push(res);
}
Ok(Some(ScalarImpl::Struct(StructValue::new(datums))))
}
}

#[build_function("construct_struct(...) -> struct", type_infer = "panic")]
fn build(return_type: DataType, children: Vec<BoxedExpression>) -> Result<BoxedExpression> {
assert!(return_type.is_struct());
return_type
.as_struct()
.types()
.zip_eq_fast(children.iter())
.for_each(|(ty, child)| {
assert_eq!(*ty, child.return_type());
});

Ok(Box::new(ConstructStructExpression {
return_type,
children,
}))
}

#[cfg(test)]
mod tests {
use risingwave_common::array::DataChunk;
use risingwave_common::row::Row;
use risingwave_common::test_prelude::DataChunkTestExt;
use risingwave_common::types::ToOwnedDatum;
use risingwave_common::util::iter_util::ZipEqDebug;
use risingwave_expr::expr::build_from_pretty;

#[tokio::test]
async fn test_construct_struct_expr() {
let expr = build_from_pretty(
"(construct_struct:struct<a_int4,b_int4,c_int4> $0:int4 $1:int4 $2:int4)",
);
let (input, expected) = DataChunk::from_pretty(
"i i i <i,i,i>
1 2 3 (1,2,3)
4 2 1 (4,2,1)
9 1 3 (9,1,3)
1 1 1 (1,1,1)",
)
.split_column_at(3);

// test eval
let output = expr.eval(&input).await.unwrap();
assert_eq!(&output, expected.column_at(0));

// test eval_row
for (row, expected) in input.rows().zip_eq_debug(expected.rows()) {
let result = expr.eval_row(&row.to_owned_row()).await.unwrap();
assert_eq!(result, expected.datum_at(0).to_owned_datum());
}
}
}
1 change: 1 addition & 0 deletions src/expr/impl/src/scalar/mod.rs
Original file line number Diff line number Diff line change
@@ -78,6 +78,7 @@ mod to_char;
mod to_jsonb;
mod vnode;
pub use to_jsonb::*;
mod construct_struct;
mod to_timestamp;
mod translate;
mod trigonometric;
9 changes: 8 additions & 1 deletion src/frontend/src/expr/pure.rs
Original file line number Diff line number Diff line change
@@ -225,7 +225,14 @@ impl ExprVisitor for ImpureAnalyzer {
| expr_node::Type::Greatest
| expr_node::Type::Least
| expr_node::Type::ConvertFrom
| expr_node::Type::ConvertTo =>
| expr_node::Type::ConvertTo
| expr_node::Type::ConstructStruct
| expr_node::Type::IcebergBucket
| expr_node::Type::IcebergTruncate
| expr_node::Type::IcebergYear
| expr_node::Type::IcebergMonth
| expr_node::Type::IcebergDay
| expr_node::Type::IcebergHour =>
// expression output is deterministic(same result for the same input)
{
func_call
8 changes: 6 additions & 2 deletions src/workspace-hack/Cargo.toml
Original file line number Diff line number Diff line change
@@ -30,6 +30,7 @@ aws-smithy-runtime = { version = "1", default-features = false, features = ["cli
aws-smithy-types = { version = "1", default-features = false, features = ["byte-stream-poll-next", "http-body-0-4-x", "hyper-0-14-x", "rt-tokio"] }
axum = { version = "0.6" }
base64 = { version = "0.21", features = ["alloc"] }
bigdecimal = { version = "0.4" }
bit-vec = { version = "0.6" }
bitflags = { version = "2", default-features = false, features = ["serde", "std"] }
byteorder = { version = "1" }
@@ -63,7 +64,7 @@ hashbrown-5ef9efb8ec2df382 = { package = "hashbrown", version = "0.12", features
hmac = { version = "0.12", default-features = false, features = ["reset"] }
hyper = { version = "0.14", features = ["full"] }
indexmap = { version = "1", default-features = false, features = ["serde", "std"] }
itertools = { version = "0.11" }
itertools = { version = "0.10" }
jni = { version = "0.21", features = ["invocation"] }
lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] }
lexical-core = { version = "0.8", features = ["format"] }
@@ -145,6 +146,9 @@ url = { version = "2", features = ["serde"] }
uuid = { version = "1", features = ["fast-rng", "serde", "v4"] }
whoami = { version = "1" }
zeroize = { version = "1" }
zstd = { version = "0.13" }
zstd-safe = { version = "7", default-features = false, features = ["arrays", "legacy", "std", "zdict_builder"] }
zstd-sys = { version = "2", default-features = false, features = ["legacy", "std", "zdict_builder"] }

[build-dependencies]
ahash = { version = "0.8" }
@@ -161,7 +165,7 @@ fixedbitset = { version = "0.4" }
frunk_core = { version = "0.4", default-features = false, features = ["std"] }
generic-array = { version = "0.14", default-features = false, features = ["more_lengths", "zeroize"] }
hashbrown-582f2526e08bb6a0 = { package = "hashbrown", version = "0.14", features = ["nightly", "raw"] }
itertools = { version = "0.11" }
itertools = { version = "0.10" }
lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] }
libc = { version = "0.2", features = ["extra_traits"] }
log = { version = "0.4", default-features = false, features = ["kv_unstable", "std"] }

0 comments on commit 6d55b5c

Please sign in to comment.