blob: fc938759182fea348497b10b61c86a4a1293d0ee [file] [log] [blame]
Eugenia Gabrielova12de0302013-10-18 02:25:39 -07001/**
2* Asterix SDK - Beta Version
3* @author Eugenia Gabrielov <genia.likes.science@gmail.com>
4*
5* This is a Javascript helper file for generating AQL queries for AsterixDB (https://code.google.com/p/asterixdb/)
6*/
7
8/**
9* AsterixDBConnection
10*
11* This is a handler for connections to a local AsterixDB REST API Endpoint.
12* This initialization takes as input a configuraiton object, and initializes
13* same basic functionality.
14*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070015function AsterixDBConnection(configuration) {
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -070016 // Initialize AsterixDBConnection properties
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070017 this._properties = {};
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -070018
19 // Set dataverse as null for now, this needs to be set by the user.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070020 this._properties["dataverse"] = "";
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -070021
22 // By default, we will wait for calls to the REST API to complete. The query method
23 // sends a different setting when executed asynchronously. Calls that do not specify a mode
24 // will be executed synchronously.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070025 this._properties["mode"] = "synchronous";
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -070026
27 // These are the default error behaviors for Asterix and ajax errors, respectively.
28 // They can be overridden by calling initializing your AsterixDBConnection like so:
29 // adb = new AsterixDBConnection({
30 // "error" : function(data) {
31 // // override here...
32 // });
33 // and similarly for ajax_error, just pass in the configuration as a json object.
34 this._properties["error"] = function(data) {
35 alert("Asterix REST API Error:\n" + data["error-code"][0] + "\n" + data["error-code"][1]);
36 };
37
38 this._properties["ajax_error"] = function(message) {
39 alert("[Ajax Error]\n" + message);
40 };
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070041
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -070042 // This is the default path to the local Asterix REST API. Can be overwritten for remote configurations
43 // or for demo setup purposes (such as with a proxy handler with Python or PHP.
44 this._properties["endpoint_root"] = "http://localhost:19002/";
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070045
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -070046 // If we have passed in a configuration, we will update the internal properties
47 // using that configuration. You can do things such as include a new endpoint_root,
48 // a new error function, a new dataverse, etc. You can even store extra info.
49 //
50 // NOTE Long-term, this should have more strict limits.
51 var configuration = configuration || {};
52
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070053 for (var key in configuration) {
54 this._properties[key] = configuration[key];
55 }
56
57 return this;
58}
59
60
Eugenia Gabrielova12de0302013-10-18 02:25:39 -070061/**
62* dataverse
63*
64* Sets dataverse for execution for the AsterixDBConnection.
65*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070066AsterixDBConnection.prototype.dataverse = function(dataverseName) {
67 this._properties["dataverse"] = dataverseName;
68
69 return this;
70};
71
72
Eugenia Gabrielova12de0302013-10-18 02:25:39 -070073/**
74* query (http://asterix.ics.uci.edu/documentation/api.html#QueryApi)
75*
76* @param statements, statements of an AQL query
77* @param successFn, a function to execute if this query is run successfully
78* @param mode, a string either "synchronous" or "asynchronous", depending on preferred
79* execution mode.
80*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070081AsterixDBConnection.prototype.query = function(statements, successFn, mode) {
82
83 if ( typeof statements === 'string') {
84 statements = [ statements ];
85 }
86
87 var m = typeof mode ? mode : "synchronous";
Eugenia Gabrielova12de0302013-10-18 02:25:39 -070088
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -070089 var query = "use dataverse " + this._properties["dataverse"] + ";\n" + statements.join("\n");
90
91 this._api(
92 {
93 "query" : query,
94 "mode" : m
95 },
96 successFn,
97 "query"
98 );
99
100 return this;
101};
102
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700103/**
104* query_status (http://asterix.ics.uci.edu/documentation/api.html#QueryStatusApi)
105*
106* @param handle, a json object of the form {"handle" : handleObject}, where
107* the handle object is an opaque handle previously returned
108* from an asynchronous call.
109* @param successFn, a function to call on successful execution of this API call.
110*/
111AsterixDBConnection.prototype.query_status = function(handle, successFn) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700112 this._api(
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700113 handle,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700114 successFn,
115 "query/status"
116 );
117
118 return this;
119};
120
121
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700122/**
123* query_result (http://asterix.ics.uci.edu/documentation/api.html#AsynchronousResultApi)
124*
125* handle, a json object of the form {"handle" : handleObject}, where
126* the handle object is an opaque handle previously returned
127* from an asynchronous call.
128* successFn, a function to call on successful execution of this API call.
129*/
130AsterixDBConnection.prototype.query_result = function(handle, successFn) {
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700131 this._api(
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700132 handle,
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700133 successFn,
134 "query/result"
135 );
136
137 return this;
138};
139
140
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700141/**
142* ddl (http://asterix.ics.uci.edu/documentation/api.html#DdlApi)
143*
144* @param statements, statements to run through ddl api
145* @param successFn, a function to execute if they are successful
146*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700147AsterixDBConnection.prototype.ddl = function(statements, successFn) {
148 if ( typeof statements === 'string') {
149 statements = [ statements ];
150 }
151
152 this._api(
153 {
154 "ddl" : "use dataverse " + this._properties["dataverse"] + ";\n" + statements.join("\n")
155 },
156 successFn,
157 "ddl"
158 );
159}
160
161
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700162/**
163* update (http://asterix.ics.uci.edu/documentation/api.html#UpdateApi)
164*
165* @param statements, statement(s) for an update API call
166* @param successFn, a function to run if this is executed successfully.
167*
168* This is an AsterixDBConnection handler for the update API. It passes statements provided
169* to the internal API endpoint handler.
170*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700171AsterixDBConnection.prototype.update = function(statements, successFn) {
172 if ( typeof statements === 'string') {
173 statements = [ statements ];
174 }
175
176 this._api(
177 {
178 "statements" : "use dataverse " + this._properties["dataverse"] + ";\n" + statements.join("\n")
179 },
180 successFn,
181 "update"
182 );
183}
184
185
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700186/**
187* meta
188* @param statements, a string or a list of strings representing an Asterix query object
189* @param successFn, a function to execute if call succeeds
190*
191* Queries without a dataverse. This is a work-around for an Asterix REST API behavior
192* that sometiems throws an error. This is handy for Asterix Metadata queries.
193*/
194AsterixDBConnection.prototype.meta = function(statements, successFn) {
195
196 if ( typeof statements === 'string') {
197 statements = [ statements ];
198 }
199
200 var query = statements.join("\n");
201
202 this._api(
203 {
204 "query" : query,
205 "mode" : "synchronous"
206 },
207 successFn,
208 "query"
209 );
210
211 return this;
212}
213
214
215/**
216* _api
217*
218* @param json, the data to be passed with the request
219* @param onSuccess, the success function to be run if this succeeds
220* @param endpoint, a string representing one of the Asterix API endpoints
221*
222* Documentation of endpoints is here:
223* http://asterix.ics.uci.edu/documentation/api.html
224*
225* This is treated as an internal method for making the actual call to the API.
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700226*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700227AsterixDBConnection.prototype._api = function(json, onSuccess, endpoint) {
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700228
229 // The success function is called if the response is successful and returns data,
230 // or is just OK.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700231 var success_fn = onSuccess;
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700232
233 // This is the error function. Called if something breaks either on the Asterix side
234 // or in the Ajax call.
Eugenia Gabrielova12de0302013-10-18 02:25:39 -0700235 var error_fn = this._properties["error"];
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700236 var ajax_error_fn = this._properties["ajax_error"];
237
238 // This is the target endpoint from the REST api, called as a string.
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700239 var endpoint_url = this._properties["endpoint_root"] + endpoint;
240
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700241 // This SDK does not rely on jQuery, but utilizes its Ajax capabilities when present.
242 if (window.jQuery) {
243 $.ajax({
244
245 // The Asterix API does not accept post requests.
246 type : 'GET',
247
248 // This is the endpoint url provided by combining the default
249 // or reconfigured endpoint root along with the appropriate api endpoint
250 // such as "query" or "update".
251 url : endpoint_url,
252
253 // This is the data in the format specified on the API documentation.
254 data : json,
255
256 // We send out the json datatype to make sure our data is parsed correctly.
257 dataType : "json",
258
259 // The success option calls a function on success, which in this case means
260 // something was returned from the API. However, this does not mean the call succeeded
261 // on the REST API side, it just means we got something back. This also contains the
262 // error return codes, which need to be handled before we call th success function.
263 success : function(data) {
264
265 // Check Asterix Response for errors
266 // See http://asterix.ics.uci.edu/documentation/api.html#ErrorCodes
267 if (data["error-code"]) {
268 error_fn(data);
269
270 // Otherwise, run our provided success function
271 } else {
272 success_fn(data);
273 }
274 },
275
276 // This is the function that gets called if there is an ajax-related (non-Asterix)
277 // error. Network errors, empty response bodies, syntax errors, and a number of others
278 // can pop up.
279 error : function(data) {
280 // Some of the Asterix API endpoints return empty responses on success.
281 // However, the ajax function treats these as errors while reporting a
282 // 200 OK code with no payload. So we will check for that, otherwise
283 // alert of an error. An example response is as follows:
284 // {"readyState":4,"responseText":"","status":200,"statusText":"OK"}
285 if (data["status"] == 200 && data["responseText"] == "") {
286 success_fn(data);
287 } else {
288 thi
289 alert("[Ajax Error]\n" + JSON.stringify(data));
290 }
291 }
292 });
293 } else {
294 alert("no jquery");
295 var xmlhttp;
296 }
297 // XML Http Request
298 /*var xmlhttp;
299
300 xmlhttp = new XMLHttpRequest();
301 xmlhttp.onreadystatechange = function(){
302 if (xmlhttp.readyState == 4 && xmlhttp.status == 200){
303 alert(xmlhttp.responseText);
304 //callback(xmlhttp.responseText);
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700305 }
Eugenia Gabrielovadbd50a42013-10-19 00:30:27 -0700306 }
307 xmlhttp.open("GET", endpoint_url, true);
308 xmlhttp.send();*/
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700309
310 return this;
311};
312
genia.likes.science@gmail.com6d6aa8e2013-07-23 01:23:21 -0700313// Asterix Expressions - Base
314function AExpression () {
315
316 this._properties = {};
317 this._success = function() {};
318
319 if (arguments.length == 1) {
320 this._properties["value"] = arguments[0];
321 }
322
323 return this;
324}
325
326
327AExpression.prototype.bind = function(options) {
328 var options = options || {};
329
330 if (options.hasOwnProperty("success")) {
331 this._success = options["success"];
332 }
333
334 if (options.hasOwnProperty("return")) {
335 this._properties["return"] = " return " + options["return"].val();
336 }
337};
338
339
340AExpression.prototype.run = function(successFn) {
341 return this;
342};
343
344
345AExpression.prototype.val = function() {
346
347 var value = "";
348
349 // If there is a dataverse defined, provide it.
350 if (this._properties.hasOwnProperty("dataverse")) {
351 value += "use dataverse " + this._properties["dataverse"] + ";\n";
352 };
353
354 if (this._properties.hasOwnProperty("value")) {
355 value += this._properties["value"].toString();
356 }
357
358 return value;
359};
360
361
362// @param expressionValue [String]
363AExpression.prototype.set = function(expressionValue) {
364 this._properties["value"] = expressionValue;
365 return this;
366};
367
368
369// AQL Statements
370// SingleStatement ::= DataverseDeclaration
371// | FunctionDeclaration
372// | CreateStatement
373// | DropStatement
374// | LoadStatement
375// | SetStatement
376// | InsertStatement
377// | DeleteStatement
378// | Query
379function InsertStatement(quantifiedName, query) {
380 AExpression.call(this);
381
382 var innerQuery = "";
383 if (query instanceof AExpression) {
384 innerQuery = query.val();
385 } else if (typeof query == "object" && Object.getPrototypeOf( query ) === Object.prototype ) {
386
387 var insertStatements = [];
388 for (querykey in query) {
389 if (query[querykey] instanceof AExpression) {
390 insertStatements.push('"' + querykey + '" : ' + query[querykey].val());
391 } else if (typeof query[querykey] == "string") {
392 insertStatements.push('"' + querykey + '" : ' + query[querykey]);
393 } else {
394 insertStatements.push('"' + querykey + '" : ' + query[querykey].toString());
395 }
396 }
397
398 innerQuery = "{" + insertStatements.join(', ') + "}";
399 }
400
401 var statement = "insert into dataset " + quantifiedName + "(" + innerQuery + ");";
402
403 AExpression.prototype.set.call(this, statement);
404
405 return this;
406}
407
408InsertStatement.prototype = Object.create(AExpression.prototype);
409InsertStatement.prototype.constructor = InsertStatement;
410
411
412// Delete Statement
413// DeleteStatement ::= "delete" Variable "from" "dataset" QualifiedName ( "where" Expression )?
414function DeleteStatement (variable, quantifiedName, whereExpression) {
415 AExpression.call(this);
416
417 var statement = "delete " + variable + " from dataset " + quantifiedName;
418
419 if (whereExpression instanceof AExpression) {
420 statement += " where " + whereExpression.val();
421 }
422
423 AExpression.prototype.set.call(this, statement);
424
425 return this;
426}
427
428DeleteStatement.prototype = Object.create(AExpression.prototype);
429DeleteStatement.prototype.constructor = DeleteStatement;
430
431// SetStatement
432//
433// Grammar
434// "set" Identifier StringLiteral
435function SetStatement (identifier, stringLiteral) {
436 AExpression.call(this);
437
438 var statement = "set " + identifier + ' "' + stringLiteral + '";';
439
440 AExpression.prototype.set.call(this, statement);
441
442 return this;
443}
444
445SetStatement.prototype = Object.create(AExpression.prototype);
446SetStatement.prototype.constructor = SetStatement;
447
448
449// Other Expressions
450
451// FunctionExpression
452// Parent: AsterixExpression
453//
454// @param options [Various],
455// @key function [String], a function to be applid to the expression
456// @key expression [AsterixExpression or AQLClause] an AsterixExpression/Clause to which the fn will be applied
457function FunctionExpression() {
458
459 // Initialize superclass
460 AExpression.call(this);
461
462 this._properties["function"] = "";
463 this._properties["expressions"] = [];
464
465 // Check for fn/expression input
466 if (arguments.length >= 2 && typeof arguments[0] == "string") {
467
468 this._properties["function"] = arguments[0];
469
470 for (i = 1; i < arguments.length; i++) {
471 if (arguments[i] instanceof AExpression || arguments[i] instanceof AQLClause) {
472 this._properties["expressions"].push(arguments[i]);
473 } else {
474 this._properties["expressions"].push(new AExpression(arguments[i]));
475 }
476 }
477 }
478
479 // Return FunctionCallExpression object
480 return this;
481}
482
483
484FunctionExpression.prototype = Object.create(AExpression.prototype);
485FunctionExpression.prototype.constructor = FunctionExpression;
486
487
488FunctionExpression.prototype.val = function () {
489 var fn_args = [];
490 for (var i = 0; i < this._properties["expressions"].length; i++) {
491 fn_args.push(this._properties["expressions"][i].val());
492 }
493
494 return this._properties["function"] + "(" + fn_args.join(", ") + ")";
495};
496
497
498// FLWOGRExpression
499//
500// FLWOGRExpression ::= ( ForClause | LetClause ) ( Clause )* "return" Expression
501function FLWOGRExpression (options) {
502 // Initialize superclass
503 AExpression.call(this);
504
505 this._properties["clauses"] = [];
506 this._properties["minSize"] = 0;
507
508 // Bind options and return
509 this.bind(options);
510 return this;
511}
512
513
514FLWOGRExpression.prototype = Object.create(AExpression.prototype);
515FLWOGRExpression.prototype.constructor = FLWOGRExpression;
516
517
518FLWOGRExpression.prototype.bind = function(options) {
519 AExpression.prototype.bind.call(this, options);
520
521 var options = options || {};
522
523 if (options instanceof SetStatement) {
524 this._properties["clauses"].push(options);
525 this._properties["minSize"] += 1;
526 }
527
528 if (this._properties["clauses"].length <= this._properties["minSize"]) {
529 // Needs to start with for or let clause
530 if (options instanceof ForClause || options instanceof LetClause) {
531 this._properties["clauses"].push(options);
532 }
533 } else {
534 if (options instanceof AQLClause) {
535 this._properties["clauses"].push(options);
536 }
537 }
538
539 return this;
540};
541
542
543FLWOGRExpression.prototype.val = function() {
544 var value = AExpression.prototype.val.call(this);
545
546 var clauseValues = [];
547 for (var c in this._properties["clauses"]) {
548 clauseValues.push(this._properties["clauses"][c].val());
549 }
550
551 return value + clauseValues.join("\n");// + ";";
552};
553
554// Pretty Expression Shorthand
555
556FLWOGRExpression.prototype.ReturnClause = function(expression) {
557 return this.bind(new ReturnClause(expression));
558};
559
560FLWOGRExpression.prototype.ForClause = function() {
561 return this.bind(new ForClause(Array.prototype.slice.call(arguments)));
562};
563
564FLWOGRExpression.prototype.LetClause = function() {
565 return this.bind(new LetClause(Array.prototype.slice.call(arguments)));
566};
567
568FLWOGRExpression.prototype.WhereClause = function() {
569 return this.bind(new WhereClause(Array.prototype.slice.call(arguments)));
570};
571
572FLWOGRExpression.prototype.and = function() {
573 var args = Array.prototype.slice.call(arguments);
574 args.push(true);
575 return this.bind(new WhereClause().and(args));
576};
577
578FLWOGRExpression.prototype.or = function() {
579 var args = Array.prototype.slice.call(arguments);
580 args.push(true);
581 return this.bind(new WhereClause().or(args));
582};
583
584FLWOGRExpression.prototype.OrderbyClause = function() {
585 return this.bind(new OrderbyClause(Array.prototype.slice.call(arguments)));
586};
587
588
589FLWOGRExpression.prototype.GroupClause = function() {
590 return this.bind(new GroupClause(Array.prototype.slice.call(arguments)));
591};
592
593FLWOGRExpression.prototype.LimitClause = function() {
594 return this.bind(new LimitClause(Array.prototype.slice.call(arguments)));
595};
596
597FLWOGRExpression.prototype.DistinctClause = function() {
598 return this.bind(new DistinctClause(Array.prototype.slice.call(arguments)));
599};
600
601FLWOGRExpression.prototype.AQLClause = function() {
602 return this.bind(new AQLClause(Array.prototype.slice.call(arguments)));
603};
604
605
606// AQLClause
607//
608// Base Clause ::= ForClause | LetClause | WhereClause | OrderbyClause | GroupClause | LimitClause | DistinctClause
609function AQLClause() {
610 this._properties = {};
611 this._properties["clause"] = "";
612 this._properties["stack"] = [];
613 if (typeof arguments[0] == 'string') {
614 this._properties["clause"] = arguments[0];
615 }
616 return this;
617}
618
619AQLClause.prototype.val = function() {
620 var value = this._properties["clause"];
621
622 return value;
623};
624
625AQLClause.prototype.bind = function(options) {
626
627 if (options instanceof AQLClause) {
628 this._properties["clause"] += " " + options.val();
629 }
630
631 return this;
632};
633
634AQLClause.prototype.set = function(value) {
635 this._properties["clause"] = value;
636 return this;
637};
638
639
640// ForClause
641//
642// Grammar:
643// "for" Variable ( "at" Variable )? "in" ( Expression )
644//
645// @param for_variable [String], REQUIRED, first variable in clause
646// @param at_variable [String], NOT REQUIRED, first variable in clause
647// @param expression [AsterixExpression], REQUIRED, expression to evaluate
648function ForClause(for_variable, at_variable, expression) {
649 AQLClause.call(this);
650
651 var parameters = [];
652 if (arguments[0] instanceof Array) {
653 parameters = arguments[0];
654 } else {
655 parameters = arguments;
656 }
657
658 this._properties["clause"] = "for " + parameters[0];
659
660 if (parameters.length == 3) {
661 this._properties["clause"] += " at " + parameters[1];
662 this._properties["clause"] += " in " + parameters[2].val();
663 } else if (parameters.length == 2) {
664 this._properties["clause"] += " in " + parameters[1].val();
665 }
666
667 return this;
668}
669
670ForClause.prototype = Object.create(AQLClause.prototype);
671ForClause.prototype.constructor = ForClause;
672
673
674// LetClause
675//
676// Grammar:
677// LetClause ::= "let" Variable ":=" Expression
678//
679// @param let_variable [String]
680// @param expression [AExpression]
681//
682// TODO Vigorous error checking
683function LetClause(let_variable, expression) {
684 AQLClause.call(this);
685
686 var parameters = [];
687 if (arguments[0] instanceof Array) {
688 parameters = arguments[0];
689 } else {
690 parameters = arguments;
691 }
692
693 this._properties["clause"] = "let " + parameters[0] + " := ";
694 this._properties["clause"] += parameters[1].val();
695
696 return this;
697}
698
699LetClause.prototype = Object.create(AQLClause.prototype);
700LetClause.prototype.constructor = LetClause;
701
702
703// ReturnClause
704//
705// Grammar:
706// return [AQLExpression]
707function ReturnClause(expression) {
708 AQLClause.call(this);
709
710 this._properties["clause"] = "return ";
711
712 if (expression instanceof AExpression || expression instanceof AQLClause) {
713 this._properties["clause"] += expression.val();
714
715 } else if ( typeof expression == "object" && Object.getPrototypeOf( expression ) === Object.prototype ) {
716
717 this._properties["clause"] += "\n{\n";
718 var returnStatements = [];
719 for (returnValue in expression) {
720
721 if (expression[returnValue] instanceof AExpression) {
722 returnStatements.push('"' + returnValue + '" ' + " : " + expression[returnValue].val());
723 } else if (typeof expression[returnValue] == "string") {
724 returnStatements.push('"' + returnValue + '" ' + " : " + expression[returnValue]);
725 }
726 }
727 this._properties["clause"] += returnStatements.join(",\n");
728 this._properties["clause"] += "\n}";
729
730 } else {
731 this._properties["clause"] += new AQLClause().set(expression).val();
732 }
733
734 return this;
735}
736
737
738ReturnClause.prototype = Object.create(AQLClause.prototype);
739ReturnClause.prototype.constructor = ReturnClause;
740
741
742// WhereClause
743//
744// Grammar:
745// ::= "where" Expression
746//
747// @param expression [BooleanExpression], pushes this expression onto the stack
748function WhereClause(expression) {
749 AQLClause.call(this);
750
751 this._properties["stack"] = [];
752
753 if (expression instanceof Array) {
754 this.bind(expression[0]);
755 } else {
756 this.bind(expression);
757 }
758
759 return this;
760}
761
762
763WhereClause.prototype = Object.create(AQLClause.prototype);
764WhereClause.prototype.constructor = WhereClause;
765
766
767WhereClause.prototype.bind = function(expression) {
768 if (expression instanceof AExpression) {
769 this._properties["stack"].push(expression);
770 }
771 return this;
772};
773
774
775WhereClause.prototype.val = function() {
776 var value = "";
777
778 if (this._properties["stack"].length == 0) {
779 return value;
780 }
781
782 var count = this._properties["stack"].length - 1;
783 while (count >= 0) {
784 value += this._properties["stack"][count].val() + " ";
785 count -= 1;
786 }
787
788 return "where " + value;
789};
790
791
792WhereClause.prototype.and = function() {
793
794 var parameters = [];
795 if (arguments[0] instanceof Array) {
796 parameters = arguments[0];
797 } else {
798 parameters = arguments;
799 }
800
801 var andClauses = [];
802 for (var expression in parameters) {
803
804 if (parameters[expression] instanceof AExpression) {
805 andClauses.push(parameters[expression].val());
806 }
807 }
808
809 if (andClauses.length > 0) {
810 this._properties["stack"].push(new AExpression().set(andClauses.join(" and ")));
811 }
812
813 return this;
814};
815
816
817WhereClause.prototype.or = function() {
818
819 var parameters = [];
820 if (arguments[0] instanceof Array) {
821 parameters = arguments[0];
822 } else {
823 parameters = arguments;
824 }
825
826 var orClauses = [];
827 for (var expression in parameters) {
828
829 if (parameters[expression] instanceof AExpression) {
830 orClauses.push(parameters[expression].val());
831 }
832 }
833
834 if (andClauses.length > 0) {
835 this._properties["stack"].push(new AExpression().set(orClauses.join(" and ")));
836 }
837
838 return this;
839};
840
841// LimitClause
842// Grammar:
843// LimitClause ::= "limit" Expression ( "offset" Expression )?
844//
845// @param limitExpression [REQUIRED, AQLExpression]
846// @param offsetExpression [OPTIONAL, AQLExpression]
847function LimitClause(limitExpression, offsetExpression) {
848
849 AQLClause.call(this);
850
851 var parameters = [];
852 if (arguments[0] instanceof Array) {
853 parameters = arguments[0];
854 } else {
855 parameters = arguments;
856 }
857
858 // limitExpression required
859 this._properties["clause"] = "limit " + parameters[0].val();
860
861 // Optional: Offset
862 if (parameters.length == 2) {
863 this._properties["clause"] += " offset " + parameters[1].val();
864 }
865
866 return this;
867}
868
869LimitClause.prototype = Object.create(AQLClause.prototype);
870LimitClause.prototype.constructor = LimitClause;
871
872
873// OrderbyClause
874//
875// Grammar:
876// OrderbyClause ::= "order" "by" Expression ( ( "asc" ) | ( "desc" ) )? ( "," Expression ( ( "asc" ) | ( "desc" ) )? )*
877//
878// @params AQLExpressions and asc/desc strings, in any quantity. At least one required.
879function OrderbyClause() {
880
881 AQLClause.call(this);
882
883 // At least one argument expression is required, and first should be expression
884 if (arguments.length == 0) {
885 this._properties["clause"] = null;
886 return this;
887 }
888
889 var parameters = [];
890 if (arguments[0] instanceof Array) {
891 parameters = arguments[0];
892 } else {
893 parameters = arguments;
894 }
895
896 var expc = 0;
897 var expressions = [];
898
899 while (expc < parameters.length) {
900
901 var expression = "";
902
903 if (parameters[expc] instanceof AExpression) {
904 expression += parameters[expc].val();
905 }
906
907 var next = expc + 1;
908 if (next < parameters.length && (parameters[next] == "asc" || parameters[next] == "desc")) {
909 expc++;
910 expression += " " + parameters[expc];
911 }
912
913 expressions.push(expression);
914
915 expc++;
916 }
917
918 this._properties["clause"] = "order by " + expressions.join(", ");
919 return this;
920}
921
922OrderbyClause.prototype = Object.create(AQLClause.prototype);
923OrderbyClause.prototype.constructor = OrderbyClause;
924
925
926// GroupClause
927//
928// Grammar:
929// GroupClause ::= "group" "by" ( Variable ":=" )? Expression ( "," ( Variable ":=" )? Expression )* ( "decor" Variable ":=" Expression ( "," "decor" Variable ":=" Expression )* )? "with" VariableRef ( "," VariableRef )*
930function GroupClause() {
931 AQLClause.call(this);
932
933 if (arguments.length == 0) {
934 this._properties["clause"] = null;
935 return this;
936 }
937
938 var parameters = [];
939 if (arguments[0] instanceof Array) {
940 parameters = arguments[0];
941 } else {
942 parameters = arguments;
943 }
944
945 var expc = 0;
946 var expressions = [];
947 var variableRefs = [];
948 var isDecor = false;
949
950 while (expc < parameters.length) {
951
952 if (parameters[expc] instanceof AExpression) {
953
954 isDecor = false;
955 expressions.push(parameters[expc].val());
956
957 } else if (typeof parameters[expc] == "string") {
958
959 // Special keywords, decor & with
960 if (parameters[expc] == "decor") {
961 isDecor = true;
962 } else if (parameters[expc] == "with") {
963 isDecor = false;
964 expc++;
965 while (expc < parameters.length) {
966 variableRefs.push(parameters[expc]);
967 expc++;
968 }
969
970 // Variables and variable refs
971 } else {
972
973 var nextc = expc + 1;
974 var expression = "";
975
976 if (isDecor) {
977 expression += "decor ";
978 isDecor = false;
979 }
980
981 expression += parameters[expc] + " := " + parameters[nextc].val();
982 expressions.push(expression);
983 expc++;
984 }
985 }
986
987 expc++;
988 }
989
990 this._properties["clause"] = "group by " + expressions.join(", ") + " with " + variableRefs.join(", ");
991 return this;
992}
993
994GroupClause.prototype = Object.create(AQLClause.prototype);
995GroupClause.prototype.constructor = GroupClause;
996
997
998// Quantified Expression
999//
1000// Grammar
1001// QuantifiedExpression ::= ( ( "some" ) | ( "every" ) ) Variable "in" Expression ( "," Variable "in" Expression )* "satisfies" Expression
1002//
1003// @param String some/every
1004// @param [AExpression]
1005// @param [Aexpression] satisfiesExpression
1006function QuantifiedExpression (keyword, expressions, satisfiesExpression) {
1007 AExpression.call(this);
1008
1009 var expression = keyword + " ";
1010 var varsInExpressions = [];
1011
1012 for (var varInExpression in expressions) {
1013 varsInExpressions.push(varInExpression + " in " + expressions[varInExpression].val());
1014 }
1015 expression += varsInExpressions.join(", ") + " satisfies " + satisfiesExpression.val();
1016
1017 AExpression.prototype.set.call(this, expression);
1018
1019 return this;
1020}
1021
1022QuantifiedExpression.prototype = Object.create(AExpression.prototype);
1023QuantifiedExpression.prototype.constructor = QuantifiedExpression;
1024
1025QuantifiedExpression.prototype.val = function() {
1026 var value = AExpression.prototype.val.call(this);
1027 return "(" + value + ")";
1028};