//
// Copyright (c) Microsoft and contributors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Module dependencies.
var _ = require('underscore');
var util = require('util');
var azureCommon = require('./../../common/common.core');
var azureutil = azureCommon.util;
var SR = azureCommon.SR;
var QueryStringConstants = azureCommon.Constants.QueryStringConstants;
var edmHandler = require('./internal/edmhandler');
var TableUtilities = require('./tableutilities');
var QueryComparisons = TableUtilities.QueryComparisons;
var TableOperators = TableUtilities.TableOperators;
var EdmType = TableUtilities.EdmType;
/**
* Creates a new TableQuery object.
*
* @constructor
*/
function TableQuery() {
this._fields = [];
this._where = [];
this._top = null;
}
/**
* Specifies the select clause. If no arguments are given, all fields will be selected.
*
* @param {array} fields The fields to be selected.
* @return {TableQuery} A table query object with the select clause.
* @example
* var tableQuery = new TableQuery().select('field1', 'field2');
*/
TableQuery.prototype.select = function () {
var self = this;
if (arguments) {
_.each(arguments, function (argument) {
self._fields.push(argument);
});
}
return this;
};
/**
* Specifies the top clause.
*
* @param {int} top The number of items to fetch.
* @return {TableQuery} A table query object with the top clause.
* @example
* var tableQuery = new TableQuery().top(10);
*
* // tasktable should already exist and have entities
* tableService.queryEntities('tasktable', tableQuery, null \/*currentToken*\/, function(error, result) {
* if(!error) {
* var entities = result.entities; // there will be 10 or less entities
* // do stuff with the returned entities if there are any
* // if result.continuationToken exists, to get the next 10 (or less) entities
* // call queryEntities as above, but with the returned token instead of null
* }
* });
*/
TableQuery.prototype.top = function (top) {
this._top = top;
return this;
};
/**
* Specifies the where clause.
*
* Valid type specifier strings include: ?string?, ?bool?, ?int32?, ?double?, ?date?, ?guid?, ?int64?, ?binary?
* A type must be specified for guid, int64, and binaries or the filter produced will be incorrect.
*
* @param {string} condition The condition string.
* @param {string|array} value Value(s) to insert in question mark (?) parameters.
* @return {TableQuery} A table query object with the where clause.
* @example
* var tableQuery = new TableQuery().where(TableQuery.guidFilter('GuidField', QueryComparisons.EQUAL, guidVal));
* OR
* var tableQuery = new TableQuery().where('Name == ? or Name <= ?', name1, name2);
* OR
* var tableQuery = new TableQuery().where('Name == ?string? && Value == ?int64?', name1, int64Val);
*
* // tasktable should already exist and have entities
* tableService.queryEntities('tasktable', tableQuery, null \/*currentToken*\/, function(error, result, response) {
* if(!error) {
* var entities = result.entities;
* // do stuff with the returned entities if there are any
* }
* });
*/
TableQuery.prototype.where = function (condition) {
this._where.push(TableQuery._encodeConditionString(condition, arguments));
return this;
};
/**
* Generates a property filter condition string for an 'int' value.
*
* @param {string} propertyName A string containing the name of the property to compare.
* @param {string} operation A string containing the comparison operator to use.
* See Constants.TableConstants.QueryComparisons for a list of allowed operations.
* @param {string|int} value An 'int' containing the value to compare with the property.
* @return {string} A string containing the formatted filter condition.
* @example
* var query = TableQuery.int32Filter('IntField', QueryComparisons.EQUAL, 5);
*/
TableQuery.int32Filter = function (propertyName, operation, value) {
return TableQuery._concatFilterString(propertyName, operation, value, EdmType.INT32);
};
/**
* Generates a property filter condition string for a 'int64' value.
*
* @param {string} propertyName A string containing the name of the property to compare.
* @param {string} operation A string containing the comparison operator to use.
* See Constants.TableConstants.QueryComparisons for a list of allowed operations.
* @param {string|int64} value An 'int64' containing the value to compare with the property.
* @return {string} A string containing the formatted filter condition.
* @example
* var query = TableQuery.int64Filter('Int64Field', QueryComparisons.EQUAL, 123);
*/
TableQuery.int64Filter = function (propertyName, operation, value) {
return TableQuery._concatFilterString(propertyName, operation, value, EdmType.INT64);
};
/**
* Generates a property filter condition string for a 'double' value.
*
* @param {string} propertyName A string containing the name of the property to compare.
* @param {string} operation A string containing the comparison operator to use.
* See Constants.TableConstants.QueryComparisons for a list of allowed operations.
* @param {string|double}value A 'double' containing the value to compare with the property.
* @return {string} A string containing the formatted filter condition.
* @example
* var query = TableQuery.doubleFilter('DoubleField', QueryComparisons.EQUAL, 123.45);
*/
TableQuery.doubleFilter = function (propertyName, operation, value) {
return TableQuery._concatFilterString(propertyName, operation, value, EdmType.DOUBLE);
};
/**
* Generates a property filter condition string for a 'boolean' value.
*
* @param {string} propertyName A string containing the name of the property to compare.
* @param {string} operation A string containing the comparison operator to use.
* See Constants.TableConstants.QueryComparisons for a list of allowed operations.
* @param {string|boolean} value A 'boolean' containing the value to compare with the property.
* @return {string} A string containing the formatted filter condition.
* @example
* var query = TableQuery.booleanFilter('BooleanField', QueryComparisons.EQUAL, false);
*/
TableQuery.booleanFilter = function (propertyName, operation, value) {
return TableQuery._concatFilterString(propertyName, operation, value, EdmType.BOOLEAN);
};
/**
* Generates a property filter condition string for a 'datetime' value.
*
* @param {string} propertyName A string containing the name of the property to compare.
* @param {string} operation A string containing the comparison operator to use.
* See Constants.TableConstants.QueryComparisons for a list of allowed operations.
* @param {string|date} value A 'datetime' containing the value to compare with the property.
* @return {string} A string containing the formatted filter condition.
* @example
* var query = TableQuery.dateFilter('DateTimeField', QueryComparisons.EQUAL, new Date(Date.UTC(2001, 1, 3, 4, 5, 6)));
*/
TableQuery.dateFilter = function (propertyName, operation, value) {
return TableQuery._concatFilterString(propertyName, operation, value, EdmType.DATETIME);
};
/**
* Generates a property filter condition string for a 'guid' value.
*
* @param {string} propertyName A string containing the name of the property to compare.
* @param {string} operation A string containing the comparison operator to use.
* See Constants.TableConstants.QueryComparisons for a list of allowed operations.
* @param {string|guid} value A 'guid' containing the value to compare with the property.
* @return {string} A string containing the formatted filter condition.
* @example
* var query = TableQuery.guidFilter('GuidField', QueryComparisons.EQUAL, guid.v1());
*/
TableQuery.guidFilter = function (propertyName, operation, value) {
return TableQuery._concatFilterString(propertyName, operation, value, EdmType.GUID);
};
/**
* Generates a property filter condition string for a 'binary' value.
*
* @param {string} propertyName A string containing the name of the property to compare.
* @param {string} operation A string containing the comparison operator to use.
* See Constants.TableConstants.QueryComparisons for a list of allowed operations.
* @param {string|buffer}value A 'buffer' containing the value to compare with the property.
* @return {string} A string containing the formatted filter condition.
* @example
* var query = TableQuery.binaryFilter('BinaryField', QueryComparisons.EQUAL, Buffer.from('hello'));
*/
TableQuery.binaryFilter = function (propertyName, operation, value) {
return TableQuery._concatFilterString(propertyName, operation, value, EdmType.BINARY);
};
/**
* Generates a property filter condition string.
*
* @param {string} propertyName A string containing the name of the property to compare.
* @param {string} operation A string containing the comparison operator to use.
* See Constants.TableConstants.QueryComparisons for a list of allowed operations.
* @param {string} value A 'string' containing the value to compare with the property.
* @return {string} A string containing the formatted filter condition.
* @example
* var query = TableQuery.stringFilter('StringField', QueryComparisons.EQUAL, 'name');
*/
TableQuery.stringFilter = function (propertyName, operation, value) {
return TableQuery._concatFilterString(propertyName, operation, value, EdmType.STRING);
};
/**
* Creates a filter condition using the specified logical operator on two filter conditions.
*
* @param {string} filterA A string containing the first formatted filter condition.
* @param {string} operatorString A string containing the operator to use (AND, OR).
* @param {string} filterB A string containing the second formatted filter condition.
* @return {string} A string containing the combined filter expression.
* @example
* var filter1 = TableQuery.stringFilter('Name', QueryComparisons.EQUAL, 'Person');
* var filter2 = TableQuery.booleanFilter('Visible', QueryComparisons.EQUAL, true);
* var combinedFilter = TableQuery.combineFilters(filter1, TableUtilities.TableOperators.AND, filter2);
*/
TableQuery.combineFilters = function (filterA, operatorString, filterB) {
return filterA + ' ' + operatorString + ' ' + filterB;
};
/**
* Specifies an AND where condition.
*
* @param {string} condition The condition string.
* @param {array} arguments Any number of arguments to be replaced in the condition by the question mark (?).
* @return {TableQuery} A table query object with the and clause.
* @example
* var tableQuery = new TableQuery()
* .where('Name == ? or Name <= ?', 'Person1', 'Person2');
* .and('Age >= ?', 18);
*/
TableQuery.prototype.and = function (condition) {
if (this._where.length === 0) {
throw new Error(util.format(SR.QUERY_OPERATOR_REQUIRES_WHERE, 'AND'));
}
this._where.push(' and ' + TableQuery._encodeConditionString(condition, arguments));
return this;
};
/**
* Specifies an OR where condition.
*
* @param {string} condition The condition.
* @param {array} arguments Any number of arguments to be replaced in the condition by the question mark (?).
* @return {TableQuery} A table query object with the or clause.
* @example
* var tableQuery = new TableQuery()
* .where('Name == ? or Name <= ?', 'Person1', 'Person2');
* .or('Age >= ?', 18);
*/
TableQuery.prototype.or = function (condition) {
if (this._where.length === 0) {
throw new Error(util.format(SR.QUERY_OPERATOR_REQUIRES_WHERE, 'OR'));
}
this._where.push(' or ' + TableQuery._encodeConditionString(condition, arguments));
return this;
};
/**
* Returns the query string object for the query.
*
* @return {object} JSON object representing the query string arguments for the query.
*/
TableQuery.prototype.toQueryObject = function () {
var query = {};
if (this._fields.length > 0) {
query[QueryStringConstants.SELECT] = this._fields.join(',');
}
if (this._where.length > 0) {
query[QueryStringConstants.FILTER] = this._where.join('');
}
if (this._top) {
query[QueryStringConstants.TOP] = this._top;
}
return query;
};
// Functions
/**
* Concat the filter string parameters.
*
* @param {string} propertyName A string containing the name of the property to compare.
* @param {string} operation A string containing the comparison operator to use.
* See Constants.TableConstants.QueryComparisons for a list of allowed operations.
* @param {object} value The value to compare with the property.
* @param {string} type A string EdmType of the property to compare.
* @return {string} A string containing the formatted filter condition.
* @ignore
*/
TableQuery._concatFilterString = function (propertyName, operation, value, type) {
if (azureutil.objectIsNull(propertyName)) {
throw new Error(util.format(SR.ARGUMENT_NULL_OR_UNDEFINED, 'propertyName'));
}
if (azureutil.objectIsNull(operation)) {
throw new Error(util.format(SR.ARGUMENT_NULL_OR_UNDEFINED, 'operation'));
}
if (azureutil.objectIsNull(value)) {
throw new Error(util.format(SR.ARGUMENT_NULL_OR_UNDEFINED, 'value'));
}
var serializedValue = edmHandler.serializeQueryValue(value, type);
return propertyName + ' ' + operation + ' ' + serializedValue;
};
/**
* Encodes a condition string.
*
* @param {string} condition The condition.
* @param {array} arguments Any number of arguments to be replaced in the condition by the question mark (?).
* @return {TableQuery} A table query object with the or clause
* @ignore
*/
TableQuery._encodeConditionString = function (condition, args) {
var encodedCondition = TableQuery._replaceOperators(condition);
if (args.length > 1) {
var sections = encodedCondition.split(/(\?string\?|\?int32\?|\?int64\?|\?bool\?|\?double\?|\?date\?|\?binary\?|\?guid\?|\?)/);
var count = 1;
for (var i = 0; i < sections.length && count < args.length; i++) {
if (sections[i].indexOf('?') === 0) {
var type = TableQuery._getEdmType(sections[i]);
sections[i] = edmHandler.serializeQueryValue(args[count], type);
count++;
}
}
encodedCondition = sections.join('');
}
return encodedCondition;
};
/**
* Converts the query string type to an Edm type.
*
* @param {string} type The type included in the query string.
* @return {string} The EdmType.
* @ignore
*/
TableQuery._getEdmType = function (type) {
switch (type) {
case '?binary?':
return EdmType.BINARY;
case '?int64?':
return EdmType.INT64;
case '?date?':
return EdmType.DATETIME;
case '?guid?':
return EdmType.GUID;
case '?int32?':
return EdmType.INT32;
case '?double?':
return EdmType.DOUBLE;
case '?bool?':
return EdmType.BOOLEAN;
case '?string?':
return EdmType.STRING;
default:
return undefined;
}
};
/**
* Replace operators.
* @ignore
* @param {string} whereClause The text where to replace the operators.
* @return {string} The string with the replaced operators.
* @ignore
*/
TableQuery._replaceOperators = function (whereClause) {
var encodedWhereClause = whereClause.replace(/ == /g, ' ' + QueryComparisons.EQUAL + ' ');
encodedWhereClause = encodedWhereClause.replace(/ != /g, ' ' + QueryComparisons.NOT_EQUAL + ' ');
encodedWhereClause = encodedWhereClause.replace(/ >= /g, ' ' + QueryComparisons.GREATER_THAN_OR_EQUAL + ' ');
encodedWhereClause = encodedWhereClause.replace(/ > /g, ' ' + QueryComparisons.GREATER_THAN + ' ');
encodedWhereClause = encodedWhereClause.replace(/ <= /g, ' ' + QueryComparisons.LESS_THAN_OR_EQUAL + ' ');
encodedWhereClause = encodedWhereClause.replace(/ < /g, ' ' + QueryComparisons.LESS_THAN + ' ');
encodedWhereClause = encodedWhereClause.replace(/ \&\& /g, ' ' + TableOperators.AND + ' ');
encodedWhereClause = encodedWhereClause.replace(/ \|\| /g, ' ' + TableOperators.OR + ' ');
encodedWhereClause = encodedWhereClause.replace(/!/g, TableOperators.NOT);
return encodedWhereClause;
};
module.exports = TableQuery;