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