1
1
use crate :: storage:: s3:: { s3_client, S3_BUCKET_NAME } ;
2
2
use failure:: { Error , Fail } ;
3
- use postgres:: Connection ;
3
+ use postgres:: { transaction :: Transaction , Connection } ;
4
4
use rusoto_s3:: { DeleteObjectsRequest , ListObjectsV2Request , ObjectIdentifier , S3Client , S3 } ;
5
5
6
6
/// List of directories in docs.rs's underlying storage (either the database or S3) containing a
@@ -14,22 +14,89 @@ enum CrateDeletionError {
14
14
}
15
15
16
16
pub fn delete_crate ( conn : & Connection , name : & str ) -> Result < ( ) , Error > {
17
- let crate_id_res = conn. query ( "SELECT id FROM crates WHERE name = $1" , & [ & name] ) ?;
18
- let crate_id = if crate_id_res. is_empty ( ) {
19
- return Err ( CrateDeletionError :: MissingCrate ( name. into ( ) ) . into ( ) ) ;
20
- } else {
21
- crate_id_res. get ( 0 ) . get ( "id" )
22
- } ;
17
+ let crate_id = get_id ( conn, name) ?;
18
+ delete_crate_from_database ( conn, name, crate_id) ?;
19
+ if let Some ( s3) = s3_client ( ) {
20
+ for prefix in STORAGE_PATHS_TO_DELETE {
21
+ delete_prefix_from_s3 ( & s3, & format ! ( "{}/{}/" , prefix, name) ) ?;
22
+ }
23
+ }
24
+ Ok ( ( ) )
25
+ }
26
+
27
+ pub fn delete_version ( conn : & Connection , name : & str , version : & str ) -> Result < ( ) , Error > {
28
+ delete_version_from_database ( conn, name, version) ?;
23
29
24
- delete_from_database ( conn, name, crate_id) ?;
25
30
if let Some ( s3) = s3_client ( ) {
26
- delete_from_s3 ( & s3, name) ?;
31
+ for prefix in STORAGE_PATHS_TO_DELETE {
32
+ delete_prefix_from_s3 ( & s3, & format ! ( "{}/{}/" , prefix, name) ) ?;
33
+ }
34
+ }
35
+
36
+ Ok ( ( ) )
37
+ }
38
+
39
+ fn get_id ( conn : & Connection , name : & str ) -> Result < i32 , Error > {
40
+ let crate_id_res = conn. query ( "SELECT id FROM crates WHERE name = $1" , & [ & name] ) ?;
41
+ if let Some ( row) = crate_id_res. into_iter ( ) . next ( ) {
42
+ Ok ( row. get ( "id" ) )
43
+ } else {
44
+ Err ( CrateDeletionError :: MissingCrate ( name. into ( ) ) . into ( ) )
27
45
}
46
+ }
28
47
48
+ // metaprogramming!
49
+ // NOTE: it is _crucial_ that table_name and column_name be hard-coded, i.e. NOT user input
50
+ fn delete_metadata (
51
+ transaction : & Transaction ,
52
+ crate_id : i32 ,
53
+ version : & str ,
54
+ table_name : & ' static str ,
55
+ column_name : & ' static str ,
56
+ ) -> Result < ( ) , Error > {
57
+ transaction. execute (
58
+ & format ! ( "DELETE FROM {} WHERE {} IN (SELECT id FROM releases WHERE crate_id = $1 AND version = $2)" , table_name, column_name) ,
59
+ & [ & crate_id, & version] ,
60
+ ) ?;
29
61
Ok ( ( ) )
30
62
}
31
63
32
- fn delete_from_database ( conn : & Connection , name : & str , crate_id : i32 ) -> Result < ( ) , Error > {
64
+ fn delete_version_from_database ( conn : & Connection , name : & str , version : & str ) -> Result < ( ) , Error > {
65
+ let crate_id = get_id ( conn, name) ?;
66
+ let transaction = conn. transaction ( ) ?;
67
+ let metadata = [
68
+ ( "author_rels" , "rid" ) ,
69
+ ( "owner_rels" , "cid" ) ,
70
+ ( "keyword_rels" , "rid" ) ,
71
+ ( "builds" , "rid" ) ,
72
+ ] ;
73
+ for & ( table, column) in & metadata {
74
+ delete_metadata ( & transaction, crate_id, version, table, column) ?;
75
+ }
76
+ transaction. execute (
77
+ "DELETE FROM releases WHERE crate_id = $1 AND version = $2" ,
78
+ & [ & crate_id, & version] ,
79
+ ) ?;
80
+ transaction. execute (
81
+ "UPDATE crates SET latest_version_id = (
82
+ SELECT id FROM releases WHERE release_time = (
83
+ SELECT MAX(release_time) FROM releases WHERE crate_id = $1
84
+ )
85
+ ) WHERE id = $1" ,
86
+ & [ & crate_id] ,
87
+ ) ?;
88
+
89
+ for prefix in STORAGE_PATHS_TO_DELETE {
90
+ transaction. execute (
91
+ "DELETE FROM files WHERE path LIKE $1;" ,
92
+ & [ & format ! ( "{}/{}/{}/%" , prefix, name, version) ] ,
93
+ ) ?;
94
+ }
95
+
96
+ transaction. commit ( ) . map_err ( Into :: into)
97
+ }
98
+
99
+ fn delete_crate_from_database ( conn : & Connection , name : & str , crate_id : i32 ) -> Result < ( ) , Error > {
33
100
let transaction = conn. transaction ( ) ?;
34
101
35
102
transaction. execute (
@@ -68,13 +135,6 @@ fn delete_from_database(conn: &Connection, name: &str, crate_id: i32) -> Result<
68
135
Ok ( ( ) )
69
136
}
70
137
71
- fn delete_from_s3 ( s3 : & S3Client , name : & str ) -> Result < ( ) , Error > {
72
- for prefix in STORAGE_PATHS_TO_DELETE {
73
- delete_prefix_from_s3 ( s3, & format ! ( "{}/{}/" , prefix, name) ) ?;
74
- }
75
- Ok ( ( ) )
76
- }
77
-
78
138
fn delete_prefix_from_s3 ( s3 : & S3Client , name : & str ) -> Result < ( ) , Error > {
79
139
let mut continuation_token = None ;
80
140
loop {
@@ -124,23 +184,25 @@ fn delete_prefix_from_s3(s3: &S3Client, name: &str) -> Result<(), Error> {
124
184
#[ cfg( test) ]
125
185
mod tests {
126
186
use super :: * ;
187
+ use crate :: test:: wrapper;
127
188
use failure:: Error ;
128
189
use postgres:: Connection ;
129
190
191
+ fn crate_exists ( conn : & Connection , name : & str ) -> Result < bool , Error > {
192
+ Ok ( !conn
193
+ . query ( "SELECT * FROM crates WHERE name = $1;" , & [ & name] ) ?
194
+ . is_empty ( ) )
195
+ }
196
+
197
+ fn release_exists ( conn : & Connection , id : i32 ) -> Result < bool , Error > {
198
+ Ok ( !conn
199
+ . query ( "SELECT * FROM releases WHERE id = $1;" , & [ & id] ) ?
200
+ . is_empty ( ) )
201
+ }
202
+
130
203
#[ test]
131
204
fn test_delete_from_database ( ) {
132
- fn crate_exists ( conn : & Connection , name : & str ) -> Result < bool , Error > {
133
- Ok ( !conn
134
- . query ( "SELECT * FROM crates WHERE name = $1;" , & [ & name] ) ?
135
- . is_empty ( ) )
136
- }
137
- fn release_exists ( conn : & Connection , id : i32 ) -> Result < bool , Error > {
138
- Ok ( !conn
139
- . query ( "SELECT * FROM releases WHERE id = $1;" , & [ & id] ) ?
140
- . is_empty ( ) )
141
- }
142
-
143
- crate :: test:: wrapper ( |env| {
205
+ wrapper ( |env| {
144
206
let db = env. db ( ) ;
145
207
146
208
// Create fake packages in the database
@@ -168,7 +230,7 @@ mod tests {
168
230
. get ( 0 )
169
231
. get ( "id" ) ;
170
232
171
- delete_from_database ( & db. conn ( ) , "package-1" , * pkg1_id) ?;
233
+ delete_crate_from_database ( & db. conn ( ) , "package-1" , * pkg1_id) ?;
172
234
173
235
assert ! ( !crate_exists( & db. conn( ) , "package-1" ) ?) ;
174
236
assert ! ( crate_exists( & db. conn( ) , "package-2" ) ?) ;
@@ -179,4 +241,60 @@ mod tests {
179
241
Ok ( ( ) )
180
242
} ) ;
181
243
}
244
+
245
+ #[ test]
246
+ fn test_delete_version ( ) {
247
+ wrapper ( |env| {
248
+ fn authors ( conn : & Connection , crate_id : i32 ) -> Result < Vec < String > , Error > {
249
+ Ok ( conn
250
+ . query (
251
+ "SELECT name FROM authors
252
+ INNER JOIN author_rels ON authors.id = author_rels.aid
253
+ INNER JOIN releases ON author_rels.rid = releases.id
254
+ WHERE releases.crate_id = $1" ,
255
+ & [ & crate_id] ,
256
+ ) ?
257
+ . into_iter ( )
258
+ . map ( |row| row. get ( 0 ) )
259
+ . collect ( ) )
260
+ }
261
+
262
+ let db = env. db ( ) ;
263
+ let v1 = db
264
+ . fake_release ( )
265
+ . name ( "a" )
266
+ . version ( "1.0.0" )
267
+ . author ( "malicious actor" )
268
+ . create ( ) ?;
269
+ let v2 = db
270
+ . fake_release ( )
271
+ . name ( "a" )
272
+ . version ( "2.0.0" )
273
+ . author ( "Peter Rabbit" )
274
+ . create ( ) ?;
275
+ assert ! ( release_exists( & db. conn( ) , v1) ?) ;
276
+ assert ! ( release_exists( & db. conn( ) , v2) ?) ;
277
+ let crate_id = db
278
+ . conn ( )
279
+ . query ( "SELECT crate_id FROM releases WHERE id = $1" , & [ & v1] ) ?
280
+ . into_iter ( )
281
+ . next ( )
282
+ . unwrap ( )
283
+ . get ( 0 ) ;
284
+ assert_eq ! (
285
+ authors( & db. conn( ) , crate_id) ?,
286
+ vec![ "malicious actor" . to_string( ) , "Peter Rabbit" . to_string( ) ]
287
+ ) ;
288
+
289
+ delete_version ( & db. conn ( ) , "a" , "1.0.0" ) ?;
290
+ assert ! ( !release_exists( & db. conn( ) , v1) ?) ;
291
+ assert ! ( release_exists( & db. conn( ) , v2) ?) ;
292
+ assert_eq ! (
293
+ authors( & db. conn( ) , crate_id) ?,
294
+ vec![ "Peter Rabbit" . to_string( ) ]
295
+ ) ;
296
+
297
+ Ok ( ( ) )
298
+ } )
299
+ }
182
300
}
0 commit comments