feat: add IntoRow and Schema derive macros (#6778)

* chore: import items

Signed-off-by: WenyXu <wenymedia@gmail.com>

* feat: add `IntoRow` and `Schema` derive macros

Signed-off-by: WenyXu <wenymedia@gmail.com>

* refactor: styling

Signed-off-by: WenyXu <wenymedia@gmail.com>

* chore: apply suggestions

Signed-off-by: WenyXu <wenymedia@gmail.com>

---------

Signed-off-by: WenyXu <wenymedia@gmail.com>
This commit is contained in:
Weny Xu
2025-08-21 14:29:59 +08:00
committed by GitHub
parent 7e573e497c
commit 5eec3485fe
9 changed files with 905 additions and 491 deletions

View File

@@ -16,8 +16,8 @@ mod admin_fn;
mod aggr_func;
mod print_caller;
mod range_fn;
mod row;
mod stack_trace_debug;
mod to_row;
mod utils;
use aggr_func::{impl_aggr_func_type_store, impl_as_aggr_func_creator};
@@ -28,7 +28,9 @@ use range_fn::process_range_fn;
use syn::{parse_macro_input, Data, DeriveInput, Fields};
use crate::admin_fn::process_admin_fn;
use crate::to_row::derive_to_row_impl;
use crate::row::into_row::derive_into_row_impl;
use crate::row::schema::derive_schema_impl;
use crate::row::to_row::derive_to_row_impl;
/// Make struct implemented trait [AggrFuncTypeStore], which is necessary when writing UDAF.
/// This derive macro is expect to be used along with attribute macro [macro@as_aggr_func_creator].
@@ -193,6 +195,10 @@ pub fn derive_meta_builder(input: TokenStream) -> TokenStream {
///
/// # Example
/// ```rust, ignore
/// use api::v1::Row;
/// use api::v1::value::ValueData;
/// use api::v1::Value;
///
/// #[derive(ToRow)]
/// struct ToRowTest {
/// my_value: i32,
@@ -206,10 +212,95 @@ pub fn derive_meta_builder(input: TokenStream) -> TokenStream {
/// datatype = "TimestampMillisecond"
/// )]
/// my_timestamp: i64,
/// #[col(skip)]
/// my_skip: i32,
/// }
///
/// let row = ToRowTest {
/// my_value: 1,
/// my_string: "test".to_string(),
/// my_bool: true,
/// my_float: 1.0,
/// my_timestamp: 1718563200000,
/// my_skip: 1,
/// }.to_row();
/// ```
#[proc_macro_derive(ToRow, attributes(col))]
pub fn derive_to_row(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let output = derive_to_row_impl(input);
output.unwrap_or_else(|e| e.to_compile_error()).into()
}
/// Derive macro to convert a struct to a row with move semantics.
///
/// # Example
/// ```rust, ignore
/// use api::v1::Row;
/// use api::v1::value::ValueData;
/// use api::v1::Value;
///
/// #[derive(IntoRow)]
/// struct IntoRowTest {
/// my_value: i32,
/// #[col(name = "string_value", datatype = "string", semantic = "tag")]
/// my_string: String,
/// my_bool: bool,
/// my_float: f32,
/// #[col(
/// name = "timestamp_value",
/// semantic = "Timestamp",
/// datatype = "TimestampMillisecond"
/// )]
/// my_timestamp: i64,
/// #[col(skip)]
/// my_skip: i32,
/// }
///
/// let row = IntoRowTest {
/// my_value: 1,
/// my_string: "test".to_string(),
/// my_bool: true,
/// my_float: 1.0,
/// my_timestamp: 1718563200000,
/// my_skip: 1,
/// }.into_row();
/// ```
#[proc_macro_derive(IntoRow, attributes(col))]
pub fn derive_into_row(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let output = derive_into_row_impl(input);
output.unwrap_or_else(|e| e.to_compile_error()).into()
}
/// Derive macro to convert a struct to a schema.
///
/// # Example
/// ```rust, ignore
/// use api::v1::ColumnSchema;
///
/// #[derive(Schema)]
/// struct SchemaTest {
/// my_value: i32,
/// #[col(name = "string_value", datatype = "string", semantic = "tag")]
/// my_string: String,
/// my_bool: bool,
/// my_float: f32,
/// #[col(
/// name = "timestamp_value",
/// semantic = "Timestamp",
/// datatype = "TimestampMillisecond"
/// )]
/// my_timestamp: i64,
/// #[col(skip)]
/// my_skip: i32,
/// }
///
/// let schema = SchemaTest::schema();
/// ```
#[proc_macro_derive(Schema, attributes(col))]
pub fn derive_schema(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let output = derive_schema_impl(input);
output.unwrap_or_else(|e| e.to_compile_error()).into()
}

View File

@@ -0,0 +1,25 @@
// Copyright 2023 Greptime Team
//
// 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(crate) mod attribute;
pub(crate) mod into_row;
pub(crate) mod schema;
pub(crate) mod to_row;
pub(crate) mod utils;
pub(crate) const META_KEY_COL: &str = "col";
pub(crate) const META_KEY_NAME: &str = "name";
pub(crate) const META_KEY_DATATYPE: &str = "datatype";
pub(crate) const META_KEY_SEMANTIC: &str = "semantic";
pub(crate) const META_KEY_SKIP: &str = "skip";

View File

@@ -0,0 +1,128 @@
// Copyright 2023 Greptime Team
//
// 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::collections::HashMap;
use once_cell::sync::Lazy;
use syn::meta::ParseNestedMeta;
use syn::spanned::Spanned;
use syn::{Attribute, LitStr, Meta, Result};
use crate::row::utils::{
column_data_type_from_str, semantic_type_from_str, ColumnDataTypeWithExtension, SemanticType,
};
use crate::row::{
META_KEY_COL, META_KEY_DATATYPE, META_KEY_NAME, META_KEY_SEMANTIC, META_KEY_SKIP,
};
/// Column attribute.
#[derive(Default)]
pub(crate) struct ColumnAttribute {
/// User-defined name of the column.,
pub(crate) name: Option<String>,
/// Data type of the column.
pub(crate) datatype: Option<ColumnDataTypeWithExtension>,
/// Semantic type of the column.
pub(crate) semantic_type: SemanticType,
/// Whether to skip the column.
pub(crate) skip: bool,
}
/// Find the column attribute in the attributes.
pub(crate) fn find_column_attribute(attrs: &[Attribute]) -> Option<&Attribute> {
attrs
.iter()
.find(|attr| matches!(&attr.meta, Meta::List(list) if list.path.is_ident(META_KEY_COL)))
}
/// Parse the column attribute.
pub(crate) fn parse_column_attribute(attr: &Attribute) -> Result<ColumnAttribute> {
match &attr.meta {
Meta::List(list) if list.path.is_ident(META_KEY_COL) => {
let mut attribute = ColumnAttribute::default();
list.parse_nested_meta(|meta| {
parse_column_attribute_field(&meta, &mut attribute)
})?;
Ok(attribute)
}
_ => Err(syn::Error::new(
attr.span(),
format!(
"expected `{META_KEY_COL}({META_KEY_NAME} = \"...\", {META_KEY_DATATYPE} = \"...\", {META_KEY_SEMANTIC} = \"...\")`"
),
)),
}
}
type ParseColumnAttributeField = fn(&ParseNestedMeta, &mut ColumnAttribute) -> Result<()>;
static PARSE_COLUMN_ATTRIBUTE_FIELDS: Lazy<HashMap<&str, ParseColumnAttributeField>> =
Lazy::new(|| {
HashMap::from([
(META_KEY_NAME, parse_name_field as _),
(META_KEY_DATATYPE, parse_datatype_field as _),
(META_KEY_SEMANTIC, parse_semantic_field as _),
(META_KEY_SKIP, parse_skip_field as _),
])
});
fn parse_name_field(meta: &ParseNestedMeta<'_>, attribute: &mut ColumnAttribute) -> Result<()> {
let value = meta.value()?;
let s: LitStr = value.parse()?;
attribute.name = Some(s.value());
Ok(())
}
fn parse_datatype_field(meta: &ParseNestedMeta<'_>, attribute: &mut ColumnAttribute) -> Result<()> {
let value = meta.value()?;
let s: LitStr = value.parse()?;
let ident = s.value();
let Some(value) = column_data_type_from_str(&ident) else {
return Err(meta.error(format!("unexpected {META_KEY_DATATYPE}: {ident}")));
};
attribute.datatype = Some(value);
Ok(())
}
fn parse_semantic_field(meta: &ParseNestedMeta<'_>, attribute: &mut ColumnAttribute) -> Result<()> {
let value = meta.value()?;
let s: LitStr = value.parse()?;
let ident = s.value();
let Some(value) = semantic_type_from_str(&ident) else {
return Err(meta.error(format!("unexpected {META_KEY_SEMANTIC}: {ident}")));
};
attribute.semantic_type = value;
Ok(())
}
fn parse_skip_field(_: &ParseNestedMeta<'_>, attribute: &mut ColumnAttribute) -> Result<()> {
attribute.skip = true;
Ok(())
}
fn parse_column_attribute_field(
meta: &ParseNestedMeta<'_>,
attribute: &mut ColumnAttribute,
) -> Result<()> {
let Some(ident) = meta.path.get_ident() else {
return Err(meta.error(format!("expected `{META_KEY_COL}({META_KEY_NAME} = \"...\", {META_KEY_DATATYPE} = \"...\", {META_KEY_SEMANTIC} = \"...\")`")));
};
let Some(parse_column_attribute) =
PARSE_COLUMN_ATTRIBUTE_FIELDS.get(ident.to_string().as_str())
else {
return Err(meta.error(format!("unexpected attribute: {ident}")));
};
parse_column_attribute(meta, attribute)
}

View File

@@ -0,0 +1,88 @@
// Copyright 2023 Greptime Team
//
// 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 proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::spanned::Spanned;
use syn::{DeriveInput, Result};
use crate::row::utils::{
convert_column_data_type_to_value_data_ident, extract_struct_fields, get_column_data_type,
parse_fields_from_fields_named, ParsedField,
};
use crate::row::{META_KEY_COL, META_KEY_DATATYPE};
pub(crate) fn derive_into_row_impl(input: DeriveInput) -> Result<TokenStream2> {
let Some(fields) = extract_struct_fields(&input.data) else {
return Err(syn::Error::new(
input.span(),
"IntoRow can only be derived for structs",
));
};
let ident = input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let fields = parse_fields_from_fields_named(fields)?;
// Implement `into_row` method.
let impl_to_row_method = impl_into_row_method_combined(&fields)?;
Ok(quote! {
impl #impl_generics #ident #ty_generics #where_clause {
#impl_to_row_method
}
})
}
fn impl_into_row_method_combined(fields: &[ParsedField<'_>]) -> Result<TokenStream2> {
let value_exprs = fields
.iter()
.map(|field| {
let ParsedField {ident, field_type, column_data_type, column_attribute} = field;
let Some(column_data_type) = get_column_data_type(column_data_type, column_attribute)
else {
return Err(syn::Error::new(
ident.span(),
format!(
"expected to set data type explicitly via [({META_KEY_COL}({META_KEY_DATATYPE} = \"...\"))]"
),
));
};
let value_data = convert_column_data_type_to_value_data_ident(&column_data_type.data_type);
let expr = if field_type.is_optional() {
quote! {
match self.#ident {
Some(v) => Value {
value_data: Some(ValueData::#value_data(v.into())),
},
None => Value { value_data: None },
}
}
} else {
quote! {
Value {
value_data: Some(ValueData::#value_data(self.#ident.into())),
}
}
};
Ok(expr)
})
.collect::<Result<Vec<_>>>()?;
Ok(quote! {
pub fn into_row(self) -> Row {
Row {
values: vec![ #( #value_exprs ),* ]
}
}
})
}

View File

@@ -0,0 +1,118 @@
// Copyright 2023 Greptime Team
//
// 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 greptime_proto::v1::column_data_type_extension::TypeExt;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::spanned::Spanned;
use syn::{DeriveInput, Result};
use crate::row::utils::{
convert_semantic_type_to_proto_semantic_type, extract_struct_fields, get_column_data_type,
parse_fields_from_fields_named, ColumnDataTypeWithExtension, ParsedField,
};
use crate::row::{META_KEY_COL, META_KEY_DATATYPE};
pub(crate) fn derive_schema_impl(input: DeriveInput) -> Result<TokenStream2> {
let Some(fields) = extract_struct_fields(&input.data) else {
return Err(syn::Error::new(
input.span(),
"Schema can only be derived for structs",
));
};
let ident = input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let fields = parse_fields_from_fields_named(fields)?;
// Implement `schema` method.
let impl_schema_method = impl_schema_method(&fields)?;
Ok(quote! {
impl #impl_generics #ident #ty_generics #where_clause {
#impl_schema_method
}
})
}
fn impl_schema_method(fields: &[ParsedField<'_>]) -> Result<TokenStream2> {
let schemas: Vec<TokenStream2> = fields
.iter()
.map(|field| {
let ParsedField{ ident, column_data_type, column_attribute, ..} = field;
let Some(ColumnDataTypeWithExtension{data_type, extension}) = get_column_data_type(column_data_type, column_attribute)
else {
return Err(syn::Error::new(
ident.span(),
format!(
"expected to set data type explicitly via [({META_KEY_COL}({META_KEY_DATATYPE} = \"...\"))]"
),
));
};
// Uses user explicit name or field name as column name.
let name = column_attribute
.name
.clone()
.unwrap_or_else(|| ident.to_string());
let name = syn::LitStr::new(&name, ident.span());
let column_data_type =
syn::LitInt::new(&(data_type as i32).to_string(), ident.span());
let semantic_type_val = convert_semantic_type_to_proto_semantic_type(column_attribute.semantic_type) as i32;
let semantic_type = syn::LitInt::new(&semantic_type_val.to_string(), ident.span());
let extension = match extension {
Some(ext) => {
match ext.type_ext {
Some(TypeExt::DecimalType(ext)) => {
let precision = syn::LitInt::new(&ext.precision.to_string(), ident.span());
let scale = syn::LitInt::new(&ext.scale.to_string(), ident.span());
quote! {
Some(ColumnDataTypeExtension { type_ext: Some(TypeExt::DecimalType(DecimalTypeExtension { precision: #precision, scale: #scale })) })
}
}
Some(TypeExt::JsonType(ext)) => {
let json_type = syn::LitInt::new(&ext.to_string(), ident.span());
quote! {
Some(ColumnDataTypeExtension { type_ext: Some(TypeExt::JsonType(#json_type)) })
}
}
Some(TypeExt::VectorType(ext)) => {
let dim = syn::LitInt::new(&ext.dim.to_string(), ident.span());
quote! {
Some(ColumnDataTypeExtension { type_ext: Some(TypeExt::VectorType(VectorTypeExtension { dim: #dim })) })
}
}
None => {
quote! { None }
}
}
}
None => quote! { None },
};
Ok(quote! {
ColumnSchema {
column_name: #name.to_string(),
datatype: #column_data_type,
datatype_extension: #extension,
options: None,
semantic_type: #semantic_type,
}
})
})
.collect::<Result<_>>()?;
Ok(quote! {
pub fn schema() -> Vec<ColumnSchema> {
vec![ #(#schemas),* ]
}
})
}

View File

@@ -0,0 +1,88 @@
// Copyright 2023 Greptime Team
//
// 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 proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::spanned::Spanned;
use syn::{DeriveInput, Result};
use crate::row::utils::{
convert_column_data_type_to_value_data_ident, extract_struct_fields, get_column_data_type,
parse_fields_from_fields_named, ParsedField,
};
use crate::row::{META_KEY_COL, META_KEY_DATATYPE};
pub(crate) fn derive_to_row_impl(input: DeriveInput) -> Result<TokenStream2> {
let Some(fields) = extract_struct_fields(&input.data) else {
return Err(syn::Error::new(
input.span(),
"ToRow can only be derived for structs",
));
};
let ident = input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let fields = parse_fields_from_fields_named(fields)?;
// Implement `to_row` method.
let impl_to_row_method = impl_to_row_method_combined(&fields)?;
Ok(quote! {
impl #impl_generics #ident #ty_generics #where_clause {
#impl_to_row_method
}
})
}
fn impl_to_row_method_combined(fields: &[ParsedField<'_>]) -> Result<TokenStream2> {
let value_exprs = fields
.iter()
.map(|field| {
let ParsedField {ident, field_type, column_data_type, column_attribute} = field;
let Some(column_data_type) = get_column_data_type(column_data_type, column_attribute)
else {
return Err(syn::Error::new(
ident.span(),
format!(
"expected to set data type explicitly via [({META_KEY_COL}({META_KEY_DATATYPE} = \"...\"))]"
),
));
};
let value_data = convert_column_data_type_to_value_data_ident(&column_data_type.data_type);
let expr = if field_type.is_optional() {
quote! {
match &self.#ident {
Some(v) => Value {
value_data: Some(ValueData::#value_data(v.clone().into())),
},
None => Value { value_data: None },
}
}
} else {
quote! {
Value {
value_data: Some(ValueData::#value_data(self.#ident.clone().into())),
}
}
};
Ok(expr)
})
.collect::<Result<Vec<_>>>()?;
Ok(quote! {
pub fn to_row(&self) -> Row {
Row {
values: vec![ #( #value_exprs ),* ]
}
}
})
}

View File

@@ -0,0 +1,313 @@
// Copyright 2023 Greptime Team
//
// 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::collections::HashMap;
use greptime_proto::v1::column_data_type_extension::TypeExt;
use greptime_proto::v1::{ColumnDataType, ColumnDataTypeExtension, JsonTypeExtension};
use once_cell::sync::Lazy;
use quote::format_ident;
use syn::{
AngleBracketedGenericArguments, Data, DataStruct, Fields, FieldsNamed, GenericArgument, Ident,
Path, PathArguments, PathSegment, Result, Type, TypePath, TypeReference,
};
use crate::row::attribute::{find_column_attribute, parse_column_attribute, ColumnAttribute};
static SEMANTIC_TYPES: Lazy<HashMap<&'static str, SemanticType>> = Lazy::new(|| {
HashMap::from([
("field", SemanticType::Field),
("tag", SemanticType::Tag),
("timestamp", SemanticType::Timestamp),
])
});
static DATATYPE_TO_COLUMN_DATA_TYPE: Lazy<HashMap<&'static str, ColumnDataTypeWithExtension>> =
Lazy::new(|| {
HashMap::from([
// Timestamp
("timestampsecond", ColumnDataType::TimestampSecond.into()),
(
"timestampmillisecond",
ColumnDataType::TimestampMillisecond.into(),
),
(
"timestampmicrosecond",
ColumnDataType::TimestampMicrosecond.into(),
),
(
"timestampnanosecond",
ColumnDataType::TimestampNanosecond.into(),
),
// Date
("date", ColumnDataType::Date.into()),
("datetime", ColumnDataType::Datetime.into()),
// Time
("timesecond", ColumnDataType::TimeSecond.into()),
("timemillisecond", ColumnDataType::TimeMillisecond.into()),
("timemicrosecond", ColumnDataType::TimeMicrosecond.into()),
("timenanosecond", ColumnDataType::TimeNanosecond.into()),
// Others
("string", ColumnDataType::String.into()),
("json", ColumnDataTypeWithExtension::json()),
// TODO(weny): support vector and decimal128.
])
});
static PRIMITIVE_TYPE_TO_COLUMN_DATA_TYPE: Lazy<HashMap<&'static str, ColumnDataType>> =
Lazy::new(|| {
HashMap::from([
("i8", ColumnDataType::Int8),
("i16", ColumnDataType::Int16),
("i32", ColumnDataType::Int32),
("i64", ColumnDataType::Int64),
("u8", ColumnDataType::Uint8),
("u16", ColumnDataType::Uint16),
("u32", ColumnDataType::Uint32),
("u64", ColumnDataType::Uint64),
("f32", ColumnDataType::Float32),
("f64", ColumnDataType::Float64),
("bool", ColumnDataType::Boolean),
])
});
/// Extract the fields of a struct.
pub(crate) fn extract_struct_fields(data: &Data) -> Option<&FieldsNamed> {
let Data::Struct(DataStruct {
fields: Fields::Named(named),
..
}) = &data
else {
return None;
};
Some(named)
}
/// Convert an identifier to a semantic type.
pub(crate) fn semantic_type_from_str(ident: &str) -> Option<SemanticType> {
// Ignores the case of the identifier.
let lowercase = ident.to_lowercase();
let lowercase_str = lowercase.as_str();
SEMANTIC_TYPES.get(lowercase_str).cloned()
}
/// Convert a field type to a column data type.
pub(crate) fn column_data_type_from_str(ident: &str) -> Option<ColumnDataTypeWithExtension> {
// Ignores the case of the identifier.
let lowercase = ident.to_lowercase();
let lowercase_str = lowercase.as_str();
DATATYPE_TO_COLUMN_DATA_TYPE.get(lowercase_str).cloned()
}
#[derive(Default, Clone, Copy)]
pub(crate) enum SemanticType {
#[default]
Field,
Tag,
Timestamp,
}
pub(crate) enum FieldType<'a> {
Required(&'a Type),
Optional(&'a Type),
}
impl FieldType<'_> {
pub(crate) fn is_optional(&self) -> bool {
matches!(self, FieldType::Optional(_))
}
pub(crate) fn extract_ident(&self) -> Option<&Ident> {
match self {
FieldType::Required(ty) => extract_ident_from_type(ty),
FieldType::Optional(ty) => extract_ident_from_type(ty),
}
}
}
fn field_type(ty: &Type) -> FieldType<'_> {
if let Type::Reference(TypeReference { elem, .. }) = ty {
return field_type(elem);
}
if let Type::Path(TypePath {
qself: _,
path: Path {
leading_colon,
segments,
},
}) = ty
{
if leading_colon.is_none() && segments.len() == 1 {
if let Some(PathSegment {
ident,
arguments:
PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }),
}) = segments.first()
{
if let (1, Some(GenericArgument::Type(t))) = (args.len(), args.first()) {
if ident == "Option" {
return FieldType::Optional(t);
}
}
}
}
}
FieldType::Required(ty)
}
fn extract_ident_from_type(ty: &Type) -> Option<&Ident> {
match ty {
Type::Path(TypePath { qself: None, path }) => path.get_ident(),
Type::Reference(type_ref) => extract_ident_from_type(&type_ref.elem),
Type::Group(type_group) => extract_ident_from_type(&type_group.elem),
_ => None,
}
}
/// Convert a semantic type to a proto semantic type.
pub(crate) fn convert_semantic_type_to_proto_semantic_type(
semantic_type: SemanticType,
) -> greptime_proto::v1::SemanticType {
match semantic_type {
SemanticType::Field => greptime_proto::v1::SemanticType::Field,
SemanticType::Tag => greptime_proto::v1::SemanticType::Tag,
SemanticType::Timestamp => greptime_proto::v1::SemanticType::Timestamp,
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ColumnDataTypeWithExtension {
pub(crate) data_type: ColumnDataType,
pub(crate) extension: Option<ColumnDataTypeExtension>,
}
impl ColumnDataTypeWithExtension {
pub(crate) fn json() -> Self {
Self {
data_type: ColumnDataType::Json,
extension: Some(ColumnDataTypeExtension {
type_ext: Some(TypeExt::JsonType(JsonTypeExtension::JsonBinary.into())),
}),
}
}
}
impl From<ColumnDataType> for ColumnDataTypeWithExtension {
fn from(data_type: ColumnDataType) -> Self {
Self {
data_type,
extension: None,
}
}
}
pub(crate) struct ParsedField<'a> {
pub(crate) ident: &'a Ident,
pub(crate) field_type: FieldType<'a>,
pub(crate) column_data_type: Option<ColumnDataTypeWithExtension>,
pub(crate) column_attribute: ColumnAttribute,
}
/// Parse fields from fields named.
pub(crate) fn parse_fields_from_fields_named(named: &FieldsNamed) -> Result<Vec<ParsedField<'_>>> {
Ok(named
.named
.iter()
.map(|field| {
let ident = field.ident.as_ref().expect("field must have an ident");
let field_type = field_type(&field.ty);
let column_data_type = field_type
.extract_ident()
.and_then(convert_primitive_type_to_column_data_type);
let column_attribute = find_column_attribute(&field.attrs)
.map(parse_column_attribute)
.transpose()?
.unwrap_or_default();
Ok(ParsedField {
ident,
field_type,
column_data_type,
column_attribute,
})
})
.collect::<Result<Vec<ParsedField<'_>>>>()?
.into_iter()
.filter(|field| !field.column_attribute.skip)
.collect::<Vec<_>>())
}
fn convert_primitive_type_to_column_data_type(
ident: &Ident,
) -> Option<ColumnDataTypeWithExtension> {
PRIMITIVE_TYPE_TO_COLUMN_DATA_TYPE
.get(ident.to_string().as_str())
.cloned()
.map(ColumnDataTypeWithExtension::from)
}
/// Get the column data type from the attribute or the inferred column data type.
pub(crate) fn get_column_data_type(
infer_column_data_type: &Option<ColumnDataTypeWithExtension>,
attribute: &ColumnAttribute,
) -> Option<ColumnDataTypeWithExtension> {
attribute.datatype.or(*infer_column_data_type)
}
/// Convert a column data type to a value data ident.
pub(crate) fn convert_column_data_type_to_value_data_ident(
column_data_type: &ColumnDataType,
) -> Ident {
match column_data_type {
ColumnDataType::Boolean => format_ident!("BoolValue"),
ColumnDataType::Int8 => format_ident!("I8Value"),
ColumnDataType::Int16 => format_ident!("I16Value"),
ColumnDataType::Int32 => format_ident!("I32Value"),
ColumnDataType::Int64 => format_ident!("I64Value"),
ColumnDataType::Uint8 => format_ident!("U8Value"),
ColumnDataType::Uint16 => format_ident!("U16Value"),
ColumnDataType::Uint32 => format_ident!("U32Value"),
ColumnDataType::Uint64 => format_ident!("U64Value"),
ColumnDataType::Float32 => format_ident!("F32Value"),
ColumnDataType::Float64 => format_ident!("F64Value"),
ColumnDataType::Binary => format_ident!("BinaryValue"),
ColumnDataType::String => format_ident!("StringValue"),
ColumnDataType::Date => format_ident!("DateValue"),
ColumnDataType::Datetime => format_ident!("DatetimeValue"),
ColumnDataType::TimestampSecond => format_ident!("TimestampSecondValue"),
ColumnDataType::TimestampMillisecond => {
format_ident!("TimestampMillisecondValue")
}
ColumnDataType::TimestampMicrosecond => {
format_ident!("TimestampMicrosecondValue")
}
ColumnDataType::TimestampNanosecond => format_ident!("TimestampNanosecondValue"),
ColumnDataType::TimeSecond => format_ident!("TimeSecondValue"),
ColumnDataType::TimeMillisecond => format_ident!("TimeMillisecondValue"),
ColumnDataType::TimeMicrosecond => format_ident!("TimeMicrosecondValue"),
ColumnDataType::TimeNanosecond => format_ident!("TimeNanosecondValue"),
ColumnDataType::IntervalYearMonth => format_ident!("IntervalYearMonthValue"),
ColumnDataType::IntervalDayTime => format_ident!("IntervalDayTimeValue"),
ColumnDataType::IntervalMonthDayNano => {
format_ident!("IntervalMonthDayNanoValue")
}
ColumnDataType::Decimal128 => format_ident!("Decimal128Value"),
// Json is a special case, it is actually a string column.
ColumnDataType::Json => format_ident!("StringValue"),
ColumnDataType::Vector => format_ident!("VectorValue"),
}
}

View File

@@ -1,469 +0,0 @@
// Copyright 2023 Greptime Team
//
// 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::collections::HashMap;
use greptime_proto::v1::ColumnDataType;
use once_cell::sync::Lazy;
use proc_macro2::{Ident, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use syn::meta::ParseNestedMeta;
use syn::spanned::Spanned;
use syn::{
AngleBracketedGenericArguments, Attribute, Data, DataStruct, DeriveInput, Fields, FieldsNamed,
GenericArgument, LitStr, Meta, Path, PathArguments, PathSegment, Result, Type, TypePath,
TypeReference,
};
const META_KEY_COL: &str = "col";
const META_KEY_NAME: &str = "name";
const META_KEY_DATATYPE: &str = "datatype";
const META_KEY_SEMANTIC: &str = "semantic";
pub(crate) fn derive_to_row_impl(input: DeriveInput) -> Result<TokenStream2> {
let Data::Struct(DataStruct {
fields: Fields::Named(FieldsNamed { named, .. }),
..
}) = &input.data
else {
return Err(syn::Error::new(
input.span(),
"ToRow can only be derived for structs",
));
};
let ident = input.ident;
let generics = input.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let fields = named.iter().map(|field| {
(
field.ident.as_ref().expect("field must have an ident"),
&field.ty,
)
});
// Ident of fields.
let idents = fields.clone().map(|(ident, _)| ident).collect::<Vec<_>>();
// Infer column data types for each field.
let infer_column_data_types = fields
.clone()
.map(|(_, ty)| field_type(ty))
.flat_map(|ty| {
ty.extract_ident()
.map(convert_primitive_type_to_column_data_type)
})
.collect::<Vec<_>>();
// Find attributes for each field.
let column_attributes = named
.iter()
.map(|field| {
let attrs = &field.attrs;
let attr = find_column_attribute(attrs);
match attr {
Some(attr) => parse_attribute(attr),
None => Ok(ColumnAttribute::default()),
}
})
.collect::<Result<Vec<ColumnAttribute>>>()?;
let field_types = fields
.clone()
.map(|(_, ty)| field_type(ty))
.collect::<Vec<_>>();
// Implement `to_row` method.
let impl_to_row_method = impl_to_row_method_combined(
&idents,
&field_types,
&infer_column_data_types,
&column_attributes,
)?;
// Implement `schema` method.
let impl_schema_method =
impl_schema_method(&idents, &infer_column_data_types, &column_attributes)?;
Ok(quote! {
impl #impl_generics #ident #ty_generics #where_clause {
#impl_to_row_method
#impl_schema_method
}
})
}
fn impl_schema_method(
idents: &[&Ident],
infer_column_data_types: &[Option<ColumnDataType>],
column_attributes: &[ColumnAttribute],
) -> Result<TokenStream2> {
let schemas: Vec<TokenStream2> = idents
.iter()
.zip(infer_column_data_types.iter())
.zip(column_attributes.iter())
.map(|((ident, column_data_type), column_attribute)| {
let Some(column_data_type) = get_column_data_type(column_data_type, column_attribute)
else {
return Err(syn::Error::new(
ident.span(),
format!(
"expected to set data type explicitly via [({META_KEY_COL}({META_KEY_DATATYPE} = \"...\"))]"
),
));
};
// Uses user explicit name or field name as column name.
let name = column_attribute
.name
.clone()
.unwrap_or_else(|| ident.to_string());
let name = syn::LitStr::new(&name, ident.span());
let column_data_type =
syn::LitInt::new(&(column_data_type as i32).to_string(), ident.span());
let semantic_type_val = match column_attribute.semantic_type {
SemanticType::Field => greptime_proto::v1::SemanticType::Field,
SemanticType::Tag => greptime_proto::v1::SemanticType::Tag,
SemanticType::Timestamp => greptime_proto::v1::SemanticType::Timestamp,
} as i32;
let semantic_type = syn::LitInt::new(&semantic_type_val.to_string(), ident.span());
Ok(quote! {
greptime_proto::v1::ColumnSchema {
column_name: #name.to_string(),
datatype: #column_data_type,
datatype_extension: None,
options: None,
semantic_type: #semantic_type,
}
})
})
.collect::<Result<_>>()?;
Ok(quote! {
pub fn schema(&self) -> Vec<greptime_proto::v1::ColumnSchema> {
vec![ #(#schemas),* ]
}
})
}
fn impl_to_row_method_combined(
idents: &[&Ident],
field_types: &[FieldType<'_>],
infer_column_data_types: &[Option<ColumnDataType>],
column_attributes: &[ColumnAttribute],
) -> Result<TokenStream2> {
let value_exprs = idents
.iter()
.zip(field_types.iter())
.zip(infer_column_data_types.iter())
.zip(column_attributes.iter())
.map(|(((ident, field_type), column_data_type), column_attribute)| {
let Some(column_data_type) = get_column_data_type(column_data_type, column_attribute)
else {
return Err(syn::Error::new(
ident.span(),
format!(
"expected to set data type explicitly via [({META_KEY_COL}({META_KEY_DATATYPE} = \"...\"))]"
),
));
};
let value_data = convert_column_data_type_to_value_data_ident(&column_data_type);
let expr = if field_type.is_optional() {
quote! {
match &self.#ident {
Some(v) => greptime_proto::v1::Value {
value_data: Some(greptime_proto::v1::value::ValueData::#value_data(v.clone().into())),
},
None => greptime_proto::v1::Value { value_data: None },
}
}
} else {
quote! {
greptime_proto::v1::Value {
value_data: Some(greptime_proto::v1::value::ValueData::#value_data(self.#ident.clone().into())),
}
}
};
Ok(expr)
})
.collect::<Result<Vec<_>>>()?;
Ok(quote! {
pub fn to_row(&self) -> greptime_proto::v1::Row {
greptime_proto::v1::Row {
values: vec![ #( #value_exprs ),* ]
}
}
})
}
fn get_column_data_type(
infer_column_data_type: &Option<ColumnDataType>,
attribute: &ColumnAttribute,
) -> Option<ColumnDataType> {
attribute.column_data_type.or(*infer_column_data_type)
}
fn convert_column_data_type_to_value_data_ident(column_data_type: &ColumnDataType) -> Ident {
match column_data_type {
ColumnDataType::Boolean => format_ident!("BoolValue"),
ColumnDataType::Int8 => format_ident!("I8Value"),
ColumnDataType::Int16 => format_ident!("I16Value"),
ColumnDataType::Int32 => format_ident!("I32Value"),
ColumnDataType::Int64 => format_ident!("I64Value"),
ColumnDataType::Uint8 => format_ident!("U8Value"),
ColumnDataType::Uint16 => format_ident!("U16Value"),
ColumnDataType::Uint32 => format_ident!("U32Value"),
ColumnDataType::Uint64 => format_ident!("U64Value"),
ColumnDataType::Float32 => format_ident!("F32Value"),
ColumnDataType::Float64 => format_ident!("F64Value"),
ColumnDataType::Binary => format_ident!("BinaryValue"),
ColumnDataType::String => format_ident!("StringValue"),
ColumnDataType::Date => format_ident!("DateValue"),
ColumnDataType::Datetime => format_ident!("DatetimeValue"),
ColumnDataType::TimestampSecond => format_ident!("TimestampSecondValue"),
ColumnDataType::TimestampMillisecond => {
format_ident!("TimestampMillisecondValue")
}
ColumnDataType::TimestampMicrosecond => {
format_ident!("TimestampMicrosecondValue")
}
ColumnDataType::TimestampNanosecond => format_ident!("TimestampNanosecondValue"),
ColumnDataType::TimeSecond => format_ident!("TimeSecondValue"),
ColumnDataType::TimeMillisecond => format_ident!("TimeMillisecondValue"),
ColumnDataType::TimeMicrosecond => format_ident!("TimeMicrosecondValue"),
ColumnDataType::TimeNanosecond => format_ident!("TimeNanosecondValue"),
ColumnDataType::IntervalYearMonth => format_ident!("IntervalYearMonthValue"),
ColumnDataType::IntervalDayTime => format_ident!("IntervalDayTimeValue"),
ColumnDataType::IntervalMonthDayNano => {
format_ident!("IntervalMonthDayNanoValue")
}
ColumnDataType::Decimal128 => format_ident!("Decimal128Value"),
ColumnDataType::Json => format_ident!("JsonValue"),
ColumnDataType::Vector => format_ident!("VectorValue"),
}
}
fn field_type(ty: &Type) -> FieldType<'_> {
if let Type::Reference(TypeReference { elem, .. }) = ty {
return field_type(elem);
}
if let Type::Path(TypePath {
qself: _,
path: Path {
leading_colon,
segments,
},
}) = ty
{
if leading_colon.is_none() && segments.len() == 1 {
if let Some(PathSegment {
ident,
arguments:
PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }),
}) = segments.first()
{
if let (1, Some(GenericArgument::Type(t))) = (args.len(), args.first()) {
if ident == "Option" {
return FieldType::Optional(t);
}
}
}
}
}
FieldType::Required(ty)
}
fn extract_ident_from_type(ty: &Type) -> Option<&Ident> {
match ty {
Type::Path(TypePath { qself: None, path }) => path.get_ident(),
Type::Reference(type_ref) => extract_ident_from_type(&type_ref.elem),
Type::Group(type_group) => extract_ident_from_type(&type_group.elem),
_ => None,
}
}
static PRIMITIVE_TYPE_TO_COLUMN_DATA_TYPE: Lazy<HashMap<&'static str, ColumnDataType>> =
Lazy::new(|| {
HashMap::from([
("i8", ColumnDataType::Int8),
("i16", ColumnDataType::Int16),
("i32", ColumnDataType::Int32),
("i64", ColumnDataType::Int64),
("u8", ColumnDataType::Uint8),
("u16", ColumnDataType::Uint16),
("u32", ColumnDataType::Uint32),
("u64", ColumnDataType::Uint64),
("f32", ColumnDataType::Float32),
("f64", ColumnDataType::Float64),
("bool", ColumnDataType::Boolean),
])
});
static DATATYPE_TO_COLUMN_DATA_TYPE: Lazy<HashMap<&'static str, ColumnDataType>> =
Lazy::new(|| {
HashMap::from([
// Timestamp
("timestampsecond", ColumnDataType::TimestampSecond),
("timestampmillisecond", ColumnDataType::TimestampMillisecond),
(
"timestamptimemicrosecond",
ColumnDataType::TimestampMicrosecond,
),
(
"timestamptimenanosecond",
ColumnDataType::TimestampNanosecond,
),
// Date
("date", ColumnDataType::Date),
("datetime", ColumnDataType::Datetime),
// Time
("timesecond", ColumnDataType::TimeSecond),
("timemillisecond", ColumnDataType::TimeMillisecond),
("timemicrosecond", ColumnDataType::TimeMicrosecond),
("timenanosecond", ColumnDataType::TimeNanosecond),
// Others
("string", ColumnDataType::String),
("json", ColumnDataType::Json),
("decimal128", ColumnDataType::Decimal128),
("vector", ColumnDataType::Vector),
])
});
static SEMANTIC_TYPES: Lazy<HashMap<&'static str, SemanticType>> = Lazy::new(|| {
HashMap::from([
("field", SemanticType::Field),
("tag", SemanticType::Tag),
("timestamp", SemanticType::Timestamp),
])
});
fn convert_primitive_type_to_column_data_type(ident: &Ident) -> Option<ColumnDataType> {
PRIMITIVE_TYPE_TO_COLUMN_DATA_TYPE
.get(ident.to_string().as_str())
.cloned()
}
fn semantic_type_from_ident(ident: &str) -> Option<SemanticType> {
// Ignores the case of the identifier.
let lowercase = ident.to_lowercase();
let lowercase_str = lowercase.as_str();
SEMANTIC_TYPES.get(lowercase_str).cloned()
}
fn convert_field_type_to_column_data_type(ident: &str) -> Option<ColumnDataType> {
// Ignores the case of the identifier.
let lowercase = ident.to_lowercase();
let lowercase_str = lowercase.as_str();
DATATYPE_TO_COLUMN_DATA_TYPE.get(lowercase_str).cloned()
}
#[derive(Default, Clone, Copy)]
enum SemanticType {
#[default]
Field,
Tag,
Timestamp,
}
enum FieldType<'a> {
Required(&'a Type),
Optional(&'a Type),
}
impl FieldType<'_> {
fn is_optional(&self) -> bool {
matches!(self, FieldType::Optional(_))
}
fn extract_ident(&self) -> Option<&Ident> {
match self {
FieldType::Required(ty) => extract_ident_from_type(ty),
FieldType::Optional(ty) => extract_ident_from_type(ty),
}
}
}
#[derive(Default)]
struct ColumnAttribute {
name: Option<String>,
column_data_type: Option<ColumnDataType>,
semantic_type: SemanticType,
}
fn find_column_attribute(attrs: &[Attribute]) -> Option<&Attribute> {
attrs
.iter()
.find(|attr| matches!(&attr.meta, Meta::List(list) if list.path.is_ident(META_KEY_COL)))
}
fn parse_attribute(attr: &Attribute) -> Result<ColumnAttribute> {
match &attr.meta {
Meta::List(list) if list.path.is_ident(META_KEY_COL) => {
let mut attribute = ColumnAttribute::default();
list.parse_nested_meta(|meta| {
parse_column_attribute(&meta, &mut attribute)
})?;
Ok(attribute)
}
_ => Err(syn::Error::new(
attr.span(),
format!(
"expected `{META_KEY_COL}({META_KEY_NAME} = \"...\", {META_KEY_DATATYPE} = \"...\", {META_KEY_SEMANTIC} = \"...\")`"
),
)),
}
}
fn parse_column_attribute(
meta: &ParseNestedMeta<'_>,
attribute: &mut ColumnAttribute,
) -> Result<()> {
let Some(ident) = meta.path.get_ident() else {
return Err(meta.error(format!("expected `{META_KEY_COL}({META_KEY_NAME} = \"...\", {META_KEY_DATATYPE} = \"...\", {META_KEY_SEMANTIC} = \"...\")`")));
};
match ident.to_string().as_str() {
META_KEY_NAME => {
let value = meta.value()?;
let s: LitStr = value.parse()?;
attribute.name = Some(s.value());
}
META_KEY_DATATYPE => {
let value = meta.value()?;
let s: LitStr = value.parse()?;
let ident = s.value();
let Some(value) = convert_field_type_to_column_data_type(&ident) else {
return Err(meta.error(format!("unexpected {META_KEY_DATATYPE}: {ident}")));
};
attribute.column_data_type = Some(value);
}
META_KEY_SEMANTIC => {
let value = meta.value()?;
let s: LitStr = value.parse()?;
let ident = s.value();
let Some(value) = semantic_type_from_ident(&ident) else {
return Err(meta.error(format!("unexpected {META_KEY_SEMANTIC}: {ident}")));
};
attribute.semantic_type = value;
}
attr => return Err(meta.error(format!("unexpected attribute: {attr}"))),
}
Ok(())
}

View File

@@ -12,10 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use common_macro::ToRow;
use greptime_proto::v1::{ColumnDataType, ColumnSchema, Row, SemanticType};
use common_macro::{IntoRow, Schema, ToRow};
use greptime_proto::v1::column_data_type_extension::TypeExt;
use greptime_proto::v1::value::ValueData;
use greptime_proto::v1::{
ColumnDataType, ColumnDataTypeExtension, ColumnSchema, JsonTypeExtension, Row, SemanticType,
Value,
};
#[derive(ToRow)]
#[derive(ToRow, Schema, IntoRow)]
struct ToRowOwned {
my_value: i32,
#[col(name = "string_value", datatype = "string", semantic = "tag")]
@@ -28,6 +33,11 @@ struct ToRowOwned {
datatype = "TimestampMillisecond"
)]
my_timestamp: i64,
#[allow(dead_code)]
#[col(skip)]
my_skip: i32,
#[col(name = "json_value", datatype = "json")]
my_json: String,
}
#[test]
@@ -38,14 +48,18 @@ fn test_to_row() {
my_bool: true,
my_float: 1.0,
my_timestamp: 1718563200000,
my_skip: 1,
my_json: r#"{"name":"John", "age":30}"#.to_string(),
};
let row = test.to_row();
assert_row(&row);
let schema = test.schema();
let schema = ToRowOwned::schema();
assert_schema(&schema);
let row2 = test.into_row();
assert_row(&row2);
}
#[derive(ToRow)]
#[derive(ToRow, Schema)]
struct ToRowRef<'a> {
my_value: &'a i32,
#[col(name = "string_value", datatype = "string", semantic = "tag")]
@@ -58,6 +72,8 @@ struct ToRowRef<'a> {
datatype = "TimestampMillisecond"
)]
my_timestamp: &'a i64,
#[col(name = "json_value", datatype = "json")]
my_json: &'a str,
}
#[test]
@@ -69,14 +85,15 @@ fn test_to_row_ref() {
my_bool: &true,
my_float: &1.0,
my_timestamp: &1718563200000,
my_json: r#"{"name":"John", "age":30}"#,
};
let row = test.to_row();
assert_row(&row);
let schema = test.schema();
let schema = ToRowRef::schema();
assert_schema(&schema);
}
#[derive(ToRow)]
#[derive(ToRow, IntoRow)]
struct ToRowOptional {
my_value: Option<i32>,
#[col(name = "string_value", datatype = "string", semantic = "tag")]
@@ -91,17 +108,7 @@ struct ToRowOptional {
my_timestamp: i64,
}
#[test]
fn test_to_row_optional() {
let test = ToRowOptional {
my_value: None,
my_string: None,
my_bool: None,
my_float: None,
my_timestamp: 1718563200000,
};
let row = test.to_row();
fn assert_row_optional(row: &Row) {
assert_eq!(row.values.len(), 5);
assert_eq!(row.values[0].value_data, None);
assert_eq!(row.values[1].value_data, None);
@@ -113,8 +120,24 @@ fn test_to_row_optional() {
);
}
#[test]
fn test_to_row_optional() {
let test = ToRowOptional {
my_value: None,
my_string: None,
my_bool: None,
my_float: None,
my_timestamp: 1718563200000,
};
let row = test.to_row();
assert_row_optional(&row);
let row2 = test.into_row();
assert_row_optional(&row2);
}
fn assert_row(row: &Row) {
assert_eq!(row.values.len(), 5);
assert_eq!(row.values.len(), 6);
assert_eq!(
row.values[0].value_data,
Some(greptime_proto::v1::value::ValueData::I32Value(1))
@@ -140,7 +163,7 @@ fn assert_row(row: &Row) {
}
fn assert_schema(schema: &[ColumnSchema]) {
assert_eq!(schema.len(), 5);
assert_eq!(schema.len(), 6);
assert_eq!(schema[0].column_name, "my_value");
assert_eq!(schema[0].datatype, ColumnDataType::Int32 as i32);
assert_eq!(schema[0].semantic_type, SemanticType::Field as i32);
@@ -159,4 +182,13 @@ fn assert_schema(schema: &[ColumnSchema]) {
ColumnDataType::TimestampMillisecond as i32
);
assert_eq!(schema[4].semantic_type, SemanticType::Timestamp as i32);
assert_eq!(schema[5].column_name, "json_value");
assert_eq!(schema[5].datatype, ColumnDataType::Json as i32);
assert_eq!(schema[5].semantic_type, SemanticType::Field as i32);
assert_eq!(
schema[5].datatype_extension,
Some(ColumnDataTypeExtension {
type_ext: Some(TypeExt::JsonType(JsonTypeExtension::JsonBinary as i32))
})
);
}