Skip to content

Commit

Permalink
feat(function): Add humanize_size and humanize_number functions
Browse files Browse the repository at this point in the history
  • Loading branch information
cadl committed Apr 29, 2022
1 parent f629fe0 commit 8162f41
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 40 deletions.
51 changes: 39 additions & 12 deletions common/functions/src/scalars/others/humanize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
// limitations under the License.

use std::fmt;
use std::marker::PhantomData;

use common_datavalues::prelude::*;
use common_datavalues::with_match_primitive_type_id;
use common_datavalues::DataTypePtr;
use common_exception::Result;
use common_io::prelude::convert_byte_size;
use common_io::prelude::*;
use num_traits::AsPrimitive;

use crate::scalars::assert_numeric;
Expand All @@ -29,15 +30,23 @@ use crate::scalars::FunctionDescription;
use crate::scalars::FunctionFeatures;

#[derive(Clone)]
pub struct HumanizeFunction {
pub struct GenericHumanizeFunction<T> {
display_name: String,
t: PhantomData<T>,
}

impl HumanizeFunction {
pub trait HumanizeConvertFunction: Send + Sync + Clone + 'static {
fn convert(v: impl AsPrimitive<f64>, _ctx: &mut EvalContext) -> Vec<u8>;
}

impl<T> GenericHumanizeFunction<T>
where T: HumanizeConvertFunction
{
pub fn try_create(display_name: &str, args: &[&DataTypePtr]) -> Result<Box<dyn Function>> {
assert_numeric(args[0])?;
Ok(Box::new(HumanizeFunction {
Ok(Box::new(GenericHumanizeFunction::<T> {
display_name: display_name.to_string(),
t: PhantomData,
}))
}

Expand All @@ -47,12 +56,9 @@ impl HumanizeFunction {
}
}

fn humanize<S>(value: S, _ctx: &mut EvalContext) -> Vec<u8>
where S: AsPrimitive<f64> {
Vec::from(convert_byte_size(value.as_()))
}

impl Function for HumanizeFunction {
impl<T> Function for GenericHumanizeFunction<T>
where T: HumanizeConvertFunction
{
fn name(&self) -> &str {
&*self.display_name
}
Expand All @@ -68,16 +74,37 @@ impl Function for HumanizeFunction {
_input_rows: usize,
) -> Result<common_datavalues::ColumnRef> {
with_match_primitive_type_id!(columns[0].data_type().data_type_id(), |$F| {
let col = scalar_unary_op::<$F, Vu8, _>(columns[0].column(), humanize, &mut EvalContext::default())?;
let col = scalar_unary_op::<$F, Vu8, _>(columns[0].column(), T::convert, &mut EvalContext::default())?;
Ok(col.arc())
},{
unreachable!()
})
}
}

impl fmt::Display for HumanizeFunction {
impl<T> fmt::Display for GenericHumanizeFunction<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.display_name)
}
}

#[derive(Clone)]
pub struct HumanizeSizeConvertFunction;

impl HumanizeConvertFunction for HumanizeSizeConvertFunction {
fn convert(v: impl AsPrimitive<f64>, _: &mut EvalContext) -> Vec<u8> {
Vec::from(convert_byte_size(v.as_()))
}
}

#[derive(Clone)]
pub struct HumanizeNumberConvertFunction;

impl HumanizeConvertFunction for HumanizeNumberConvertFunction {
fn convert(v: impl AsPrimitive<f64>, _: &mut EvalContext) -> Vec<u8> {
Vec::from(convert_number_size(v.as_()))
}
}

pub type HumanizeSizeFunction = GenericHumanizeFunction<HumanizeSizeConvertFunction>;
pub type HumanizeNumberFunction = GenericHumanizeFunction<HumanizeNumberConvertFunction>;
3 changes: 2 additions & 1 deletion common/functions/src/scalars/others/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ mod sleep;
mod type_of;

pub use exists::ExistsFunction;
pub use humanize::HumanizeFunction;
pub use humanize::HumanizeNumberFunction;
pub use humanize::HumanizeSizeFunction;
pub use ignore::IgnoreFunction;
pub use inet_aton::InetAtonFunction;
pub use inet_aton::TryInetAtonFunction;
Expand Down
6 changes: 4 additions & 2 deletions common/functions/src/scalars/others/other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use super::humanize::HumanizeFunction;
use super::humanize::HumanizeNumberFunction;
use super::humanize::HumanizeSizeFunction;
use super::inet_aton::InetAtonFunction;
use super::inet_aton::TryInetAtonFunction;
use super::inet_ntoa::InetNtoaFunction;
Expand All @@ -35,7 +36,8 @@ impl OtherFunction {

factory.register("running_difference", RunningDifferenceFunction::desc());
factory.register("ignore", IgnoreFunction::desc());
factory.register("humanize", HumanizeFunction::desc());
factory.register("humanize_size", HumanizeSizeFunction::desc());
factory.register("humanize_number", HumanizeNumberFunction::desc());

// INET string to number.
factory.register("ipv4_string_to_num", InetAtonFunction::desc());
Expand Down
62 changes: 53 additions & 9 deletions common/functions/tests/it/scalars/others/humanize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,45 +19,89 @@ use crate::scalars::scalar_function_test::test_scalar_functions;
use crate::scalars::scalar_function_test::ScalarFunctionTest;

#[test]
fn test_humanize_function() -> Result<()> {
fn test_humanize_size_function() -> Result<()> {
let tests = vec![
ScalarFunctionTest {
name: "humanize(1000)",
name: "humanize_size(1000)",
columns: vec![Series::from_data(vec![1000_u32])],
expect: Series::from_data(vec!["1 KB"]),
error: "",
},
ScalarFunctionTest {
name: "humanize(-1000)",
name: "humanize_size(-1000)",
columns: vec![Series::from_data(vec![-1000_i32])],
expect: Series::from_data(vec!["-1 KB"]),
error: "",
},
ScalarFunctionTest {
name: "humanize('abc')",
name: "humanize_size('abc')",
columns: vec![Series::from_data(vec!["abc"])],
expect: Series::from_data(vec!["-1 KB"]),
error: "Expected a numeric type, but got String",
},
ScalarFunctionTest {
name: "humanize(true)",
name: "humanize_size(true)",
columns: vec![Series::from_data(vec![true])],
expect: Series::from_data(vec!["-1 KB"]),
error: "Expected a numeric type, but got Boolean",
},
];

test_scalar_functions("humanize", &tests)
test_scalar_functions("humanize_size", &tests)
}

#[test]
fn test_humanize_nullable() -> Result<()> {
fn test_humanize_size_nullable() -> Result<()> {
let tests = vec![ScalarFunctionTest {
name: "humanize(null)",
name: "humanize_size(null)",
columns: vec![Series::from_data(vec![Some(1_000_000_i32), None])],
expect: Series::from_data(vec![Some("1 MB"), None]),
error: "",
}];

test_scalar_functions("humanize", &tests)
test_scalar_functions("humanize_size", &tests)
}

#[test]
fn test_humanize_number_function() -> Result<()> {
let tests = vec![
ScalarFunctionTest {
name: "humanize_number(1000)",
columns: vec![Series::from_data(vec![1000_u32])],
expect: Series::from_data(vec!["1 thousand"]),
error: "",
},
ScalarFunctionTest {
name: "humanize_number(-1000)",
columns: vec![Series::from_data(vec![-1000_i32])],
expect: Series::from_data(vec!["-1 thousand"]),
error: "",
},
ScalarFunctionTest {
name: "humanize_number('abc')",
columns: vec![Series::from_data(vec!["abc"])],
expect: Series::from_data(vec!["-1 thousand"]),
error: "Expected a numeric type, but got String",
},
ScalarFunctionTest {
name: "humanize_number(true)",
columns: vec![Series::from_data(vec![true])],
expect: Series::from_data(vec!["-1 thousand"]),
error: "Expected a numeric type, but got Boolean",
},
];

test_scalar_functions("humanize_number", &tests)
}

#[test]
fn test_humanize_number_nullable() -> Result<()> {
let tests = vec![ScalarFunctionTest {
name: "humanize_number(null)",
columns: vec![Series::from_data(vec![Some(1_000_000_i32), None])],
expect: Series::from_data(vec![Some("1 million"), None]),
error: "",
}];

test_scalar_functions("humanize_number", &tests)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: HUMANIZE_NUMBER
---

Returns a readable number.

## Syntax

```sql
HUMANIZE_NUMBER(x);
```

## Arguments

| Arguments | Description |
|-----------|----------------------------|
| x | The numerical size. |


## Return Type

String.

## Examples

```sql
SELECT HUMANIZE_NUMBER(1000 * 1000)
+-------------------------+
| HUMANIZE_NUMBER((1000 * 1000)) |
+-------------------------+
| 1 million |
+-------------------------+
```
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
title: HUMANIZE
title: HUMANIZE_SIZE
---

Returns the readable size with a suffix(KB, MB, etc).

## Syntax

```sql
HUMANIZE(x);
HUMANIZE_SIZE(x);
```

## Arguments
Expand All @@ -24,9 +24,9 @@ String.
## Examples

```sql
SELECT HUMANIZE(1000 * 1000)
SELECT HUMANIZE_SIZE(1000 * 1000)
+-------------------------+
| HUMANIZE((1000 * 1000)) |
| HUMANIZE_SIZE((1000 * 1000)) |
+-------------------------+
| 1 MB |
+-------------------------+
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@
-1 KB
0 B
NULL
1
1 million
1 billion
1 trillion
1 quadrillion
1000 quadrillion
-1000
0
NULL
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
SELECT HUMANIZE(1000);
SELECT HUMANIZE(1000000);
SELECT HUMANIZE(1000000000);
SELECT HUMANIZE(1000000000000);
SELECT HUMANIZE(1000000000000000);
SELECT HUMANIZE(1000000000000000000);
SELECT HUMANIZE(1000000000000000000000);
SELECT HUMANIZE(1000000000000000000000000);
SELECT HUMANIZE(1000000000000000000000000000);
SELECT HUMANIZE(-1000);
SELECT HUMANIZE(0);
SELECT HUMANIZE(NULL);
SELECT HUMANIZE_SIZE(1000);
SELECT HUMANIZE_SIZE(1000000);
SELECT HUMANIZE_SIZE(1000000000);
SELECT HUMANIZE_SIZE(1000000000000);
SELECT HUMANIZE_SIZE(1000000000000000);
SELECT HUMANIZE_SIZE(1000000000000000000);
SELECT HUMANIZE_SIZE(1000000000000000000000);
SELECT HUMANIZE_SIZE(1000000000000000000000000);
SELECT HUMANIZE_SIZE(1000000000000000000000000000);
SELECT HUMANIZE_SIZE(-1000);
SELECT HUMANIZE_SIZE(0);
SELECT HUMANIZE_SIZE(NULL);
SELECT HUMANIZE_NUMBER(1);
SELECT HUMANIZE_NUMBER(1000);
SELECT HUMANIZE_NUMBER(1000000);
SELECT HUMANIZE_NUMBER(1000000000);
SELECT HUMANIZE_NUMBER(1000000000000);
SELECT HUMANIZE_NUMBER(1000000000000000);
SELECT HUMANIZE_NUMBER(-1000);
SELECT HUMANIZE_NUMBER(0);
SELECT HUMANIZE_NUMBER(NULL);

0 comments on commit 8162f41

Please sign in to comment.