PageRenderTime 54ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/test/javascript/couch.js

http://github.com/apache/couchdb
JavaScript | 557 lines | 465 code | 54 blank | 38 comment | 115 complexity | 537c2fd5f0fa1835bea2e530b7619d7e MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  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. // A simple class to represent a database. Uses XMLHttpRequest to interface with
  13. // the CouchDB server.
  14. function CouchDB(name, httpHeaders, globalRequestOptions) {
  15. this.globalRequestOptions = globalRequestOptions || {}
  16. this.name = name;
  17. this.uri = "/" + encodeURIComponent(name) + "/";
  18. // The XMLHttpRequest object from the most recent request. Callers can
  19. // use this to check result http status and headers.
  20. this.last_req = null;
  21. this.request = function(method, uri, requestOptions) {
  22. requestOptions = requestOptions || {};
  23. requestOptions.headers = combine(requestOptions.headers, httpHeaders);
  24. requestOptions.url = globalRequestOptions;
  25. return CouchDB.request(method, uri, requestOptions);
  26. };
  27. // Creates the database on the server
  28. this.createDb = function() {
  29. this.last_req = this.request("PUT", this.uri);
  30. CouchDB.maybeThrowError(this.last_req);
  31. return JSON.parse(this.last_req.responseText);
  32. };
  33. // Deletes the database on the server
  34. this.deleteDb = function() {
  35. this.last_req = this.request("DELETE", this.uri + "?sync=true");
  36. if (this.last_req.status == 404) {
  37. return false;
  38. }
  39. CouchDB.maybeThrowError(this.last_req);
  40. return JSON.parse(this.last_req.responseText);
  41. };
  42. // Save a document to the database
  43. this.save = function(doc, options, http_headers) {
  44. if (doc._id == undefined) {
  45. doc._id = CouchDB.newUuids(1)[0];
  46. }
  47. http_headers = http_headers || {};
  48. this.last_req = this.request("PUT", this.uri +
  49. encodeURIComponent(doc._id) + encodeOptions(options),
  50. {body: JSON.stringify(doc), headers: http_headers});
  51. CouchDB.maybeThrowError(this.last_req);
  52. var result = JSON.parse(this.last_req.responseText);
  53. doc._rev = result.rev;
  54. return result;
  55. };
  56. // Open a document from the database
  57. this.open = function(docId, url_params, http_headers) {
  58. this.last_req = this.request("GET", this.uri + encodeURIComponent(docId)
  59. + encodeOptions(url_params), {headers:http_headers});
  60. if (this.last_req.status == 404) {
  61. return null;
  62. }
  63. CouchDB.maybeThrowError(this.last_req);
  64. return JSON.parse(this.last_req.responseText);
  65. };
  66. // Deletes a document from the database
  67. this.deleteDoc = function(doc) {
  68. this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id)
  69. + "?rev=" + doc._rev);
  70. CouchDB.maybeThrowError(this.last_req);
  71. var result = JSON.parse(this.last_req.responseText);
  72. doc._rev = result.rev; //record rev in input document
  73. doc._deleted = true;
  74. return result;
  75. };
  76. // Deletes an attachment from a document
  77. this.deleteDocAttachment = function(doc, attachment_name) {
  78. this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id)
  79. + "/" + attachment_name + "?rev=" + doc._rev);
  80. CouchDB.maybeThrowError(this.last_req);
  81. var result = JSON.parse(this.last_req.responseText);
  82. doc._rev = result.rev; //record rev in input document
  83. return result;
  84. };
  85. this.bulkSave = function(docs, options) {
  86. // first prepoulate the UUIDs for new documents
  87. var newCount = 0;
  88. for (var i=0; i<docs.length; i++) {
  89. if (docs[i]._id == undefined) {
  90. newCount++;
  91. }
  92. }
  93. var newUuids = CouchDB.newUuids(newCount);
  94. var newCount = 0;
  95. for (var i=0; i<docs.length; i++) {
  96. if (docs[i]._id == undefined) {
  97. docs[i]._id = newUuids.pop();
  98. }
  99. }
  100. var json = {"docs": docs};
  101. // put any options in the json
  102. for (var option in options) {
  103. json[option] = options[option];
  104. }
  105. this.last_req = this.request("POST", this.uri + "_bulk_docs", {
  106. body: JSON.stringify(json)
  107. });
  108. if (this.last_req.status == 417) {
  109. return {errors: JSON.parse(this.last_req.responseText)};
  110. }
  111. else {
  112. CouchDB.maybeThrowError(this.last_req);
  113. var results = JSON.parse(this.last_req.responseText);
  114. for (var i = 0; i < docs.length; i++) {
  115. if(results[i] && results[i].rev && results[i].ok) {
  116. docs[i]._rev = results[i].rev;
  117. }
  118. }
  119. return results;
  120. }
  121. };
  122. this.ensureFullCommit = function() {
  123. this.last_req = this.request("POST", this.uri + "_ensure_full_commit");
  124. CouchDB.maybeThrowError(this.last_req);
  125. return JSON.parse(this.last_req.responseText);
  126. };
  127. // Applies the map function to the contents of database and returns the results.
  128. this.query = function(mapFun, reduceFun, options, keys, language) {
  129. //var body = {language: language || "javascript"};
  130. var body = {}
  131. if(keys) {
  132. options.keys = keys ;
  133. }
  134. if (typeof(mapFun) != "string") {
  135. mapFun = mapFun.toSource ? mapFun.toSource() : "(" + mapFun.toString() + ")";
  136. }
  137. if ((!language) || language.toLowerCase() == "javascript") {
  138. mapFun = mapFun + "/" + "* avoid race cond " + (new Date().getTime()) + " *" + "/";
  139. }
  140. body.map = mapFun;
  141. if (reduceFun != null) {
  142. if (typeof(reduceFun) != "string") {
  143. reduceFun = reduceFun.toSource ?
  144. reduceFun.toSource() : "(" + reduceFun.toString() + ")";
  145. }
  146. body.reduce = reduceFun;
  147. }
  148. if (options && options.options != undefined) {
  149. body.options = options.options;
  150. delete options.options;
  151. }
  152. var ddoc = {
  153. language: language || "javascript",
  154. views: {
  155. view: body
  156. }
  157. };
  158. var ddoc_name = "_design/temp_" + get_random_string();
  159. this.last_req = this.request("PUT", this.uri + ddoc_name, {
  160. headers: {"Content-Type": "application/json"},
  161. body: JSON.stringify(ddoc)
  162. });
  163. CouchDB.maybeThrowError(this.last_req);
  164. var ddoc_result = JSON.parse(this.last_req.responseText)
  165. this.last_req = this.request("GET", this.uri + ddoc_name + "/_view/view"
  166. + encodeOptions(options));
  167. CouchDB.maybeThrowError(this.last_req);
  168. var query_result = JSON.parse(this.last_req.responseText);
  169. var res = this.request("DELETE", this.uri + ddoc_name + '?rev=' + ddoc_result.rev);
  170. return query_result;
  171. };
  172. this.view = function(viewname, options, keys) {
  173. var viewParts = viewname.split('/');
  174. var viewPath = this.uri + "_design/" + viewParts[0] + "/_view/"
  175. + viewParts[1] + encodeOptions(options);
  176. if(!keys) {
  177. this.last_req = this.request("GET", viewPath);
  178. } else {
  179. this.last_req = this.request("POST", viewPath, {
  180. headers: {"Content-Type": "application/json"},
  181. body: JSON.stringify({keys:keys})
  182. });
  183. }
  184. if (this.last_req.status == 404) {
  185. return null;
  186. }
  187. CouchDB.maybeThrowError(this.last_req);
  188. return JSON.parse(this.last_req.responseText);
  189. };
  190. // gets information about the database
  191. this.info = function() {
  192. this.last_req = this.request("GET", this.uri);
  193. CouchDB.maybeThrowError(this.last_req);
  194. return JSON.parse(this.last_req.responseText);
  195. };
  196. // gets information about a design doc
  197. this.designInfo = function(docid) {
  198. this.last_req = this.request("GET", this.uri + docid + "/_info");
  199. CouchDB.maybeThrowError(this.last_req);
  200. return JSON.parse(this.last_req.responseText);
  201. };
  202. this.allDocs = function(options,keys) {
  203. if(!keys) {
  204. this.last_req = this.request("GET", this.uri + "_all_docs"
  205. + encodeOptions(options));
  206. } else {
  207. this.last_req = this.request("POST", this.uri + "_all_docs"
  208. + encodeOptions(options), {
  209. headers: {"Content-Type": "application/json"},
  210. body: JSON.stringify({keys:keys})
  211. });
  212. }
  213. CouchDB.maybeThrowError(this.last_req);
  214. return JSON.parse(this.last_req.responseText);
  215. };
  216. this.designDocs = function() {
  217. return this.allDocs({startkey:"_design", endkey:"_design0"});
  218. };
  219. this.changes = function(options) {
  220. this.last_req = this.request("GET", this.uri + "_changes"
  221. + encodeOptions(options));
  222. CouchDB.maybeThrowError(this.last_req);
  223. return JSON.parse(this.last_req.responseText);
  224. };
  225. this.compact = function() {
  226. this.last_req = this.request("POST", this.uri + "_compact");
  227. CouchDB.maybeThrowError(this.last_req);
  228. return JSON.parse(this.last_req.responseText);
  229. };
  230. this.viewCleanup = function() {
  231. this.last_req = this.request("POST", this.uri + "_view_cleanup");
  232. CouchDB.maybeThrowError(this.last_req);
  233. return JSON.parse(this.last_req.responseText);
  234. };
  235. this.setDbProperty = function(propId, propValue) {
  236. this.last_req = this.request("PUT", this.uri + propId,{
  237. body:JSON.stringify(propValue)
  238. });
  239. CouchDB.maybeThrowError(this.last_req);
  240. return JSON.parse(this.last_req.responseText);
  241. };
  242. this.getDbProperty = function(propId) {
  243. this.last_req = this.request("GET", this.uri + propId);
  244. CouchDB.maybeThrowError(this.last_req);
  245. return JSON.parse(this.last_req.responseText);
  246. };
  247. this.setSecObj = function(secObj) {
  248. this.last_req = this.request("PUT", this.uri + "_security",{
  249. body:JSON.stringify(secObj)
  250. });
  251. CouchDB.maybeThrowError(this.last_req);
  252. return JSON.parse(this.last_req.responseText);
  253. };
  254. this.getSecObj = function() {
  255. this.last_req = this.request("GET", this.uri + "_security");
  256. CouchDB.maybeThrowError(this.last_req);
  257. return JSON.parse(this.last_req.responseText);
  258. };
  259. // Convert a options object to an url query string.
  260. // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"'
  261. function encodeOptions(options) {
  262. var buf = [];
  263. if (typeof(options) == "object" && options !== null) {
  264. for (var name in options) {
  265. if (!options.hasOwnProperty(name)) { continue; };
  266. var value = options[name];
  267. if (name == "key" || name == "keys" || name == "startkey" || name == "endkey" || (name == "open_revs" && value !== "all")) {
  268. value = toJSON(value);
  269. }
  270. buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
  271. }
  272. }
  273. if (!buf.length) {
  274. return "";
  275. }
  276. return "?" + buf.join("&");
  277. }
  278. function toJSON(obj) {
  279. return obj !== null ? JSON.stringify(obj) : null;
  280. }
  281. function combine(object1, object2) {
  282. if (!object2) {
  283. return object1;
  284. }
  285. if (!object1) {
  286. return object2;
  287. }
  288. for (var name in object2) {
  289. object1[name] = object2[name];
  290. }
  291. return object1;
  292. }
  293. }
  294. // this is the XMLHttpRequest object from last request made by the following
  295. // CouchDB.* functions (except for calls to request itself).
  296. // Use this from callers to check HTTP status or header values of requests.
  297. CouchDB.last_req = null;
  298. CouchDB.urlPrefix = '';
  299. CouchDB.login = function(name, password) {
  300. CouchDB.last_req = CouchDB.request("POST", "/_session", {
  301. headers: {"Content-Type": "application/x-www-form-urlencoded",
  302. "X-CouchDB-WWW-Authenticate": "Cookie"},
  303. body: "name=" + encodeURIComponent(name) + "&password="
  304. + encodeURIComponent(password)
  305. });
  306. return JSON.parse(CouchDB.last_req.responseText);
  307. }
  308. CouchDB.logout = function() {
  309. CouchDB.last_req = CouchDB.request("DELETE", "/_session", {
  310. headers: {"Content-Type": "application/x-www-form-urlencoded",
  311. "X-CouchDB-WWW-Authenticate": "Cookie"}
  312. });
  313. return JSON.parse(CouchDB.last_req.responseText);
  314. };
  315. CouchDB.session = function(options) {
  316. options = options || {};
  317. CouchDB.last_req = CouchDB.request("GET", "/_session", options);
  318. CouchDB.maybeThrowError(CouchDB.last_req);
  319. return JSON.parse(CouchDB.last_req.responseText);
  320. };
  321. CouchDB.allDbs = function() {
  322. CouchDB.last_req = CouchDB.request("GET", "/_all_dbs");
  323. CouchDB.maybeThrowError(CouchDB.last_req);
  324. return JSON.parse(CouchDB.last_req.responseText);
  325. };
  326. CouchDB.allDesignDocs = function() {
  327. var ddocs = {}, dbs = CouchDB.allDbs();
  328. for (var i=0; i < dbs.length; i++) {
  329. var db = new CouchDB(dbs[i]);
  330. ddocs[dbs[i]] = db.designDocs();
  331. };
  332. return ddocs;
  333. };
  334. CouchDB.getVersion = function() {
  335. CouchDB.last_req = CouchDB.request("GET", "/");
  336. CouchDB.maybeThrowError(CouchDB.last_req);
  337. return JSON.parse(CouchDB.last_req.responseText).version;
  338. };
  339. CouchDB.reloadConfig = function() {
  340. // diabled until cluser port gets /_config
  341. return {};
  342. CouchDB.last_req = CouchDB.request("POST", "/_config/_reload");
  343. CouchDB.maybeThrowError(CouchDB.last_req);
  344. return JSON.parse(CouchDB.last_req.responseText);
  345. };
  346. CouchDB.replicate = function(source, target, rep_options) {
  347. rep_options = rep_options || {};
  348. var headers = rep_options.headers || {};
  349. var body = rep_options.body || {};
  350. body.source = source;
  351. body.target = target;
  352. CouchDB.last_req = CouchDB.request("POST", "/_replicate", {
  353. headers: headers,
  354. body: JSON.stringify(body)
  355. });
  356. CouchDB.maybeThrowError(CouchDB.last_req);
  357. return JSON.parse(CouchDB.last_req.responseText);
  358. };
  359. CouchDB.newXhr = function() {
  360. if (typeof(XMLHttpRequest) != "undefined") {
  361. return new XMLHttpRequest();
  362. } else if (typeof(ActiveXObject) != "undefined") {
  363. return new ActiveXObject("Microsoft.XMLHTTP");
  364. } else {
  365. throw new Error("No XMLHTTPRequest support detected");
  366. }
  367. };
  368. CouchDB.xhrbody = function(xhr) {
  369. if (xhr.responseText) {
  370. return xhr.responseText;
  371. } else if (xhr.body) {
  372. return xhr.body
  373. } else {
  374. throw new Error("No XMLHTTPRequest support detected");
  375. }
  376. }
  377. CouchDB.xhrheader = function(xhr, header) {
  378. if(xhr.getResponseHeader) {
  379. return xhr.getResponseHeader(header);
  380. } else if(xhr.headers) {
  381. return xhr.headers[header] || null;
  382. } else {
  383. throw new Error("No XMLHTTPRequest support detected");
  384. }
  385. }
  386. CouchDB.proxyUrl = function(uri) {
  387. if(uri.substr(0, CouchDB.protocol.length) != CouchDB.protocol) {
  388. uri = CouchDB.urlPrefix + uri;
  389. }
  390. return uri;
  391. }
  392. CouchDB.request = function(method, uri, options) {
  393. options = typeof(options) == 'object' ? options : {};
  394. options.headers = typeof(options.headers) == 'object' ? options.headers : {};
  395. options.headers["Content-Type"] = options.headers["Content-Type"] || options.headers["content-type"] || "application/json";
  396. options.headers["Accept"] = options.headers["Accept"] || options.headers["accept"] || "application/json";
  397. var req = CouchDB.newXhr();
  398. uri = CouchDB.proxyUrl(uri);
  399. if (options.url) {
  400. var params = '';
  401. for (var key in options.url) {
  402. var value = options.url[key]
  403. params += key + '=' + value + '&'
  404. }
  405. // if uri already has a ? append with &
  406. if (uri.indexOf('?') === -1) {
  407. uri += '?' + params;
  408. } else {
  409. uri += '&' + params;
  410. }
  411. }
  412. // console.log(uri);
  413. // console.log(JSON.stringify(options, null, 2));
  414. req.open(method, uri, false);
  415. if (options.headers) {
  416. var headers = options.headers;
  417. for (var headerName in headers) {
  418. if (!headers.hasOwnProperty(headerName)) { continue; }
  419. req.setRequestHeader(headerName, headers[headerName]);
  420. }
  421. }
  422. req.send(options.body || "");
  423. return req;
  424. };
  425. CouchDB.requestStats = function(path, test) {
  426. var query_arg = "";
  427. if(test !== null) {
  428. query_arg = "?flush=true";
  429. }
  430. var url = "/_node/_local/_stats/" + path.join("/") + query_arg;
  431. var stat = CouchDB.request("GET", url).responseText;
  432. return JSON.parse(stat);
  433. };
  434. CouchDB.uuids_cache = [];
  435. CouchDB.newUuids = function(n, buf) {
  436. buf = buf || 100;
  437. if (CouchDB.uuids_cache.length >= n) {
  438. var uuids = CouchDB.uuids_cache.slice(CouchDB.uuids_cache.length - n);
  439. if(CouchDB.uuids_cache.length - n == 0) {
  440. CouchDB.uuids_cache = [];
  441. } else {
  442. CouchDB.uuids_cache =
  443. CouchDB.uuids_cache.slice(0, CouchDB.uuids_cache.length - n);
  444. }
  445. return uuids;
  446. } else {
  447. CouchDB.last_req = CouchDB.request("GET", "/_uuids?count=" + (buf + n));
  448. CouchDB.maybeThrowError(CouchDB.last_req);
  449. var result = JSON.parse(CouchDB.last_req.responseText);
  450. CouchDB.uuids_cache =
  451. CouchDB.uuids_cache.concat(result.uuids.slice(0, buf));
  452. return result.uuids.slice(buf);
  453. }
  454. };
  455. CouchDB.maybeThrowError = function(req) {
  456. if (req.status >= 400) {
  457. try {
  458. var result = JSON.parse(req.responseText);
  459. } catch (ParseError) {
  460. var result = {error:"unknown", reason:req.responseText};
  461. }
  462. throw (new CouchError(result));
  463. }
  464. }
  465. CouchDB.params = function(options) {
  466. options = options || {};
  467. var returnArray = [];
  468. for(var key in options) {
  469. var value = options[key];
  470. returnArray.push(key + "=" + value);
  471. }
  472. return returnArray.join("&");
  473. };
  474. // Used by replication test
  475. if (typeof window == 'undefined' || !window) {
  476. var hostRE = RegExp("https?://([^\/]+)");
  477. var getter = function () {
  478. return (new CouchHTTP).base_url.match(hostRE)[1];
  479. };
  480. if(Object.defineProperty) {
  481. Object.defineProperty(CouchDB, "host", {
  482. get : getter,
  483. enumerable : true
  484. });
  485. } else {
  486. CouchDB.__defineGetter__("host", getter);
  487. }
  488. CouchDB.protocol = "http://";
  489. CouchDB.inBrowser = false;
  490. } else {
  491. CouchDB.host = window.location.host;
  492. CouchDB.inBrowser = true;
  493. CouchDB.protocol = window.location.protocol + "//";
  494. }
  495. // Turns an {error: ..., reason: ...} response into an Error instance
  496. function CouchError(error) {
  497. var inst = new Error(error.reason);
  498. inst.name = 'CouchError';
  499. inst.error = error.error;
  500. inst.reason = error.reason;
  501. return inst;
  502. }
  503. CouchError.prototype.constructor = CouchError;