PageRenderTime 59ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/test/System.Web.OData.Test/OData/Query/Expressions/FilterBinderTests.cs

https://github.com/huyq2002/aspnetwebstack
C# | 2089 lines | 1778 code | 256 blank | 55 comment | 13 complexity | 575e2f5ba89b2fee515a917c248a6fbe MD5 | raw file
Possible License(s): Apache-2.0
  1. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
  2. using System.Collections.Generic;
  3. using System.Data.Linq;
  4. using System.Globalization;
  5. using System.Linq;
  6. using System.Linq.Expressions;
  7. using System.Reflection;
  8. using System.Web.Http;
  9. using System.Web.Http.Dispatcher;
  10. using System.Web.OData.Builder;
  11. using System.Xml.Linq;
  12. using Microsoft.OData.Core;
  13. using Microsoft.OData.Core.UriParser;
  14. using Microsoft.OData.Core.UriParser.Semantic;
  15. using Microsoft.OData.Edm;
  16. using Microsoft.TestCommon;
  17. namespace System.Web.OData.Query.Expressions
  18. {
  19. public class FilterBinderTests
  20. {
  21. private const string NotTesting = "";
  22. private static readonly Uri _serviceBaseUri = new Uri("http://server/service/");
  23. private static Dictionary<Type, IEdmModel> _modelCache = new Dictionary<Type, IEdmModel>();
  24. public static TheoryDataSet<decimal?, bool, object> MathRoundDecimal_DataSet
  25. {
  26. get
  27. {
  28. return new TheoryDataSet<decimal?, bool, object>
  29. {
  30. { null, false, typeof(InvalidOperationException) },
  31. { 5.9m, true, true },
  32. { 5.4m, false, false },
  33. };
  34. }
  35. }
  36. public static TheoryDataSet<decimal?, bool, object> MathFloorDecimal_DataSet
  37. {
  38. get
  39. {
  40. return new TheoryDataSet<decimal?, bool, object>
  41. {
  42. { null, false, typeof(InvalidOperationException) },
  43. { 5.4m, true, true },
  44. { 4.4m, false, false },
  45. };
  46. }
  47. }
  48. public static TheoryDataSet<decimal?, bool, object> MathCeilingDecimal_DataSet
  49. {
  50. get
  51. {
  52. return new TheoryDataSet<decimal?, bool, object>
  53. {
  54. { null, false, typeof(InvalidOperationException) },
  55. { 4.1m, true, true },
  56. { 5.9m, false, false },
  57. };
  58. }
  59. }
  60. #region Inequalities
  61. [Theory]
  62. [InlineData(null, true, true)]
  63. [InlineData("", false, false)]
  64. [InlineData("Doritos", false, false)]
  65. public void EqualityOperatorWithNull(string productName, bool withNullPropagation, bool withoutNullPropagation)
  66. {
  67. var filters = VerifyQueryDeserialization(
  68. "ProductName eq null",
  69. "$it => ($it.ProductName == null)");
  70. RunFilters(filters,
  71. new Product { ProductName = productName },
  72. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  73. }
  74. [Theory]
  75. [InlineData(null, false, false)]
  76. [InlineData("", false, false)]
  77. [InlineData("Doritos", true, true)]
  78. public void EqualityOperator(string productName, bool withNullPropagation, bool withoutNullPropagation)
  79. {
  80. var filters = VerifyQueryDeserialization(
  81. "ProductName eq 'Doritos'",
  82. "$it => ($it.ProductName == \"Doritos\")");
  83. RunFilters(filters,
  84. new Product { ProductName = productName },
  85. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  86. }
  87. [Theory]
  88. [InlineData(null, true, true)]
  89. [InlineData("", true, true)]
  90. [InlineData("Doritos", false, false)]
  91. public void NotEqualOperator(string productName, bool withNullPropagation, bool withoutNullPropagation)
  92. {
  93. var filters = VerifyQueryDeserialization(
  94. "ProductName ne 'Doritos'",
  95. "$it => ($it.ProductName != \"Doritos\")");
  96. RunFilters(filters,
  97. new Product { ProductName = productName },
  98. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  99. }
  100. [Theory]
  101. [InlineData(null, false, false)]
  102. [InlineData(5.01, true, true)]
  103. [InlineData(4.99, false, false)]
  104. public void GreaterThanOperator(object unitPrice, bool withNullPropagation, bool withoutNullPropagation)
  105. {
  106. var filters = VerifyQueryDeserialization(
  107. "UnitPrice gt 5.00m",
  108. String.Format(CultureInfo.InvariantCulture, "$it => ($it.UnitPrice > Convert({0:0.00}))", 5.0),
  109. String.Format(CultureInfo.InvariantCulture, "$it => (($it.UnitPrice > Convert({0:0.00})) == True)", 5.0));
  110. RunFilters(filters,
  111. new Product { UnitPrice = ToNullable<decimal>(unitPrice) },
  112. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  113. }
  114. [Theory]
  115. [InlineData(null, false, false)]
  116. [InlineData(5.0, true, true)]
  117. [InlineData(4.99, false, false)]
  118. public void GreaterThanEqualOperator(object unitPrice, bool withNullPropagation, bool withoutNullPropagation)
  119. {
  120. var filters = VerifyQueryDeserialization(
  121. "UnitPrice ge 5.00m",
  122. String.Format(CultureInfo.InvariantCulture, "$it => ($it.UnitPrice >= Convert({0:0.00}))", 5.0),
  123. String.Format(CultureInfo.InvariantCulture, "$it => (($it.UnitPrice >= Convert({0:0.00})) == True)", 5.0));
  124. RunFilters(filters,
  125. new Product { UnitPrice = ToNullable<decimal>(unitPrice) },
  126. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  127. }
  128. [Theory]
  129. [InlineData(null, false, false)]
  130. [InlineData(4.99, true, true)]
  131. [InlineData(5.01, false, false)]
  132. public void LessThanOperator(object unitPrice, bool withNullPropagation, bool withoutNullPropagation)
  133. {
  134. var filters = VerifyQueryDeserialization(
  135. "UnitPrice lt 5.00m",
  136. String.Format(CultureInfo.InvariantCulture, "$it => ($it.UnitPrice < Convert({0:0.00}))", 5.0),
  137. NotTesting);
  138. RunFilters(filters,
  139. new Product { UnitPrice = ToNullable<decimal>(unitPrice) },
  140. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  141. }
  142. [Theory]
  143. [InlineData(null, false, false)]
  144. [InlineData(5.0, true, true)]
  145. [InlineData(5.01, false, false)]
  146. public void LessThanOrEqualOperator(object unitPrice, bool withNullPropagation, bool withoutNullPropagation)
  147. {
  148. var filters = VerifyQueryDeserialization(
  149. "UnitPrice le 5.00m",
  150. String.Format(CultureInfo.InvariantCulture, "$it => ($it.UnitPrice <= Convert({0:0.00}))", 5.0),
  151. NotTesting);
  152. RunFilters(filters,
  153. new Product { UnitPrice = ToNullable<decimal>(unitPrice) },
  154. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  155. }
  156. [Fact]
  157. public void NegativeNumbers()
  158. {
  159. VerifyQueryDeserialization(
  160. "UnitPrice le -5.00m",
  161. String.Format(CultureInfo.InvariantCulture, "$it => ($it.UnitPrice <= Convert({0:0.00}))", -5.0),
  162. NotTesting);
  163. }
  164. [Theory]
  165. [InlineData("DateTimeOffsetProp eq DateTimeOffsetProp", "$it => ($it.DateTimeOffsetProp == $it.DateTimeOffsetProp)")]
  166. [InlineData("DateTimeOffsetProp ne DateTimeOffsetProp", "$it => ($it.DateTimeOffsetProp != $it.DateTimeOffsetProp)")]
  167. [InlineData("DateTimeOffsetProp ge DateTimeOffsetProp", "$it => ($it.DateTimeOffsetProp >= $it.DateTimeOffsetProp)")]
  168. [InlineData("DateTimeOffsetProp le DateTimeOffsetProp", "$it => ($it.DateTimeOffsetProp <= $it.DateTimeOffsetProp)")]
  169. public void DateTimeOffsetInEqualities(string clause, string expectedExpression)
  170. {
  171. // There's currently a bug here. For now, the test checks for the presence of the bug (as a reminder to fix
  172. // the test once the bug is fixed).
  173. // The following assert shows the behavior with the bug and should be removed once the bug is fixed.
  174. Assert.Throws<ODataException>(() => Bind("" + clause));
  175. // TODO: Enable once ODataUriParser handles DateTimeOffsets
  176. // The following call shows the behavior without the bug, and should be enabled once the bug is fixed.
  177. //VerifyQueryDeserialization<DataTypes>("" + clause, expectedExpression);
  178. }
  179. [Theory]
  180. [InlineData("DateTimeProp eq DateTimeProp", "$it => ($it.DateTimeProp == $it.DateTimeProp)")]
  181. [InlineData("DateTimeProp ne DateTimeProp", "$it => ($it.DateTimeProp != $it.DateTimeProp)")]
  182. [InlineData("DateTimeProp ge DateTimeProp", "$it => ($it.DateTimeProp >= $it.DateTimeProp)")]
  183. [InlineData("DateTimeProp le DateTimeProp", "$it => ($it.DateTimeProp <= $it.DateTimeProp)")]
  184. public void DateInEqualities(string clause, string expectedExpression)
  185. {
  186. VerifyQueryDeserialization<DataTypes>(
  187. "" + clause,
  188. expectedExpression);
  189. }
  190. #endregion
  191. #region Logical Operators
  192. [Fact]
  193. [ReplaceCulture]
  194. public void BooleanOperatorNullableTypes()
  195. {
  196. VerifyQueryDeserialization(
  197. "UnitPrice eq 5.00m or CategoryID eq 0",
  198. "$it => (($it.UnitPrice == Convert(5.00)) OrElse ($it.CategoryID == 0))",
  199. NotTesting);
  200. }
  201. [Fact]
  202. public void BooleanComparisonOnNullableAndNonNullableType()
  203. {
  204. VerifyQueryDeserialization(
  205. "Discontinued eq true",
  206. "$it => ($it.Discontinued == Convert(True))",
  207. "$it => (($it.Discontinued == Convert(True)) == True)");
  208. }
  209. [Fact]
  210. public void BooleanComparisonOnNullableType()
  211. {
  212. VerifyQueryDeserialization(
  213. "Discontinued eq Discontinued",
  214. "$it => ($it.Discontinued == $it.Discontinued)",
  215. "$it => (($it.Discontinued == $it.Discontinued) == True)");
  216. }
  217. [Theory]
  218. [InlineData(null, null, false, false)]
  219. [InlineData(5.0, 0, true, true)]
  220. [InlineData(null, 1, false, false)]
  221. public void OrOperator(object unitPrice, object unitsInStock, bool withNullPropagation, bool withoutNullPropagation)
  222. {
  223. var filters = VerifyQueryDeserialization(
  224. "UnitPrice eq 5.00m or UnitsInStock eq 0",
  225. String.Format(CultureInfo.InvariantCulture, "$it => (($it.UnitPrice == Convert({0:0.00})) OrElse (Convert($it.UnitsInStock) == Convert({1})))", 5.0, 0),
  226. NotTesting);
  227. RunFilters(filters,
  228. new Product { UnitPrice = ToNullable<decimal>(unitPrice), UnitsInStock = ToNullable<short>(unitsInStock) },
  229. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  230. }
  231. [Theory]
  232. [InlineData(null, null, false, false)]
  233. [InlineData(5.0, 10, true, true)]
  234. [InlineData(null, 1, false, false)]
  235. public void AndOperator(object unitPrice, object unitsInStock, bool withNullPropagation, bool withoutNullPropagation)
  236. {
  237. var filters = VerifyQueryDeserialization(
  238. "UnitPrice eq 5.00m and UnitsInStock eq 10.00m",
  239. String.Format(CultureInfo.InvariantCulture, "$it => (($it.UnitPrice == Convert({0:0.00})) AndAlso (Convert($it.UnitsInStock) == Convert({1:0.00})))", 5.0, 10.0),
  240. NotTesting);
  241. RunFilters(filters,
  242. new Product { UnitPrice = ToNullable<decimal>(unitPrice), UnitsInStock = ToNullable<short>(unitsInStock) },
  243. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  244. }
  245. [Theory]
  246. [InlineData(null, false, true)] // This is an interesting cas for null propagation.
  247. [InlineData(5.0, false, false)]
  248. [InlineData(5.5, true, true)]
  249. public void Negation(object unitPrice, bool withNullPropagation, bool withoutNullPropagation)
  250. {
  251. var filters = VerifyQueryDeserialization(
  252. "not (UnitPrice eq 5.00m)",
  253. String.Format(CultureInfo.InvariantCulture, "$it => Not(($it.UnitPrice == Convert({0:0.00})))", 5.0),
  254. NotTesting);
  255. RunFilters(filters,
  256. new Product { UnitPrice = ToNullable<decimal>(unitPrice) },
  257. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  258. }
  259. [Theory]
  260. [InlineData(null, true, true)] // This is an interesting cas for null propagation.
  261. [InlineData(true, false, false)]
  262. [InlineData(false, true, true)]
  263. public void BoolNegation(bool discontinued, bool withNullPropagation, bool withoutNullPropagation)
  264. {
  265. var filters = VerifyQueryDeserialization(
  266. "not Discontinued",
  267. "$it => Convert(Not($it.Discontinued))",
  268. "$it => (Not($it.Discontinued) == True)");
  269. RunFilters(filters,
  270. new Product { Discontinued = ToNullable<bool>(discontinued) },
  271. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  272. }
  273. [Fact]
  274. public void NestedNegation()
  275. {
  276. VerifyQueryDeserialization(
  277. "not (not(not (Discontinued)))",
  278. "$it => Convert(Not(Not(Not($it.Discontinued))))",
  279. "$it => (Not(Not(Not($it.Discontinued))) == True)");
  280. }
  281. #endregion
  282. #region Arithmetic Operators
  283. [Theory]
  284. [InlineData(null, false, false)]
  285. [InlineData(5.0, true, true)]
  286. [InlineData(15.01, false, false)]
  287. public void Subtraction(object unitPrice, bool withNullPropagation, bool withoutNullPropagation)
  288. {
  289. var filters = VerifyQueryDeserialization(
  290. "UnitPrice sub 1.00m lt 5.00m",
  291. String.Format(CultureInfo.InvariantCulture, "$it => (($it.UnitPrice - Convert({0:0.00})) < Convert({1:0.00}))", 1.0, 5.0),
  292. String.Format(CultureInfo.InvariantCulture, "$it => ((($it.UnitPrice - Convert({0:0.00})) < Convert({1:0.00})) == True)", 1.0, 5.0));
  293. RunFilters(filters,
  294. new Product { UnitPrice = ToNullable<decimal>(unitPrice) },
  295. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  296. }
  297. [Fact]
  298. public void Addition()
  299. {
  300. VerifyQueryDeserialization(
  301. "UnitPrice add 1.00m lt 5.00m",
  302. String.Format(CultureInfo.InvariantCulture, "$it => (($it.UnitPrice + Convert({0:0.00})) < Convert({1:0.00}))", 1.0, 5.0),
  303. NotTesting);
  304. }
  305. [Fact]
  306. public void Multiplication()
  307. {
  308. VerifyQueryDeserialization(
  309. "UnitPrice mul 1.00m lt 5.00m",
  310. String.Format(CultureInfo.InvariantCulture, "$it => (($it.UnitPrice * Convert({0:0.00})) < Convert({1:0.00}))", 1.0, 5.0),
  311. NotTesting);
  312. }
  313. [Fact]
  314. public void Division()
  315. {
  316. VerifyQueryDeserialization(
  317. "UnitPrice div 1.00m lt 5.00m",
  318. String.Format(CultureInfo.InvariantCulture, "$it => (($it.UnitPrice / Convert({0:0.00})) < Convert({1:0.00}))", 1.0, 5.0),
  319. NotTesting);
  320. }
  321. [Fact]
  322. public void Modulo()
  323. {
  324. VerifyQueryDeserialization(
  325. "UnitPrice mod 1.00m lt 5.00m",
  326. String.Format(CultureInfo.InvariantCulture, "$it => (($it.UnitPrice % Convert({0:0.00})) < Convert({1:0.00}))", 1.0, 5.0),
  327. NotTesting);
  328. }
  329. #endregion
  330. # region NULL handling
  331. [Theory]
  332. [InlineData("UnitsInStock eq UnitsOnOrder", null, null, false, true)]
  333. [InlineData("UnitsInStock ne UnitsOnOrder", null, null, false, false)]
  334. [InlineData("UnitsInStock gt UnitsOnOrder", null, null, false, false)]
  335. [InlineData("UnitsInStock ge UnitsOnOrder", null, null, false, false)]
  336. [InlineData("UnitsInStock lt UnitsOnOrder", null, null, false, false)]
  337. [InlineData("UnitsInStock le UnitsOnOrder", null, null, false, false)]
  338. [InlineData("(UnitsInStock add UnitsOnOrder) eq UnitsInStock", null, null, false, true)]
  339. [InlineData("(UnitsInStock sub UnitsOnOrder) eq UnitsInStock", null, null, false, true)]
  340. [InlineData("(UnitsInStock mul UnitsOnOrder) eq UnitsInStock", null, null, false, true)]
  341. [InlineData("(UnitsInStock div UnitsOnOrder) eq UnitsInStock", null, null, false, true)]
  342. [InlineData("(UnitsInStock mod UnitsOnOrder) eq UnitsInStock", null, null, false, true)]
  343. [InlineData("UnitsInStock eq UnitsOnOrder", 1, null, false, false)]
  344. [InlineData("UnitsInStock eq UnitsOnOrder", 1, 1, true, true)]
  345. public void NullHandling(string filter, object unitsInStock, object unitsOnOrder, bool withNullPropagation, bool withoutNullPropagation)
  346. {
  347. var filters = VerifyQueryDeserialization("" + filter);
  348. RunFilters(filters,
  349. new Product { UnitsInStock = ToNullable<short>(unitsInStock), UnitsOnOrder = ToNullable<short>(unitsOnOrder) },
  350. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  351. }
  352. [Theory]
  353. [InlineData("UnitsInStock eq null", null, true, true)] // NULL == constant NULL is true when null propagation is enabled
  354. [InlineData("UnitsInStock ne null", null, false, false)] // NULL != constant NULL is false when null propagation is enabled
  355. public void NullHandling_LiteralNull(string filter, object unitsInStock, bool withNullPropagation, bool withoutNullPropagation)
  356. {
  357. var filters = VerifyQueryDeserialization("" + filter);
  358. RunFilters(filters,
  359. new Product { UnitsInStock = ToNullable<short>(unitsInStock) },
  360. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  361. }
  362. #endregion
  363. [Theory]
  364. [InlineData("StringProp gt 'Middle'", "Middle", false)]
  365. [InlineData("StringProp ge 'Middle'", "Middle", true)]
  366. [InlineData("StringProp lt 'Middle'", "Middle", false)]
  367. [InlineData("StringProp le 'Middle'", "Middle", true)]
  368. [InlineData("StringProp ge StringProp", "", true)]
  369. [InlineData("StringProp gt null", "", true)]
  370. [InlineData("null gt StringProp", "", false)]
  371. [InlineData("'Middle' gt StringProp", "Middle", false)]
  372. [InlineData("'a' lt 'b'", "", true)]
  373. public void StringComparisons_Work(string filter, string value, bool expectedResult)
  374. {
  375. var filters = VerifyQueryDeserialization<DataTypes>(filter);
  376. var result = RunFilter(filters.WithoutNullPropagation, new DataTypes { StringProp = value });
  377. Assert.Equal(result, expectedResult);
  378. }
  379. // Issue: 477
  380. [Theory]
  381. [InlineData("indexof('hello', StringProp) gt UIntProp")]
  382. [InlineData("indexof('hello', StringProp) gt ULongProp")]
  383. [InlineData("indexof('hello', StringProp) gt UShortProp")]
  384. [InlineData("indexof('hello', StringProp) gt NullableUShortProp")]
  385. [InlineData("indexof('hello', StringProp) gt NullableUIntProp")]
  386. [InlineData("indexof('hello', StringProp) gt NullableULongProp")]
  387. public void ComparisonsInvolvingCastsAndNullableValues(string filter)
  388. {
  389. var filters = VerifyQueryDeserialization<DataTypes>(filter);
  390. RunFilters(filters,
  391. new DataTypes(),
  392. new { WithNullPropagation = false, WithoutNullPropagation = typeof(ArgumentNullException) });
  393. }
  394. [Theory]
  395. [InlineData(null, null, true, true)]
  396. [InlineData("not doritos", 0, true, true)]
  397. [InlineData("Doritos", 1, false, false)]
  398. public void Grouping(string productName, object unitsInStock, bool withNullPropagation, bool withoutNullPropagation)
  399. {
  400. var filters = VerifyQueryDeserialization(
  401. "((ProductName ne 'Doritos') or (UnitPrice lt 5.00m))",
  402. String.Format(CultureInfo.InvariantCulture, "$it => (($it.ProductName != \"Doritos\") OrElse ($it.UnitPrice < Convert({0:0.00})))", 5.0),
  403. NotTesting);
  404. RunFilters(filters,
  405. new Product { ProductName = productName, UnitsInStock = ToNullable<short>(unitsInStock) },
  406. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  407. }
  408. [Fact]
  409. public void MemberExpressions()
  410. {
  411. var filters = VerifyQueryDeserialization(
  412. "Category/CategoryName eq 'Snacks'",
  413. "$it => ($it.Category.CategoryName == \"Snacks\")",
  414. "$it => (IIF(($it.Category == null), null, $it.Category.CategoryName) == \"Snacks\")");
  415. RunFilters(filters,
  416. new Product { },
  417. new { WithNullPropagation = false, WithoutNullPropagation = typeof(NullReferenceException) });
  418. RunFilters(filters,
  419. new Product { Category = new Category { CategoryName = "Snacks" } },
  420. new { WithNullPropagation = true, WithoutNullPropagation = true });
  421. }
  422. [Fact]
  423. public void MemberExpressionsRecursive()
  424. {
  425. var filters = VerifyQueryDeserialization(
  426. "Category/Product/Category/CategoryName eq 'Snacks'",
  427. "$it => ($it.Category.Product.Category.CategoryName == \"Snacks\")",
  428. NotTesting);
  429. RunFilters(filters,
  430. new Product { },
  431. new { WithNullPropagation = false, WithoutNullPropagation = typeof(NullReferenceException) });
  432. }
  433. [Fact]
  434. public void ComplexPropertyNavigation()
  435. {
  436. var filters = VerifyQueryDeserialization(
  437. "SupplierAddress/City eq 'Redmond'",
  438. "$it => ($it.SupplierAddress.City == \"Redmond\")",
  439. "$it => (IIF(($it.SupplierAddress == null), null, $it.SupplierAddress.City) == \"Redmond\")");
  440. RunFilters(filters,
  441. new Product { },
  442. new { WithNullPropagation = false, WithoutNullPropagation = typeof(NullReferenceException) });
  443. RunFilters(filters,
  444. new Product { SupplierAddress = new Address { City = "Redmond" } },
  445. new { WithNullPropagation = true, WithoutNullPropagation = true });
  446. }
  447. #region Any/All
  448. [Fact]
  449. public void AnyOnNavigationEnumerableCollections()
  450. {
  451. var filters = VerifyQueryDeserialization(
  452. "Category/EnumerableProducts/any(P: P/ProductName eq 'Snacks')",
  453. "$it => $it.Category.EnumerableProducts.Any(P => (P.ProductName == \"Snacks\"))",
  454. NotTesting);
  455. RunFilters(filters,
  456. new Product
  457. {
  458. Category = new Category
  459. {
  460. EnumerableProducts = new Product[]
  461. {
  462. new Product { ProductName = "Snacks" },
  463. new Product { ProductName = "NonSnacks" }
  464. }
  465. }
  466. },
  467. new { WithNullPropagation = true, WithoutNullPropagation = true });
  468. RunFilters(filters,
  469. new Product
  470. {
  471. Category = new Category
  472. {
  473. EnumerableProducts = new Product[]
  474. {
  475. new Product { ProductName = "NonSnacks" }
  476. }
  477. }
  478. },
  479. new { WithNullPropagation = false, WithoutNullPropagation = false });
  480. }
  481. [Fact]
  482. public void AnyOnNavigationQueryableCollections()
  483. {
  484. var filters = VerifyQueryDeserialization(
  485. "Category/QueryableProducts/any(P: P/ProductName eq 'Snacks')",
  486. "$it => $it.Category.QueryableProducts.Any(P => (P.ProductName == \"Snacks\"))",
  487. NotTesting);
  488. RunFilters(filters,
  489. new Product
  490. {
  491. Category = new Category
  492. {
  493. QueryableProducts = new Product[]
  494. {
  495. new Product { ProductName = "Snacks" },
  496. new Product { ProductName = "NonSnacks" }
  497. }.AsQueryable()
  498. }
  499. },
  500. new { WithNullPropagation = true, WithoutNullPropagation = true });
  501. RunFilters(filters,
  502. new Product
  503. {
  504. Category = new Category
  505. {
  506. QueryableProducts = new Product[]
  507. {
  508. new Product { ProductName = "NonSnacks" }
  509. }.AsQueryable()
  510. }
  511. },
  512. new { WithNullPropagation = false, WithoutNullPropagation = false });
  513. }
  514. [Fact]
  515. public void AnyOnNavigation_NullCollection()
  516. {
  517. var filters = VerifyQueryDeserialization(
  518. "Category/EnumerableProducts/any(P: P/ProductName eq 'Snacks')",
  519. "$it => $it.Category.EnumerableProducts.Any(P => (P.ProductName == \"Snacks\"))",
  520. NotTesting);
  521. RunFilters(filters,
  522. new Product
  523. {
  524. Category = new Category
  525. {
  526. }
  527. },
  528. new { WithNullPropagation = false, WithoutNullPropagation = typeof(ArgumentNullException) });
  529. RunFilters(filters,
  530. new Product
  531. {
  532. Category = new Category
  533. {
  534. EnumerableProducts = new Product[]
  535. {
  536. new Product { ProductName = "Snacks" }
  537. }
  538. }
  539. },
  540. new { WithNullPropagation = true, WithoutNullPropagation = true });
  541. }
  542. [Fact]
  543. public void AllOnNavigation_NullCollection()
  544. {
  545. var filters = VerifyQueryDeserialization(
  546. "Category/EnumerableProducts/all(P: P/ProductName eq 'Snacks')",
  547. "$it => $it.Category.EnumerableProducts.All(P => (P.ProductName == \"Snacks\"))",
  548. NotTesting);
  549. RunFilters(filters,
  550. new Product
  551. {
  552. Category = new Category
  553. {
  554. }
  555. },
  556. new { WithNullPropagation = false, WithoutNullPropagation = typeof(ArgumentNullException) });
  557. RunFilters(filters,
  558. new Product
  559. {
  560. Category = new Category
  561. {
  562. EnumerableProducts = new Product[]
  563. {
  564. new Product { ProductName = "Snacks" }
  565. }
  566. }
  567. },
  568. new { WithNullPropagation = true, WithoutNullPropagation = true });
  569. }
  570. [Fact]
  571. public void MultipleAnys_WithSameRangeVariableName()
  572. {
  573. VerifyQueryDeserialization(
  574. "AlternateIDs/any(n: n eq 42) and AlternateAddresses/any(n : n/City eq 'Redmond')",
  575. "$it => ($it.AlternateIDs.Any(n => (n == 42)) AndAlso $it.AlternateAddresses.Any(n => (n.City == \"Redmond\")))",
  576. NotTesting);
  577. }
  578. [Fact]
  579. public void MultipleAlls_WithSameRangeVariableName()
  580. {
  581. VerifyQueryDeserialization(
  582. "AlternateIDs/all(n: n eq 42) and AlternateAddresses/all(n : n/City eq 'Redmond')",
  583. "$it => ($it.AlternateIDs.All(n => (n == 42)) AndAlso $it.AlternateAddresses.All(n => (n.City == \"Redmond\")))",
  584. NotTesting);
  585. }
  586. [Fact]
  587. public void AnyOnNavigationEnumerableCollections_EmptyFilter()
  588. {
  589. VerifyQueryDeserialization(
  590. "Category/EnumerableProducts/any()",
  591. "$it => $it.Category.EnumerableProducts.Any()",
  592. NotTesting);
  593. }
  594. [Fact]
  595. public void AnyOnNavigationQueryableCollections_EmptyFilter()
  596. {
  597. VerifyQueryDeserialization(
  598. "Category/QueryableProducts/any()",
  599. "$it => $it.Category.QueryableProducts.Any()",
  600. NotTesting);
  601. }
  602. [Fact]
  603. public void AllOnNavigationEnumerableCollections()
  604. {
  605. VerifyQueryDeserialization(
  606. "Category/EnumerableProducts/all(P: P/ProductName eq 'Snacks')",
  607. "$it => $it.Category.EnumerableProducts.All(P => (P.ProductName == \"Snacks\"))",
  608. NotTesting);
  609. }
  610. [Fact]
  611. public void AllOnNavigationQueryableCollections()
  612. {
  613. VerifyQueryDeserialization(
  614. "Category/QueryableProducts/all(P: P/ProductName eq 'Snacks')",
  615. "$it => $it.Category.QueryableProducts.All(P => (P.ProductName == \"Snacks\"))",
  616. NotTesting);
  617. }
  618. [Fact]
  619. public void AnyInSequenceNotNested()
  620. {
  621. VerifyQueryDeserialization(
  622. "Category/QueryableProducts/any(P: P/ProductName eq 'Snacks') or Category/QueryableProducts/any(P2: P2/ProductName eq 'Snacks')",
  623. "$it => ($it.Category.QueryableProducts.Any(P => (P.ProductName == \"Snacks\")) OrElse $it.Category.QueryableProducts.Any(P2 => (P2.ProductName == \"Snacks\")))",
  624. NotTesting);
  625. }
  626. [Fact]
  627. public void AllInSequenceNotNested()
  628. {
  629. VerifyQueryDeserialization(
  630. "Category/QueryableProducts/all(P: P/ProductName eq 'Snacks') or Category/QueryableProducts/all(P2: P2/ProductName eq 'Snacks')",
  631. "$it => ($it.Category.QueryableProducts.All(P => (P.ProductName == \"Snacks\")) OrElse $it.Category.QueryableProducts.All(P2 => (P2.ProductName == \"Snacks\")))",
  632. NotTesting);
  633. }
  634. [Fact]
  635. public void AnyOnPrimitiveCollection()
  636. {
  637. var filters = VerifyQueryDeserialization(
  638. "AlternateIDs/any(id: id eq 42)",
  639. "$it => $it.AlternateIDs.Any(id => (id == 42))",
  640. NotTesting);
  641. RunFilters(
  642. filters,
  643. new Product { AlternateIDs = new[] { 1, 2, 42 } },
  644. new { WithNullPropagation = true, WithoutNullPropagation = true });
  645. RunFilters(
  646. filters,
  647. new Product { AlternateIDs = new[] { 1, 2 } },
  648. new { WithNullPropagation = false, WithoutNullPropagation = false });
  649. }
  650. [Fact]
  651. public void AllOnPrimitiveCollection()
  652. {
  653. VerifyQueryDeserialization(
  654. "AlternateIDs/all(id: id eq 42)",
  655. "$it => $it.AlternateIDs.All(id => (id == 42))",
  656. NotTesting);
  657. }
  658. [Fact]
  659. public void AnyOnComplexCollection()
  660. {
  661. var filters = VerifyQueryDeserialization(
  662. "AlternateAddresses/any(address: address/City eq 'Redmond')",
  663. "$it => $it.AlternateAddresses.Any(address => (address.City == \"Redmond\"))",
  664. NotTesting);
  665. RunFilters(
  666. filters,
  667. new Product { AlternateAddresses = new[] { new Address { City = "Redmond" } } },
  668. new { WithNullPropagation = true, WithoutNullPropagation = true });
  669. RunFilters(
  670. filters,
  671. new Product(),
  672. new { WithNullPropagation = false, WithoutNullPropagation = typeof(ArgumentNullException) });
  673. }
  674. [Fact]
  675. public void AllOnComplexCollection()
  676. {
  677. VerifyQueryDeserialization(
  678. "AlternateAddresses/all(address: address/City eq 'Redmond')",
  679. "$it => $it.AlternateAddresses.All(address => (address.City == \"Redmond\"))",
  680. NotTesting);
  681. }
  682. [Fact]
  683. public void RecursiveAllAny()
  684. {
  685. VerifyQueryDeserialization(
  686. "Category/QueryableProducts/all(P: P/Category/EnumerableProducts/any(PP: PP/ProductName eq 'Snacks'))",
  687. "$it => $it.Category.QueryableProducts.All(P => P.Category.EnumerableProducts.Any(PP => (PP.ProductName == \"Snacks\")))",
  688. NotTesting);
  689. }
  690. #endregion
  691. #region String Functions
  692. [Theory]
  693. [InlineData("Abcd", -1, "Abcd", true, typeof(ArgumentOutOfRangeException))]
  694. [InlineData("Abcd", 0, "Abcd", true, true)]
  695. [InlineData("Abcd", 1, "bcd", true, true)]
  696. [InlineData("Abcd", 3, "d", true, true)]
  697. [InlineData("Abcd", 4, "", true, true)]
  698. [InlineData("Abcd", 5, "", true, typeof(ArgumentOutOfRangeException))]
  699. public void StringSubstringStart(string productName, int startIndex, string compareString, bool withNullPropagation, object withoutNullPropagation)
  700. {
  701. string filter = String.Format("substring(ProductName, {0}) eq '{1}'", startIndex, compareString);
  702. var filters = VerifyQueryDeserialization(filter);
  703. RunFilters(filters,
  704. new Product { ProductName = productName },
  705. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  706. }
  707. [Theory]
  708. [InlineData("Abcd", -1, 4, "Abcd", true, typeof(ArgumentOutOfRangeException))]
  709. [InlineData("Abcd", -1, 3, "Abc", true, typeof(ArgumentOutOfRangeException))]
  710. [InlineData("Abcd", 0, 1, "A", true, true)]
  711. [InlineData("Abcd", 0, 4, "Abcd", true, true)]
  712. [InlineData("Abcd", 0, 3, "Abc", true, true)]
  713. [InlineData("Abcd", 0, 5, "Abcd", true, typeof(ArgumentOutOfRangeException))]
  714. [InlineData("Abcd", 1, 3, "bcd", true, true)]
  715. [InlineData("Abcd", 1, 5, "bcd", true, typeof(ArgumentOutOfRangeException))]
  716. [InlineData("Abcd", 2, 1, "c", true, true)]
  717. [InlineData("Abcd", 3, 1, "d", true, true)]
  718. [InlineData("Abcd", 4, 1, "", true, typeof(ArgumentOutOfRangeException))]
  719. [InlineData("Abcd", 0, -1, "", true, typeof(ArgumentOutOfRangeException))]
  720. [InlineData("Abcd", 5, -1, "", true, typeof(ArgumentOutOfRangeException))]
  721. public void StringSubstringStartAndLength(string productName, int startIndex, int length, string compareString, bool withNullPropagation, object withoutNullPropagation)
  722. {
  723. string filter = String.Format("substring(ProductName, {0}, {1}) eq '{2}'", startIndex, length, compareString);
  724. var filters = VerifyQueryDeserialization(filter);
  725. RunFilters(filters,
  726. new Product { ProductName = productName },
  727. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  728. }
  729. [Theory]
  730. [InlineData(null, false, typeof(NullReferenceException))]
  731. [InlineData("Abcd", true, true)]
  732. [InlineData("Abd", false, false)]
  733. public void StringSubstringOf(string productName, bool withNullPropagation, object withoutNullPropagation)
  734. {
  735. // In OData, the order of parameters is actually reversed in the resulting
  736. // String.Contains expression
  737. var filters = VerifyQueryDeserialization(
  738. "contains(ProductName, 'Abc')",
  739. "$it => $it.ProductName.Contains(\"Abc\")",
  740. NotTesting);
  741. RunFilters(filters,
  742. new Product { ProductName = productName },
  743. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  744. filters = VerifyQueryDeserialization(
  745. "contains(ProductName, 'Abc')",
  746. "$it => $it.ProductName.Contains(\"Abc\")",
  747. NotTesting);
  748. }
  749. [Theory]
  750. [InlineData(null, false, typeof(NullReferenceException))]
  751. [InlineData("Abcd", true, true)]
  752. [InlineData("Abd", false, false)]
  753. public void StringStartsWith(string productName, bool withNullPropagation, object withoutNullPropagation)
  754. {
  755. var filters = VerifyQueryDeserialization(
  756. "startswith(ProductName, 'Abc')",
  757. "$it => $it.ProductName.StartsWith(\"Abc\")",
  758. NotTesting);
  759. RunFilters(filters,
  760. new Product { ProductName = productName },
  761. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  762. }
  763. [Theory]
  764. [InlineData(null, false, typeof(NullReferenceException))]
  765. [InlineData("AAbc", true, true)]
  766. [InlineData("Abcd", false, false)]
  767. public void StringEndsWith(string productName, bool withNullPropagation, object withoutNullPropagation)
  768. {
  769. var filters = VerifyQueryDeserialization(
  770. "endswith(ProductName, 'Abc')",
  771. "$it => $it.ProductName.EndsWith(\"Abc\")",
  772. NotTesting);
  773. RunFilters(filters,
  774. new Product { ProductName = productName },
  775. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  776. }
  777. [Theory]
  778. [InlineData(null, false, typeof(NullReferenceException))]
  779. [InlineData("AAbc", true, true)]
  780. [InlineData("", false, false)]
  781. public void StringLength(string productName, bool withNullPropagation, object withoutNullPropagation)
  782. {
  783. var filters = VerifyQueryDeserialization(
  784. "length(ProductName) gt 0",
  785. "$it => ($it.ProductName.Length > 0)",
  786. "$it => ((IIF(($it.ProductName == null), null, Convert($it.ProductName.Length)) > Convert(0)) == True)");
  787. RunFilters(filters,
  788. new Product { ProductName = productName },
  789. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  790. }
  791. [Theory]
  792. [InlineData(null, false, typeof(NullReferenceException))]
  793. [InlineData("12345Abc", true, true)]
  794. [InlineData("1234Abc", false, false)]
  795. public void StringIndexOf(string productName, bool withNullPropagation, object withoutNullPropagation)
  796. {
  797. var filters = VerifyQueryDeserialization(
  798. "indexof(ProductName, 'Abc') eq 5",
  799. "$it => ($it.ProductName.IndexOf(\"Abc\") == 5)",
  800. NotTesting);
  801. RunFilters(filters,
  802. new Product { ProductName = productName },
  803. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  804. }
  805. [Theory]
  806. [InlineData(null, false, typeof(NullReferenceException))]
  807. [InlineData("123uctName", true, true)]
  808. [InlineData("1234Abc", false, false)]
  809. public void StringSubstring(string productName, bool withNullPropagation, object withoutNullPropagation)
  810. {
  811. var filters = VerifyQueryDeserialization(
  812. "substring(ProductName, 3) eq 'uctName'",
  813. "$it => ($it.ProductName.Substring(3) == \"uctName\")",
  814. NotTesting);
  815. RunFilters(filters,
  816. new Product { ProductName = productName },
  817. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  818. VerifyQueryDeserialization(
  819. "substring(ProductName, 3, 4) eq 'uctN'",
  820. "$it => ($it.ProductName.Substring(3, 4) == \"uctN\")",
  821. NotTesting);
  822. }
  823. [Theory]
  824. [InlineData(null, false, typeof(NullReferenceException))]
  825. [InlineData("Tasty Treats", true, true)]
  826. [InlineData("Tasty Treatss", false, false)]
  827. public void StringToLower(string productName, bool withNullPropagation, object withoutNullPropagation)
  828. {
  829. var filters = VerifyQueryDeserialization(
  830. "tolower(ProductName) eq 'tasty treats'",
  831. "$it => ($it.ProductName.ToLower() == \"tasty treats\")",
  832. NotTesting);
  833. RunFilters(filters,
  834. new Product { ProductName = productName },
  835. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  836. }
  837. [Theory]
  838. [InlineData(null, false, typeof(NullReferenceException))]
  839. [InlineData("Tasty Treats", true, true)]
  840. [InlineData("Tasty Treatss", false, false)]
  841. public void StringToUpper(string productName, bool withNullPropagation, object withoutNullPropagation)
  842. {
  843. var filters = VerifyQueryDeserialization(
  844. "toupper(ProductName) eq 'TASTY TREATS'",
  845. "$it => ($it.ProductName.ToUpper() == \"TASTY TREATS\")",
  846. NotTesting);
  847. RunFilters(filters,
  848. new Product { ProductName = productName },
  849. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  850. }
  851. [Theory]
  852. [InlineData(null, false, typeof(NullReferenceException))]
  853. [InlineData("Tasty Treats", true, true)]
  854. [InlineData("Tasty Treatss", false, false)]
  855. public void StringTrim(string productName, bool withNullPropagation, object withoutNullPropagation)
  856. {
  857. var filters = VerifyQueryDeserialization(
  858. "trim(ProductName) eq 'Tasty Treats'",
  859. "$it => ($it.ProductName.Trim() == \"Tasty Treats\")",
  860. NotTesting);
  861. RunFilters(filters,
  862. new Product { ProductName = productName },
  863. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  864. }
  865. [Fact]
  866. public void StringConcat()
  867. {
  868. var filters = VerifyQueryDeserialization(
  869. "concat('Food', 'Bar') eq 'FoodBar'",
  870. "$it => (\"Food\".Concat(\"Bar\") == \"FoodBar\")",
  871. NotTesting);
  872. RunFilters(filters,
  873. new Product { },
  874. new { WithNullPropagation = true, WithoutNullPropagation = true });
  875. }
  876. [Fact]
  877. public void RecursiveMethodCall()
  878. {
  879. var filters = VerifyQueryDeserialization(
  880. "floor(floor(UnitPrice)) eq 123m",
  881. "$it => ($it.UnitPrice.Value.Floor().Floor() == 123)",
  882. NotTesting);
  883. RunFilters(filters,
  884. new Product { },
  885. new { WithNullPropagation = false, WithoutNullPropagation = typeof(InvalidOperationException) });
  886. }
  887. #endregion
  888. #region Date Functions
  889. [Fact]
  890. public void DateDay()
  891. {
  892. var filters = VerifyQueryDeserialization(
  893. "day(DiscontinuedDate) eq 8",
  894. "$it => ($it.DiscontinuedDate.Value.Day == 8)",
  895. NotTesting);
  896. RunFilters(filters,
  897. new Product { },
  898. new { WithNullPropagation = false, WithoutNullPropagation = typeof(InvalidOperationException) });
  899. RunFilters(filters,
  900. new Product { DiscontinuedDate = new DateTime(2000, 10, 8) },
  901. new { WithNullPropagation = true, WithoutNullPropagation = true });
  902. }
  903. public void DateDayNonNullable()
  904. {
  905. VerifyQueryDeserialization(
  906. "day(NonNullableDiscontinuedDate) eq 8",
  907. "$it => ($it.NonNullableDiscontinuedDate.Day == 8)");
  908. }
  909. [Fact]
  910. public void DateMonth()
  911. {
  912. VerifyQueryDeserialization(
  913. "month(DiscontinuedDate) eq 8",
  914. "$it => ($it.DiscontinuedDate.Value.Month == 8)",
  915. NotTesting);
  916. }
  917. [Fact]
  918. public void DateYear()
  919. {
  920. VerifyQueryDeserialization(
  921. "year(DiscontinuedDate) eq 1974",
  922. "$it => ($it.DiscontinuedDate.Value.Year == 1974)",
  923. NotTesting);
  924. }
  925. [Fact]
  926. public void DateHour()
  927. {
  928. VerifyQueryDeserialization("hour(DiscontinuedDate) eq 8",
  929. "$it => ($it.DiscontinuedDate.Value.Hour == 8)",
  930. NotTesting);
  931. }
  932. [Fact]
  933. public void DateMinute()
  934. {
  935. VerifyQueryDeserialization(
  936. "minute(DiscontinuedDate) eq 12",
  937. "$it => ($it.DiscontinuedDate.Value.Minute == 12)",
  938. NotTesting);
  939. }
  940. [Fact]
  941. public void DateSecond()
  942. {
  943. VerifyQueryDeserialization(
  944. "second(DiscontinuedDate) eq 33",
  945. "$it => ($it.DiscontinuedDate.Value.Second == 33)",
  946. NotTesting);
  947. }
  948. [Theory]
  949. [InlineData("year(DiscontinuedOffset) eq 100", "$it => ($it.DiscontinuedOffset.Year == 100)")]
  950. [InlineData("month(DiscontinuedOffset) eq 100", "$it => ($it.DiscontinuedOffset.Month == 100)")]
  951. [InlineData("day(DiscontinuedOffset) eq 100", "$it => ($it.DiscontinuedOffset.Day == 100)")]
  952. [InlineData("hour(DiscontinuedOffset) eq 100", "$it => ($it.DiscontinuedOffset.Hour == 100)")]
  953. [InlineData("minute(DiscontinuedOffset) eq 100", "$it => ($it.DiscontinuedOffset.Minute == 100)")]
  954. [InlineData("second(DiscontinuedOffset) eq 100", "$it => ($it.DiscontinuedOffset.Second == 100)")]
  955. public void DateTimeOffsetFunctions(string filter, string expression)
  956. {
  957. VerifyQueryDeserialization(filter, expression);
  958. }
  959. [Theory]
  960. [InlineData("years(DiscontinuedSince) eq 100", "$it => $it.DiscontinuedSince.Years == 100")]
  961. [InlineData("months(DiscontinuedSince) eq 100", "$it => $it.DiscontinuedSince.Months == 100")]
  962. [InlineData("days(DiscontinuedSince) eq 100", "$it => $it.DiscontinuedSince.Days == 100")]
  963. [InlineData("hours(DiscontinuedSince) eq 100", "$it => $it.DiscontinuedSince.Hours == 100")]
  964. [InlineData("minutes(DiscontinuedSince) eq 100", "$it => $it.DiscontinuedSince.Minutes == 100")]
  965. [InlineData("seconds(DiscontinuedSince) eq 100", "$it => $it.DiscontinuedSince.Seconds == 100")]
  966. public void TimespanFunctions(string filter, string expression)
  967. {
  968. // There's currently a bug here. For now, the test checks for the presence of the bug (as a reminder to fix
  969. // the test once the bug is fixed).
  970. // The following assert shows the behavior with the bug and should be removed once the bug is fixed.
  971. Assert.Throws<ODataException>(() => Bind(filter));
  972. // TODO: Timespans are not handled well in the uri parser
  973. // The following call shows the behavior without the bug, and should be enabled once the bug is fixed.
  974. //VerifyQueryDeserialization(filter, expression);
  975. }
  976. #endregion
  977. #region Math Functions
  978. [Theory, PropertyData("MathRoundDecimal_DataSet")]
  979. public void MathRoundDecimal(decimal? unitPrice, bool withNullPropagation, object withoutNullPropagation)
  980. {
  981. var filters = VerifyQueryDeserialization(
  982. "round(UnitPrice) gt 5.00m",
  983. String.Format(CultureInfo.InvariantCulture, "$it => ($it.UnitPrice.Value.Round() > {0:0.00})", 5.0),
  984. NotTesting);
  985. RunFilters(filters,
  986. new Product { UnitPrice = ToNullable<decimal>(unitPrice) },
  987. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  988. }
  989. [Theory]
  990. [InlineData(null, false, typeof(InvalidOperationException))]
  991. [InlineData(5.9d, true, true)]
  992. [InlineData(5.4d, false, false)]
  993. public void MathRoundDouble(double? weight, bool withNullPropagation, object withoutNullPropagation)
  994. {
  995. var filters = VerifyQueryDeserialization(
  996. "round(Weight) gt 5d",
  997. String.Format(CultureInfo.InvariantCulture, "$it => ($it.Weight.Value.Round() > {0})", 5),
  998. NotTesting);
  999. RunFilters(filters,
  1000. new Product { Weight = ToNullable<double>(weight) },
  1001. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  1002. }
  1003. [Theory]
  1004. [InlineData(null, false, typeof(InvalidOperationException))]
  1005. [InlineData(5.9f, true, true)]
  1006. [InlineData(5.4f, false, false)]
  1007. public void MathRoundFloat(float? width, bool withNullPropagation, object withoutNullPropagation)
  1008. {
  1009. var filters = VerifyQueryDeserialization(
  1010. "round(Width) gt 5f",
  1011. String.Format(CultureInfo.InvariantCulture, "$it => (Convert($it.Width).Value.Round() > {0})", 5),
  1012. NotTesting);
  1013. RunFilters(filters,
  1014. new Product { Width = ToNullable<float>(width) },
  1015. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  1016. }
  1017. [Theory, PropertyData("MathFloorDecimal_DataSet")]
  1018. public void MathFloorDecimal(decimal? unitPrice, bool withNullPropagation, object withoutNullPropagation)
  1019. {
  1020. var filters = VerifyQueryDeserialization(
  1021. "floor(UnitPrice) eq 5",
  1022. "$it => ($it.UnitPrice.Value.Floor() == 5)",
  1023. NotTesting);
  1024. RunFilters(filters,
  1025. new Product { UnitPrice = ToNullable<decimal>(unitPrice) },
  1026. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  1027. }
  1028. [Theory]
  1029. [InlineData(null, false, typeof(InvalidOperationException))]
  1030. [InlineData(5.4d, true, true)]
  1031. [InlineData(4.4d, false, false)]
  1032. public void MathFloorDouble(double? weight, bool withNullPropagation, object withoutNullPropagation)
  1033. {
  1034. var filters = VerifyQueryDeserialization(
  1035. "floor(Weight) eq 5",
  1036. "$it => ($it.Weight.Value.Floor() == 5)",
  1037. NotTesting);
  1038. RunFilters(filters,
  1039. new Product { Weight = ToNullable<double>(weight) },
  1040. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  1041. }
  1042. [Theory]
  1043. [InlineData(null, false, typeof(InvalidOperationException))]
  1044. [InlineData(5.4f, true, true)]
  1045. [InlineData(4.4f, false, false)]
  1046. public void MathFloorFloat(float? width, bool withNullPropagation, object withoutNullPropagation)
  1047. {
  1048. var filters = VerifyQueryDeserialization(
  1049. "floor(Width) eq 5",
  1050. "$it => (Convert($it.Width).Value.Floor() == 5)",
  1051. NotTesting);
  1052. RunFilters(filters,
  1053. new Product { Width = ToNullable<float>(width) },
  1054. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  1055. }
  1056. [Theory, PropertyData("MathCeilingDecimal_DataSet")]
  1057. public void MathCeilingDecimal(object unitPrice, bool withNullPropagation, object withoutNullPropagation)
  1058. {
  1059. var filters = VerifyQueryDeserialization(
  1060. "ceiling(UnitPrice) eq 5",
  1061. "$it => ($it.UnitPrice.Value.Ceiling() == 5)",
  1062. NotTesting);
  1063. RunFilters(filters,
  1064. new Product { UnitPrice = ToNullable<decimal>(unitPrice) },
  1065. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  1066. }
  1067. [Theory]
  1068. [InlineData(null, false, typeof(InvalidOperationException))]
  1069. [InlineData(4.1d, true, true)]
  1070. [InlineData(5.9d, false, false)]
  1071. public void MathCeilingDouble(double? weight, bool withNullPropagation, object withoutNullPropagation)
  1072. {
  1073. var filters = VerifyQueryDeserialization(
  1074. "ceiling(Weight) eq 5",
  1075. "$it => ($it.Weight.Value.Ceiling() == 5)",
  1076. NotTesting);
  1077. RunFilters(filters,
  1078. new Product { Weight = ToNullable<double>(weight) },
  1079. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  1080. }
  1081. [Theory]
  1082. [InlineData(null, false, typeof(InvalidOperationException))]
  1083. [InlineData(4.1f, true, true)]
  1084. [InlineData(5.9f, false, false)]
  1085. public void MathCeilingFloat(float? width, bool withNullPropagation, object withoutNullPropagation)
  1086. {
  1087. var filters = VerifyQueryDeserialization(
  1088. "ceiling(Width) eq 5",
  1089. "$it => (Convert($it.Width).Value.Ceiling() == 5)",
  1090. NotTesting);
  1091. RunFilters(filters,
  1092. new Product { Width = ToNullable<float>(width) },
  1093. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  1094. }
  1095. [Theory]
  1096. [InlineData("floor(FloatProp) eq floor(FloatProp)")]
  1097. [InlineData("round(FloatProp) eq round(FloatProp)")]
  1098. [InlineData("ceiling(FloatProp) eq ceiling(FloatProp)")]
  1099. [InlineData("floor(DoubleProp) eq floor(DoubleProp)")]
  1100. [InlineData("round(DoubleProp) eq round(DoubleProp)")]
  1101. [InlineData("ceiling(DoubleProp) eq ceiling(DoubleProp)")]
  1102. [InlineData("floor(DecimalProp) eq floor(DecimalProp)")]
  1103. [InlineData("round(DecimalProp) eq round(DecimalProp)")]
  1104. [InlineData("ceiling(DecimalProp) eq ceiling(DecimalProp)")]
  1105. public void MathFunctions_VariousTypes(string filter)
  1106. {
  1107. var filters = VerifyQueryDeserialization<DataTypes>(filter);
  1108. RunFilters(filters, new DataTypes(), new { WithNullPropagation = true, WithoutNullPropagation = true });
  1109. }
  1110. #endregion
  1111. #region Data Types
  1112. [Fact]
  1113. public void GuidExpression()
  1114. {
  1115. VerifyQueryDeserialization<DataTypes>(
  1116. "GuidProp eq 0EFDAECF-A9F0-42F3-A384-1295917AF95E",
  1117. "$it => ($it.GuidProp == 0efdaecf-a9f0-42f3-a384-1295917af95e)");
  1118. // verify case insensitivity
  1119. VerifyQueryDeserialization<DataTypes>(
  1120. "GuidProp eq 0EFDAECF-A9F0-42F3-A384-1295917AF95E",
  1121. "$it => ($it.GuidProp == 0efdaecf-a9f0-42f3-a384-1295917af95e)");
  1122. }
  1123. [Theory]
  1124. [InlineData("DateTimeProp eq 2000-12-12T12:00:00Z", "$it => ($it.DateTimeProp == {0})")]
  1125. [InlineData("DateTimeProp lt 2000-12-12T12:00:00Z", "$it => ($it.DateTimeProp < {0})")]
  1126. // TODO: [InlineData("DateTimeProp ge datetime'2000-12-12T12:00'", "$it => ($it.DateTimeProp >= {0})")] (uriparser fails on optional seconds)
  1127. public void DateTimeExpression(string clause, string expectedExpression)
  1128. {
  1129. var dateTime = new DateTimeOffset(new DateTime(2000, 12, 12, 12, 0, 0), TimeSpan.Zero);
  1130. VerifyQueryDeserialization<DataTypes>(
  1131. "" + clause,
  1132. String.Format(CultureInfo.InvariantCulture, expectedExpression, dateTime));
  1133. }
  1134. [Theory]
  1135. [InlineData("DateTimeOffsetProp eq datetimeoffset'2002-10-10T17:00:00Z'", "$it => ($it.DateTimeOffsetProp == {0})", 0)]
  1136. [InlineData("DateTimeOffsetProp ge datetimeoffset'2002-10-10T17:00:00Z'", "$it => ($it.DateTimeOffsetProp >= {0})", 0)]
  1137. [InlineData("DateTimeOffsetProp le datetimeoffset'2002-10-10T17:00:00-07:00'", "$it => ($it.DateTimeOffsetProp <= {0})", -7)]
  1138. [InlineData("DateTimeOffsetProp eq datetimeoffset'2002-10-10T17:00:00-0600'", "$it => ($it.DateTimeOffsetProp == {0})", -6)]
  1139. [InlineData("DateTimeOffsetProp lt datetimeoffset'2002-10-10T17:00:00-05'", "$it => ($it.DateTimeOffsetProp < {0})", -5)]
  1140. [InlineData("DateTimeOffsetProp ne datetimeoffset'2002-10-10T17:00:00%2B09:30'", "$it => ($it.DateTimeOffsetProp != {0})", 9.5)]
  1141. [InlineData("DateTimeOffsetProp gt datetimeoffset'2002-10-10T17:00:00%2B0545'", "$it => ($it.DateTimeOffsetProp > {0})", 5.75)]
  1142. public void DateTimeOffsetExpression(string clause, string expectedExpression, double offsetHours)
  1143. {
  1144. var dateTimeOffset = new DateTimeOffset(2002, 10, 10, 17, 0, 0, TimeSpan.FromHours(offsetHours));
  1145. // There's currently a bug here. For now, the test checks for the presence of the bug (as a reminder to fix
  1146. // the test once the bug is fixed).
  1147. // The following assert shows the behavior with the bug and should be removed once the bug is fixed.
  1148. Assert.Throws<ODataException>(() => Bind("" + clause));
  1149. // TODO: No DateTimeOffset parsing in ODataUriParser
  1150. // The following call shows the behavior without the bug, and should be enabled once the bug is fixed.
  1151. //VerifyQueryDeserialization<DataTypes>(
  1152. // "" + clause,
  1153. // String.Format(CultureInfo.InvariantCulture, expectedExpression, dateTimeOffset));
  1154. }
  1155. [Fact]
  1156. public void IntegerLiteralSuffix()
  1157. {
  1158. // long L
  1159. VerifyQueryDeserialization<DataTypes>(
  1160. "LongProp lt 987654321L and LongProp gt 123456789l",
  1161. "$it => (($it.LongProp < 987654321) AndAlso ($it.LongProp > 123456789))");
  1162. VerifyQueryDeserialization<DataTypes>(
  1163. "LongProp lt -987654321L and LongProp gt -123456789l",
  1164. "$it => (($it.LongProp < -987654321) AndAlso ($it.LongProp > -123456789))");
  1165. }
  1166. [Fact]
  1167. public void RealLiteralSuffixes()
  1168. {
  1169. // Float F
  1170. VerifyQueryDeserialization<DataTypes>(
  1171. "FloatProp lt 4321.56F and FloatProp gt 1234.56f",
  1172. String.Format(CultureInfo.InvariantCulture, "$it => (($it.FloatProp < {0:0.00}) AndAlso ($it.FloatProp > {1:0.00}))", 4321.56, 1234.56));
  1173. // Decimal M
  1174. VerifyQueryDeserialization<DataTypes>(
  1175. "DecimalProp lt 4321.56M and DecimalProp gt 1234.56m",
  1176. String.Format(CultureInfo.InvariantCulture, "$it => (($it.DecimalProp < {0:0.00}) AndAlso ($it.DecimalProp > {1:0.00}))", 4321.56, 1234.56));
  1177. }
  1178. [Theory]
  1179. [InlineData("'hello,world'", "hello,world")]
  1180. [InlineData("'''hello,world'", "'hello,world")]
  1181. [InlineData("'hello,world'''", "hello,world'")]
  1182. [InlineData("'hello,''wor''ld'", "hello,'wor'ld")]
  1183. [InlineData("'hello,''''''world'", "hello,'''world")]
  1184. [InlineData("'\"hello,world\"'", "\"hello,world\"")]
  1185. [InlineData("'\"hello,world'", "\"hello,world")]
  1186. [InlineData("'hello,world\"'", "hello,world\"")]
  1187. [InlineData("'hello,\"world'", "hello,\"world")]
  1188. [InlineData("'México D.F.'", "México D.F.")]
  1189. [InlineData("'æææøøøååå'", "æææøøøååå")]
  1190. [InlineData("'いくつかのテキスト'", "いくつかのテキスト")]
  1191. public void StringLiterals(string literal, string expected)
  1192. {
  1193. VerifyQueryDeserialization<Product>(
  1194. "ProductName eq " + literal,
  1195. String.Format("$it => ($it.ProductName == \"{0}\")", expected));
  1196. }
  1197. [Theory]
  1198. [InlineData('$')]
  1199. [InlineData('&')]
  1200. [InlineData('+')]
  1201. [InlineData(',')]
  1202. [InlineData('/')]
  1203. [InlineData(':')]
  1204. [InlineData(';')]
  1205. [InlineData('=')]
  1206. [InlineData('?')]
  1207. [InlineData('@')]
  1208. [InlineData(' ')]
  1209. [InlineData('<')]
  1210. [InlineData('>')]
  1211. [InlineData('#')]
  1212. [InlineData('%')]
  1213. [InlineData('{')]
  1214. [InlineData('}')]
  1215. [InlineData('|')]
  1216. [InlineData('\\')]
  1217. [InlineData('^')]
  1218. [InlineData('~')]
  1219. [InlineData('[')]
  1220. [InlineData(']')]
  1221. [InlineData('`')]
  1222. public void SpecialCharactersInStringLiteral(char c)
  1223. {
  1224. var filters = VerifyQueryDeserialization<Product>(
  1225. "ProductName eq '" + c + "'",
  1226. String.Format("$it => ($it.ProductName == \"{0}\")", c));
  1227. RunFilters(
  1228. filters,
  1229. new Product { ProductName = c.ToString() },
  1230. new { WithNullPropagation = true, WithoutNullPropagation = true });
  1231. }
  1232. #endregion
  1233. #region Casts
  1234. [Fact]
  1235. public void NSCast_OnEnumerableEntityCollection_GeneratesExpression_WithOfTypeOnEnumerable()
  1236. {
  1237. var filters = VerifyQueryDeserialization(
  1238. "Category/EnumerableProducts/System.Web.OData.Query.Expressions.DerivedProduct/any(p: p/ProductName eq 'ProductName')",
  1239. "$it => $it.Category.EnumerableProducts.OfType().Any(p => (p.ProductName == \"ProductName\"))",
  1240. NotTesting);
  1241. Assert.NotNull(filters.WithoutNullPropagation);
  1242. }
  1243. [Fact]
  1244. public void NSCast_OnQueryableEntityCollection_GeneratesExpression_WithOfTypeOnQueryable()
  1245. {
  1246. var filters = VerifyQueryDeserialization(
  1247. "Category/QueryableProducts/System.Web.OData.Query.Expressions.DerivedProduct/any(p: p/ProductName eq 'ProductName')",
  1248. "$it => $it.Category.QueryableProducts.OfType().Any(p => (p.ProductName == \"ProductName\"))",
  1249. NotTesting);
  1250. }
  1251. [Fact]
  1252. public void NSCast_OnEntityCollection_CanAccessDerivedInstanceProperty()
  1253. {
  1254. var filters = VerifyQueryDeserialization(
  1255. "Category/Products/System.Web.OData.Query.Expressions.DerivedProduct/any(p: p/DerivedProductName eq 'DerivedProductName')");
  1256. RunFilters(
  1257. filters,
  1258. new Product { Category = new Category { Products = new Product[] { new DerivedProduct { DerivedProductName = "DerivedProductName" } } } },
  1259. new { WithNullPropagation = true, WithoutNullPropagation = true });
  1260. RunFilters(
  1261. filters,
  1262. new Product { Category = new Category { Products = new Product[] { new DerivedProduct { DerivedProductName = "NotDerivedProductName" } } } },
  1263. new { WithNullPropagation = false, WithoutNullPropagation = false });
  1264. }
  1265. [Fact]
  1266. public void NSCast_OnSingleEntity_GeneratesExpression_WithAsOperator()
  1267. {
  1268. var filters = VerifyQueryDeserialization(
  1269. "System.Web.OData.Query.Expressions.Product/ProductName eq 'ProductName'",
  1270. "$it => (($it As Product).ProductName == \"ProductName\")",
  1271. NotTesting);
  1272. }
  1273. [Theory]
  1274. [InlineData("System.Web.OData.Query.Expressions.Product/ProductName eq 'ProductName'")]
  1275. [InlineData("System.Web.OData.Query.Expressions.DerivedProduct/DerivedProductName eq 'DerivedProductName'")]
  1276. [InlineData("System.Web.OData.Query.Expressions.DerivedProduct/Category/CategoryID eq 123")]
  1277. [InlineData("System.Web.OData.Query.Expressions.DerivedProduct/Category/System.Web.OData.Query.Expressions.DerivedCategory/CategoryID eq 123")]
  1278. public void Inheritance_WithDerivedInstance(string filter)
  1279. {
  1280. var filters = VerifyQueryDeserialization<DerivedProduct>(filter);
  1281. RunFilters<DerivedProduct>(filters,
  1282. new DerivedProduct { Category = new DerivedCategory { CategoryID = 123 }, ProductName = "ProductName", DerivedProductName = "DerivedProductName" },
  1283. new { WithNullPropagation = true, WithoutNullPropagation = true });
  1284. }
  1285. [Theory]
  1286. [InlineData("System.Web.OData.Query.Expressions.DerivedProduct/DerivedProductName eq 'ProductName'")]
  1287. [InlineData("System.Web.OData.Query.Expressions.DerivedProduct/Category/CategoryID eq 123")]
  1288. [InlineData("System.Web.OData.Query.Expressions.DerivedProduct/Category/System.Web.OData.Query.Expressions.DerivedCategory/CategoryID eq 123")]
  1289. public void Inheritance_WithBaseInstance(string filter)
  1290. {
  1291. var filters = VerifyQueryDeserialization<Product>(filter);
  1292. RunFilters<Product>(filters,
  1293. new Product(),
  1294. new { WithNullPropagation = false, WithoutNullPropagation = typeof(NullReferenceException) });
  1295. }
  1296. [Fact]
  1297. public void CastToNonDerivedType_Throws()
  1298. {
  1299. Assert.Throws<ODataException>(
  1300. () => VerifyQueryDeserialization<Product>("System.Web.OData.Query.Expressions.DerivedCategory/CategoryID eq 123"),
  1301. "Encountered invalid type cast. 'System.Web.OData.Query.Expressions.DerivedCategory' is not assignable from 'System.Web.OData.Query.Expressions.Product'.");
  1302. }
  1303. [Theory]
  1304. [InlineData("Edm.Int32 eq 123", "A binary operator with incompatible types was detected. Found operand types 'Edm.String' and 'Edm.Int32' for operator kind 'Equal'.")]
  1305. [InlineData("ProductName/Edm.String eq 123", "Can only bind segments that are Navigation, Structural, Complex, or Collections. We found a segment " +
  1306. "'ProductName' that isn't any of those. Please revise the query.")]
  1307. public void CastToNonEntityType_Throws(string filter, string error)
  1308. {
  1309. Assert.Throws<ODataException>(
  1310. () => VerifyQueryDeserialization<Product>(filter), error);
  1311. }
  1312. [Theory]
  1313. [InlineData("Edm.NonExistentType eq 123")]
  1314. [InlineData("Category/Edm.NonExistentType eq 123")]
  1315. [InlineData("Category/Products/Edm.NonExistentType eq 123")]
  1316. public void CastToNonExistantType_Throws(string filter)
  1317. {
  1318. Assert.Throws<ODataException>(
  1319. () => VerifyQueryDeserialization<Product>(filter),
  1320. "The child type 'Edm.NonExistentType' in a cast was not an entity type. Casts can only be performed on entity types.");
  1321. }
  1322. #endregion
  1323. #region cast in query option
  1324. [Theory]
  1325. [InlineData("cast(null,Edm.Int16) eq null", "$it => (null == null)")]
  1326. [InlineData("cast(null,Edm.Int32) eq 123", "$it => (null == Convert(123))")]
  1327. [InlineData("cast(null,Edm.Int64) ne 123", "$it => (null != Convert(123))")]
  1328. [InlineData("cast(null,Edm.Single) ne 123", "$it => (null != Convert(123))")]
  1329. [InlineData("cast(null,Edm.Double) ne 123", "$it => (null != Convert(123))")]
  1330. [InlineData("cast(null,Edm.Decimal) ne 123", "$it => (null != Convert(123))")]
  1331. [InlineData("cast(null,Edm.Boolean) ne true", "$it => (null != Convert(True))")]
  1332. [InlineData("cast(null,Edm.Byte) ne 1", "$it => (null != Convert(1))")]
  1333. [InlineData("cast(null,Edm.Guid) eq 00000000-0000-0000-0000-000000000000", "$it => (null == Convert(00000000-0000-0000-0000-000000000000))")]
  1334. [InlineData("cast(null,Edm.String) ne '123'", "$it => (null != \"123\")")]
  1335. [InlineData("cast(null,Edm.DateTimeOffset) eq 2001-01-01T12:00:00.000+08:00", "$it => (null == Convert(01/01/2001 12:00:00 +08:00))")]
  1336. [InlineData("cast(null,Edm.Duration) eq duration'P8DT23H59M59.9999S'", "$it => (null == Convert(8.23:59:59.9999000))")]
  1337. [InlineData("cast(null,'Microsoft.TestCommon.Types.SimpleEnum') eq null", "$it => (null == null)")]
  1338. [InlineData("cast(null,'Microsoft.TestCommon.Types.FlagsEnum') eq null", "$it => (null == null)")]
  1339. [InlineData("cast(IntProp,Edm.String) eq '123'", "$it => (Convert($it.IntProp.ToString()) == \"123\")")]
  1340. [InlineData("cast(LongProp,Edm.String) eq '123'", "$it => (Convert($it.LongProp.ToString()) == \"123\")")]
  1341. [InlineData("cast(SingleProp,Edm.String) eq '123'", "$it => (Convert($it.SingleProp.ToString()) == \"123\")")]
  1342. [InlineData("cast(DoubleProp,Edm.String) eq '123'", "$it => (Convert($it.DoubleProp.ToString()) == \"123\")")]
  1343. [InlineData("cast(DecimalProp,Edm.String) eq '123'", "$it => (Convert($it.DecimalProp.ToString()) == \"123\")")]
  1344. [InlineData("cast(BoolProp,Edm.String) eq '123'", "$it => (Convert($it.BoolProp.ToString()) == \"123\")")]
  1345. [InlineData("cast(ByteProp,Edm.String) eq '123'", "$it => (Convert($it.ByteProp.ToString()) == \"123\")")]
  1346. [InlineData("cast(GuidProp,Edm.String) eq '123'", "$it => (Convert($it.GuidProp.ToString()) == \"123\")")]
  1347. [InlineData("cast(StringProp,Edm.String) eq '123'", "$it => (Convert($it.StringProp) == \"123\")")]
  1348. [InlineData("cast(DateTimeOffsetProp,Edm.String) eq '123'", "$it => (Convert($it.DateTimeOffsetProp.ToString()) == \"123\")")]
  1349. [InlineData("cast(TimeSpanProp,Edm.String) eq '123'", "$it => (Convert($it.TimeSpanProp.ToString()) == \"123\")")]
  1350. [InlineData("cast(SimpleEnumProp,Edm.String) eq '123'", "$it => (Convert(Convert($it.SimpleEnumProp).ToString()) == \"123\")")]
  1351. [InlineData("cast(FlagsEnumProp,Edm.String) eq '123'", "$it => (Convert(Convert($it.FlagsEnumProp).ToString()) == \"123\")")]
  1352. [InlineData("cast(LongEnumProp,Edm.String) eq '123'", "$it => (Convert(Convert($it.LongEnumProp).ToString()) == \"123\")")]
  1353. [InlineData("cast(NullableIntProp,Edm.String) eq '123'", "$it => (Convert(IIF($it.NullableIntProp.HasValue, $it.NullableIntProp.Value.ToString(), null)) == \"123\")")]
  1354. [InlineData("cast(NullableIntProp,Edm.String) eq '123'", "$it => (Convert(IIF($it.NullableIntProp.HasValue, $it.NullableIntProp.Value.ToString(), null)) == \"123\")")]
  1355. [InlineData("cast(NullableLongProp,Edm.String) eq '123'", "$it => (Convert(IIF($it.NullableLongProp.HasValue, $it.NullableLongProp.Value.ToString(), null)) == \"123\")")]
  1356. [InlineData("cast(NullableSingleProp,Edm.String) eq '123'", "$it => (Convert(IIF($it.NullableSingleProp.HasValue, $it.NullableSingleProp.Value.ToString(), null)) == \"123\")")]
  1357. [InlineData("cast(NullableDoubleProp,Edm.String) eq '123'", "$it => (Convert(IIF($it.NullableDoubleProp.HasValue, $it.NullableDoubleProp.Value.ToString(), null)) == \"123\")")]
  1358. [InlineData("cast(NullableDecimalProp,Edm.String) eq '123'", "$it => (Convert(IIF($it.NullableDecimalProp.HasValue, $it.NullableDecimalProp.Value.ToString(), null)) == \"123\")")]
  1359. [InlineData("cast(NullableBoolProp,Edm.String) eq '123'", "$it => (Convert(IIF($it.NullableBoolProp.HasValue, $it.NullableBoolProp.Value.ToString(), null)) == \"123\")")]
  1360. [InlineData("cast(NullableByteProp,Edm.String) eq '123'", "$it => (Convert(IIF($it.NullableByteProp.HasValue, $it.NullableByteProp.Value.ToString(), null)) == \"123\")")]
  1361. [InlineData("cast(NullableGuidProp,Edm.String) eq '123'", "$it => (Convert(IIF($it.NullableGuidProp.HasValue, $it.NullableGuidProp.Value.ToString(), null)) == \"123\")")]
  1362. [InlineData("cast(NullableDateTimeOffsetProp,Edm.String) eq '123'", "$it => (Convert(IIF($it.NullableDateTimeOffsetProp.HasValue, $it.NullableDateTimeOffsetProp.Value.ToString(), null)) == \"123\")")]
  1363. [InlineData("cast(NullableTimeSpanProp,Edm.String) eq '123'", "$it => (Convert(IIF($it.NullableTimeSpanProp.HasValue, $it.NullableTimeSpanProp.Value.ToString(), null)) == \"123\")")]
  1364. [InlineData("cast(NullableSimpleEnumProp,Edm.String) eq '123'", "$it => (Convert(IIF($it.NullableSimpleEnumProp.HasValue, Convert($it.NullableSimpleEnumProp.Value).ToString(), null)) == \"123\")")]
  1365. [InlineData("cast(IntProp,Edm.Int64) eq 123", "$it => (Convert($it.IntProp) == 123)")]
  1366. [InlineData("cast(NullableLongProp,Edm.Double) eq 1.23", "$it => (Convert($it.NullableLongProp) == Convert(1.23))")]
  1367. [InlineData("cast(2147483647,Edm.Int16) ne null", "$it => (Convert(Convert(2147483647)) != null)")]
  1368. [InlineData("cast(Microsoft.TestCommon.Types.SimpleEnum'1',Edm.String) eq '1'", "$it => (Convert(Convert(Second).ToString()) == \"1\")")]
  1369. [InlineData("cast(cast(cast(IntProp,Edm.Int64),Edm.Int16),Edm.String) eq '123'", "$it => (Convert(Convert(Convert($it.IntProp)).ToString()) == \"123\")")]
  1370. [InlineData("cast('123',Microsoft.TestCommon.Types.SimpleEnum) ne null", "$it => (Convert(123) != null)")]
  1371. public void CastMethod_Succeeds(string filter, string expectedResult)
  1372. {
  1373. VerifyQueryDeserialization<DataTypes>(
  1374. filter,
  1375. expectedResult,
  1376. NotTesting);
  1377. }
  1378. [Theory]
  1379. [InlineData("cast(NoSuchProperty,Edm.Int32) ne null", "Could not find a property named 'NoSuchProperty' on type 'System.Web.OData.Query.Expressions.DataTypes'.")]
  1380. [InlineData("cast(null,Edm.Unknown) ne null", "The child type 'Edm.Unknown' in a cast was not an entity type. Casts can only be performed on entity types.")]
  1381. public void CastFails_UndefinedSourceOrTarget_Throws(string filter, string errorMessage)
  1382. {
  1383. Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), errorMessage);
  1384. }
  1385. [Theory]
  1386. [InlineData("cast(SimpleEnumProp,Microsoft.TestCommon.Types.SimpleEnum) ne null")]
  1387. [InlineData("cast(FlagsEnumProp,Microsoft.TestCommon.Types.FlagsEnum) ne null")]
  1388. [InlineData("cast(NullableSimpleEnumProp,Microsoft.TestCommon.Types.SimpleEnum) ne null")]
  1389. [InlineData("cast(IntProp,Microsoft.TestCommon.Types.SimpleEnum) ne null")]
  1390. [InlineData("cast(DateTimeOffsetProp,Microsoft.TestCommon.Types.SimpleEnum) ne null")]
  1391. [InlineData("cast(FlagsEnumProp,Edm.Int32) eq 123")]
  1392. [InlineData("cast(NullableSimpleEnumProp,Edm.Guid) ne null")]
  1393. public void CastFails_UnsupportedSourceOrTargetForEnumCast_Throws(string filter)
  1394. {
  1395. // TODO : 1824 Should not throw exception for invalid enum cast in query option.
  1396. Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), "Enumeration type value can only be casted to or from string.");
  1397. }
  1398. [Theory]
  1399. [InlineData("cast(IntProp,Edm.DateTimeOffset) eq null")]
  1400. [InlineData("cast(ByteProp,Edm.Guid) eq null")]
  1401. [InlineData("cast(NullableLongProp,Edm.Duration) eq null")]
  1402. [InlineData("cast(StringProp,Edm.Double) eq null")]
  1403. [InlineData("cast(StringProp,Edm.Int16) eq null")]
  1404. [InlineData("cast(DateTimeOffsetProp,Edm.Int32) eq null")]
  1405. [InlineData("cast(NullableGuidProp,Edm.Int64) eq null")]
  1406. [InlineData("cast(Edm.Int32) eq null")]
  1407. [InlineData("cast($it,Edm.String) eq null")]
  1408. [InlineData("cast(ComplexProp,Edm.Double) eq null")]
  1409. [InlineData("cast(ComplexProp,Edm.String) eq null")]
  1410. [InlineData("cast(StringProp,Microsoft.TestCommon.Types.SimpleEnum) eq null")]
  1411. [InlineData("cast(StringProp,Microsoft.TestCommon.Types.FlagsEnum) eq null")]
  1412. public void CastFails_UnsupportedTarget_ReturnsNull(string filter)
  1413. {
  1414. VerifyQueryDeserialization<DataTypes>(filter, "$it => (null == null)");
  1415. }
  1416. [Theory]
  1417. [InlineData("cast(null,System.Web.OData.Query.Expressions.Address) ne null",
  1418. "Encountered invalid type cast. 'System.Web.OData.Query.Expressions.Address' is not assignable from 'System.Web.OData.Query.Expressions.DataTypes'.")]
  1419. [InlineData("cast(null,System.Web.OData.Query.Expressions.DataTypes) ne null",
  1420. "Cast or IsOf Function must have a type in its arguments.")]
  1421. public void CastFails_NonPrimitiveTarget_Throws(string filter, string expectErrorMessage)
  1422. {
  1423. // TODO : 1827 Should not throw when the target type of cast is not primitive or enumeration type.
  1424. Assert.Throws<ODataException>(() => Bind<DataTypes>(filter), expectErrorMessage);
  1425. }
  1426. [Theory]
  1427. [InlineData("cast(null,'Edm.Int32') ne null")]
  1428. [InlineData("cast(StringProp,'Microsoft.TestCommon.Types.SimpleEnum') eq null")]
  1429. [InlineData("cast(IntProp,'Edm.String') eq '123'")]
  1430. [InlineData("cast('System.Web.OData.Query.Expressions.DataTypes') eq null")]
  1431. [InlineData("cast($it,'System.Web.OData.Query.Expressions.DataTypes') eq null")]
  1432. public void SingleQuotesOnTypeNameOfCast_WorksForNow(string filter)
  1433. {
  1434. // Arrange
  1435. var builder = new ODataConventionModelBuilder();
  1436. builder.EntitySet<DataTypes>("Customers");
  1437. IEdmModel model = builder.GetEdmModel();
  1438. IEdmEntitySet entitySet = model.FindDeclaredEntitySet("Customers");
  1439. IEdmEntityType entityType = entitySet.EntityType();
  1440. var parser = new ODataQueryOptionParser(model, entityType, entitySet,
  1441. new Dictionary<string, string> { { "$filter", filter } });
  1442. // Act & Assert
  1443. // TODO : 1927 ODL parser should throw when there are single quotes on type name of cast.
  1444. Assert.NotNull(parser.ParseFilter());
  1445. }
  1446. [Fact]
  1447. public void SingleQuotesOnEnumTypeNameOfCast_WorksForNow()
  1448. {
  1449. // Arrange
  1450. var builder = new ODataConventionModelBuilder();
  1451. builder.EntitySet<DataTypes>("Customers");
  1452. IEdmModel model = builder.GetEdmModel();
  1453. IEdmEntitySet entitySet = model.FindDeclaredEntitySet("Customers");
  1454. IEdmEntityType entityType = entitySet.EntityType();
  1455. var parser = new ODataQueryOptionParser(model, entityType, entitySet,
  1456. new Dictionary<string, string>
  1457. {
  1458. { "$filter", "cast(StringProp,'Microsoft.TestCommon.Types.SimpleEnum') eq null" }
  1459. });
  1460. // Act
  1461. // TODO : 1927 ODL parser should throw when there are single quotes on type name of cast.
  1462. FilterClause filterClause = parser.ParseFilter();
  1463. // Assert
  1464. Assert.NotNull(filterClause);
  1465. var castNode = Assert.IsType<SingleValueFunctionCallNode>(((BinaryOperatorNode)filterClause.Expression).Left);
  1466. Assert.Equal("cast", castNode.Name);
  1467. Assert.Equal("Microsoft.TestCommon.Types.SimpleEnum", ((ConstantNode)castNode.Parameters.Last()).Value);
  1468. }
  1469. #endregion
  1470. #region parameter alias for filter query option
  1471. [Theory]
  1472. // Parameter alias value is not null.
  1473. [InlineData("IntProp eq @p", "1", "$it => ($it.IntProp == 1)")]
  1474. [InlineData("BoolProp eq @p", "true", "$it => ($it.BoolProp == True)")]
  1475. [InlineData("LongProp eq @p", "-123", "$it => ($it.LongProp == Convert(-123))")]
  1476. [InlineData("FloatProp eq @p", "1.23", "$it => ($it.FloatProp == 1.23)")]
  1477. [InlineData("DoubleProp eq @p", "4.56", "$it => ($it.DoubleProp == Convert(4.56))")]
  1478. [InlineData("StringProp eq @p", "'abc'", "$it => ($it.StringProp == \"abc\")")]
  1479. [InlineData("DateTimeOffsetProp eq @p", "2001-01-01T12:00:00.000+08:00", "$it => ($it.DateTimeOffsetProp == 01/01/2001 12:00:00 +08:00)")]
  1480. [InlineData("TimeSpanProp eq @p", "duration'P8DT23H59M59.9999S'", "$it => ($it.TimeSpanProp == 8.23:59:59.9999000)")]
  1481. [InlineData("GuidProp eq @p", "00000000-0000-0000-0000-000000000000", "$it => ($it.GuidProp == 00000000-0000-0000-0000-000000000000)")]
  1482. [InlineData("SimpleEnumProp eq @p", "Microsoft.TestCommon.Types.SimpleEnum'First'", "$it => (Convert($it.SimpleEnumProp) == 0)")]
  1483. // Parameter alias value is null.
  1484. [InlineData("NullableIntProp eq @p", "null", "$it => ($it.NullableIntProp == null)")]
  1485. [InlineData("NullableBoolProp eq @p", "null", "$it => ($it.NullableBoolProp == null)")]
  1486. [InlineData("NullableLongProp eq @p", "null", "$it => ($it.NullableLongProp == null)")]
  1487. [InlineData("NullableSingleProp eq @p", "null", "$it => ($it.NullableSingleProp == null)")]
  1488. [InlineData("NullableDoubleProp eq @p", "null", "$it => ($it.NullableDoubleProp == null)")]
  1489. [InlineData("StringProp eq @p", "null", "$it => ($it.StringProp == null)")]
  1490. [InlineData("NullableDateTimeOffsetProp eq @p", "null", "$it => ($it.NullableDateTimeOffsetProp == null)")]
  1491. [InlineData("NullableTimeSpanProp eq @p", "null", "$it => ($it.NullableTimeSpanProp == null)")]
  1492. [InlineData("NullableGuidProp eq @p", "null", "$it => ($it.NullableGuidProp == null)")]
  1493. [InlineData("NullableSimpleEnumProp eq @p", "null", "$it => (Convert($it.NullableSimpleEnumProp) == null)")]
  1494. // Parameter alias value is property.
  1495. [InlineData("@p eq 1", "IntProp", "$it => ($it.IntProp == 1)")]
  1496. [InlineData("@p eq true", "NullableBoolProp", "$it => ($it.NullableBoolProp == Convert(True))")]
  1497. [InlineData("@p eq -123", "LongProp", "$it => ($it.LongProp == -123)")]
  1498. [InlineData("@p eq 1.23", "FloatProp", "$it => ($it.FloatProp == 1.23)")]
  1499. [InlineData("@p eq 4.56", "NullableDoubleProp", "$it => ($it.NullableDoubleProp == Convert(4.56))")]
  1500. [InlineData("@p eq 'abc'", "StringProp", "$it => ($it.StringProp == \"abc\")")]
  1501. [InlineData("@p eq 2001-01-01T12:00:00.000+08:00", "DateTimeOffsetProp", "$it => ($it.DateTimeOffsetProp == 01/01/2001 12:00:00 +08:00)")]
  1502. [InlineData("@p eq duration'P8DT23H59M59.9999S'", "TimeSpanProp", "$it => ($it.TimeSpanProp == 8.23:59:59.9999000)")]
  1503. [InlineData("@p eq 00000000-0000-0000-0000-000000000000", "GuidProp", "$it => ($it.GuidProp == 00000000-0000-0000-0000-000000000000)")]
  1504. [InlineData("@p eq Microsoft.TestCommon.Types.SimpleEnum'First'", "SimpleEnumProp", "$it => (Convert($it.SimpleEnumProp) == 0)")]
  1505. // Parameter alias value has built-in functions.
  1506. [InlineData("@p eq 'abc'", "substring(StringProp,5)", "$it => ($it.StringProp.Substring(5) == \"abc\")")]
  1507. [InlineData("2 eq @p", "IntProp add 1", "$it => (2 == ($it.IntProp + 1))")]
  1508. [InlineData("EntityProp/AlternateAddresses/all(a: a/City ne @p)", "'abc'", "$it => $it.EntityProp.AlternateAddresses.All(a => (a.City != \"abc\"))")]
  1509. public void ParameterAlias_Succeeds(string filter, string parameterAliasValue, string expectedResult)
  1510. {
  1511. // Arrange
  1512. IEdmModel model = GetModel<DataTypes>();
  1513. IEdmType targetEdmType = model.FindType("System.Web.OData.Query.Expressions.DataTypes");
  1514. IEdmNavigationSource targetNavigationSource = model.FindDeclaredEntitySet("System.Web.OData.Query.Expressions.Products");
  1515. IDictionary<string, string> queryOptions = new Dictionary<string, string> { { "$filter", filter } };
  1516. queryOptions.Add("@p", parameterAliasValue);
  1517. ODataQueryOptionParser parser = new ODataQueryOptionParser(model, targetEdmType, targetNavigationSource, queryOptions);
  1518. FilterClause filterClause = new FilterQueryOption(filter, new ODataQueryContext(model, typeof(DataTypes)), parser).FilterClause;
  1519. // Act
  1520. Expression actualExpression = FilterBinder.Bind(
  1521. filterClause,
  1522. typeof(DataTypes),
  1523. model,
  1524. CreateFakeAssembliesResolver(),
  1525. new ODataQuerySettings { HandleNullPropagation = HandleNullPropagationOption.False });
  1526. // Assert
  1527. VerifyExpression(actualExpression, expectedResult);
  1528. }
  1529. [Theory]
  1530. [InlineData("NullableIntProp eq @p", "$it => ($it.NullableIntProp == null)")]
  1531. [InlineData("NullableBoolProp eq @p", "$it => ($it.NullableBoolProp == null)")]
  1532. [InlineData("NullableDoubleProp eq @p", "$it => ($it.NullableDoubleProp == null)")]
  1533. [InlineData("StringProp eq @p", "$it => ($it.StringProp == null)")]
  1534. [InlineData("NullableDateTimeOffsetProp eq @p", "$it => ($it.NullableDateTimeOffsetProp == null)")]
  1535. [InlineData("NullableSimpleEnumProp eq @p", "$it => (Convert($it.NullableSimpleEnumProp) == null)")]
  1536. [InlineData("EntityProp/AlternateAddresses/any(a: a/City eq @p)", "$it => $it.EntityProp.AlternateAddresses.Any(a => (a.City == null))")]
  1537. public void ParameterAlias_AssumedToBeNull_ValueNotFound(string filter, string expectedResult)
  1538. {
  1539. // Arrange
  1540. IEdmModel model = GetModel<DataTypes>();
  1541. IEdmType targetEdmType = model.FindType("System.Web.OData.Query.Expressions.DataTypes");
  1542. IEdmNavigationSource targetNavigationSource = model.FindDeclaredEntitySet("System.Web.OData.Query.Expressions.Products");
  1543. IDictionary<string, string> queryOptions = new Dictionary<string, string> { { "$filter", filter } };
  1544. ODataQueryOptionParser parser = new ODataQueryOptionParser(model, targetEdmType, targetNavigationSource, queryOptions);
  1545. FilterClause filterClause = new FilterQueryOption(filter, new ODataQueryContext(model, typeof(DataTypes)), parser).FilterClause;
  1546. // Act
  1547. Expression actualExpression = FilterBinder.Bind(
  1548. filterClause,
  1549. typeof(DataTypes),
  1550. model,
  1551. CreateFakeAssembliesResolver(),
  1552. new ODataQuerySettings { HandleNullPropagation = HandleNullPropagationOption.False });
  1553. // Assert
  1554. VerifyExpression(actualExpression, expectedResult);
  1555. }
  1556. [Fact]
  1557. public void ParameterAlias_NestedCase_Succeeds()
  1558. {
  1559. // Arrange
  1560. IEdmModel model = GetModel<DataTypes>();
  1561. IEdmType targetEdmType = model.FindType("System.Web.OData.Query.Expressions.DataTypes");
  1562. IEdmNavigationSource targetNavigationSource = model.FindDeclaredEntitySet("System.Web.OData.Query.Expressions.Products");
  1563. ODataQueryOptionParser parser = new ODataQueryOptionParser(
  1564. model,
  1565. targetEdmType,
  1566. targetNavigationSource,
  1567. new Dictionary<string, string> { { "$filter", "IntProp eq @p1" }, { "@p1", "@p2" }, { "@p2", "123" } });
  1568. FilterClause filterClause = new FilterQueryOption("IntProp eq @p1", new ODataQueryContext(model, typeof(DataTypes)), parser).FilterClause;
  1569. // Act
  1570. Expression actualExpression = FilterBinder.Bind(
  1571. filterClause,
  1572. typeof(DataTypes),
  1573. model,
  1574. CreateFakeAssembliesResolver(),
  1575. new ODataQuerySettings { HandleNullPropagation = HandleNullPropagationOption.False });
  1576. // Assert
  1577. VerifyExpression(actualExpression, "$it => ($it.IntProp == 123)");
  1578. }
  1579. [Fact]
  1580. public void ParameterAlias_Throws_NotStartWithAt()
  1581. {
  1582. // Arrange
  1583. IEdmModel model = GetModel<DataTypes>();
  1584. IEdmType targetEdmType = model.FindType("System.Web.OData.Query.Expressions.DataTypes");
  1585. IEdmNavigationSource targetNavigationSource = model.FindDeclaredEntitySet("System.Web.OData.Query.Expressions.Products");
  1586. ODataQueryOptionParser parser = new ODataQueryOptionParser(
  1587. model,
  1588. targetEdmType,
  1589. targetNavigationSource,
  1590. new Dictionary<string, string> { { "$filter", "IntProp eq #p" }, { "#p", "123" } });
  1591. // Act & Assert
  1592. Assert.Throws<ODataException>(
  1593. () => parser.ParseFilter(),
  1594. "Syntax error: character '#' is not valid at position 11 in 'IntProp eq #p'.");
  1595. }
  1596. #endregion
  1597. [Theory]
  1598. [InlineData("UShortProp eq 12", "$it => (Convert($it.UShortProp) == 12)")]
  1599. [InlineData("ULongProp eq 12L", "$it => (Convert($it.ULongProp) == 12)")]
  1600. [InlineData("UIntProp eq 12", "$it => (Convert($it.UIntProp) == 12)")]
  1601. [InlineData("CharProp eq 'a'", "$it => (Convert($it.CharProp.ToString()) == \"a\")")]
  1602. [InlineData("CharArrayProp eq 'a'", "$it => (new String($it.CharArrayProp) == \"a\")")]
  1603. [InlineData("BinaryProp eq binary'TWFu'", "$it => ($it.BinaryProp.ToArray() == System.Byte[])")]
  1604. [InlineData("XElementProp eq '<name />'", "$it => ($it.XElementProp.ToString() == \"<name />\")")]
  1605. public void NonstandardEdmPrimtives(string filter, string expression)
  1606. {
  1607. var filters = VerifyQueryDeserialization<DataTypes>(filter, expression, NotTesting);
  1608. RunFilters(filters,
  1609. new DataTypes
  1610. {
  1611. UShortProp = 12,
  1612. ULongProp = 12,
  1613. UIntProp = 12,
  1614. CharProp = 'a',
  1615. CharArrayProp = new[] { 'a' },
  1616. BinaryProp = new Binary(new byte[] { 77, 97, 110 }),
  1617. XElementProp = new XElement("name")
  1618. },
  1619. new { WithNullPropagation = true, WithoutNullPropagation = true });
  1620. }
  1621. [Theory]
  1622. [InlineData("BinaryProp eq binary'I6v/'", "$it => ($it.BinaryProp.ToArray() == System.Byte[])", true, true)]
  1623. [InlineData("BinaryProp ne binary'I6v/'", "$it => ($it.BinaryProp.ToArray() != System.Byte[])", false, false)]
  1624. [InlineData("ByteArrayProp eq binary'I6v/'", "$it => ($it.ByteArrayProp == System.Byte[])", true, true)]
  1625. [InlineData("ByteArrayProp ne binary'I6v/'", "$it => ($it.ByteArrayProp != System.Byte[])", false, false)]
  1626. [InlineData("binary'I6v/' eq binary'I6v/'", "$it => (System.Byte[] == System.Byte[])", true, true)]
  1627. [InlineData("binary'I6v/' ne binary'I6v/'", "$it => (System.Byte[] != System.Byte[])", false, false)]
  1628. [InlineData("ByteArrayPropWithNullValue ne binary'I6v/'", "$it => ($it.ByteArrayPropWithNullValue != System.Byte[])", true, true)]
  1629. [InlineData("ByteArrayPropWithNullValue ne ByteArrayPropWithNullValue", "$it => ($it.ByteArrayPropWithNullValue != $it.ByteArrayPropWithNullValue)", false, false)]
  1630. [InlineData("ByteArrayPropWithNullValue ne null", "$it => ($it.ByteArrayPropWithNullValue != null)", false, false)]
  1631. [InlineData("ByteArrayPropWithNullValue eq null", "$it => ($it.ByteArrayPropWithNullValue == null)", true, true)]
  1632. [InlineData("null ne ByteArrayPropWithNullValue", "$it => (null != $it.ByteArrayPropWithNullValue)", false, false)]
  1633. [InlineData("null eq ByteArrayPropWithNullValue", "$it => (null == $it.ByteArrayPropWithNullValue)", true, true)]
  1634. public void ByteArrayComparisons(string filter, string expression, bool withNullPropagation, object withoutNullPropagation)
  1635. {
  1636. var filters = VerifyQueryDeserialization<DataTypes>(filter, expression, NotTesting);
  1637. RunFilters(filters,
  1638. new DataTypes
  1639. {
  1640. BinaryProp = new Binary(new byte[] { 35, 171, 255 }),
  1641. ByteArrayProp = new byte[] { 35, 171, 255 }
  1642. },
  1643. new { WithNullPropagation = withNullPropagation, WithoutNullPropagation = withoutNullPropagation });
  1644. }
  1645. [Theory]
  1646. [InlineData("binary'AP8Q' ge binary'AP8Q'", "GreaterThanOrEqual")]
  1647. [InlineData("binary'AP8Q' le binary'AP8Q'", "LessThanOrEqual")]
  1648. [InlineData("binary'AP8Q' lt binary'AP8Q'", "LessThan")]
  1649. [InlineData("binary'AP8Q' gt binary'AP8Q'", "GreaterThan")]
  1650. [InlineData("binary'AP8Q' add binary'AP8Q'", "Add")]
  1651. [InlineData("binary'AP8Q' sub binary'AP8Q'", "Subtract")]
  1652. [InlineData("binary'AP8Q' mul binary'AP8Q'", "Multiply")]
  1653. [InlineData("binary'AP8Q' div binary'AP8Q'", "Divide")]
  1654. public void DisAllowed_ByteArrayComparisons(string filter, string op)
  1655. {
  1656. Assert.Throws<ODataException>(
  1657. () => Bind<DataTypes>(filter),
  1658. String.Format(CultureInfo.InvariantCulture, "A binary operator with incompatible types was detected. Found operand types 'Edm.Binary' and 'Edm.Binary' for operator kind '{0}'.", op));
  1659. }
  1660. [Theory]
  1661. [InlineData("NullableUShortProp eq 12", "$it => (Convert($it.NullableUShortProp.Value) == Convert(12))")]
  1662. [InlineData("NullableULongProp eq 12L", "$it => (Convert($it.NullableULongProp.Value) == Convert(12))")]
  1663. [InlineData("NullableUIntProp eq 12", "$it => (Convert($it.NullableUIntProp.Value) == Convert(12))")]
  1664. [InlineData("NullableCharProp eq 'a'", "$it => ($it.NullableCharProp.Value.ToString() == \"a\")")]
  1665. public void Nullable_NonstandardEdmPrimitives(string filter, string expression)
  1666. {
  1667. var filters = VerifyQueryDeserialization<DataTypes>(filter, expression, NotTesting);
  1668. RunFilters(filters,
  1669. new DataTypes(),
  1670. new { WithNullPropagation = false, WithoutNullPropagation = typeof(InvalidOperationException) });
  1671. }
  1672. [Fact]
  1673. public void MultipleConstants_Are_Parameterized()
  1674. {
  1675. VerifyQueryDeserialization("ProductName eq '1' or ProductName eq '2' or ProductName eq '3' or ProductName eq '4'",
  1676. "$it => (((($it.ProductName == \"1\") OrElse ($it.ProductName == \"2\")) OrElse ($it.ProductName == \"3\")) OrElse ($it.ProductName == \"4\"))",
  1677. NotTesting);
  1678. }
  1679. [Fact]
  1680. public void Constants_Are_Not_Parameterized_IfDisabled()
  1681. {
  1682. var filters = VerifyQueryDeserialization("ProductName eq '1'", settingsCustomizer: (settings) =>
  1683. {
  1684. settings.EnableConstantParameterization = false;
  1685. });
  1686. Assert.Equal("$it => ($it.ProductName == \"1\")", (filters.WithoutNullPropagation as Expression).ToString());
  1687. }
  1688. #region Negative Tests
  1689. [Fact]
  1690. public void TypeMismatchInComparison()
  1691. {
  1692. Assert.Throws<ODataException>(() => Bind("length(123) eq 12"));
  1693. }
  1694. #endregion
  1695. private Expression<Func<Product, bool>> Bind(string filter, ODataQuerySettings querySettings = null)
  1696. {
  1697. return Bind<Product>(filter, querySettings);
  1698. }
  1699. private Expression<Func<T, bool>> Bind<T>(string filter, ODataQuerySettings querySettings = null) where T : class
  1700. {
  1701. IEdmModel model = GetModel<T>();
  1702. FilterClause filterNode = CreateFilterNode(filter, model, typeof(T));
  1703. if (querySettings == null)
  1704. {
  1705. querySettings = CreateSettings();
  1706. }
  1707. return Bind<T>(filterNode, model, CreateFakeAssembliesResolver(), querySettings);
  1708. }
  1709. private static Expression<Func<TEntityType, bool>> Bind<TEntityType>(FilterClause filterNode, IEdmModel model, IAssembliesResolver assembliesResolver, ODataQuerySettings querySettings)
  1710. {
  1711. return FilterBinder.Bind<TEntityType>(filterNode, model, assembliesResolver, querySettings);
  1712. }
  1713. private IAssembliesResolver CreateFakeAssembliesResolver()
  1714. {
  1715. return new NoAssembliesResolver();
  1716. }
  1717. private FilterClause CreateFilterNode(string filter, IEdmModel model, Type entityType)
  1718. {
  1719. IEdmEntityType productType = model.SchemaElements.OfType<IEdmEntityType>().Single(t => t.Name == entityType.Name);
  1720. Assert.NotNull(productType); // Guard
  1721. IEdmEntitySet products = model.EntityContainer.FindEntitySet("Products");
  1722. Assert.NotNull(products); // Guard
  1723. ODataQueryOptionParser parser = new ODataQueryOptionParser(model, productType, products,
  1724. new Dictionary<string, string> { { "$filter", filter } });
  1725. return parser.ParseFilter();
  1726. }
  1727. private static ODataQuerySettings CreateSettings()
  1728. {
  1729. return new ODataQuerySettings
  1730. {
  1731. HandleNullPropagation = HandleNullPropagationOption.False // A value other than Default is required for calls to Bind.
  1732. };
  1733. }
  1734. private void RunFilters<T>(dynamic filters, T product, dynamic expectedValue)
  1735. {
  1736. var filterWithNullPropagation = filters.WithNullPropagation as Expression<Func<T, bool>>;
  1737. if (expectedValue.WithNullPropagation is Type)
  1738. {
  1739. Assert.Throws(expectedValue.WithNullPropagation as Type, () => RunFilter(filterWithNullPropagation, product));
  1740. }
  1741. else
  1742. {
  1743. Assert.Equal(RunFilter(filterWithNullPropagation, product), expectedValue.WithNullPropagation);
  1744. }
  1745. var filterWithoutNullPropagation = filters.WithoutNullPropagation as Expression<Func<T, bool>>;
  1746. if (expectedValue.WithoutNullPropagation is Type)
  1747. {
  1748. Assert.Throws(expectedValue.WithoutNullPropagation as Type, () => RunFilter(filterWithoutNullPropagation, product));
  1749. }
  1750. else
  1751. {
  1752. Assert.Equal(RunFilter(filterWithoutNullPropagation, product), expectedValue.WithoutNullPropagation);
  1753. }
  1754. }
  1755. private bool RunFilter<T>(Expression<Func<T, bool>> filter, T instance)
  1756. {
  1757. return filter.Compile().Invoke(instance);
  1758. }
  1759. private dynamic VerifyQueryDeserialization(string filter, string expectedResult = null, string expectedResultWithNullPropagation = null, Action<ODataQuerySettings> settingsCustomizer = null)
  1760. {
  1761. return VerifyQueryDeserialization<Product>(filter, expectedResult, expectedResultWithNullPropagation, settingsCustomizer);
  1762. }
  1763. private dynamic VerifyQueryDeserialization<T>(string filter, string expectedResult = null, string expectedResultWithNullPropagation = null, Action<ODataQuerySettings> settingsCustomizer = null) where T : class
  1764. {
  1765. IEdmModel model = GetModel<T>();
  1766. FilterClause filterNode = CreateFilterNode(filter, model, typeof(T));
  1767. IAssembliesResolver assembliesResolver = CreateFakeAssembliesResolver();
  1768. Func<ODataQuerySettings, ODataQuerySettings> customizeSettings = (settings) =>
  1769. {
  1770. if (settingsCustomizer != null)
  1771. {
  1772. settingsCustomizer.Invoke(settings);
  1773. }
  1774. return settings;
  1775. };
  1776. var filterExpr = Bind<T>(
  1777. filterNode,
  1778. model,
  1779. assembliesResolver,
  1780. customizeSettings(new ODataQuerySettings { HandleNullPropagation = HandleNullPropagationOption.False }));
  1781. if (!String.IsNullOrEmpty(expectedResult))
  1782. {
  1783. VerifyExpression(filterExpr, expectedResult);
  1784. }
  1785. expectedResultWithNullPropagation = expectedResultWithNullPropagation ?? expectedResult;
  1786. var filterExprWithNullPropagation = Bind<T>(
  1787. filterNode,
  1788. model,
  1789. assembliesResolver,
  1790. customizeSettings(new ODataQuerySettings { HandleNullPropagation = HandleNullPropagationOption.True }));
  1791. if (!String.IsNullOrEmpty(expectedResultWithNullPropagation))
  1792. {
  1793. VerifyExpression(filterExprWithNullPropagation, expectedResultWithNullPropagation ?? expectedResult);
  1794. }
  1795. return new
  1796. {
  1797. WithNullPropagation = filterExprWithNullPropagation,
  1798. WithoutNullPropagation = filterExpr
  1799. };
  1800. }
  1801. private void VerifyExpression(Expression filter, string expectedExpression)
  1802. {
  1803. // strip off the beginning part of the expression to get to the first
  1804. // actual query operator
  1805. string resultExpression = ExpressionStringBuilder.ToString(filter);
  1806. Assert.True(resultExpression == expectedExpression,
  1807. String.Format("Expected expression '{0}' but the deserializer produced '{1}'", expectedExpression, resultExpression));
  1808. }
  1809. private IEdmModel GetModel<T>() where T : class
  1810. {
  1811. Type key = typeof(T);
  1812. IEdmModel value;
  1813. if (!_modelCache.TryGetValue(key, out value))
  1814. {
  1815. ODataModelBuilder model = new ODataConventionModelBuilder();
  1816. model.EntitySet<T>("Products");
  1817. value = _modelCache[key] = model.GetEdmModel();
  1818. }
  1819. return value;
  1820. }
  1821. private T? ToNullable<T>(object value) where T : struct
  1822. {
  1823. return value == null ? null : (T?)Convert.ChangeType(value, typeof(T));
  1824. }
  1825. private class NoAssembliesResolver : IAssembliesResolver
  1826. {
  1827. public ICollection<Assembly> GetAssemblies()
  1828. {
  1829. return new Assembly[0];
  1830. }
  1831. }
  1832. }
  1833. }