/**
* @namespace LiveView
*/
;(function(window, $, undefined){
'use strict';
var tmpPrototype;
/** @alias LiveView.prototype */
window.LiveView = {
Table: Table,
Query: Query,
QueryProperties: QueryProperties,
Schema: Schema,
Field: Field,
Tuple: Tuple,
Error: Error,
_internal: {}
};
/**
* Defines the properties of a LiveView Table. Properties are read-only.
* @class Table
* @memberOf LiveView
* @param {String} name The name of the table.
* @param {String} group The group the table belongs to.
* @param {String} shortDescription A short description of the table. Ideal for length-limited fields.
* @param {String} description A full description of the table.
* @param {boolean} isEnabled A flag indicating whether or not the table is enabled.
* @param {String} queryLanguages A CSV string of supported query languages for this table.
* @param {Array} keys Field names of key fields
* @param {LiveView.Schema} schema The Schema that defines the table fields.
*/
function Table(name, group, shortDescription, description, isEnabled, queryLanguages, keys, schema){
if(!(this instanceof Table)){
return new Table(name, group, shortDescription, description, isEnabled, queryLanguages, keys, schema);
}
/**
* The name of the table.
* @memberOf LiveView.Table
* @instance
* @member {String} name
*/
this.name = name;
/**
* The group the table belongs to.
* @memberOf LiveView.Table
* @instance
* @member {String} group
*/
this.group = group;
/**
* A short description of the table. Ideal for length-limited fields.
* @memberOf LiveView.Table
* @instance
* @member {String} shortDescription
*/
this.shortDescription = shortDescription;
/**
* A full description of the table.
* @memberOf LiveView.Table
* @instance
* @member {String} description
*/
this.description = description;
/**
* A flag indicating whether or not the table is enabled.
* @memberOf LiveView.Table
* @instance
* @member {boolean} isEnabled
*/
this.isEnabled = isEnabled;
/**
* A CSV string of supported query languages for this table.
* @memberOf LiveView.Table
* @instance
* @member {String} queryLanguages
*/
this.queryLanguages = queryLanguages;
/**
* An array of string field names identifying the table's key fields.
* @memberOf LiveView.Table
* @instance
* @member {Array} keys
*/
this.keys = keys;
/**
* The {@link LiveView.Schema|Schema} for the table.
* @memberOf LiveView.Table
* @instance
* @member {LiveView.Schema} schema
*/
this.schema = schema;
}
/** @alias LiveView.Table.prototype */
Table.prototype = {
constructor: Table
};
/**
* Object that represents a LiveView query. This object is used as a parameter when calling
* {@link LiveView.Connection.execute|execute} or {@link LiveView.Connection.subscribe|subscribe}. It stores the
* query string (in parameterized format) as well as the parameters to apply when invoking execute or subscribe.
* @class Query
* @memberOf LiveView
* @param {String} query The optionally-parametrized query string
* @param {Object} [parameters] A key-value map of parameters to use to compute the query string, if query is a parametrized string. Users are essentially free to define the key value used to identify a parameter as they wish. The query string is generated using regular expression substitution, so avoidance of regular expression special characters is recommended as it will likely cause unexpected behavior. The '@' character as a parameter prefix works well (e.g. <span style="lvCode">{'@priceMin':100, '@lastUpdated':1415230518223}</span>).
* @param {boolean} [includeInternal] A flag indicating whether or not to include internal fields in (non-aggregate) query results.
*/
function Query(query, parameters, includeInternal){
if(!(this instanceof Query)){
return new Query(query, parameters, includeInternal);
}
/**
* The query string in parametrized form. For example: <span class="lvCode">"Select * From ItemsSales Where lastSoldPrice > @minPrice"</span>
* @memberOf LiveView.Query
* @instance
* @member {String} query
*/
this.query = query;
/**
* A key-value map of parameters to use to compute the query string, if query is a parametrized string. Users are essentially free to define the key value used to identify a parameter as they wish. The query string is generated using regular expression substitution, so avoidance of regular expression special characters is recommended as it will likely cause unexpected behavior. The '@' character as a parameter prefix works well (e.g. <span style="lvCode">{'@priceMin':100, '@lastUpdated':1415230518223}</span>).
* @memberOf LiveView.Query
* @instance
* @member {Object} parameters
*/
this.parameters = parameters;
/**
* A flag indicating whether or not to include internal fields in (non-aggregate) query results.
* @memberOf LiveView.Query
* @instance
* @member {boolean} includeInternal
*/
this.includeInternal = includeInternal;
}
/** @alias LiveView.Query.prototype */
Query.prototype = {
constructor: Query,
/**
* Applies parameters to the query and returns the resulting query string. If this is not a parametrized query,
* this will be equivalent to the query field.
* @function
* @memberOf LiveView.Query.prototype
* @returns {String} -- The parameter-applied query string
*/
getQueryString: function(){
var key,
escapedKey,
queryString = this.query;
for(key in this.parameters){
if(this.parameters.hasOwnProperty(key)){
//we need to escape the key which could contain special characters, we need to treat them as literals
escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
queryString = queryString.replace(new RegExp(escapedKey + '\\b', 'g'), this.parameters[key]);
}
}
return queryString;
},
/**
* Formats the query string as required by the LiveView server.
* @function
* @memberOf LiveView.Query.prototype
* @returns {String} -- The parameter-applied, server-formatted query string
*/
serialize: function(){
return this.getQueryString.call(this).replace(/^delete\sfrom\s/i ,'SELECT * FROM ').replace(/^delete/i,'SELECT * FROM ');
}
};
/**
* Stores detailed information about a LiveView query.
* @class QueryProperties
* @memberOf LiveView
* @param {Object} [initialValues] An object containing initial values for the QueryProperties object's properties.
* The property names defined in the initialValues object should match those of the corresponding QueryProperties
* member properties.
*/
function QueryProperties(initialValues){
initialValues = initialValues || {};
/**
* Flag indicating whether or not the query was configured to include LiveView-internal field values.
* @memberOf LiveView.QueryProperties
* @instance
* @member {boolean} includeInternal
* @default false
*/
this.includeInternal = initialValues.includeInternal === true;
/**
* Flag indicating whether or not the query is an aggregate query.
* @memberOf LiveView.QueryProperties
* @instance
* @member {boolean} isAggregate
* @default false
*/
this.isAggregate = initialValues.isAggregate === true;
/**
* If a limit was specified in the query, this is the numerical value of that limit. For example, if the query
* was 'SELECT * FROM ItemsSales LIMIT 30', then the limit value would be 30. If no limit was specified, then
* the value will be -1;
* @memberOf LiveView.QueryProperties
* @instance
* @member {Number} limit
* @default -1
*/
this.limit = isNaN(initialValues.limit) ? -1:initialValues.limit;
/**
* An array of objects that define the ORDER BY clause of the query. The order of elements in the array
* determines the priority of ordering (i.e. orderBy[0] is of the highest priority). The elements in the array
* contain two properties: <strong>fieldName</strong> and <strong>direction</strong>. The
* <strong>fieldName</strong> property is the string name that identifies the ORDER BY field. The
* <strong>direction</strong> property indicates is a string that indicates what direction to order the values
* ('ASC' for ascending order and 'DESC' for descending order).
* @memberOf LiveView.QueryProperties
* @instance
* @member {Array} orderBy
* @default []
*/
this.orderBy = initialValues.orderBy || [];
/**
* The predicate or set of conditions appearing in the query's WHERE clause.
* @memberOf LiveView.QueryProperties
* @instance
* @member {String} predicate
* @default null
*/
this.predicate = initialValues.predicate || null;
/**
* If a time-delay modifier was added to the WHEN clause, predicateDelay is the value of the delay in
* milliseconds. For example, if the query was 'SELECT * FROM ItemsSales WHERE price > 100 FOR 1000', then
* predicateDelay would be 1000). If no time-delay modifier was specified, predicateDelay will be -1.
* @memberOf LiveView.QueryProperties
* @instance
* @member {Number} predicateDelay
* @default 0
*/
this.predicateDelay = isNaN(initialValues.predicateDelay) ? 0:initialValues.predicateDelay;
/**
* The projection of the query (i.e. those fields appearing in the query's FROM clause). Stored in CSV format.
* @memberOf LiveView.QueryProperties
* @instance
* @member {String} projection
* @default null
*/
this.projection = initialValues.projection || null;
/**
* One of ['SNAPSHOT', 'CONTINUOUS', 'SNAPSHOT_AND_CONTINUOUS', 'DELETE']
* @memberOf LiveView.QueryProperties
* @instance
* @member {String} queryType
* @default null
*/
this.queryType = initialValues.queryType || null;
/**
* The {@link LiveView.Schema|Schema} of the parsed query.
* @memberOf LiveView.QueryProperties
* @instance
* @member {Object} schema
* @default null
*/
this.schema = initialValues.schema || null;
/**
* The name of the table against which the query will be performed.
* @memberOf LiveView.QueryProperties
* @instance
* @member {String} table
* @default null
*/
this.table = initialValues.table || null;
/**
* If a time-window is specified in the query, this object will contain three properties: <strong>field</strong>,
* <strong>begin</strong>, and <strong>end</strong>. The <strong>field</strong> property is the string name of
* the field specified in the WHEN clause. The <strong>begin</strong> property defines the beginning of the
* time-window. The <strong>end</strong> property defines the end of the time-window. For example, if the query
* is 'SELECT * FROM ItemsSales WHEN transactionTime BETWEEN now()-seconds(30) AND now()', the field would be
* 'transactionTime', the begin would be 'now()-seconds(30)' and the end would be 'now()'.
* @memberOf LiveView.QueryProperties
* @instance
* @member {Object} when
* @default null
*/
this.when = initialValues.when || null;
}
//QueryProperties.fromServerQueryModel - used for internal parsing
QueryProperties.fromDescribeQueryResult = function(describeQueryResult){
var queryProps = buildPropsFromServerModel(describeQueryResult.alert_query_config);
queryProps.schema = parseSchemaString(describeQueryResult.schema_xml.schema);
return queryProps;
function buildPropsFromServerModel(serverQueryModel){
serverQueryModel = serverQueryModel || {};
return new QueryProperties({
includeInternal: serverQueryModel.include_internal || false,
isAggregate: serverQueryModel.is_aggregate || false,
limit: isNaN(serverQueryModel.limit) ? 0:serverQueryModel.limit,
orderBy: (serverQueryModel.order_by && serverQueryModel.order_by.directions instanceof Array) ? serverQueryModel.order_by.directions:[],
predicate: serverQueryModel.predicate || '',
predicateDelay: isNaN(serverQueryModel.predicate_delay_in_millis) ? 0:serverQueryModel.predicate_delay_in_millis,
projection: serverQueryModel.projection || '',
queryType: serverQueryModel.query_type || '',
table: serverQueryModel.table,
when: (!serverQueryModel.time_window ? {}: {
field: serverQueryModel.time_window.timstamp_field,
begin: serverQueryModel.time_window.window_start_expr,
end: serverQueryModel.time_window.window_end_expr
})
});
}
function parseSchemaString(schemaStr){
var schemaObj, fields = [], i;
schemaObj = JSON.parse(schemaStr);
for(i = 0; i < schemaObj.fields.length; i++){
fields.push(new Field(
schemaObj.fields[i].name,
schemaObj.fields[i].description || '',
schemaObj.fields[i].type.type,
schemaObj.fields[i].schema || null
));
}
return new Schema(schemaObj.name || '', fields);
}
};
/**
* A LiveView Schema is used for Queries and Tables to define their respective schemas.
* @class Schema
* @memberOf LiveView
* @param {String} name The name of the schema
* @param {Array} fields Array of {@link LiveView.Field|fields} that compose the schema.
*/
function Schema(name, fields){
var i;
if(!(this instanceof Schema)){
return new Schema(name, fields);
}
/**
* Name of the Schema.
* @memberOf LiveView.Schema
* @instance
* @member {String} name
*/
this.name = name;
/**
* The fields that define this schema as an array. The array preserves field order as received from the
* LiveView server.
* @memberOf LiveView.Schema
* @instance
* @member {Array} fields
*/
this.fields = fields;
/**
* The fields that define this schema mapped by field name. The order of fields in the field map may not be
* consistent with the actual schema ordering. The fields array maintains fields in the correct order.
* @memberOf LiveView.Schema
* @instance
* @member {Object<String, LiveView.Field>} fieldsMap
*/
this.fieldsMap = {};
for(i = 0; i < fields.length; i++){
this.fieldsMap[fields[i].name] = fields[i];
}
}
/** @alias LiveView.Schema.prototype */
Schema.prototype = {
constructor: Schema
};
/**
* Defines the properties of a LiveView Field.
* @class Field
* @memberOf LiveView
* @param {String} name The field name. This is used as the unique identifier for this field.
* @param {String} description A description of the field.
* @param {String} type The type of the field (e.g "string", "int", etc).
* @param {LiveView.Schema} [schema] The schema of this field if its type is "tuple".
*/
function Field(name, description, type, schema){
if(!(this instanceof Field)){
return new Field(name, description, type, schema);
}
/**
* The field name. This is used as the unique identifier for this field.
* @memberOf LiveView.Field
* @instance
* @member {String} name
*/
this.name = name;
/**
* A full length description of the field.
* @memberOf LiveView.Field
* @instance
* @member {String} description
*/
this.description = description;
/**
* The type of the field (e.g "string", "int", etc).
* @memberOf LiveView.Field
* @instance
* @member {String} type
*/
this.type = type;
/**
* The schema of this field if its type is "tuple".
* @memberOf LiveView.Field
* @instance
* @member {LiveView.Schema} schema
*/
this.schema = schema;
}
/** @alias LiveView.Field.prototype */
Field.prototype = {
constructor: Field
};
/**
*
* @class Tuple
* @memberOf LiveView
* @param {Number} id The tuple's unique ID
* @param {Object} fieldMap Map of fieldName -> fieldValue
*/
function Tuple(id, fieldMap){
if(!(this instanceof Tuple)){
return new Tuple(id, fieldMap);
}
/**
* The tuple's unique ID
* @memberOf LiveView.Tuple
* @instance
* @member {Number} id
*/
this.id = id;
/**
* Map of fieldName -> fieldValue for all fields in the tuple. Note that the order of fields in the map will not
* necessarily correspond to the order of fields in the corresponding schema. One should refer to the
* {@link LiveView.Schema#fields|fields} array of the corresponding schema to obtain the correct ordering of
* fields.
* @memberOf LiveView.Tuple
* @instance
* @member {Object} fieldMap
*/
this.fieldMap = fieldMap;
}
Tuple.prototype = {
constructor: Tuple
};
/**
* LiveView error object that will be passed as an argument to error handling callbacks and promise rejection
* handlers. LiveView.Error inherits from window.Error if available.
* @class Error
* @memberOf LiveView
* @param {Number} code The error code that identifies the error.
* @param {String} detail A detailed message about what happened.
* @param {String} message A brief description summarizing the error.
* @constructor
*/
function Error(code, detail, message){
var envErr;
if(!(this instanceof Error)){
return new Error(code, detail, message);
}
if(typeof(code) === 'object'){
detail = code.detail || '';
message = code.message || '';
code = code.code || null;
}
//Add useful error info to our LiveView.Error from the native Error object (some values won't be set)
if(typeof(window.Error) === 'function'){
envErr = new window.Error(message);
/**
* The error stack trace if available in the current browser.
* @memberOf LiveView.Error
* @instance
* @member {String} stack
*/
this.stack = envErr.stack;
}
//TODO: Enumerate error codes and update Error creations to use appropriate code
/**
* The error code that identifies the error.
* @memberOf LiveView.Error
* @instance
* @member {Number} code
*/
this.code = code;
/**
* A detailed message about what happened.
* @memberOf LiveView.Error
* @instance
* @member {String} detail
*/
this.detail = detail;
/**
* A brief description summarizing the error.
* @memberOf LiveView.Error
* @instance
* @member {String} message
*/
this.message = message;
}
tmpPrototype = {
constructor: Error,
toString: function(){
return JSON.stringify(this);
}
};
if(window.Error && typeof(window.Error.prototype) === 'object'){
Error.prototype = jQuery.extend(Object.create(window.Error.prototype), tmpPrototype); //so LiveView.Error instance of Error is true
}
else{
Error.prototype = tmpPrototype;
}
}(window, jQuery));