|
//---------------------------------------------------------------------
// <copyright file="CqlQuery.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
namespace System.Data.Common.EntitySql
{
using System;
using System.Collections.Generic;
using System.Data.Common.CommandTrees;
using System.Data.Metadata.Edm;
using System.Diagnostics;
/// <summary>
/// Provides eSQL text Parsing and Compilation services.
/// </summary>
/// <remarks>
/// This class exposes services that perform syntactic and semantic analysis of eSQL commands.
/// The syntactic validation ensures the given command conforms to eSQL formal grammar. The semantic analysis will
/// perform (list not exhaustive): type resolution and validation, ensure semantic and scoping rules, etc.
/// The services exposed by this class are:
/// <list>
/// <item>Translation from eSQL text commands to valid <see cref="DbCommandTree"/>s</item>
/// <item>Translation from eSQL text commands to valid <see cref="DbExpression"/>s</item>
/// </list>
/// Queries can be formulated in O-Space, C-Space and S-Space and the services exposed by this class are agnostic of the especific typespace or
/// metadata instance passed as required parameter in the semantic analysis by the perspective parameter. It is assumed that the perspective and
/// metadata was properly initialized.
/// Provided that the command is syntacticaly correct and meaningful within the given typespace, the result will be a valid <see cref="DbCommandTree"/> or
/// <see cref="DbExpression"/> otherwise EntityException will be thrown indicating the reason(s) why the given command cannot be accepted.
/// It is also possible that MetadataException and MappingException be thrown if mapping or metadata related problems are encountered during compilation.
/// </remarks>
/// <list>
/// <item><seealso cref="ParserOptions"/></item>
/// <item><seealso cref="DbCommandTree"/></item>
/// <item><seealso cref="DbExpression"/></item>
/// </list>
internal static class CqlQuery
{
/// <summary>
/// Compiles an eSQL command producing a validated <see cref="DbCommandTree"/>.
/// </summary>
/// <param name="commandText">eSQL command text</param>
/// <param name="perspective">perspective</param>
/// <param name="parserOptions">parser options<seealso cref="ParserOptions"/></param>
/// <param name="parameters">ordinary parameters</param>
/// <param name="parseResult"></param>
/// <returns>A parse result with the command tree produced by parsing the given command.</returns>
/// <exception cref="System.Data.EntityException">Thrown when Syntatic or Semantic rules are violated and the query cannot be accepted</exception>
/// <exception cref="System.Data.MetadataException">Thrown when metadata related service requests fail</exception>
/// <exception cref="System.Data.MappingException">Thrown when mapping related service requests fail</exception>
/// <remarks>
/// This method is not thread safe.
/// </remarks>
/// <seealso cref="ParserOptions"/>
/// <seealso cref="DbCommandTree"/>
internal static ParseResult Compile(string commandText,
Perspective perspective,
ParserOptions parserOptions,
IEnumerable<DbParameterReferenceExpression> parameters)
{
ParseResult result = CompileCommon(commandText, perspective, parserOptions,
(astCommand, validatedParserOptions) =>
{
var parseResultInternal = AnalyzeCommandSemantics(astCommand, perspective, validatedParserOptions, parameters);
Debug.Assert(parseResultInternal != null, "parseResultInternal != null post-condition FAILED");
Debug.Assert(parseResultInternal.CommandTree != null, "parseResultInternal.CommandTree != null post-condition FAILED");
TypeHelpers.AssertEdmType(parseResultInternal.CommandTree);
return parseResultInternal;
});
return result;
}
/// <summary>
/// Compiles an eSQL query command producing a validated <see cref="DbLambda"/>.
/// </summary>
/// <param name="queryCommandText">eSQL query command text</param>
/// <param name="perspective">perspective</param>
/// <param name="parserOptions">parser options<seealso cref="ParserOptions"/></param>
/// <param name="parameters">ordinary command parameters</param>
/// <param name="variables">command free variables</param>
/// <returns>The query expression tree produced by parsing the given query command.</returns>
/// <exception cref="System.Data.EntityException">Thrown when Syntatic or Semantic rules are violated and the query expression cannot be accepted</exception>
/// <exception cref="System.Data.MetadataException">Thrown when metadata related service requests fail</exception>
/// <exception cref="System.Data.MappingException">Thrown when mapping related service requests fail</exception>
/// <remarks>
/// This method is not thread safe.
/// </remarks>
/// <seealso cref="ParserOptions"/>
/// <seealso cref="DbExpression"/>
internal static DbLambda CompileQueryCommandLambda(string queryCommandText,
Perspective perspective,
ParserOptions parserOptions,
IEnumerable<DbParameterReferenceExpression> parameters,
IEnumerable<DbVariableReferenceExpression> variables)
{
return CompileCommon(queryCommandText, perspective, parserOptions, (astCommand, validatedParserOptions) =>
{
DbLambda lambda = AnalyzeQueryExpressionSemantics(astCommand,
perspective,
validatedParserOptions,
parameters,
variables);
TypeHelpers.AssertEdmType(lambda.Body.ResultType);
Debug.Assert(lambda != null, "lambda != null post-condition FAILED");
return lambda;
});
}
#region Private
/// <summary>
/// Parse eSQL command string into an AST
/// </summary>
/// <param name="commandText">eSQL command</param>
/// <param name="parserOptions">parser options<seealso cref="ParserOptions"/></param>
/// <returns>Ast</returns>
/// <exception cref="System.Data.EntityException">Thrown when Syntatic or Semantic rules are violated and the query cannot be accepted</exception>
/// <remarks>
/// This method is not thread safe.
/// </remarks>
/// <seealso cref="ParserOptions"/>
private static AST.Node Parse(string commandText, ParserOptions parserOptions)
{
AST.Node astExpr = null;
//
// commandText and parserOptions are validated inside of CqlParser
//
//
// Create Parser
//
CqlParser cqlParser = new CqlParser(parserOptions, true);
//
// Invoke parser
//
astExpr = cqlParser.Parse(commandText);
if (null == astExpr)
{
throw EntityUtil.EntitySqlError(commandText, System.Data.Entity.Strings.InvalidEmptyQuery, 0);
}
return astExpr;
}
private static TResult CompileCommon<TResult>(string commandText,
Perspective perspective,
ParserOptions parserOptions,
Func<AST.Node, ParserOptions, TResult> compilationFunction)
where TResult : class
{
TResult result = null;
//
// Validate arguments
//
EntityUtil.CheckArgumentNull(perspective, "commandText");
EntityUtil.CheckArgumentNull(perspective, "perspective");
//
// Validate parser options - if null, give default options
//
parserOptions = parserOptions ?? new ParserOptions();
//
// Invoke Parser
//
AST.Node astCommand = Parse(commandText, parserOptions);
//
// Perform Semantic Analysis/Conversion
//
result = compilationFunction(astCommand, parserOptions);
return result;
}
/// <summary>
/// Performs semantic conversion, validation on a command AST and creates a <see cref="DbCommandTree"/>
/// </summary>
/// <param name="astExpr">Abstract Syntax Tree of the command</param>
/// <param name="perspective">perspective</param>
/// <param name="parserOptions">parser options<seealso cref="ParserOptions"/></param>
/// <param name="parameters">ordinary command parameters</param>
/// <returns>a parse result with a valid command tree</returns>
/// <remarks>Parameters name/types must be bound before invoking this method</remarks>
/// <exception cref="System.Data.EntityException">Thrown when Syntatic or Semantic rules are violated and the query cannot be accepted.</exception>
/// <exception cref="System.Data.MetadataException">Thrown as inner exception of a EntityException when metadata related service requests fail.</exception>
/// <exception cref="System.Data.MappingException">Thrown as inner exception of a EntityException when mapping related service requests fail.</exception>
/// <remarks>
/// This method is not thread safe.
/// </remarks>
/// <seealso cref="ParserOptions"/>
/// <seealso cref="DbCommandTree"/>
private static ParseResult AnalyzeCommandSemantics(AST.Node astExpr,
Perspective perspective,
ParserOptions parserOptions,
IEnumerable<DbParameterReferenceExpression> parameters)
{
ParseResult result = AnalyzeSemanticsCommon(astExpr, perspective, parserOptions, parameters, null/*variables*/,
(analyzer, astExpression) =>
{
var parseResultInternal = analyzer.AnalyzeCommand(astExpression);
Debug.Assert(parseResultInternal != null, "parseResultInternal != null post-condition FAILED");
Debug.Assert(parseResultInternal.CommandTree != null, "parseResultInternal.CommandTree != null post-condition FAILED");
return parseResultInternal;
});
return result;
}
/// <summary>
/// Performs semantic conversion, validation on a query command AST and creates a <see cref="DbLambda"/>
/// </summary>
/// <param name="astQueryCommand">Abstract Syntax Tree of the query command</param>
/// <param name="perspective">perspective</param>
/// <param name="parserOptions">parser options<seealso cref="ParserOptions"/></param>
/// <param name="parameters">ordinary command parameters</param>
/// <param name="variables">command free variables</param>
/// <remarks>Parameters name/types must be bound before invoking this method</remarks>
/// <exception cref="System.Data.EntityException">Thrown when Syntatic or Semantic rules are violated and the query cannot be accepted.</exception>
/// <exception cref="System.Data.MetadataException">Thrown as inner exception of a EntityException when metadata related service requests fail.</exception>
/// <exception cref="System.Data.MappingException">Thrown as inner exception of a EntityException when mapping related service requests fail.</exception>
/// <remarks>
/// This method is not thread safe.
/// </remarks>
/// <seealso cref="ParserOptions"/>
/// <seealso cref="DbExpression"/>
private static DbLambda AnalyzeQueryExpressionSemantics(AST.Node astQueryCommand,
Perspective perspective,
ParserOptions parserOptions,
IEnumerable<DbParameterReferenceExpression> parameters,
IEnumerable<DbVariableReferenceExpression> variables)
{
return AnalyzeSemanticsCommon(
astQueryCommand,
perspective,
parserOptions,
parameters,
variables,
(analyzer, astExpr) =>
{
DbLambda lambda = analyzer.AnalyzeQueryCommand(astExpr);
Debug.Assert(null != lambda, "null != lambda post-condition FAILED");
return lambda;
});
}
private static TResult AnalyzeSemanticsCommon<TResult>(AST.Node astExpr,
Perspective perspective,
ParserOptions parserOptions,
IEnumerable<DbParameterReferenceExpression> parameters,
IEnumerable<DbVariableReferenceExpression> variables,
Func<SemanticAnalyzer, AST.Node, TResult> analysisFunction)
where TResult : class
{
TResult result = null;
try
{
//
// Validate arguments
//
EntityUtil.CheckArgumentNull(astExpr, "astExpr");
EntityUtil.CheckArgumentNull(perspective, "perspective");
//
// Invoke semantic analysis
//
SemanticAnalyzer analyzer = (new SemanticAnalyzer(SemanticResolver.Create(perspective, parserOptions, parameters, variables)));
result = analysisFunction(analyzer, astExpr);
}
//
// Wrap MetadataException as EntityException inner exception
//
catch (System.Data.MetadataException metadataException)
{
throw EntityUtil.EntitySqlError(System.Data.Entity.Strings.GeneralExceptionAsQueryInnerException("Metadata"), metadataException);
}
//
// Wrap MappingException as EntityException inner exception
//
catch (System.Data.MappingException mappingException)
{
throw EntityUtil.EntitySqlError(System.Data.Entity.Strings.GeneralExceptionAsQueryInnerException("Mapping"), mappingException);
}
return result;
}
#endregion
}
}
|