mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-05-17 13:30:38 +00:00
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:
@@ -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()
|
||||
}
|
||||
|
||||
25
src/common/macro/src/row.rs
Normal file
25
src/common/macro/src/row.rs
Normal 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";
|
||||
128
src/common/macro/src/row/attribute.rs
Normal file
128
src/common/macro/src/row/attribute.rs
Normal 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)
|
||||
}
|
||||
88
src/common/macro/src/row/into_row.rs
Normal file
88
src/common/macro/src/row/into_row.rs
Normal 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 ),* ]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
118
src/common/macro/src/row/schema.rs
Normal file
118
src/common/macro/src/row/schema.rs
Normal 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),* ]
|
||||
}
|
||||
})
|
||||
}
|
||||
88
src/common/macro/src/row/to_row.rs
Normal file
88
src/common/macro/src/row/to_row.rs
Normal 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 ),* ]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
313
src/common/macro/src/row/utils.rs
Normal file
313
src/common/macro/src/row/utils.rs
Normal 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"),
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user