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