PageRenderTime 65ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 1ms

/test/javascript/tests/replication.js

http://github.com/apache/couchdb
JavaScript | 1897 lines | 1346 code | 294 blank | 257 comment | 113 complexity | 300db67197bfe964cf1ddbdffbeb0b25 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. // Licensed under the Apache License, Version 2.0 (the "License"); you may not
  2. // use this file except in compliance with the License. You may obtain a copy of
  3. // the License at
  4. //
  5. // http://www.apache.org/licenses/LICENSE-2.0
  6. //
  7. // Unless required by applicable law or agreed to in writing, software
  8. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. // License for the specific language governing permissions and limitations under
  11. // the License.
  12. couchTests.replication = function(debug) {
  13. // return console.log('TODO');
  14. if (debug) debugger;
  15. var host = CouchDB.host;
  16. // as we change names during execution, do NOT use test_suite_db or a
  17. // pre-computed value like ''+sourceDb.name (compute only on use)
  18. var sourceDb;
  19. var targetDb;
  20. var dbPairsPrefixes = [
  21. {
  22. source: "",
  23. target: ""
  24. },
  25. {
  26. source: CouchDB.protocol + host + "/",
  27. target: ""
  28. },
  29. {
  30. source: "",
  31. target: CouchDB.protocol + host + "/"
  32. },
  33. {
  34. source: CouchDB.protocol + host + "/",
  35. target: CouchDB.protocol + host + "/"
  36. }
  37. ];
  38. var att1_data = CouchDB.request("GET", "/_utils/script/test/lorem.txt");
  39. att1_data = att1_data.responseText;
  40. var att2_data = CouchDB.request("GET", "/_utils/script/test/lorem_b64.txt");
  41. att2_data = att2_data.responseText;
  42. var sourceInfo, targetInfo;
  43. var docs, doc, copy;
  44. var repResult;
  45. var i, j, k;
  46. function makeAttData(minSize) {
  47. var data = att1_data;
  48. while (data.length < minSize) {
  49. data = data + att1_data;
  50. }
  51. return data;
  52. }
  53. function runAllNodes(callback) {
  54. // new and fancy: clustered version: pull cluster_members and walk over all of them
  55. var xhr = CouchDB.request("GET", "/_membership");
  56. T(xhr.status === 200);
  57. JSON.parse(xhr.responseText).cluster_nodes.forEach(callback);
  58. }
  59. function runFirstNode(callback) {
  60. // new and fancy: clustered version: pull cluster_members and walk over all of them
  61. var xhr = CouchDB.request("GET", "/_membership");
  62. T(xhr.status === 200);
  63. var node = JSON.parse(xhr.responseText).cluster_nodes[0];
  64. return callback(node);
  65. }
  66. function getCompressionInfo() {
  67. return runFirstNode(function(node) {
  68. var xhr = CouchDB.request(
  69. "GET",
  70. "_node/" + node + "/_config/attachments"
  71. );
  72. T(xhr.status === 200);
  73. var res = JSON.parse(xhr.responseText);
  74. return {"level": res.compression_level, "types": res.compressible_types};
  75. });
  76. }
  77. function enableAttCompression(level, types) {
  78. runAllNodes(function(node) {
  79. var xhr = CouchDB.request(
  80. "PUT",
  81. "_node/" + node + "/_config/attachments/compression_level",
  82. {
  83. body: JSON.stringify(level),
  84. headers: {"X-Couch-Persist": "false"}
  85. }
  86. );
  87. T(xhr.status === 200);
  88. xhr = CouchDB.request(
  89. "PUT",
  90. "_node/" + node + "/_config/attachments/compressible_types",
  91. {
  92. body: JSON.stringify(types),
  93. headers: {"X-Couch-Persist": "false"}
  94. }
  95. );
  96. T(xhr.status === 200);
  97. });
  98. }
  99. function disableAttCompression() {
  100. runAllNodes(function(node) {
  101. var xhr = CouchDB.request(
  102. "PUT",
  103. "_node/" + node + "/_config/attachments/compression_level",
  104. {
  105. body: JSON.stringify("0"),
  106. headers: {"X-Couch-Persist": "false"}
  107. }
  108. );
  109. T(xhr.status === 200);
  110. });
  111. }
  112. function populateSourceDb(docs, dontRecreateDb) {
  113. if(dontRecreateDb !== true) {
  114. if(sourceDb) {
  115. sourceDb.deleteDb();
  116. }
  117. sourceDb = new CouchDB(get_random_db_name() + "_src",{"X-Couch-Full-Commit":"false"});
  118. sourceDb.createDb();
  119. }
  120. for (var i = 0; i < docs.length; i++) {
  121. var doc = docs[i];
  122. delete doc._rev;
  123. }
  124. if (docs.length > 0) {
  125. sourceDb.bulkSave(docs);
  126. }
  127. }
  128. function populateTargetDb(docs, dontRecreateDb) {
  129. if(dontRecreateDb !== true) {
  130. if(targetDb) {
  131. targetDb.deleteDb();
  132. }
  133. targetDb = new CouchDB(get_random_db_name() + "_tgt",{"X-Couch-Full-Commit":"false"});
  134. targetDb.createDb();
  135. }
  136. for (var i = 0; i < docs.length; i++) {
  137. var doc = docs[i];
  138. delete doc._rev;
  139. }
  140. if (docs.length > 0) {
  141. targetDb.bulkSave(docs);
  142. }
  143. }
  144. function addAtt(db, doc, attName, attData, type) {
  145. var uri = "/" + db.name + "/" + encodeURIComponent(doc._id) + "/" + attName;
  146. if (doc._rev) {
  147. uri += "?rev=" + doc._rev;
  148. }
  149. var xhr = CouchDB.request("PUT", uri, {
  150. headers: {
  151. "Content-Type": type
  152. },
  153. body: attData
  154. });
  155. T(xhr.status === 201);
  156. doc._rev = JSON.parse(xhr.responseText).rev;
  157. }
  158. function compareObjects(o1, o2) {
  159. for (var p in o1) {
  160. if (o1[p] === null && o2[p] !== null) {
  161. return false;
  162. } else if (typeof o1[p] === "object") {
  163. if ((typeof o2[p] !== "object") || o2[p] === null) {
  164. return false;
  165. }
  166. if (!arguments.callee(o1[p], o2[p])) {
  167. return false;
  168. }
  169. } else {
  170. if (o1[p] !== o2[p]) {
  171. return false;
  172. }
  173. }
  174. }
  175. return true;
  176. }
  177. function getTask(rep_id, delay) {
  178. var t0 = new Date();
  179. var t1;
  180. do {
  181. var xhr = CouchDB.request("GET", "/_active_tasks");
  182. var tasks = JSON.parse(xhr.responseText);
  183. for(var i = 0; i < tasks.length; i++) {
  184. if(tasks[i].replication_id == repResult._local_id) {
  185. return tasks[i];
  186. }
  187. }
  188. sleep(500);
  189. t1 = new Date();
  190. } while((t1 - t0) <= delay);
  191. return null;
  192. }
  193. function waitForSeq(sourceDb, targetDb, rep_id) {
  194. var sourceSeq = sourceDb.info().update_seq,
  195. t0 = new Date(),
  196. t1,
  197. ms = 3000;
  198. do {
  199. var task = getTask(rep_id, 0);
  200. if(task && task["through_seq"] == sourceSeq) {
  201. return;
  202. }
  203. t1 = new Date();
  204. } while (((t1 - t0) <= ms));
  205. }
  206. // test simple replications (not continuous, not filtered), including
  207. // conflict creation
  208. docs = makeDocs(1, 21);
  209. docs.push({
  210. _id: "_design/foo",
  211. language: "javascript",
  212. value: "ddoc"
  213. });
  214. for (i = 0; i < dbPairsPrefixes.length; i++) {
  215. populateSourceDb(docs);
  216. populateTargetDb([]);
  217. // add some attachments
  218. for (j = 10; j < 15; j++) {
  219. addAtt(sourceDb, docs[j], "readme.txt", att1_data, "text/plain");
  220. }
  221. repResult = CouchDB.replicate(dbPairsPrefixes[i].source+sourceDb.name, dbPairsPrefixes[i].target+targetDb.name);
  222. TEquals(true, repResult.ok);
  223. sourceInfo = sourceDb.info();
  224. targetInfo = targetDb.info();
  225. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  226. TEquals('string', typeof repResult.session_id);
  227. // we can't rely on sequences in a cluster
  228. //TEquals(repResult.source_last_seq, sourceInfo.update_seq);
  229. TEquals(true, repResult.history instanceof Array);
  230. TEquals(1, repResult.history.length);
  231. TEquals(repResult.history[0].session_id, repResult.session_id);
  232. TEquals('string', typeof repResult.history[0].start_time);
  233. TEquals('string', typeof repResult.history[0].end_time);
  234. TEquals(0, repResult.history[0].start_last_seq);
  235. // we can't rely on sequences in a cluster
  236. //TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  237. //TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  238. TEquals(sourceInfo.doc_count, repResult.history[0].missing_checked);
  239. TEquals(sourceInfo.doc_count, repResult.history[0].missing_found);
  240. TEquals(sourceInfo.doc_count, repResult.history[0].docs_read);
  241. TEquals(sourceInfo.doc_count, repResult.history[0].docs_written);
  242. TEquals(0, repResult.history[0].doc_write_failures);
  243. for (j = 0; j < docs.length; j++) {
  244. doc = docs[j];
  245. copy = targetDb.open(doc._id);
  246. T(copy !== null);
  247. TEquals(true, compareObjects(doc, copy));
  248. if (j >= 10 && j < 15) {
  249. var atts = copy._attachments;
  250. TEquals('object', typeof atts);
  251. TEquals('object', typeof atts["readme.txt"]);
  252. TEquals(2, atts["readme.txt"].revpos);
  253. TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
  254. TEquals(true, atts["readme.txt"].stub);
  255. var att_copy = CouchDB.request(
  256. "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
  257. ).responseText;
  258. TEquals(att1_data.length, att_copy.length);
  259. TEquals(att1_data, att_copy);
  260. }
  261. }
  262. // add one more doc to source, more attachments to some existing docs
  263. // and replicate again
  264. var newDoc = {
  265. _id: "foo666",
  266. value: "d"
  267. };
  268. TEquals(true, sourceDb.save(newDoc).ok);
  269. // add some more attachments
  270. for (j = 10; j < 15; j++) {
  271. addAtt(sourceDb, docs[j], "data.dat", att2_data, "application/binary");
  272. }
  273. repResult = CouchDB.replicate(dbPairsPrefixes[i].source+sourceDb.name, dbPairsPrefixes[i].target+targetDb.name);
  274. TEquals(true, repResult.ok);
  275. sourceInfo = sourceDb.info();
  276. targetInfo = targetDb.info();
  277. TEquals(targetInfo.doc_count, sourceInfo.doc_count);
  278. TEquals('string', typeof repResult.session_id);
  279. // we can't rely on sequences in a cluster
  280. //TEquals(sourceInfo.update_seq, repResult.source_last_seq);
  281. TEquals(true, repResult.history instanceof Array);
  282. TEquals(2, repResult.history.length);
  283. TEquals(repResult.history[0].session_id, repResult.session_id);
  284. TEquals('string', typeof repResult.history[0].start_time);
  285. TEquals('string', typeof repResult.history[0].end_time);
  286. // we can't rely on sequences in a cluster
  287. //TEquals((sourceInfo.update_seq - 6), repResult.history[0].start_last_seq);
  288. //TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  289. //TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  290. TEquals(6, repResult.history[0].missing_checked);
  291. TEquals(6, repResult.history[0].missing_found);
  292. TEquals(6, repResult.history[0].docs_read);
  293. TEquals(6, repResult.history[0].docs_written);
  294. TEquals(0, repResult.history[0].doc_write_failures);
  295. copy = targetDb.open(newDoc._id);
  296. T(copy !== null);
  297. TEquals(newDoc._id, copy._id);
  298. TEquals(newDoc.value, copy.value);
  299. for (j = 10; j < 15; j++) {
  300. doc = docs[j];
  301. copy = targetDb.open(doc._id);
  302. T(copy !== null);
  303. TEquals(true, compareObjects(doc, copy));
  304. var atts = copy._attachments;
  305. TEquals('object', typeof atts);
  306. TEquals('object', typeof atts["readme.txt"]);
  307. TEquals(2, atts["readme.txt"].revpos);
  308. TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
  309. TEquals(true, atts["readme.txt"].stub);
  310. var att1_copy = CouchDB.request(
  311. "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
  312. ).responseText;
  313. TEquals(att1_data.length, att1_copy.length);
  314. TEquals(att1_data, att1_copy);
  315. TEquals('object', typeof atts["data.dat"]);
  316. TEquals(3, atts["data.dat"].revpos);
  317. TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
  318. TEquals(true, atts["data.dat"].stub);
  319. var att2_copy = CouchDB.request(
  320. "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
  321. ).responseText;
  322. TEquals(att2_data.length, att2_copy.length);
  323. TEquals(att2_data, att2_copy);
  324. }
  325. // test deletion is replicated
  326. doc = sourceDb.open(docs[1]._id);
  327. TEquals(true, sourceDb.deleteDoc(doc).ok);
  328. repResult = CouchDB.replicate(dbPairsPrefixes[i].source+sourceDb.name, dbPairsPrefixes[i].target+targetDb.name);
  329. TEquals(true, repResult.ok);
  330. sourceInfo = sourceDb.info();
  331. targetInfo = targetDb.info();
  332. TEquals(targetInfo.doc_count, sourceInfo.doc_count);
  333. TEquals(targetInfo.doc_del_count, sourceInfo.doc_del_count);
  334. TEquals(1, targetInfo.doc_del_count);
  335. TEquals(true, repResult.history instanceof Array);
  336. TEquals(3, repResult.history.length);
  337. // we can't rely on sequences in a cluster
  338. //TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
  339. //TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  340. //TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  341. TEquals(1, repResult.history[0].missing_checked);
  342. TEquals(1, repResult.history[0].missing_found);
  343. TEquals(1, repResult.history[0].docs_read);
  344. TEquals(1, repResult.history[0].docs_written);
  345. TEquals(0, repResult.history[0].doc_write_failures);
  346. copy = targetDb.open(docs[1]._id);
  347. TEquals(null, copy);
  348. var changes = targetDb.changes({since: 0});
  349. // there is no guarantee of ordering also
  350. // however: the doc has to appear somewhere
  351. //var idx = changes.results.length - 1;
  352. var changesResDoc1 = changes.results.filter(function(c){return c.id == docs[1]._id;});
  353. TEquals(1, changesResDoc1.length);
  354. TEquals(docs[1]._id, changesResDoc1[0].id);
  355. TEquals(true, changesResDoc1[0].deleted);
  356. // test conflict
  357. doc = sourceDb.open(docs[0]._id);
  358. doc.value = "white";
  359. TEquals(true, sourceDb.save(doc).ok);
  360. copy = targetDb.open(docs[0]._id);
  361. copy.value = "black";
  362. TEquals(true, targetDb.save(copy).ok);
  363. repResult = CouchDB.replicate(dbPairsPrefixes[i].source+sourceDb.name, dbPairsPrefixes[i].target+targetDb.name);
  364. TEquals(true, repResult.ok);
  365. sourceInfo = sourceDb.info();
  366. targetInfo = targetDb.info();
  367. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  368. TEquals(true, repResult.history instanceof Array);
  369. TEquals(4, repResult.history.length);
  370. // we can't rely on sequences in a cluster
  371. //TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
  372. //TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  373. //TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  374. TEquals(1, repResult.history[0].missing_checked);
  375. TEquals(1, repResult.history[0].missing_found);
  376. TEquals(1, repResult.history[0].docs_read);
  377. TEquals(1, repResult.history[0].docs_written);
  378. TEquals(0, repResult.history[0].doc_write_failures);
  379. copy = targetDb.open(docs[0]._id, {conflicts: true});
  380. TEquals(0, copy._rev.indexOf("2-"));
  381. TEquals(true, copy._conflicts instanceof Array);
  382. TEquals(1, copy._conflicts.length);
  383. TEquals(0, copy._conflicts[0].indexOf("2-"));
  384. // replicate again with conflict
  385. doc.value = "yellow";
  386. TEquals(true, sourceDb.save(doc).ok);
  387. repResult = CouchDB.replicate(dbPairsPrefixes[i].source+sourceDb.name, dbPairsPrefixes[i].target+targetDb.name);
  388. TEquals(true, repResult.ok);
  389. sourceInfo = sourceDb.info();
  390. targetInfo = targetDb.info();
  391. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  392. TEquals(true, repResult.history instanceof Array);
  393. TEquals(5, repResult.history.length);
  394. // we can't rely on sequences in a cluster
  395. //TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
  396. //TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  397. //TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  398. TEquals(1, repResult.history[0].missing_checked);
  399. TEquals(1, repResult.history[0].missing_found);
  400. TEquals(1, repResult.history[0].docs_read);
  401. TEquals(1, repResult.history[0].docs_written);
  402. TEquals(0, repResult.history[0].doc_write_failures);
  403. copy = targetDb.open(docs[0]._id, {conflicts: true});
  404. TEquals(0, copy._rev.indexOf("3-"));
  405. TEquals(true, copy._conflicts instanceof Array);
  406. TEquals(1, copy._conflicts.length);
  407. TEquals(0, copy._conflicts[0].indexOf("2-"));
  408. // resolve the conflict
  409. TEquals(true, targetDb.deleteDoc({_id: copy._id, _rev: copy._conflicts[0]}).ok);
  410. // replicate again, check there are no more conflicts
  411. doc.value = "rainbow";
  412. TEquals(true, sourceDb.save(doc).ok);
  413. repResult = CouchDB.replicate(dbPairsPrefixes[i].source+sourceDb.name, dbPairsPrefixes[i].target+targetDb.name);
  414. TEquals(true, repResult.ok);
  415. sourceInfo = sourceDb.info();
  416. targetInfo = targetDb.info();
  417. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  418. TEquals(true, repResult.history instanceof Array);
  419. TEquals(6, repResult.history.length);
  420. // we can't rely on sequences in a cluster
  421. //TEquals((sourceInfo.update_seq - 1), repResult.history[0].start_last_seq);
  422. //TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  423. //TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  424. TEquals(1, repResult.history[0].missing_checked);
  425. TEquals(1, repResult.history[0].missing_found);
  426. TEquals(1, repResult.history[0].docs_read);
  427. TEquals(1, repResult.history[0].docs_written);
  428. TEquals(0, repResult.history[0].doc_write_failures);
  429. copy = targetDb.open(docs[0]._id, {conflicts: true});
  430. TEquals(0, copy._rev.indexOf("4-"));
  431. TEquals('undefined', typeof copy._conflicts);
  432. // test that revisions already in a target are not copied
  433. TEquals(true, sourceDb.save({_id: "foo1", value: 111}).ok);
  434. TEquals(true, targetDb.save({_id: "foo1", value: 111}).ok);
  435. TEquals(true, sourceDb.save({_id: "foo2", value: 222}).ok);
  436. TEquals(true, sourceDb.save({_id: "foo3", value: 333}).ok);
  437. TEquals(true, targetDb.save({_id: "foo3", value: 333}).ok);
  438. repResult = CouchDB.replicate(dbPairsPrefixes[i].source+sourceDb.name, dbPairsPrefixes[i].target+targetDb.name);
  439. TEquals(true, repResult.ok);
  440. sourceInfo = sourceDb.info();
  441. // we can't rely on sequences in a cluster
  442. //TEquals(sourceInfo.update_seq, repResult.source_last_seq);
  443. //TEquals(sourceInfo.update_seq - 3, repResult.history[0].start_last_seq);
  444. //TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  445. //TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  446. TEquals(3, repResult.history[0].missing_checked);
  447. TEquals(1, repResult.history[0].missing_found);
  448. TEquals(1, repResult.history[0].docs_read);
  449. TEquals(1, repResult.history[0].docs_written);
  450. TEquals(0, repResult.history[0].doc_write_failures);
  451. TEquals(true, sourceDb.save({_id: "foo4", value: 444}).ok);
  452. TEquals(true, targetDb.save({_id: "foo4", value: 444}).ok);
  453. TEquals(true, sourceDb.save({_id: "foo5", value: 555}).ok);
  454. TEquals(true, targetDb.save({_id: "foo5", value: 555}).ok);
  455. repResult = CouchDB.replicate(dbPairsPrefixes[i].source+sourceDb.name, dbPairsPrefixes[i].target+targetDb.name);
  456. TEquals(true, repResult.ok);
  457. sourceInfo = sourceDb.info();
  458. // we can't rely on sequences in a cluster
  459. //TEquals(sourceInfo.update_seq, repResult.source_last_seq);
  460. //TEquals(sourceInfo.update_seq - 2, repResult.history[0].start_last_seq);
  461. //TEquals(sourceInfo.update_seq, repResult.history[0].end_last_seq);
  462. //TEquals(sourceInfo.update_seq, repResult.history[0].recorded_seq);
  463. TEquals(2, repResult.history[0].missing_checked);
  464. TEquals(0, repResult.history[0].missing_found);
  465. TEquals(0, repResult.history[0].docs_read);
  466. TEquals(0, repResult.history[0].docs_written);
  467. TEquals(0, repResult.history[0].doc_write_failures);
  468. repResult = CouchDB.replicate(dbPairsPrefixes[i].source+sourceDb.name, dbPairsPrefixes[i].target+targetDb.name);
  469. TEquals(true, repResult.ok);
  470. TEquals(true, repResult.no_changes);
  471. sourceInfo = sourceDb.info();
  472. // we can't rely on sequences in a cluster
  473. //TEquals(sourceInfo.update_seq, repResult.source_last_seq);
  474. }
  475. // test error when source database does not exist
  476. try {
  477. CouchDB.replicate("foobar", "test_suite_db");
  478. T(false, "should have failed with db_not_found error");
  479. } catch (x) {
  480. TEquals("db_not_found", x.error);
  481. }
  482. // validate COUCHDB-317
  483. try {
  484. CouchDB.replicate("/foobar", "test_suite_db");
  485. T(false, "should have failed with db_not_found error");
  486. } catch (x) {
  487. TEquals("db_not_found", x.error);
  488. }
  489. try {
  490. CouchDB.replicate(CouchDB.protocol + host + "/foobar", "test_suite_db");
  491. T(false, "should have failed with db_not_found error");
  492. } catch (x) {
  493. TEquals("db_not_found", x.error);
  494. }
  495. // test since_seq parameter
  496. docs = makeDocs(1, 6);
  497. for (i = 0; i < dbPairsPrefixes.length; i++) {
  498. populateSourceDb(docs);
  499. populateTargetDb([]);
  500. // sequences are no longer simple numbers - so pull #3 from a feed
  501. var since_seq = sourceDb.changes().results[2].seq;
  502. var expected_ids = [];
  503. var changes = sourceDb.changes({since: JSON.stringify(since_seq)});
  504. for (j = 0; j < changes.results.length; j++) {
  505. expected_ids.push(changes.results[j].id);
  506. }
  507. TEquals(2, expected_ids.length, "2 documents since since_seq");
  508. // For OTP < R14B03, temporary child specs are kept in the supervisor
  509. // after the child terminates, so cancel the replication to delete the
  510. // child spec in those OTP releases, otherwise since_seq will have no
  511. // effect.
  512. try {
  513. CouchDB.replicate(
  514. dbPairsPrefixes[i].source+sourceDb.name,
  515. dbPairsPrefixes[i].target+targetDb.name,
  516. {body: {cancel: true}}
  517. );
  518. } catch (x) {
  519. // OTP R14B03 onwards
  520. TEquals("not_found", x.error);
  521. }
  522. repResult = CouchDB.replicate(
  523. dbPairsPrefixes[i].source+sourceDb.name,
  524. dbPairsPrefixes[i].target+targetDb.name,
  525. {body: {since_seq: since_seq}}
  526. );
  527. // Same reason as before. But here we don't want since_seq to affect
  528. // subsequent replications, so we need to delete the child spec from the
  529. // supervisor (since_seq is not used to calculate the replication ID).
  530. try {
  531. CouchDB.replicate(
  532. dbPairsPrefixes[i].source+sourceDb.name,
  533. dbPairsPrefixes[i].target+targetDb.name,
  534. {body: {cancel: true}}
  535. );
  536. } catch (x) {
  537. // OTP R14B03 onwards
  538. TEquals("not_found", x.error);
  539. }
  540. TEquals(true, repResult.ok);
  541. TEquals(2, repResult.history[0].missing_checked);
  542. TEquals(2, repResult.history[0].missing_found);
  543. TEquals(2, repResult.history[0].docs_read);
  544. TEquals(2, repResult.history[0].docs_written);
  545. TEquals(0, repResult.history[0].doc_write_failures);
  546. for (j = 0; j < docs.length; j++) {
  547. doc = docs[j];
  548. copy = targetDb.open(doc._id);
  549. if (expected_ids.indexOf(doc._id) === -1) {
  550. T(copy === null);
  551. } else {
  552. T(copy !== null);
  553. TEquals(true, compareObjects(doc, copy));
  554. }
  555. }
  556. }
  557. // test errors due to doc validate_doc_update functions in the target endpoint
  558. docs = makeDocs(1, 8);
  559. docs[2]["_attachments"] = {
  560. "hello.txt": {
  561. "content_type": "text/plain",
  562. "data": "aGVsbG8gd29ybGQ=" // base64:encode("hello world")
  563. }
  564. };
  565. var ddoc = {
  566. _id: "_design/test",
  567. language: "javascript",
  568. validate_doc_update: (function(newDoc, oldDoc, userCtx, secObj) {
  569. if ((newDoc.integer % 2) !== 0) {
  570. throw {forbidden: "I only like multiples of 2."};
  571. }
  572. }).toString()
  573. };
  574. for (i = 0; i < dbPairsPrefixes.length; i++) {
  575. populateSourceDb(docs);
  576. populateTargetDb([ddoc]);
  577. repResult = CouchDB.replicate(
  578. dbPairsPrefixes[i].source+sourceDb.name,
  579. dbPairsPrefixes[i].target+targetDb.name
  580. );
  581. TEquals(true, repResult.ok);
  582. TEquals(7, repResult.history[0].missing_checked);
  583. TEquals(7, repResult.history[0].missing_found);
  584. TEquals(7, repResult.history[0].docs_read);
  585. TEquals(3, repResult.history[0].docs_written);
  586. TEquals(4, repResult.history[0].doc_write_failures);
  587. for (j = 0; j < docs.length; j++) {
  588. doc = docs[j];
  589. copy = targetDb.open(doc._id);
  590. if (doc.integer % 2 === 0) {
  591. T(copy !== null);
  592. TEquals(copy.integer, doc.integer);
  593. } else {
  594. T(copy === null);
  595. }
  596. }
  597. }
  598. // test create_target option
  599. docs = makeDocs(1, 2);
  600. for (i = 0; i < dbPairsPrefixes.length; i++) {
  601. populateSourceDb(docs);
  602. targetDb.deleteDb();
  603. repResult = CouchDB.replicate(
  604. dbPairsPrefixes[i].source+sourceDb.name,
  605. dbPairsPrefixes[i].target+targetDb.name,
  606. {body: {create_target: true}}
  607. );
  608. TEquals(true, repResult.ok);
  609. sourceInfo = sourceDb.info();
  610. targetInfo = targetDb.info();
  611. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  612. TEquals(sourceInfo.update_seq, targetInfo.update_seq);
  613. }
  614. // test filtered replication
  615. docs = makeDocs(1, 31);
  616. docs.push({
  617. _id: "_design/mydesign",
  618. language: "javascript",
  619. filters: {
  620. myfilter: (function(doc, req) {
  621. var modulus = Number(req.query.modulus);
  622. var special = req.query.special;
  623. return (doc.integer % modulus === 0) || (doc.string === special);
  624. }).toString()
  625. }
  626. });
  627. for (i = 0; i < dbPairsPrefixes.length; i++) {
  628. populateSourceDb(docs);
  629. populateTargetDb([]);
  630. repResult = CouchDB.replicate(
  631. dbPairsPrefixes[i].source+sourceDb.name,
  632. dbPairsPrefixes[i].target+targetDb.name,
  633. {
  634. body: {
  635. filter: "mydesign/myfilter",
  636. query_params: {
  637. modulus: "2",
  638. special: "7"
  639. }
  640. }
  641. }
  642. );
  643. TEquals(true, repResult.ok);
  644. for (j = 0; j < docs.length; j++) {
  645. doc = docs[j];
  646. copy = targetDb.open(doc._id);
  647. if ((doc.integer && (doc.integer % 2 === 0)) || (doc.string === "7")) {
  648. T(copy !== null);
  649. TEquals(true, compareObjects(doc, copy));
  650. } else {
  651. TEquals(null, copy);
  652. }
  653. }
  654. TEquals(true, repResult.history instanceof Array);
  655. TEquals(1, repResult.history.length);
  656. // We (incorrectly) don't record update sequences for things
  657. // that don't pass the changse feed filter. Historically the
  658. // last document to pass was the second to last doc which has
  659. // an update sequence of 30. Work that has been applied to avoid
  660. // conflicts from duplicate IDs breaking _bulk_docs updates added
  661. // a sort to the logic which changes this. Now the last document
  662. // to pass has an doc id of "8" and is at update_seq 29 (because only
  663. // "9" and the design doc are after it).
  664. //
  665. // In the future the fix ought to be that we record that update
  666. // sequence of the database. BigCouch has some existing work on
  667. // this in the clustered case because if you have very few documents
  668. // that pass the filter then (given single node's behavior) you end
  669. // up having to rescan a large portion of the database.
  670. // we can't rely on sequences in a cluster
  671. // not only can one figure appear twice (at least for n>1), there's also hashes involved now - so comparing seq==29 is lottery (= cutting off hashes is nonsense)
  672. // above, there was brute-force comparing all attrs of all docs - now we did check if excluded docs did NOT make it
  673. // in any way, we can't rely on sequences in a cluster (so leave out)
  674. //TEquals(29, repResult.source_last_seq);
  675. //TEquals(0, repResult.history[0].start_last_seq);
  676. //TEquals(29, repResult.history[0].end_last_seq);
  677. //TEquals(29, repResult.history[0].recorded_seq);
  678. // 16 => 15 docs with even integer field + 1 doc with string field "7"
  679. TEquals(16, repResult.history[0].missing_checked);
  680. TEquals(16, repResult.history[0].missing_found);
  681. TEquals(16, repResult.history[0].docs_read);
  682. TEquals(16, repResult.history[0].docs_written);
  683. TEquals(0, repResult.history[0].doc_write_failures);
  684. // add new docs to source and resume the same replication
  685. var newDocs = makeDocs(50, 56);
  686. populateSourceDb(newDocs, true);
  687. repResult = CouchDB.replicate(
  688. dbPairsPrefixes[i].source+sourceDb.name,
  689. dbPairsPrefixes[i].target+targetDb.name,
  690. {
  691. body: {
  692. filter: "mydesign/myfilter",
  693. query_params: {
  694. modulus: "2",
  695. special: "7"
  696. }
  697. }
  698. }
  699. );
  700. TEquals(true, repResult.ok);
  701. for (j = 0; j < newDocs.length; j++) {
  702. doc = newDocs[j];
  703. copy = targetDb.open(doc._id);
  704. if (doc.integer && (doc.integer % 2 === 0)) {
  705. T(copy !== null);
  706. TEquals(true, compareObjects(doc, copy));
  707. } else {
  708. TEquals(null, copy);
  709. }
  710. }
  711. // last doc has even integer field, so last replicated seq is 36
  712. // cluster - so no seq (ditto above)
  713. //TEquals(36, repResult.source_last_seq);
  714. TEquals(true, repResult.history instanceof Array);
  715. TEquals(2, repResult.history.length);
  716. //TEquals(29, repResult.history[0].start_last_seq);
  717. //TEquals(36, repResult.history[0].end_last_seq);
  718. //TEquals(36, repResult.history[0].recorded_seq);
  719. TEquals(3, repResult.history[0].missing_checked);
  720. TEquals(3, repResult.history[0].missing_found);
  721. TEquals(3, repResult.history[0].docs_read);
  722. TEquals(3, repResult.history[0].docs_written);
  723. TEquals(0, repResult.history[0].doc_write_failures);
  724. }
  725. // test filtered replication works as expected after changing the filter's
  726. // code (ticket COUCHDB-892)
  727. var filterFun1 = (function(doc, req) {
  728. if (doc.value < Number(req.query.maxvalue)) {
  729. return true;
  730. } else {
  731. return false;
  732. }
  733. }).toString();
  734. var filterFun2 = (function(doc, req) {
  735. return true;
  736. }).toString();
  737. for (i = 0; i < dbPairsPrefixes.length; i++) {
  738. populateTargetDb([]);
  739. populateSourceDb([]);
  740. TEquals(true, sourceDb.save({_id: "foo1", value: 1}).ok);
  741. TEquals(true, sourceDb.save({_id: "foo2", value: 2}).ok);
  742. TEquals(true, sourceDb.save({_id: "foo3", value: 3}).ok);
  743. TEquals(true, sourceDb.save({_id: "foo4", value: 4}).ok);
  744. var ddoc = {
  745. "_id": "_design/mydesign",
  746. "language": "javascript",
  747. "filters": {
  748. "myfilter": filterFun1
  749. }
  750. };
  751. TEquals(true, sourceDb.save(ddoc).ok);
  752. repResult = CouchDB.replicate(
  753. dbPairsPrefixes[i].source+sourceDb.name,
  754. dbPairsPrefixes[i].target+targetDb.name,
  755. {
  756. body: {
  757. filter: "mydesign/myfilter",
  758. query_params: {
  759. maxvalue: "3"
  760. }
  761. }
  762. }
  763. );
  764. TEquals(true, repResult.ok);
  765. TEquals(true, repResult.history instanceof Array);
  766. TEquals(1, repResult.history.length);
  767. TEquals(2, repResult.history[0].docs_written);
  768. TEquals(2, repResult.history[0].docs_read);
  769. TEquals(0, repResult.history[0].doc_write_failures);
  770. var docFoo1 = targetDb.open("foo1");
  771. T(docFoo1 !== null);
  772. TEquals(1, docFoo1.value);
  773. var docFoo2 = targetDb.open("foo2");
  774. T(docFoo2 !== null);
  775. TEquals(2, docFoo2.value);
  776. var docFoo3 = targetDb.open("foo3");
  777. TEquals(null, docFoo3);
  778. var docFoo4 = targetDb.open("foo4");
  779. TEquals(null, docFoo4);
  780. // replication should start from scratch after the filter's code changed
  781. ddoc.filters.myfilter = filterFun2;
  782. TEquals(true, sourceDb.save(ddoc).ok);
  783. repResult = CouchDB.replicate(
  784. dbPairsPrefixes[i].source+sourceDb.name,
  785. dbPairsPrefixes[i].target+targetDb.name,
  786. {
  787. body: {
  788. filter: "mydesign/myfilter",
  789. query_params : {
  790. maxvalue: "3"
  791. }
  792. }
  793. }
  794. );
  795. TEquals(true, repResult.ok);
  796. TEquals(true, repResult.history instanceof Array);
  797. TEquals(1, repResult.history.length);
  798. TEquals(3, repResult.history[0].docs_written);
  799. TEquals(3, repResult.history[0].docs_read);
  800. TEquals(0, repResult.history[0].doc_write_failures);
  801. docFoo1 = targetDb.open("foo1");
  802. T(docFoo1 !== null);
  803. TEquals(1, docFoo1.value);
  804. docFoo2 = targetDb.open("foo2");
  805. T(docFoo2 !== null);
  806. TEquals(2, docFoo2.value);
  807. docFoo3 = targetDb.open("foo3");
  808. T(docFoo3 !== null);
  809. TEquals(3, docFoo3.value);
  810. docFoo4 = targetDb.open("foo4");
  811. T(docFoo4 !== null);
  812. TEquals(4, docFoo4.value);
  813. T(targetDb.open("_design/mydesign") !== null);
  814. }
  815. // test replication by doc IDs
  816. docs = makeDocs(1, 11);
  817. docs.push({
  818. _id: "_design/foo",
  819. language: "javascript",
  820. integer: 1
  821. });
  822. var target_doc_ids = [
  823. { initial: ["1", "2", "10"], after: [], conflict_id: "2" },
  824. { initial: ["1", "2"], after: ["7"], conflict_id: "1" },
  825. { initial: ["1", "foo_666", "10"], after: ["7"], conflict_id: "10" },
  826. { initial: ["_design/foo", "8"], after: ["foo_5"], conflict_id: "8" },
  827. { initial: ["_design%2Ffoo", "8"], after: ["foo_5"], conflict_id: "8" },
  828. { initial: [], after: ["foo_1000", "_design/foo", "1"], conflict_id: "1" }
  829. ];
  830. var doc_ids, after_doc_ids;
  831. var id, num_inexistent_docs, after_num_inexistent_docs;
  832. var total, after_total;
  833. for (i = 0; i < dbPairsPrefixes.length; i++) {
  834. for (j = 0; j < target_doc_ids.length; j++) {
  835. doc_ids = target_doc_ids[j].initial;
  836. num_inexistent_docs = 0;
  837. for (k = 0; k < doc_ids.length; k++) {
  838. id = doc_ids[k];
  839. if (id.indexOf("foo_") === 0) {
  840. num_inexistent_docs += 1;
  841. }
  842. }
  843. populateSourceDb(docs);
  844. populateTargetDb([]);
  845. repResult = CouchDB.replicate(
  846. dbPairsPrefixes[i].source+sourceDb.name,
  847. dbPairsPrefixes[i].target+targetDb.name,
  848. {
  849. body: {
  850. doc_ids: doc_ids
  851. }
  852. }
  853. );
  854. total = doc_ids.length - num_inexistent_docs;
  855. TEquals(true, repResult.ok);
  856. if (total === 0) {
  857. TEquals(true, repResult.no_changes);
  858. } else {
  859. TEquals('string', typeof repResult.start_time);
  860. TEquals('string', typeof repResult.end_time);
  861. TEquals(total, repResult.docs_read);
  862. TEquals(total, repResult.docs_written);
  863. TEquals(0, repResult.doc_write_failures);
  864. }
  865. for (k = 0; k < doc_ids.length; k++) {
  866. id = decodeURIComponent(doc_ids[k]);
  867. doc = sourceDb.open(id);
  868. copy = targetDb.open(id);
  869. if (id.indexOf("foo_") === 0) {
  870. TEquals(null, doc);
  871. TEquals(null, copy);
  872. } else {
  873. T(doc !== null);
  874. T(copy !== null);
  875. TEquals(true, compareObjects(doc, copy));
  876. }
  877. }
  878. // be absolutely sure that other docs were not replicated
  879. for (k = 0; k < docs.length; k++) {
  880. var base_id = docs[k]._id;
  881. id = encodeURIComponent(base_id);
  882. doc = targetDb.open(base_id);
  883. if ((doc_ids.indexOf(id) >= 0) || (doc_ids.indexOf(base_id) >= 0)) {
  884. T(doc !== null);
  885. } else {
  886. TEquals(null, doc);
  887. }
  888. }
  889. targetInfo = targetDb.info();
  890. TEquals(total, targetInfo.doc_count);
  891. // add more docs throught replication by doc IDs
  892. after_doc_ids = target_doc_ids[j].after;
  893. after_num_inexistent_docs = 0;
  894. for (k = 0; k < after_doc_ids.length; k++) {
  895. id = after_doc_ids[k];
  896. if (id.indexOf("foo_") === 0) {
  897. after_num_inexistent_docs += 1;
  898. }
  899. }
  900. repResult = CouchDB.replicate(
  901. dbPairsPrefixes[i].source+sourceDb.name,
  902. dbPairsPrefixes[i].target+targetDb.name,
  903. {
  904. body: {
  905. doc_ids: after_doc_ids
  906. }
  907. }
  908. );
  909. after_total = after_doc_ids.length - after_num_inexistent_docs;
  910. TEquals(true, repResult.ok);
  911. if (after_total === 0) {
  912. TEquals(true, repResult.no_changes);
  913. } else {
  914. TEquals('string', typeof repResult.start_time);
  915. TEquals('string', typeof repResult.end_time);
  916. TEquals(after_total, repResult.docs_read);
  917. TEquals(after_total, repResult.docs_written);
  918. TEquals(0, repResult.doc_write_failures);
  919. }
  920. for (k = 0; k < after_doc_ids.length; k++) {
  921. id = after_doc_ids[k];
  922. doc = sourceDb.open(id);
  923. copy = targetDb.open(id);
  924. if (id.indexOf("foo_") === 0) {
  925. TEquals(null, doc);
  926. TEquals(null, copy);
  927. } else {
  928. T(doc !== null);
  929. T(copy !== null);
  930. TEquals(true, compareObjects(doc, copy));
  931. }
  932. }
  933. // be absolutely sure that other docs were not replicated
  934. for (k = 0; k < docs.length; k++) {
  935. var base_id = docs[k]._id;
  936. id = encodeURIComponent(base_id);
  937. doc = targetDb.open(base_id);
  938. if ((doc_ids.indexOf(id) >= 0) || (after_doc_ids.indexOf(id) >= 0) ||
  939. (doc_ids.indexOf(base_id) >= 0) ||
  940. (after_doc_ids.indexOf(base_id) >= 0)) {
  941. T(doc !== null);
  942. } else {
  943. TEquals(null, doc);
  944. }
  945. }
  946. targetInfo = targetDb.info();
  947. TEquals((total + after_total), targetInfo.doc_count);
  948. // replicate again the same doc after updated on source (no conflict)
  949. id = target_doc_ids[j].conflict_id;
  950. doc = sourceDb.open(id);
  951. T(doc !== null);
  952. doc.integer = 666;
  953. TEquals(true, sourceDb.save(doc).ok);
  954. addAtt(sourceDb, doc, "readme.txt", att1_data, "text/plain");
  955. addAtt(sourceDb, doc, "data.dat", att2_data, "application/binary");
  956. repResult = CouchDB.replicate(
  957. dbPairsPrefixes[i].source+sourceDb.name,
  958. dbPairsPrefixes[i].target+targetDb.name,
  959. {
  960. body: {
  961. doc_ids: [id]
  962. }
  963. }
  964. );
  965. TEquals(true, repResult.ok);
  966. TEquals(1, repResult.docs_read);
  967. TEquals(1, repResult.docs_written);
  968. TEquals(0, repResult.doc_write_failures);
  969. copy = targetDb.open(id, {conflicts: true});
  970. TEquals(666, copy.integer);
  971. TEquals(0, copy._rev.indexOf("4-"));
  972. TEquals('undefined', typeof copy._conflicts);
  973. var atts = copy._attachments;
  974. TEquals('object', typeof atts);
  975. TEquals('object', typeof atts["readme.txt"]);
  976. TEquals(3, atts["readme.txt"].revpos);
  977. TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
  978. TEquals(true, atts["readme.txt"].stub);
  979. var att1_copy = CouchDB.request(
  980. "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
  981. ).responseText;
  982. TEquals(att1_data.length, att1_copy.length);
  983. TEquals(att1_data, att1_copy);
  984. TEquals('object', typeof atts["data.dat"]);
  985. TEquals(4, atts["data.dat"].revpos);
  986. TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
  987. TEquals(true, atts["data.dat"].stub);
  988. var att2_copy = CouchDB.request(
  989. "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
  990. ).responseText;
  991. TEquals(att2_data.length, att2_copy.length);
  992. TEquals(att2_data, att2_copy);
  993. // generate a conflict throught replication by doc IDs
  994. id = target_doc_ids[j].conflict_id;
  995. doc = sourceDb.open(id);
  996. copy = targetDb.open(id);
  997. T(doc !== null);
  998. T(copy !== null);
  999. doc.integer += 100;
  1000. copy.integer += 1;
  1001. TEquals(true, sourceDb.save(doc).ok);
  1002. TEquals(true, targetDb.save(copy).ok);
  1003. repResult = CouchDB.replicate(
  1004. dbPairsPrefixes[i].source+sourceDb.name,
  1005. dbPairsPrefixes[i].target+targetDb.name,
  1006. {
  1007. body: {
  1008. doc_ids: [id]
  1009. }
  1010. }
  1011. );
  1012. TEquals(true, repResult.ok);
  1013. TEquals(1, repResult.docs_read);
  1014. TEquals(1, repResult.docs_written);
  1015. TEquals(0, repResult.doc_write_failures);
  1016. copy = targetDb.open(id, {conflicts: true});
  1017. TEquals(0, copy._rev.indexOf("5-"));
  1018. TEquals(true, copy._conflicts instanceof Array);
  1019. TEquals(1, copy._conflicts.length);
  1020. TEquals(0, copy._conflicts[0].indexOf("5-"));
  1021. }
  1022. }
  1023. docs = makeDocs(1, 25);
  1024. docs.push({
  1025. _id: "_design/foo",
  1026. language: "javascript",
  1027. filters: {
  1028. myfilter: (function(doc, req) { return true; }).toString()
  1029. }
  1030. });
  1031. for (i = 0; i < dbPairsPrefixes.length; i++) {
  1032. populateSourceDb(docs);
  1033. populateTargetDb([]);
  1034. // add some attachments
  1035. for (j = 10; j < 15; j++) {
  1036. addAtt(sourceDb, docs[j], "readme.txt", att1_data, "text/plain");
  1037. }
  1038. repResult = CouchDB.replicate(
  1039. dbPairsPrefixes[i].source+sourceDb.name,
  1040. dbPairsPrefixes[i].target+targetDb.name,
  1041. {
  1042. body: {
  1043. continuous: true
  1044. }
  1045. }
  1046. );
  1047. TEquals(true, repResult.ok);
  1048. TEquals('string', typeof repResult._local_id);
  1049. var rep_id = repResult._local_id;
  1050. waitForSeq(sourceDb, targetDb, rep_id);
  1051. for (j = 0; j < docs.length; j++) {
  1052. doc = docs[j];
  1053. copy = targetDb.open(doc._id);
  1054. T(copy !== null);
  1055. TEquals(true, compareObjects(doc, copy));
  1056. if (j >= 10 && j < 15) {
  1057. var atts = copy._attachments;
  1058. TEquals('object', typeof atts);
  1059. TEquals('object', typeof atts["readme.txt"]);
  1060. TEquals(2, atts["readme.txt"].revpos);
  1061. TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
  1062. TEquals(true, atts["readme.txt"].stub);
  1063. var att_copy = CouchDB.request(
  1064. "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
  1065. ).responseText;
  1066. TEquals(att1_data.length, att_copy.length);
  1067. TEquals(att1_data, att_copy);
  1068. }
  1069. }
  1070. sourceInfo = sourceDb.info();
  1071. targetInfo = targetDb.info();
  1072. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  1073. // add attachments to docs in source
  1074. for (j = 10; j < 15; j++) {
  1075. addAtt(sourceDb, docs[j], "data.dat", att2_data, "application/binary");
  1076. }
  1077. var ddoc = docs[docs.length - 1]; // design doc
  1078. addAtt(sourceDb, ddoc, "readme.txt", att1_data, "text/plain");
  1079. waitForSeq(sourceDb, targetDb, rep_id);
  1080. var modifDocs = docs.slice(10, 15).concat([ddoc]);
  1081. for (j = 0; j < modifDocs.length; j++) {
  1082. doc = modifDocs[j];
  1083. copy = targetDb.open(doc._id);
  1084. T(copy !== null);
  1085. TEquals(true, compareObjects(doc, copy));
  1086. var atts = copy._attachments;
  1087. TEquals('object', typeof atts);
  1088. TEquals('object', typeof atts["readme.txt"]);
  1089. TEquals(2, atts["readme.txt"].revpos);
  1090. TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
  1091. TEquals(true, atts["readme.txt"].stub);
  1092. var att1_copy = CouchDB.request(
  1093. "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
  1094. ).responseText;
  1095. TEquals(att1_data.length, att1_copy.length);
  1096. TEquals(att1_data, att1_copy);
  1097. if (doc._id.indexOf("_design/") === -1) {
  1098. TEquals('object', typeof atts["data.dat"]);
  1099. TEquals(3, atts["data.dat"].revpos);
  1100. TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
  1101. TEquals(true, atts["data.dat"].stub);
  1102. var att2_copy = CouchDB.request(
  1103. "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
  1104. ).responseText;
  1105. TEquals(att2_data.length, att2_copy.length);
  1106. TEquals(att2_data, att2_copy);
  1107. }
  1108. }
  1109. sourceInfo = sourceDb.info();
  1110. targetInfo = targetDb.info();
  1111. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  1112. // add another attachment to the ddoc on source
  1113. addAtt(sourceDb, ddoc, "data.dat", att2_data, "application/binary");
  1114. waitForSeq(sourceDb, targetDb, rep_id);
  1115. copy = targetDb.open(ddoc._id);
  1116. var atts = copy._attachments;
  1117. TEquals('object', typeof atts);
  1118. TEquals('object', typeof atts["readme.txt"]);
  1119. TEquals(2, atts["readme.txt"].revpos);
  1120. TEquals(0, atts["readme.txt"].content_type.indexOf("text/plain"));
  1121. TEquals(true, atts["readme.txt"].stub);
  1122. var att1_copy = CouchDB.request(
  1123. "GET", "/" + targetDb.name + "/" + copy._id + "/readme.txt"
  1124. ).responseText;
  1125. TEquals(att1_data.length, att1_copy.length);
  1126. TEquals(att1_data, att1_copy);
  1127. TEquals('object', typeof atts["data.dat"]);
  1128. TEquals(3, atts["data.dat"].revpos);
  1129. TEquals(0, atts["data.dat"].content_type.indexOf("application/binary"));
  1130. TEquals(true, atts["data.dat"].stub);
  1131. var att2_copy = CouchDB.request(
  1132. "GET", "/" + targetDb.name + "/" + copy._id + "/data.dat"
  1133. ).responseText;
  1134. TEquals(att2_data.length, att2_copy.length);
  1135. TEquals(att2_data, att2_copy);
  1136. sourceInfo = sourceDb.info();
  1137. targetInfo = targetDb.info();
  1138. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  1139. // add more docs to source
  1140. var newDocs = makeDocs(25, 35);
  1141. populateSourceDb(newDocs, true);
  1142. waitForSeq(sourceDb, targetDb, rep_id);
  1143. for (j = 0; j < newDocs.length; j++) {
  1144. doc = newDocs[j];
  1145. copy = targetDb.open(doc._id);
  1146. T(copy !== null);
  1147. TEquals(true, compareObjects(doc, copy));
  1148. }
  1149. sourceInfo = sourceDb.info();
  1150. targetInfo = targetDb.info();
  1151. TEquals(sourceInfo.doc_count, targetInfo.doc_count);
  1152. // delete docs from source
  1153. TEquals(true, sourceDb.deleteDoc(newDocs[0]).ok);
  1154. TEquals(true, sourceDb.deleteDoc(newDocs[6]).ok);
  1155. waitForSeq(sourceDb, targetDb, rep_id);
  1156. copy = targetDb.open(newDocs[0]._id);
  1157. TEquals(null, copy);
  1158. copy = targetDb.open(newDocs[6]._id);
  1159. TEquals(null, copy);
  1160. var changes = targetDb.changes({since: targetInfo.update_seq});
  1161. // quite unfortunately, there is no way on relying on ordering in a cluster
  1162. // but we can assume a length of 2
  1163. var line1 = changes.results[changes.results.length - 2];
  1164. var line2 = changes.results[changes.results.length - 1];
  1165. T(newDocs[0]._id == line1.id || newDocs[0]._id == line2.id);
  1166. T(newDocs[6]._id == line1.id || newDocs[6]._id == line2.id);
  1167. T(line1.deleted && line2.deleted);
  1168. // cancel the replication
  1169. repResult = CouchDB.replicate(
  1170. dbPairsPrefixes[i].source+sourceDb.name,
  1171. dbPairsPrefixes[i].target+targetDb.name,
  1172. {
  1173. body: {
  1174. continuous: true,
  1175. cancel: true
  1176. }
  1177. }
  1178. );
  1179. TEquals(true, repResult.ok);
  1180. TEquals(rep_id, repResult._local_id);
  1181. doc = {
  1182. _id: 'foobar',
  1183. value: 666
  1184. };
  1185. TEquals(true, sourceDb.save(doc).ok);
  1186. waitForSeq(sourceDb, targetDb, rep_id);
  1187. copy = targetDb.open(doc._id);
  1188. TEquals(null, copy);
  1189. }
  1190. // COUCHDB-1093 - filtered and continuous _changes feed dies when the
  1191. // database is compacted
  1192. // no more relevant when clustering, you can't compact (per se at least)
  1193. /*
  1194. docs = makeDocs(1, 10);
  1195. docs.push({
  1196. _id: "_design/foo",
  1197. language: "javascript",
  1198. filters: {
  1199. myfilter: (function(doc, req) { return true; }).toString()
  1200. }
  1201. });
  1202. populateSourceDb(docs);
  1203. populateTargetDb([]);
  1204. repResult = CouchDB.replicate(
  1205. CouchDB.protocol + host + "/" + sourceDb.name,
  1206. targetDb.name,
  1207. {
  1208. body: {
  1209. continuous: true,
  1210. filter: "foo/myfilter"
  1211. }
  1212. }
  1213. );
  1214. TEquals(true, repResult.ok);
  1215. TEquals('string', typeof repResult._local_id);
  1216. TEquals(true, sourceDb.compact().ok);
  1217. while (sourceDb.info().compact_running) {};
  1218. TEquals(true, sourceDb.save(makeDocs(30, 31)[0]).ok);
  1219. var task = getTask(repResult._local_id, 1000);
  1220. T(task != null);
  1221. waitForSeq(sourceDb, targetDb, repResult._local_id);
  1222. T(sourceDb.open("30") !== null);
  1223. // cancel replication
  1224. repResult = CouchDB.replicate(
  1225. CouchDB.protocol + host + "/" + sourceDb.name,
  1226. targetDb.name,
  1227. {
  1228. body: {
  1229. continuous: true,
  1230. filter: "foo/myfilter",
  1231. cancel: true
  1232. }
  1233. }
  1234. );
  1235. TEquals(true, repResult.ok);
  1236. TEquals('string', typeof repResult._local_id);
  1237. */
  1238. //
  1239. // test replication of compressed attachments
  1240. //
  1241. doc = {
  1242. _id: "foobar"
  1243. };
  1244. var bigTextAtt = makeAttData(128 * 1024);
  1245. var attName = "readme.txt";
  1246. var oldSettings = getCompressionInfo();
  1247. var compressionLevel = oldSettings.level;
  1248. var compressibleTypes = oldSettings.types;
  1249. for (i = 0; i < dbPairsPrefixes.length; i++) {
  1250. populateSourceDb([doc]);
  1251. populateTargetDb([]);
  1252. // enable compression of text types
  1253. enableAttCompression("8", "text/*");
  1254. // add text attachment to foobar doc
  1255. xhr = CouchDB.request(
  1256. "PUT",
  1257. "/" + sourceDb.name + "/" + doc._id + "/" + attName + "?rev=" + doc._rev,
  1258. {
  1259. body: bigTextAtt,
  1260. headers: {"Content-Type": "text/plain"}
  1261. }
  1262. );
  1263. TEquals(201, xhr.status);
  1264. // disable compression and replicate
  1265. disableAttCompression();
  1266. repResult = CouchDB.replicate(dbPairsPrefixes[i].source+sourceDb.name, dbPairsPrefixes[i].target+targetDb.name);
  1267. TEquals(true, repResult.ok);
  1268. TEquals(true, repResult.history instanceof Array);
  1269. TEquals(1, repResult.history.length);
  1270. TEquals(1, repResult.history[0].missing_checked);
  1271. TEquals(1, repResult.history[0].missing_found);
  1272. TEquals(1, repResult.history[0].docs_read);
  1273. TEquals(1, repResult.history[0].docs_written);
  1274. TEquals(0, repResult.history[0].doc_write_failures);
  1275. copy = targetDb.open(
  1276. doc._id,
  1277. {att_encoding_info: true, bypass_cache: Math.round(Math.random() * 1000)}
  1278. );
  1279. T(copy !== null);
  1280. T(attName in copy._attachments);
  1281. TEquals("gzip", copy._attachments[attName].encoding);
  1282. TEquals("number", typeof copy._attachments[attName].length);
  1283. TEquals("number", typeof copy._attachments[attName].encoded_length);
  1284. T(copy._attachments[attName].encoded_length < copy._attachments[attName].length);
  1285. }
  1286. delete bigTextAtt;
  1287. // restore original settings
  1288. enableAttCompression(compressionLevel, compressibleTypes);
  1289. //
  1290. // test replication triggered by non admins
  1291. //
  1292. // case 1) user triggering the replication is not a DB admin of the target DB
  1293. var joeUserDoc = CouchDB.prepareUserDoc({
  1294. name: "joe",
  1295. roles: ["erlanger"]
  1296. }, "erly");
  1297. var defaultUsersDb = new CouchDB("_users", {"X-Couch-Full-Commit":"false"});
  1298. try { defaultUsersDb.createDb(); } catch (e) { /* ignore if exists*/ }
  1299. //var usersDb = new CouchDB("test_suite_auth", {"X-Couch-Full-Commit":"false"});
  1300. /*var server_config = [
  1301. {
  1302. section: "couch_httpd_auth",
  1303. key: "authentication_db",
  1304. value: usersDb.name
  1305. }
  1306. ];*/
  1307. docs = makeDocs(1, 6);
  1308. docs.push({
  1309. _id: "_design/foo",
  1310. language: "javascript"
  1311. });
  1312. dbPairsPrefixes = [
  1313. {
  1314. source: "",
  1315. target: ""
  1316. },
  1317. {
  1318. source: CouchDB.protocol + host + "/",

Large files files are truncated, but you can click here to view the full file