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 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 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 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 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 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}