//---------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// @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;
///
/// Provides eSQL text Parsing and Compilation services.
///
///
/// 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:
///
/// - Translation from eSQL text commands to valid s
/// - Translation from eSQL text commands to valid s
///
/// 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 or
/// 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.
///
///
///
///
///
///
internal static class CqlQuery
{
///
/// Compiles an eSQL command producing a validated .
///
/// eSQL command text
/// perspective
/// parser options
/// ordinary parameters
///
/// A parse result with the command tree produced by parsing the given command.
/// Thrown when Syntatic or Semantic rules are violated and the query cannot be accepted
/// Thrown when metadata related service requests fail
/// Thrown when mapping related service requests fail
///
/// This method is not thread safe.
///
///
///
internal static ParseResult Compile(string commandText,
Perspective perspective,
ParserOptions parserOptions,
IEnumerable 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;
}
///
/// Compiles an eSQL query command producing a validated .
///
/// eSQL query command text
/// perspective
/// parser options
/// ordinary command parameters
/// command free variables
/// The query expression tree produced by parsing the given query command.
/// Thrown when Syntatic or Semantic rules are violated and the query expression cannot be accepted
/// Thrown when metadata related service requests fail
/// Thrown when mapping related service requests fail
///
/// This method is not thread safe.
///
///
///
internal static DbLambda CompileQueryCommandLambda(string queryCommandText,
Perspective perspective,
ParserOptions parserOptions,
IEnumerable parameters,
IEnumerable 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
///
/// Parse eSQL command string into an AST
///
/// eSQL command
/// parser options
/// Ast
/// Thrown when Syntatic or Semantic rules are violated and the query cannot be accepted
///
/// This method is not thread safe.
///
///
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(string commandText,
Perspective perspective,
ParserOptions parserOptions,
Func 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;
}
///
/// Performs semantic conversion, validation on a command AST and creates a
///
/// Abstract Syntax Tree of the command
/// perspective
/// parser options
/// ordinary command parameters
/// a parse result with a valid command tree
/// Parameters name/types must be bound before invoking this method
/// Thrown when Syntatic or Semantic rules are violated and the query cannot be accepted.
/// Thrown as inner exception of a EntityException when metadata related service requests fail.
/// Thrown as inner exception of a EntityException when mapping related service requests fail.
///
/// This method is not thread safe.
///
///
///
private static ParseResult AnalyzeCommandSemantics(AST.Node astExpr,
Perspective perspective,
ParserOptions parserOptions,
IEnumerable 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;
}
///
/// Performs semantic conversion, validation on a query command AST and creates a
///
/// Abstract Syntax Tree of the query command
/// perspective
/// parser options
/// ordinary command parameters
/// command free variables
/// Parameters name/types must be bound before invoking this method
/// Thrown when Syntatic or Semantic rules are violated and the query cannot be accepted.
/// Thrown as inner exception of a EntityException when metadata related service requests fail.
/// Thrown as inner exception of a EntityException when mapping related service requests fail.
///
/// This method is not thread safe.
///
///
///
private static DbLambda AnalyzeQueryExpressionSemantics(AST.Node astQueryCommand,
Perspective perspective,
ParserOptions parserOptions,
IEnumerable parameters,
IEnumerable 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(AST.Node astExpr,
Perspective perspective,
ParserOptions parserOptions,
IEnumerable parameters,
IEnumerable variables,
Func 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
}
}