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