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