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