Skip to content

Commit 21d77d0

Browse files
authored
Merge pull request #156 from nicklan/schema-derive-macro
Schema derive macro (reborn)
2 parents 2df263d + 12a5241 commit 21d77d0

19 files changed

+482
-390
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[workspace]
22
members = [
33
"acceptance",
4+
"derive-macros",
45
"ffi",
56
"kernel",
67
"kernel/examples/dump-table", # todo: put back to `examples/*` when inspect-table is fixed

acceptance/src/meta.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ impl TestCaseInfo {
8787
properties: metadata
8888
.configuration
8989
.iter()
90-
.map(|(k, v)| (k.clone(), v.clone().unwrap()))
90+
.map(|(k, v)| (k.clone(), v.clone()))
9191
.collect(),
9292
min_reader_version: protocol.min_reader_version as u32,
9393
min_writer_version: protocol.min_writer_version as u32,

derive-macros/Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "derive-macros"
3+
authors.workspace = true
4+
edition.workspace = true
5+
homepage.workspace = true
6+
keywords.workspace = true
7+
license.workspace = true
8+
repository.workspace = true
9+
readme.workspace = true
10+
version.workspace = true
11+
12+
[lib]
13+
proc-macro = true
14+
15+
[dependencies]
16+
proc-macro2 = "1"
17+
syn = { version = "2.0", features = ["extra-traits"] }
18+
quote = "1.0"
19+
20+

derive-macros/src/lib.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use proc_macro2::{Ident, TokenStream};
2+
use quote::{quote, quote_spanned};
3+
use syn::spanned::Spanned;
4+
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, PathArguments, Type};
5+
6+
/// Derive a `deltakernel::schemas::ToDataType` implementation for the annotated struct. The actual
7+
/// field names in the schema (and therefore of the struct members) are all mandated by the Delta
8+
/// spec, and so the user of this macro is responsible for ensuring that
9+
/// e.g. `Metadata::schema_string` is the snake_case-ified version of `schemaString` from [Delta's
10+
/// Change Metadata](https://github.com/delta-io/delta/blob/master/PROTOCOL.md#change-metadata)
11+
/// action (this macro allows the use of standard rust snake_case, and will convert to the correct
12+
/// delta schema camelCase version).
13+
#[proc_macro_derive(Schema)]
14+
pub fn derive_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
15+
let input = parse_macro_input!(input as DeriveInput);
16+
let struct_ident = input.ident;
17+
18+
let schema_fields = gen_schema_fields(&input.data);
19+
let output = quote! {
20+
#[automatically_derived]
21+
impl crate::actions::schemas::ToDataType for #struct_ident {
22+
fn to_data_type() -> crate::schema::DataType {
23+
use crate::actions::schemas::{ToDataType, GetStructField};
24+
crate::schema::StructType::new(vec![
25+
#schema_fields
26+
]).into()
27+
}
28+
}
29+
};
30+
proc_macro::TokenStream::from(output)
31+
}
32+
33+
// turn our struct name into the schema name, goes from snake_case to camelCase
34+
fn get_schema_name(name: &Ident) -> Ident {
35+
let snake_name = name.to_string();
36+
let mut next_caps = false;
37+
let ret: String = snake_name
38+
.chars()
39+
.filter_map(|c| {
40+
if c == '_' {
41+
next_caps = true;
42+
None
43+
} else if next_caps {
44+
next_caps = false;
45+
// This assumes we're using ascii, should be okay
46+
Some(c.to_ascii_uppercase())
47+
} else {
48+
Some(c)
49+
}
50+
})
51+
.collect();
52+
Ident::new(&ret, name.span())
53+
}
54+
55+
fn gen_schema_fields(data: &Data) -> TokenStream {
56+
let fields = match data {
57+
Data::Struct(DataStruct {
58+
fields: Fields::Named(fields),
59+
..
60+
}) => &fields.named,
61+
_ => panic!("this derive macro only works on structs with named fields"),
62+
};
63+
64+
let schema_fields = fields.iter().map(|field| {
65+
let name = field.ident.as_ref().unwrap(); // we know these are named fields
66+
let name = get_schema_name(name);
67+
match field.ty {
68+
Type::Path(ref type_path) => {
69+
let type_path_quoted = type_path.path.segments.iter().map(|segment| {
70+
let segment_ident = &segment.ident;
71+
match &segment.arguments {
72+
PathArguments::None => quote! { #segment_ident :: },
73+
PathArguments::AngleBracketed(angle_args) => quote! { #segment_ident::#angle_args :: },
74+
_ => panic!("Can only handle <> type path args"),
75+
}
76+
});
77+
quote_spanned! { field.span() => #(#type_path_quoted),* get_struct_field(stringify!(#name))}
78+
}
79+
_ => {
80+
panic!("Can't handle type: {:?}", field.ty);
81+
}
82+
}
83+
});
84+
quote! { #(#schema_fields),* }
85+
}

kernel/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ url = "2"
2727
uuid = "1.3.0"
2828
z85 = "3.0.5"
2929

30+
# bring in our derive macros
31+
derive-macros = { path = "../derive-macros" }
32+
3033
# used for developer-visibility
3134
visibility = "0.1.0"
3235

kernel/src/actions/deletion_vector.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ use std::io::{Cursor, Read};
44
use std::sync::Arc;
55

66
use bytes::Bytes;
7+
use derive_macros::Schema;
78
use roaring::RoaringTreemap;
89
use url::Url;
910

1011
use crate::{DeltaResult, Error, FileSystemClient};
1112

12-
#[derive(Debug, Clone, PartialEq, Eq)]
13+
#[derive(Debug, Clone, PartialEq, Eq, Schema)]
1314
pub struct DeletionVectorDescriptor {
1415
/// A single character to indicate how to access the DV. Legal options are: ['u', 'i', 'p'].
1516
pub storage_type: String,

0 commit comments

Comments
 (0)