From 7d831cdcbee550d9dbb808abc21d7cec7129aaff Mon Sep 17 00:00:00 2001 From: Hasan Khan Date: Mon, 3 Nov 2014 17:21:31 -0800 Subject: [PATCH] [client][managed][offline] Allow query on memer name datetime --- .../Table/Query/OData/ODataExpressionLexer.cs | 100 ++++++++++-------- .../Query/OData/ODataExpressionParser.cs | 12 ++- .../OData/ODataExpressionParser.Test.cs | 55 +++++++++- 3 files changed, 117 insertions(+), 50 deletions(-) diff --git a/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Query/OData/ODataExpressionLexer.cs b/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Query/OData/ODataExpressionLexer.cs index f2191532c..4707a52d6 100644 --- a/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Query/OData/ODataExpressionLexer.cs +++ b/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Query/OData/ODataExpressionLexer.cs @@ -3,11 +3,6 @@ // ---------------------------------------------------------------------------- using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.WindowsAzure.MobileServices.Query { @@ -16,9 +11,9 @@ internal sealed class ODataExpressionLexer private string text; private int textLen; private int textPos; - private char ch; + public char CurrentChar { get; private set; } - public QueryToken Token {get; private set; } + public QueryToken Token { get; private set; } public ODataExpressionLexer(string expression) { @@ -31,14 +26,14 @@ public ODataExpressionLexer(string expression) public QueryToken NextToken() { - while (Char.IsWhiteSpace(this.ch)) + while (Char.IsWhiteSpace(this.CurrentChar)) { this.NextChar(); } QueryTokenKind t = QueryTokenKind.Unknown; int tokenPos = this.textPos; - switch (this.ch) + switch (this.CurrentChar) { case '(': this.NextChar(); @@ -61,7 +56,7 @@ public QueryToken NextToken() t = QueryTokenKind.Dot; break; case '\'': - char quote = this.ch; + char quote = this.CurrentChar; do { this.AdvanceToNextOccuranceOf(quote); @@ -71,29 +66,29 @@ public QueryToken NextToken() } this.NextChar(); } - while (this.ch == quote); + while (this.CurrentChar == quote); t = QueryTokenKind.StringLiteral; break; default: - if (this.IsIdentifierStart(this.ch) || this.ch == '@' || this.ch == '_') + if (this.IsIdentifierStart(this.CurrentChar) || this.CurrentChar == '@' || this.CurrentChar == '_') { do { this.NextChar(); } - while (this.IsIdentifierPart(this.ch) || this.ch == '_'); + while (this.IsIdentifierPart(this.CurrentChar) || this.CurrentChar == '_'); t = QueryTokenKind.Identifier; break; } - if (Char.IsDigit(this.ch)) + if (Char.IsDigit(this.CurrentChar)) { t = QueryTokenKind.IntegerLiteral; do { this.NextChar(); } - while (Char.IsDigit(this.ch)); - if (this.ch == '.') + while (Char.IsDigit(this.CurrentChar)); + if (this.CurrentChar == '.') { t = QueryTokenKind.RealLiteral; this.NextChar(); @@ -102,13 +97,13 @@ public QueryToken NextToken() { this.NextChar(); } - while (Char.IsDigit(this.ch)); + while (Char.IsDigit(this.CurrentChar)); } - if (this.ch == 'E' || this.ch == 'e') + if (this.CurrentChar == 'E' || this.CurrentChar == 'e') { t = QueryTokenKind.RealLiteral; this.NextChar(); - if (this.ch == '+' || this.ch == '-') + if (this.CurrentChar == '+' || this.CurrentChar == '-') { this.NextChar(); } @@ -117,9 +112,9 @@ public QueryToken NextToken() { this.NextChar(); } - while (Char.IsDigit(this.ch)); + while (Char.IsDigit(this.CurrentChar)); } - if (this.ch == 'F' || this.ch == 'f' || this.ch == 'M' || this.ch == 'm' || this.ch == 'D' || this.ch == 'd') + if (this.CurrentChar == 'F' || this.CurrentChar == 'f' || this.CurrentChar == 'M' || this.CurrentChar == 'm' || this.CurrentChar == 'D' || this.CurrentChar == 'd') { t = QueryTokenKind.RealLiteral; this.NextChar(); @@ -145,64 +140,81 @@ public QueryToken NextToken() private void ValidateDigit() { - if (!Char.IsDigit(this.ch)) { + if (!Char.IsDigit(this.CurrentChar)) + { this.ParseError(Resources.ODataExpressionParser_DigitExpected, this.textPos); } } - private void ReClassifyToken() { - if (Token.Kind == QueryTokenKind.Identifier) { - if (this.Token.Text == "or") { + private void ReClassifyToken() + { + if (Token.Kind == QueryTokenKind.Identifier) + { + if (this.Token.Text == "or") + { this.Token.Kind = QueryTokenKind.Or; } - else if (this.Token.Text == "add") { + else if (this.Token.Text == "add") + { this.Token.Kind = QueryTokenKind.Add; } - else if (this.Token.Text == "and") { + else if (this.Token.Text == "and") + { this.Token.Kind = QueryTokenKind.And; } - else if (this.Token.Text == "div") { + else if (this.Token.Text == "div") + { this.Token.Kind = QueryTokenKind.Divide; } - else if (this.Token.Text == "sub") { + else if (this.Token.Text == "sub") + { this.Token.Kind = QueryTokenKind.Sub; } - else if (this.Token.Text == "mul") { + else if (this.Token.Text == "mul") + { this.Token.Kind = QueryTokenKind.Multiply; } - else if (this.Token.Text == "mod") { + else if (this.Token.Text == "mod") + { this.Token.Kind = QueryTokenKind.Modulo; } - else if (this.Token.Text == "ne") { + else if (this.Token.Text == "ne") + { this.Token.Kind = QueryTokenKind.NotEqual; } - else if (this.Token.Text == "not") { + else if (this.Token.Text == "not") + { this.Token.Kind = QueryTokenKind.Not; } - else if (this.Token.Text == "le") { + else if (this.Token.Text == "le") + { this.Token.Kind = QueryTokenKind.LessThanEqual; } - else if (this.Token.Text == "lt") { + else if (this.Token.Text == "lt") + { this.Token.Kind = QueryTokenKind.LessThan; } - else if (this.Token.Text == "eq") { + else if (this.Token.Text == "eq") + { this.Token.Kind = QueryTokenKind.Equal; } - else if (this.Token.Text == "ge") { + else if (this.Token.Text == "ge") + { this.Token.Kind = QueryTokenKind.GreaterThanEqual; } - else if (this.Token.Text == "gt") { + else if (this.Token.Text == "gt") + { this.Token.Kind = QueryTokenKind.GreaterThan; } } } - private bool IsIdentifierStart (char ch) + private bool IsIdentifierStart(char ch) { return Char.IsLetter(ch); } - private bool IsIdentifierPart (char ch) + private bool IsIdentifierPart(char ch) { bool result = this.IsIdentifierStart(ch) || Char.IsDigit(ch) || (ch == '_' || ch == '-'); return result; @@ -211,7 +223,7 @@ private bool IsIdentifierPart (char ch) private void AdvanceToNextOccuranceOf(char endingValue) { this.NextChar(); - while (this.textPos < this.textLen && this.ch != endingValue) + while (this.textPos < this.textLen && this.CurrentChar != endingValue) { this.NextChar(); } @@ -223,14 +235,14 @@ private void NextChar() { this.textPos++; } - this.ch = (this.textPos < this.textLen) ? this.text[this.textPos] : '\0'; + this.CurrentChar = (this.textPos < this.textLen) ? this.text[this.textPos] : '\0'; } private void SetTextPos(int pos) { this.textPos = pos; - this.ch = (this.textPos < this.textLen) ? this.text[this.textPos] : '\0'; - } + this.CurrentChar = (this.textPos < this.textLen) ? this.text[this.textPos] : '\0'; + } private void ParseError(string message, int errorPos) { diff --git a/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Query/OData/ODataExpressionParser.cs b/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Query/OData/ODataExpressionParser.cs index e7959e39b..1a232aab6 100644 --- a/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Query/OData/ODataExpressionParser.cs +++ b/sdk/Managed/src/Microsoft.WindowsAzure.MobileServices/Table/Query/OData/ODataExpressionParser.cs @@ -454,12 +454,18 @@ private QueryNode ParseIdentifier() QueryNode value; if (keywords.TryGetValue(this.lexer.Token.Text, out value)) { - if (value == null) + // type construction has the format of type'value' e.g. datetime'2001-04-01T00:00:00Z' + // therefore if the next character is a single quote then we try to + // interpret this as type construction else its a normal member access + if (value == null && this.lexer.CurrentChar == '\'') { return this.ParseTypeConstruction(); } - this.lexer.NextToken(); - return value; + else if (value != null) // this is a constant + { + this.lexer.NextToken(); + return value; + } } return this.ParseMemberAccess(null); diff --git a/sdk/Managed/test/Microsoft.WindowsAzure.MobileServices.Test/UnitTests/OData/ODataExpressionParser.Test.cs b/sdk/Managed/test/Microsoft.WindowsAzure.MobileServices.Test/UnitTests/OData/ODataExpressionParser.Test.cs index d0bf3a74a..07878e325 100644 --- a/sdk/Managed/test/Microsoft.WindowsAzure.MobileServices.Test/UnitTests/OData/ODataExpressionParser.Test.cs +++ b/sdk/Managed/test/Microsoft.WindowsAzure.MobileServices.Test/UnitTests/OData/ODataExpressionParser.Test.cs @@ -17,13 +17,13 @@ public void ParseFilter_Guid() Assert.IsNotNull(queryNode); - BinaryOperatorNode comparisonNode = queryNode as BinaryOperatorNode; + var comparisonNode = queryNode as BinaryOperatorNode; Assert.IsNotNull(comparisonNode); - MemberAccessNode left = comparisonNode.LeftOperand as MemberAccessNode; + var left = comparisonNode.LeftOperand as MemberAccessNode; Assert.IsNotNull(left); - ConstantNode right = comparisonNode.RightOperand as ConstantNode; + var right = comparisonNode.RightOperand as ConstantNode; Assert.IsNotNull(right); Assert.AreEqual("Field", left.MemberName); @@ -31,6 +31,55 @@ public void ParseFilter_Guid() Assert.AreEqual(filterGuid, right.Value); } + [TestMethod] + public void ParseFilter_TrueToken() + { + QueryNode queryNode = ODataExpressionParser.ParseFilter("(true eq null) and false"); + + Assert.IsNotNull(queryNode); + + var comparisonNode = queryNode as BinaryOperatorNode; + Assert.IsNotNull(comparisonNode); + + var left = comparisonNode.LeftOperand as BinaryOperatorNode; + Assert.IsNotNull(left); + + var trueNode = left.LeftOperand as ConstantNode; + Assert.IsNotNull(trueNode); + Assert.AreEqual(true, trueNode.Value); + + var nullNode = left.RightOperand as ConstantNode; + Assert.IsNotNull(nullNode); + Assert.AreEqual(null, nullNode.Value); + + var falseNode = comparisonNode.RightOperand as ConstantNode; + Assert.IsNotNull(falseNode); + + Assert.AreEqual(BinaryOperatorKind.And, comparisonNode.OperatorKind); + Assert.AreEqual(false, falseNode.Value); + } + + [TestMethod] + public void ParseFilter_DateTimeMember() + { + QueryNode queryNode = ODataExpressionParser.ParseFilter("datetime eq 1"); + + Assert.IsNotNull(queryNode); + + var comparisonNode = queryNode as BinaryOperatorNode; + Assert.IsNotNull(comparisonNode); + + var left = comparisonNode.LeftOperand as MemberAccessNode; + Assert.IsNotNull(left); + + var right = comparisonNode.RightOperand as ConstantNode; + Assert.IsNotNull(right); + + Assert.AreEqual("datetime", left.MemberName); + Assert.AreEqual(BinaryOperatorKind.Equal, comparisonNode.OperatorKind); + Assert.AreEqual(1L, right.Value); + } + [TestMethod] public void ParseFilter_Guid_InvalidGuidString() {