1 //Copyright 2010 Microsoft Corporation 2 // 3 //Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 4 //You may obtain a copy of the License at 5 // 6 //http://www.apache.org/licenses/LICENSE-2.0 7 // 8 //Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 9 //"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 //See the License for the specific language governing permissions and limitations under the License. 11 12 13 namespace System.Data.Services.Client 14 { 15 #region Namespaces. 16 17 using System; 18 using System.Collections; 19 using System.Collections.Generic; 20 using System.Diagnostics; 21 using System.Linq; 22 using System.Linq.Expressions; 23 using System.Text; 24 25 #endregion Namespaces. 26 27 internal class UriWriter : DataServiceExpressionVisitor 28 { 29 private readonly DataServiceContext context; 30 31 private readonly StringBuilder uriBuilder; 32 33 private Version uriVersion; 34 35 private ResourceSetExpression leafResourceSet; 36 UriWriter(DataServiceContext context)37 private UriWriter(DataServiceContext context) 38 { 39 Debug.Assert(context != null, "context != null"); 40 this.context = context; 41 this.uriBuilder = new StringBuilder(); 42 this.uriVersion = Util.DataServiceVersion1; 43 } 44 Translate(DataServiceContext context, bool addTrailingParens, Expression e, out Uri uri, out Version version)45 internal static void Translate(DataServiceContext context, bool addTrailingParens, Expression e, out Uri uri, out Version version) 46 { 47 var writer = new UriWriter(context); 48 writer.leafResourceSet = addTrailingParens ? (e as ResourceSetExpression) : null; 49 writer.Visit(e); 50 uri = Util.CreateUri(context.BaseUriWithSlash, Util.CreateUri(writer.uriBuilder.ToString(), UriKind.Relative)); 51 version = writer.uriVersion; 52 } 53 VisitMethodCall(MethodCallExpression m)54 internal override Expression VisitMethodCall(MethodCallExpression m) 55 { 56 throw Error.MethodNotSupported(m); 57 } 58 VisitUnary(UnaryExpression u)59 internal override Expression VisitUnary(UnaryExpression u) 60 { 61 throw new NotSupportedException(Strings.ALinq_UnaryNotSupported(u.NodeType.ToString())); 62 } 63 VisitBinary(BinaryExpression b)64 internal override Expression VisitBinary(BinaryExpression b) 65 { 66 throw new NotSupportedException(Strings.ALinq_BinaryNotSupported(b.NodeType.ToString())); 67 } 68 VisitConstant(ConstantExpression c)69 internal override Expression VisitConstant(ConstantExpression c) 70 { 71 throw new NotSupportedException(Strings.ALinq_ConstantNotSupported(c.Value)); 72 } 73 VisitTypeIs(TypeBinaryExpression b)74 internal override Expression VisitTypeIs(TypeBinaryExpression b) 75 { 76 throw new NotSupportedException(Strings.ALinq_TypeBinaryNotSupported); 77 } 78 VisitConditional(ConditionalExpression c)79 internal override Expression VisitConditional(ConditionalExpression c) 80 { 81 throw new NotSupportedException(Strings.ALinq_ConditionalNotSupported); 82 } 83 VisitParameter(ParameterExpression p)84 internal override Expression VisitParameter(ParameterExpression p) 85 { 86 throw new NotSupportedException(Strings.ALinq_ParameterNotSupported); 87 } 88 VisitMemberAccess(MemberExpression m)89 internal override Expression VisitMemberAccess(MemberExpression m) 90 { 91 throw new NotSupportedException(Strings.ALinq_MemberAccessNotSupported(m.Member.Name)); 92 } 93 VisitLambda(LambdaExpression lambda)94 internal override Expression VisitLambda(LambdaExpression lambda) 95 { 96 throw new NotSupportedException(Strings.ALinq_LambdaNotSupported); 97 } 98 VisitNew(NewExpression nex)99 internal override NewExpression VisitNew(NewExpression nex) 100 { 101 throw new NotSupportedException(Strings.ALinq_NewNotSupported); 102 } 103 VisitMemberInit(MemberInitExpression init)104 internal override Expression VisitMemberInit(MemberInitExpression init) 105 { 106 throw new NotSupportedException(Strings.ALinq_MemberInitNotSupported); 107 } 108 VisitListInit(ListInitExpression init)109 internal override Expression VisitListInit(ListInitExpression init) 110 { 111 throw new NotSupportedException(Strings.ALinq_ListInitNotSupported); 112 } 113 VisitNewArray(NewArrayExpression na)114 internal override Expression VisitNewArray(NewArrayExpression na) 115 { 116 throw new NotSupportedException(Strings.ALinq_NewArrayNotSupported); 117 } 118 VisitInvocation(InvocationExpression iv)119 internal override Expression VisitInvocation(InvocationExpression iv) 120 { 121 throw new NotSupportedException(Strings.ALinq_InvocationNotSupported); 122 } 123 VisitNavigationPropertySingletonExpression(NavigationPropertySingletonExpression npse)124 internal override Expression VisitNavigationPropertySingletonExpression(NavigationPropertySingletonExpression npse) 125 { 126 this.Visit(npse.Source); 127 this.uriBuilder.Append(UriHelper.FORWARDSLASH).Append(this.ExpressionToString(npse.MemberExpression)); 128 this.VisitQueryOptions(npse); 129 return npse; 130 } 131 VisitResourceSetExpression(ResourceSetExpression rse)132 internal override Expression VisitResourceSetExpression(ResourceSetExpression rse) 133 { 134 if ((ResourceExpressionType)rse.NodeType == ResourceExpressionType.ResourceNavigationProperty) 135 { 136 this.Visit(rse.Source); 137 this.uriBuilder.Append(UriHelper.FORWARDSLASH).Append(this.ExpressionToString(rse.MemberExpression)); 138 } 139 else 140 { 141 this.uriBuilder.Append(UriHelper.FORWARDSLASH).Append((string)((ConstantExpression)rse.MemberExpression).Value); 142 } 143 144 if (rse.KeyPredicate != null) 145 { 146 this.uriBuilder.Append(UriHelper.LEFTPAREN); 147 if (rse.KeyPredicate.Count == 1) 148 { 149 this.uriBuilder.Append(this.ExpressionToString(rse.KeyPredicate.Values.First())); 150 } 151 else 152 { 153 bool addComma = false; 154 foreach (var kvp in rse.KeyPredicate) 155 { 156 if (addComma) 157 { 158 this.uriBuilder.Append(UriHelper.COMMA); 159 } 160 161 this.uriBuilder.Append(kvp.Key.Name); 162 this.uriBuilder.Append(UriHelper.EQUALSSIGN); 163 this.uriBuilder.Append(this.ExpressionToString(kvp.Value)); 164 addComma = true; 165 } 166 } 167 168 this.uriBuilder.Append(UriHelper.RIGHTPAREN); 169 } 170 else if (rse == this.leafResourceSet) 171 { 172 this.uriBuilder.Append(UriHelper.LEFTPAREN); 173 this.uriBuilder.Append(UriHelper.RIGHTPAREN); 174 } 175 176 if (rse.CountOption == CountOption.ValueOnly) 177 { 178 this.uriBuilder.Append(UriHelper.FORWARDSLASH).Append(UriHelper.DOLLARSIGN).Append(UriHelper.COUNT); 179 this.EnsureMinimumVersion(2, 0); 180 } 181 182 this.VisitQueryOptions(rse); 183 return rse; 184 } 185 VisitQueryOptions(ResourceExpression re)186 internal void VisitQueryOptions(ResourceExpression re) 187 { 188 bool needAmpersand = false; 189 190 if (re.HasQueryOptions) 191 { 192 this.uriBuilder.Append(UriHelper.QUESTIONMARK); 193 194 ResourceSetExpression rse = re as ResourceSetExpression; 195 if (rse != null) 196 { 197 IEnumerator options = rse.SequenceQueryOptions.GetEnumerator(); 198 while (options.MoveNext()) 199 { 200 if (needAmpersand) 201 { 202 this.uriBuilder.Append(UriHelper.AMPERSAND); 203 } 204 205 Expression e = ((Expression)options.Current); 206 ResourceExpressionType et = (ResourceExpressionType)e.NodeType; 207 switch (et) 208 { 209 case ResourceExpressionType.SkipQueryOption: 210 this.VisitQueryOptionExpression((SkipQueryOptionExpression)e); 211 break; 212 case ResourceExpressionType.TakeQueryOption: 213 this.VisitQueryOptionExpression((TakeQueryOptionExpression)e); 214 break; 215 case ResourceExpressionType.OrderByQueryOption: 216 this.VisitQueryOptionExpression((OrderByQueryOptionExpression)e); 217 break; 218 case ResourceExpressionType.FilterQueryOption: 219 this.VisitQueryOptionExpression((FilterQueryOptionExpression)e); 220 break; 221 default: 222 Debug.Assert(false, "Unexpected expression type " + (int)et); 223 break; 224 } 225 226 needAmpersand = true; 227 } 228 } 229 230 if (re.ExpandPaths.Count > 0) 231 { 232 if (needAmpersand) 233 { 234 this.uriBuilder.Append(UriHelper.AMPERSAND); 235 } 236 237 this.VisitExpandOptions(re.ExpandPaths); 238 needAmpersand = true; 239 } 240 241 if (re.Projection != null && re.Projection.Paths.Count > 0) 242 { 243 if (needAmpersand) 244 { 245 this.uriBuilder.Append(UriHelper.AMPERSAND); 246 } 247 248 this.VisitProjectionPaths(re.Projection.Paths); 249 needAmpersand = true; 250 } 251 252 if (re.CountOption == CountOption.InlineAll) 253 { 254 if (needAmpersand) 255 { 256 this.uriBuilder.Append(UriHelper.AMPERSAND); 257 } 258 259 this.VisitCountOptions(); 260 needAmpersand = true; 261 } 262 263 if (re.CustomQueryOptions.Count > 0) 264 { 265 if (needAmpersand) 266 { 267 this.uriBuilder.Append(UriHelper.AMPERSAND); 268 } 269 270 this.VisitCustomQueryOptions(re.CustomQueryOptions); 271 needAmpersand = true; 272 } 273 } 274 } 275 VisitQueryOptionExpression(SkipQueryOptionExpression sqoe)276 internal void VisitQueryOptionExpression(SkipQueryOptionExpression sqoe) 277 { 278 this.uriBuilder.Append(UriHelper.DOLLARSIGN); 279 this.uriBuilder.Append(UriHelper.OPTIONSKIP); 280 this.uriBuilder.Append(UriHelper.EQUALSSIGN); 281 this.uriBuilder.Append(this.ExpressionToString(sqoe.SkipAmount)); 282 } 283 VisitQueryOptionExpression(TakeQueryOptionExpression tqoe)284 internal void VisitQueryOptionExpression(TakeQueryOptionExpression tqoe) 285 { 286 this.uriBuilder.Append(UriHelper.DOLLARSIGN); 287 this.uriBuilder.Append(UriHelper.OPTIONTOP); 288 this.uriBuilder.Append(UriHelper.EQUALSSIGN); 289 this.uriBuilder.Append(this.ExpressionToString(tqoe.TakeAmount)); 290 } 291 VisitQueryOptionExpression(FilterQueryOptionExpression fqoe)292 internal void VisitQueryOptionExpression(FilterQueryOptionExpression fqoe) 293 { 294 this.uriBuilder.Append(UriHelper.DOLLARSIGN); 295 this.uriBuilder.Append(UriHelper.OPTIONFILTER); 296 this.uriBuilder.Append(UriHelper.EQUALSSIGN); 297 this.uriBuilder.Append(this.ExpressionToString(fqoe.Predicate)); 298 } 299 VisitQueryOptionExpression(OrderByQueryOptionExpression oboe)300 internal void VisitQueryOptionExpression(OrderByQueryOptionExpression oboe) 301 { 302 this.uriBuilder.Append(UriHelper.DOLLARSIGN); 303 this.uriBuilder.Append(UriHelper.OPTIONORDERBY); 304 this.uriBuilder.Append(UriHelper.EQUALSSIGN); 305 306 int ii = 0; 307 while (true) 308 { 309 var selector = oboe.Selectors[ii]; 310 311 this.uriBuilder.Append(this.ExpressionToString(selector.Expression)); 312 if (selector.Descending) 313 { 314 this.uriBuilder.Append(UriHelper.SPACE); 315 this.uriBuilder.Append(UriHelper.OPTIONDESC); 316 } 317 318 if (++ii == oboe.Selectors.Count) 319 { 320 break; 321 } 322 323 this.uriBuilder.Append(UriHelper.COMMA); 324 } 325 } 326 VisitExpandOptions(List<string> paths)327 internal void VisitExpandOptions(List<string> paths) 328 { 329 this.uriBuilder.Append(UriHelper.DOLLARSIGN); 330 this.uriBuilder.Append(UriHelper.OPTIONEXPAND); 331 this.uriBuilder.Append(UriHelper.EQUALSSIGN); 332 333 int ii = 0; 334 while (true) 335 { 336 this.uriBuilder.Append(paths[ii]); 337 338 if (++ii == paths.Count) 339 { 340 break; 341 } 342 343 this.uriBuilder.Append(UriHelper.COMMA); 344 } 345 } 346 VisitProjectionPaths(List<string> paths)347 internal void VisitProjectionPaths(List<string> paths) 348 { 349 this.uriBuilder.Append(UriHelper.DOLLARSIGN); 350 this.uriBuilder.Append(UriHelper.OPTIONSELECT); 351 this.uriBuilder.Append(UriHelper.EQUALSSIGN); 352 353 int ii = 0; 354 while (true) 355 { 356 string path = paths[ii]; 357 358 this.uriBuilder.Append(path); 359 360 if (++ii == paths.Count) 361 { 362 break; 363 } 364 365 this.uriBuilder.Append(UriHelper.COMMA); 366 } 367 368 this.EnsureMinimumVersion(2, 0); 369 } 370 VisitCountOptions()371 internal void VisitCountOptions() 372 { 373 this.uriBuilder.Append(UriHelper.DOLLARSIGN); 374 this.uriBuilder.Append(UriHelper.OPTIONCOUNT); 375 this.uriBuilder.Append(UriHelper.EQUALSSIGN); 376 this.uriBuilder.Append(UriHelper.COUNTALL); 377 this.EnsureMinimumVersion(2, 0); 378 } 379 VisitCustomQueryOptions(Dictionary<ConstantExpression, ConstantExpression> options)380 internal void VisitCustomQueryOptions(Dictionary<ConstantExpression, ConstantExpression> options) 381 { 382 List<ConstantExpression> keys = options.Keys.ToList(); 383 List<ConstantExpression> values = options.Values.ToList(); 384 385 int ii = 0; 386 while (true) 387 { 388 this.uriBuilder.Append(keys[ii].Value); 389 this.uriBuilder.Append(UriHelper.EQUALSSIGN); 390 this.uriBuilder.Append(values[ii].Value); 391 392 if (keys[ii].Value.ToString().Equals(UriHelper.DOLLARSIGN + UriHelper.OPTIONCOUNT, StringComparison.OrdinalIgnoreCase)) 393 { 394 this.EnsureMinimumVersion(2, 0); 395 } 396 397 if (++ii == keys.Count) 398 { 399 break; 400 } 401 402 this.uriBuilder.Append(UriHelper.AMPERSAND); 403 } 404 } 405 ExpressionToString(Expression expression)406 private string ExpressionToString(Expression expression) 407 { 408 return ExpressionWriter.ExpressionToString(this.context, expression); 409 } 410 EnsureMinimumVersion(int major, int minor)411 private void EnsureMinimumVersion(int major, int minor) 412 { 413 if (major > this.uriVersion.Major || 414 (major == this.uriVersion.Major && minor > this.uriVersion.Minor)) 415 { 416 this.uriVersion = new Version(major, minor); 417 } 418 } 419 } 420 } 421