Skip to main content

tariff/
client.rs

1use std::fs::{File, OpenOptions};
2use std::fmt::Write as FmtWrite;
3use std::io::{BufRead, BufReader, Write};
4
5use std::error::Error;
6
7use bson::{Bson, Document};
8use mongodb::{Client, ClientOptions, ThreadedClient};
9use mongodb::common::{ReadMode, ReadPreference};
10use mongodb::cursor::Cursor;
11use mongodb::db::ThreadedDatabase;
12use rustc_serialize::json::{Json, Object};
13
14pub struct ImportExportClient {
15    client: Client,
16}
17
18impl ImportExportClient {
19    /// Create a new client for importing/exporting MongoDB data
20    ///
21    /// # Arguments
22    ///
23    /// `host` - The host to connect to.
24    /// `port` - The port to connect to.
25    ///
26    /// # Return value
27    ///
28    /// Returns a new client if the client successfully connects to the database,
29    /// or an error string on failure.
30    pub fn new<'a>(host: &str, port: u16, secondary: bool) -> Result<Self, &'a str> {
31        let client_result = if secondary {
32            let mut options = ClientOptions::new();
33            let preference = ReadPreference::new(ReadMode::Secondary, None);
34            options.read_preference = Some(preference);
35            let uri = format!("mongodb://{}:{}", host, port);
36            println!("Trying to connect to secondary...");
37            Client::with_uri_and_options(&uri, options)
38        } else {
39            Client::connect(host, port)
40        };
41
42        match client_result {
43            Ok(client) => Ok(ImportExportClient { client: client }),
44            Err(_) => Err("Unable to connect to database.")
45        }
46    }
47
48    /// Export a collection in the database to a JSON file.
49    ///
50    /// # Arguments
51    ///
52    /// `db_name` - The name of the database to export from.
53    /// `coll_name` - The name of the collection to export.
54    /// `out` - The name of the file to store the outputted data.
55    ///
56    /// # Return value
57    ///
58    /// Returns nothing on success, or an error string on failure.
59    pub fn export_collection(&mut self, db_name: &str, coll_name: &str, out: &str) -> Result<(), &str> {
60        let mut out = match OpenOptions::new().write(true).create(true).truncate(true).open(out) {
61            Ok(file) => file,
62            Err(_) => return Err("Unable to open file.")
63        };
64
65        let cursor = try!(self.get_collection(db_name, coll_name));
66
67        for result in cursor {
68            let doc = match result {
69                Ok(doc) => doc,
70                Err(_) => return Err("Unable to read document from database")
71            };
72
73            let json = Bson::Document(doc).to_json();
74
75            match writeln!(out, "{}", json) {
76                Ok(_) => (),
77                Err(_) => return Err("Unable to write document to file.")
78            };
79        }
80
81        Ok(())
82    }
83
84    /// Export all collections in the database to a JSON file.
85    ///
86    /// # Arguments
87    ///
88    /// `db_name` - The name of the database to export.
89    /// `out` - The name of the file to store the outputted data.
90    ///
91    /// # Return value
92    ///
93    /// Returns nothing on success, or an error string on failure.
94    pub fn export_all(&self, db_name: &str, out: &str) -> Result<(), &str> {
95        let colls = try!(self.get_collection_names(db_name));
96        let mut object = Object::new();
97
98        for coll in colls {
99            let cursor = try!(self.get_collection(db_name, &coll));
100            let mut jsons = vec![];
101
102            for result in cursor {
103                let doc = match result {
104                    Ok(doc) => doc,
105                    Err(_) => return Err("Unable to read document from database")
106                };
107
108                jsons.push(Bson::Document(doc).to_json());
109            }
110
111            object.insert(coll, Json::Array(jsons));
112        }
113
114        let mut out = match OpenOptions::new().write(true).create(true).truncate(true).open(out) {
115            Ok(file) => file,
116            Err(_) => return Err("Unable to open file.")
117        };
118
119        match writeln!(out, "{}", Json::Object(object)) {
120            Ok(_) => Ok(()),
121            Err(_) => return Err("Unable to write document to file.")
122        }
123    }
124
125    fn get_collection_names(&self, db_name: &str) -> Result<Vec<String>, &str> {
126        let db = self.client.db(db_name);
127
128        let cursor = match db.list_collections(None) {
129            Ok(cursor) => cursor,
130            Err(_) => return Err("Unable to get list of collections")
131        };
132
133        let mut collections = vec![];
134
135        for result in cursor {
136            let doc = match result {
137                Ok(doc) => doc,
138                Err(_) => return Err("Error getting collection document")
139            };
140
141            match doc.get("name") {
142                Some(&Bson::String(ref s)) => collections.push(s.to_owned()),
143                _ => return Err("Invalid collection document returned")
144            };
145        }
146
147        Ok(collections)
148    }
149
150    fn get_collection(&self, db_name: &str, coll_name: &str) -> Result<Cursor, &str> {
151        let db = self.client.db(db_name);
152        let coll = db.collection(coll_name);
153
154        match coll.find(None, None) {
155            Ok(docs) => Ok(docs),
156            Err(_) => Err("Unable to query database.")
157        }
158    }
159
160    /// Import a collection to the database from a JSON file.
161    ///
162    /// # Arguments
163    ///
164    /// `db_name` - The name of the database to import into.
165    /// `coll_name` - The name of the collection to import into.
166    /// `input` - The name of the file containing the data to import.
167    ///
168    /// # Return value
169    ///
170    /// Returns nothing on success, or an error string on failure.
171    pub fn import_all(&self, db_name: &str, input: &str) -> Result<(), &str> {
172        let mut file = match File::open(input) {
173            Ok(file) => file,
174            Err(_) => return Err("Unable to open file")
175        };
176
177        let obj = match Json::from_reader(&mut file) {
178            Ok(Json::Object(obj)) => obj,
179            _ => return Err("Invalid top-level JSON object in file")
180        };
181
182        for (coll_name, json) in obj {
183            let mut docs = vec![];
184
185            let array = match json {
186                Json::Array(array) => array,
187                _ => return Err("Invalid JSON array as value of top-level object")
188            };
189
190            for json in array {
191                match json {
192                    Json::Object(obj) => {
193                        let mut doc = Document::new();
194
195                        for (key, value) in obj {
196                            doc.insert(key, Bson::from_json(&value));
197                        }
198
199                        docs.push(doc);
200                    },
201                    _ => return Err("Invalid JSON object in collection array")
202                };
203            }
204
205            println!("{}.{}", db_name, coll_name);
206            try!(self.import_documents(db_name, &coll_name, docs));
207        }
208
209        Ok(())
210    }
211
212    /// Import a set of collections to the database from a JSON file.
213    ///
214    /// # Arguments
215    ///
216    /// `db_name` - The name of the database to import into.
217    /// `input` - The name of the file containing the data to import.
218    ///
219    /// # Return value
220    ///
221    /// Returns nothing on success, or an error string on failure.
222    pub fn import_collection(&self, db_name: &str, coll_name: &str, input: &str) -> Result<(), &str> {
223        let file = match File::open(input) {
224            Ok(file) => file,
225            Err(_) => return Err("Unable to open file")
226        };
227
228        let reader = BufReader::new(file);
229        let mut docs = vec![];
230
231        for result in reader.lines() {
232            let line = match result {
233                Ok(line) => line,
234                Err(_) => return Err("Unable to read document from file")
235            };
236
237            match Json::from_str(&line) {
238                Ok(Json::Object(obj)) => {
239                    let mut doc = Document::new();
240
241                    for (key, value) in obj {
242                        doc.insert(key, Bson::from_json(&value));
243                    }
244
245                    docs.push(doc);
246                }
247                _ => return Err("Invalid JSON object in file")
248            };
249        }
250
251        self.import_documents(db_name, coll_name, docs)
252    }
253
254    fn import_documents(&self, db_name: &str, coll_name: &str, docs: Vec<Document>) -> Result<(), &str> {
255        let db = self.client.db(db_name);
256        let coll = db.collection(coll_name);
257        let chunk_size = if coll_name.eq("system.indexes") {
258            1
259        } else {
260            1000
261        };
262
263        for chunk in docs.chunks(chunk_size) {
264            match coll.insert_many(chunk.to_owned(), None) {
265                Ok(_) => (),
266                Err(e) => {
267                    println!("{}", e.description());
268                    return Err("Unable to insert documents")
269                }
270            };
271        }
272
273        Ok(())
274    }
275}