Skip to content

Commit

Permalink
[Rust] support row format (#1196)
Browse files Browse the repository at this point in the history
* feature: rust support row

* chore: format code

* chore: format code

* chore: format code
  • Loading branch information
theweipeng authored Dec 3, 2023
1 parent ddfc841 commit 0dc98ef
Show file tree
Hide file tree
Showing 12 changed files with 579 additions and 132 deletions.
2 changes: 1 addition & 1 deletion rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ resolver = "2"

[workspace.package]
version = "0.0.1"
rust-version = "1.65.0"
rust-version = "1.70"
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/alipay/fury"
Expand Down
149 changes: 149 additions & 0 deletions rust/fury-derive/src/fury_meta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright 2023 The Fury Authors
//
// 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_macro::TokenStream;
use quote::quote;
use syn::{Field, Fields};

pub fn sorted_fields(fields: &Fields) -> Vec<&Field> {
let mut fields = fields.iter().collect::<Vec<&Field>>();
fields.sort_by(|a, b| a.ident.cmp(&b.ident));
fields
}

pub fn derive_fury_meta(ast: &syn::DeriveInput, tag: String) -> TokenStream {
let name = &ast.ident;
let fields = match &ast.data {
syn::Data::Struct(s) => sorted_fields(&s.fields),
_ => {
panic!("only struct be supported")
}
};
let props = fields.iter().map(|field| {
let ty = &field.ty;
let name = format!("{}", field.ident.as_ref().expect("should be field name"));
quote! {
(#name, <#ty as fury::FuryMeta>::ty(), <#ty as fury::FuryMeta>::tag())
}
});
let name_hash_static: proc_macro2::Ident =
syn::Ident::new(&format!("HASH_{}", name).to_uppercase(), name.span());

let gen = quote! {

lazy_static::lazy_static! {
static ref #name_hash_static: u32 = fury::compute_struct_hash(vec![#(#props),*]);
}

impl fury::FuryMeta for #name {
fn tag() -> &'static str {
#tag
}

fn hash() -> u32 {
*(#name_hash_static)
}

fn ty() -> fury::FieldType {
fury::FieldType::FuryTypeTag
}
}
};
gen.into()
}

pub fn derive_serialize(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let fields = match &ast.data {
syn::Data::Struct(s) => sorted_fields(&s.fields),
_ => {
panic!("only struct be supported")
}
};

let accessor_exprs = fields.iter().map(|field| {
let ty = &field.ty;
let ident = &field.ident;
quote! {
<#ty as fury::Serialize>::serialize(&self.#ident, serializer);
}
});

let reserved_size_exprs = fields.iter().map(|field| {
let ty = &field.ty;
// each field have one byte ref tag and two byte type id
quote! {
<#ty as fury::Serialize>::reserved_space() + fury::SIZE_OF_REF_AND_TYPE
}
});

let tag_bytelen = format!("{}", name).len();

let gen = quote! {
impl fury::Serialize for #name {
fn write(&self, serializer: &mut fury::SerializerState) {
// write tag string
serializer.write_tag(<#name as fury::FuryMeta>::tag());
// write tag hash
serializer.writer.u32(<#name as fury::FuryMeta>::hash());
// write fields
#(#accessor_exprs)*
}

fn reserved_space() -> usize {
// struct have four byte hash
#tag_bytelen + 4 + #(#reserved_size_exprs)+*
}
}
};
gen.into()
}

pub fn derive_deserilize(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let fields = match &ast.data {
syn::Data::Struct(s) => sorted_fields(&s.fields),
_ => {
panic!("only struct be supported")
}
};

let exprs = fields.iter().map(|field| {
let ty = &field.ty;
let ident = &field.ident;
quote! {
#ident: <#ty as fury::Deserialize>::deserialize(deserializer)?
}
});

let gen = quote! {
impl<'de> fury::Deserialize for #name {
fn read(deserializer: &mut fury::DeserializerState) -> Result<Self, fury::Error> {
// read tag string
deserializer.read_tag()?;
// read tag hash
let hash = deserializer.reader.u32();
let expected = <#name as fury::FuryMeta>::hash();
if(hash != expected) {
Err(fury::Error::StructHash{ expected, actial: hash })
} else {
Ok(Self {
#(#exprs),*
})
}
}
}
};
gen.into()
}
98 changes: 98 additions & 0 deletions rust/fury-derive/src/fury_row.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2023 The Fury Authors
//
// 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_macro::TokenStream;
use quote::quote;

use crate::fury_meta::sorted_fields;

pub fn derive_row(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let fields = match &ast.data {
syn::Data::Struct(s) => sorted_fields(&s.fields),
_ => {
panic!("only struct be supported")
}
};

let write_exprs = fields.iter().map(|field| {
let ty = &field.ty;
let ident = field.ident.as_ref().expect("field should provide ident");

quote! {
if <#ty as fury::row::Row<'a>>::schema().is_container() {
row_writer.write_offset_size(0);
row_writer.point_to(<#ty as fury::row::Row<'a>>::schema().num_fields());
<#ty as fury::row::Row<'a>>::write(&v.#ident, row_writer);
} else {
let size = <#ty as fury::row::Row<'a>>::write(&v.#ident, row_writer);
row_writer.write_offset_size(size)
}
}
});

let getter_exprs = fields.iter().enumerate().map(|(index, field)| {
let ty = &field.ty;
let ident = field.ident.as_ref().expect("field should provide ident");
let getter_name: proc_macro2::Ident =
syn::Ident::new(&format!("get_{}", ident), ident.span());

quote! {
pub fn #getter_name(&self) -> <#ty as fury::row::Row<'a>>::ReadResult {
if <#ty as fury::row::Row<'a>>::schema().is_container() {
let row_reader = self.row_reader.point_to(<#ty as fury::row::Row<'a>>::schema().num_fields(), self.row_reader.get_offset_size_absolute(#index).0 as usize);
<#ty as fury::row::Row<'a>>::read(#index, row_reader)
} else {
<#ty as fury::row::Row<'a>>::read(#index, self.row_reader)
}
}
}
});

let getter: proc_macro2::Ident =
syn::Ident::new(&format!("{}FuryRowGetter", name), name.span());

let num_fields = fields.len();

let gen = quote! {
struct #getter<'a> {
row_reader: fury::row::RowReader<'a>
}

impl<'a> #getter<'a> {
#(#getter_exprs)*
}

impl<'a> fury::row::Row<'a> for #name {

type ReadResult = #getter<'a>;

fn write(v: &Self, row_writer: &mut fury::row::RowWriter) -> usize {
let start = row_writer.writer.len();
#(#write_exprs);*;
let end = row_writer.writer.len();
end - start
}

fn read(idx: usize, row_reader: fury::row::RowReader<'a>) -> Self::ReadResult {
#getter::<'a>{ row_reader }
}

fn schema() -> fury::row::Schema {
fury::row::Schema::new(#num_fields, true)
}
}
};
gen.into()
}
Loading

0 comments on commit 0dc98ef

Please sign in to comment.