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