@@ -86,11 +86,13 @@ pub struct S3CompatibleObjectStorage {
8686 s3_client : S3Client ,
8787 uri : Uri ,
8888 bucket : String ,
89- prefix : PathBuf ,
89+ prefix : String ,
9090 multipart_policy : MultiPartPolicy ,
9191 retry_params : RetryParams ,
9292 disable_multi_object_delete : bool ,
9393 disable_multipart_upload : bool ,
94+ // If 0, we don't have any prefix
95+ hash_prefix_cardinality : usize ,
9496}
9597
9698impl fmt:: Debug for S3CompatibleObjectStorage {
@@ -99,6 +101,7 @@ impl fmt::Debug for S3CompatibleObjectStorage {
99101 . debug_struct ( "S3CompatibleObjectStorage" )
100102 . field ( "bucket" , & self . bucket )
101103 . field ( "prefix" , & self . prefix )
104+ . field ( "hash_prefix_cardinality" , & self . hash_prefix_cardinality )
102105 . finish ( )
103106 }
104107}
@@ -181,19 +184,20 @@ impl S3CompatibleObjectStorage {
181184 s3_client,
182185 uri : uri. clone ( ) ,
183186 bucket,
184- prefix,
187+ prefix : prefix . to_string_lossy ( ) . to_string ( ) ,
185188 multipart_policy : MultiPartPolicy :: default ( ) ,
186189 retry_params,
187190 disable_multi_object_delete,
188191 disable_multipart_upload,
192+ hash_prefix_cardinality : s3_storage_config. hash_prefix_cardinality ,
189193 } )
190194 }
191195
192196 /// Sets a specific for all buckets.
193197 ///
194198 /// This method overrides any existing prefix. (It does NOT
195199 /// append the argument to any existing prefix.)
196- pub fn with_prefix ( self , prefix : PathBuf ) -> Self {
200+ pub fn with_prefix ( self , prefix : String ) -> Self {
197201 Self {
198202 s3_client : self . s3_client ,
199203 uri : self . uri ,
@@ -203,6 +207,7 @@ impl S3CompatibleObjectStorage {
203207 retry_params : self . retry_params ,
204208 disable_multi_object_delete : self . disable_multi_object_delete ,
205209 disable_multipart_upload : self . disable_multipart_upload ,
210+ hash_prefix_cardinality : self . hash_prefix_cardinality ,
206211 }
207212 }
208213
@@ -262,12 +267,49 @@ async fn compute_md5<T: AsyncRead + std::marker::Unpin>(mut read: T) -> io::Resu
262267 }
263268 }
264269}
270+ const HEX_ALPHABET : [ u8 ; 16 ] = * b"0123456789abcdef" ;
271+ const UNINITIALIZED_HASH_PREFIX : & str = "00000000" ;
272+
273+ fn build_key ( prefix : & str , relative_path : & str , hash_prefix_cardinality : usize ) -> String {
274+ let mut key = String :: with_capacity (
275+ UNINITIALIZED_HASH_PREFIX . len ( ) + 1 + prefix. len ( ) + 1 + relative_path. len ( ) ,
276+ ) ;
277+ if hash_prefix_cardinality > 1 {
278+ key. push_str ( UNINITIALIZED_HASH_PREFIX ) ;
279+ key. push ( '/' ) ;
280+ }
281+ key. push_str ( prefix) ;
282+ if key. as_bytes ( ) . last ( ) . copied ( ) != Some ( b'/' ) {
283+ key. push ( '/' ) ;
284+ }
285+ key. push_str ( relative_path) ;
286+ // We then set up the prefix.
287+ if hash_prefix_cardinality > 1 {
288+ let key_without_prefix = & key. as_bytes ( ) [ UNINITIALIZED_HASH_PREFIX . len ( ) + 1 ..] ;
289+ let mut prefix_hash: usize =
290+ murmurhash32:: murmurhash3 ( key_without_prefix) as usize % hash_prefix_cardinality;
291+ unsafe {
292+ let prefix_buf: & mut [ u8 ] = & mut key. as_bytes_mut ( ) [ ..UNINITIALIZED_HASH_PREFIX . len ( ) ] ;
293+ for prefix_byte in prefix_buf {
294+ let hex: u8 = HEX_ALPHABET [ ( prefix_hash % 16 ) as usize ] ;
295+ * prefix_byte = hex;
296+ if prefix_hash < 16 {
297+ break ;
298+ }
299+ prefix_hash /= 16 ;
300+ }
301+ }
302+ }
303+ key
304+ }
265305
266306impl S3CompatibleObjectStorage {
267307 fn key ( & self , relative_path : & Path ) -> String {
268- // FIXME: This may not work on Windows.
269- let key_path = self . prefix . join ( relative_path) ;
270- key_path. to_string_lossy ( ) . to_string ( )
308+ build_key (
309+ & self . prefix ,
310+ relative_path. to_string_lossy ( ) . as_ref ( ) ,
311+ self . hash_prefix_cardinality ,
312+ )
271313 }
272314
273315 fn relative_path ( & self , key : & str ) -> PathBuf {
@@ -945,13 +987,13 @@ mod tests {
945987 let s3_client = S3Client :: new ( & sdk_config) ;
946988 let uri = Uri :: for_test ( "s3://bucket/indexes" ) ;
947989 let bucket = "bucket" . to_string ( ) ;
948- let prefix = PathBuf :: new ( ) ;
949990
950991 let mut s3_storage = S3CompatibleObjectStorage {
951992 s3_client,
952993 uri,
953994 bucket,
954- prefix,
995+ prefix : String :: new ( ) ,
996+ hash_prefix_cardinality : 0 ,
955997 multipart_policy : MultiPartPolicy :: default ( ) ,
956998 retry_params : RetryParams :: for_test ( ) ,
957999 disable_multi_object_delete : false ,
@@ -962,7 +1004,7 @@ mod tests {
9621004 PathBuf :: from( "indexes/foo" )
9631005 ) ;
9641006
965- s3_storage. prefix = PathBuf :: from ( "indexes" ) ;
1007+ s3_storage. prefix = "indexes" . to_string ( ) ;
9661008
9671009 assert_eq ! (
9681010 s3_storage. relative_path( "indexes/foo" ) ,
@@ -1000,13 +1042,13 @@ mod tests {
10001042 let s3_client = S3Client :: from_conf ( config) ;
10011043 let uri = Uri :: for_test ( "s3://bucket/indexes" ) ;
10021044 let bucket = "bucket" . to_string ( ) ;
1003- let prefix = PathBuf :: new ( ) ;
10041045
10051046 let s3_storage = S3CompatibleObjectStorage {
10061047 s3_client,
10071048 uri,
10081049 bucket,
1009- prefix,
1050+ prefix : String :: new ( ) ,
1051+ hash_prefix_cardinality : 0 ,
10101052 multipart_policy : MultiPartPolicy :: default ( ) ,
10111053 retry_params : RetryParams :: for_test ( ) ,
10121054 disable_multi_object_delete : true ,
@@ -1041,13 +1083,13 @@ mod tests {
10411083 let s3_client = S3Client :: from_conf ( config) ;
10421084 let uri = Uri :: for_test ( "s3://bucket/indexes" ) ;
10431085 let bucket = "bucket" . to_string ( ) ;
1044- let prefix = PathBuf :: new ( ) ;
10451086
10461087 let s3_storage = S3CompatibleObjectStorage {
10471088 s3_client,
10481089 uri,
10491090 bucket,
1050- prefix,
1091+ prefix : String :: new ( ) ,
1092+ hash_prefix_cardinality : 0 ,
10511093 multipart_policy : MultiPartPolicy :: default ( ) ,
10521094 retry_params : RetryParams :: for_test ( ) ,
10531095 disable_multi_object_delete : false ,
@@ -1123,13 +1165,13 @@ mod tests {
11231165 let s3_client = S3Client :: from_conf ( config) ;
11241166 let uri = Uri :: for_test ( "s3://bucket/indexes" ) ;
11251167 let bucket = "bucket" . to_string ( ) ;
1126- let prefix = PathBuf :: new ( ) ;
11271168
11281169 let s3_storage = S3CompatibleObjectStorage {
11291170 s3_client,
11301171 uri,
11311172 bucket,
1132- prefix,
1173+ prefix : String :: new ( ) ,
1174+ hash_prefix_cardinality : 0 ,
11331175 multipart_policy : MultiPartPolicy :: default ( ) ,
11341176 retry_params : RetryParams :: for_test ( ) ,
11351177 disable_multi_object_delete : false ,
@@ -1216,13 +1258,13 @@ mod tests {
12161258 let s3_client = S3Client :: from_conf ( config) ;
12171259 let uri = Uri :: for_test ( "s3://bucket/indexes" ) ;
12181260 let bucket = "bucket" . to_string ( ) ;
1219- let prefix = PathBuf :: new ( ) ;
12201261
12211262 let s3_storage = S3CompatibleObjectStorage {
12221263 s3_client,
12231264 uri,
12241265 bucket,
1225- prefix,
1266+ prefix : String :: new ( ) ,
1267+ hash_prefix_cardinality : 0 ,
12261268 multipart_policy : MultiPartPolicy :: default ( ) ,
12271269 retry_params : RetryParams :: for_test ( ) ,
12281270 disable_multi_object_delete : false ,
@@ -1233,4 +1275,19 @@ mod tests {
12331275 . await
12341276 . unwrap ( ) ;
12351277 }
1278+
1279+ #[ test]
1280+ fn test_build_key ( ) {
1281+ assert_eq ! ( build_key( "hello" , "coucou" , 0 ) , "hello/coucou" ) ;
1282+ assert_eq ! ( build_key( "hello/" , "coucou" , 0 ) , "hello/coucou" ) ;
1283+ assert_eq ! ( build_key( "hello/" , "coucou" , 1 ) , "hello/coucou" ) ;
1284+ assert_eq ! ( build_key( "hello" , "coucou" , 1 ) , "hello/coucou" ) ;
1285+ assert_eq ! ( build_key( "hello/" , "coucou" , 2 ) , "10000000/hello/coucou" ) ;
1286+ assert_eq ! ( build_key( "hello" , "coucou" , 2 ) , "10000000/hello/coucou" ) ;
1287+ assert_eq ! ( build_key( "hello/" , "coucou" , 16 ) , "d0000000/hello/coucou" ) ;
1288+ assert_eq ! ( build_key( "hello" , "coucou" , 16 ) , "d0000000/hello/coucou" ) ;
1289+ assert_eq ! ( build_key( "hello/" , "coucou" , 17 ) , "50000000/hello/coucou" ) ;
1290+ assert_eq ! ( build_key( "hello" , "coucou" , 17 ) , "50000000/hello/coucou" ) ;
1291+ assert_eq ! ( build_key( "hello/" , "coucou" , 70 ) , "f0000000/hello/coucou" ) ;
1292+ }
12361293}
0 commit comments