diff --git a/.gitignore b/.gitignore
index 3e759b7..989fa36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,9 @@
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+# Project specific ignores
+**/*.private.*
+
# User-specific files
*.suo
*.user
diff --git a/README.md b/README.md
index b81a84e..ba0239d 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,54 @@
+# openCypher Transpiler
-# Contributing
+
+This library help you to build an [openCypher](http://www.opencypher.org/) query layer on top of a relational database or structured data in data lakes. Built on top of this library, you can transpile openCypher query into a target query language used by the relational database. We have provided a sample target language renderer for [T-SQL](https://docs.microsoft.com/en-us/sql/t-sql/language-reference?view=sql-server-2017).
+
+Originally we built this library to provide a [Property Graph](https://neo4j.com/developer/graph-database/#property-graph) data-layer for the petabyte-scale data assets in the Azure Data Lake. The property graph enables us to organize a vast catalog of data in an intuitive and traceable way. The openCypher query language allows data users that are already familiar with SQL-alike declarative query language to query the data assets with ease: in a graph where the relationship between data entities is a first class citizen, the data users no longer need to spend time on figuring out how individual data assets should be joined together. The data producers who are experts on their data would be the one to help define the relationship and is able to ensure its quality for downstream consumptions.
+
+This library has three main components:
+
+* A openCypher parser built on top of ANTLR4 and official openCypher grammar to parse and create an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) to abstract the syntactical structure of the graph query;
+* A logic planner transforms the AST into relational query logical plan similar to the [Relational Algebra](https://en.wikipedia.org/wiki/Relational_algebra);
+* A query code renderer produces the actual query code from the logical plan. In this repository, we provides a T-SQL renderer.
+
+The library, written in [.Net Core](https://dotnet.microsoft.com/download), is cross-platform.
+
+
+## Using the library
+
+```CSharp
+// To Be Provided
+```
+
+
+## Build on top of this library
+
+```CSharp
+// To Be Provided
+```
+
+
+## Test designs
+
+Transpiler is tested using the T-SQL target renderer and its results are compared against what is produced by Cypher from the [Neo4j Graph Database](https://neo4j.com/graph-database). Each test compares the query results from the transpiled query on [Microsoft SQL for Linux](https://www.microsoft.com/en-us/sql-server/sql-server-2017) against Cypher on Neo4j 3.x to ensure they are consistent in term of data type and contents.
+
+To run the tests, simply run under the project root folder:
+```batch
+dotnet test
+```
+
+
+## Limitations To Be Addressed
+
+* MATCH pattern with unspecified labels which cannot be implied to a single label/relationship type
+* MATCH pattern with variable-length relationship
+* The logical plan is currently not further optimized with assumption the underlying RDBMS engine does this work
+* list,collect,UNWIND is currently not supported
+* Source table must already been normalized to be used as graph node/edge
+* Readonly query is supported. CALL and write (such as MERGE) is not supported.
+
+
+## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
diff --git a/oCTranspiler.sln b/oCTranspiler.sln
new file mode 100644
index 0000000..11b6127
--- /dev/null
+++ b/oCTranspiler.sln
@@ -0,0 +1,72 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28307.572
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "openCypherParser", "src\openCypherParser\openCypherParser.csproj", "{81AECE85-4C3D-4921-93F3-0619B4691B9E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Common", "src\Common\Common.csproj", "{7BAC3CBC-9B05-4E3D-A530-F909E2D1BE95}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LogicalPlanner", "src\LogicalPlanner\LogicalPlanner.csproj", "{ECA217C3-9DAF-48CF-93B3-43B150D06434}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "openCypherParser.Test", "tests\openCypherParser.Test\openCypherParser.Test.csproj", "{1B8A8206-1D57-483E-8F62-C9E0E3DEC2A1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonTest", "tests\CommonTest\CommonTest.csproj", "{D61781A2-CEAC-45E5-A16F-C07C76AB5B58}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LogicalPlanner.Test", "tests\LogicalPlanner.Test\LogicalPlanner.Test.csproj", "{9FE97D48-A6A2-40B2-B373-BB6FC9E03989}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SQLRenderer", "src\SQLRenderer\SQLRenderer.csproj", "{6B944CB5-0E6E-41A8-A19B-CB9AC23FA65A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E43CA3A8-7668-47D1-ABD1-07A3F2497C59}"
+ ProjectSection(SolutionItems) = preProject
+ README.md = README.md
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLRenderer.Test", "tests\SQLRenderer.Test\SQLRenderer.Test.csproj", "{A4874F73-84FF-4633-925D-BB80823FF8CD}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {81AECE85-4C3D-4921-93F3-0619B4691B9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {81AECE85-4C3D-4921-93F3-0619B4691B9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {81AECE85-4C3D-4921-93F3-0619B4691B9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {81AECE85-4C3D-4921-93F3-0619B4691B9E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7BAC3CBC-9B05-4E3D-A530-F909E2D1BE95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7BAC3CBC-9B05-4E3D-A530-F909E2D1BE95}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7BAC3CBC-9B05-4E3D-A530-F909E2D1BE95}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7BAC3CBC-9B05-4E3D-A530-F909E2D1BE95}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ECA217C3-9DAF-48CF-93B3-43B150D06434}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ECA217C3-9DAF-48CF-93B3-43B150D06434}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ECA217C3-9DAF-48CF-93B3-43B150D06434}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ECA217C3-9DAF-48CF-93B3-43B150D06434}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1B8A8206-1D57-483E-8F62-C9E0E3DEC2A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1B8A8206-1D57-483E-8F62-C9E0E3DEC2A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1B8A8206-1D57-483E-8F62-C9E0E3DEC2A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1B8A8206-1D57-483E-8F62-C9E0E3DEC2A1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D61781A2-CEAC-45E5-A16F-C07C76AB5B58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D61781A2-CEAC-45E5-A16F-C07C76AB5B58}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D61781A2-CEAC-45E5-A16F-C07C76AB5B58}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D61781A2-CEAC-45E5-A16F-C07C76AB5B58}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9FE97D48-A6A2-40B2-B373-BB6FC9E03989}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9FE97D48-A6A2-40B2-B373-BB6FC9E03989}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9FE97D48-A6A2-40B2-B373-BB6FC9E03989}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9FE97D48-A6A2-40B2-B373-BB6FC9E03989}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6B944CB5-0E6E-41A8-A19B-CB9AC23FA65A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B944CB5-0E6E-41A8-A19B-CB9AC23FA65A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6B944CB5-0E6E-41A8-A19B-CB9AC23FA65A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6B944CB5-0E6E-41A8-A19B-CB9AC23FA65A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A4874F73-84FF-4633-925D-BB80823FF8CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A4874F73-84FF-4633-925D-BB80823FF8CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A4874F73-84FF-4633-925D-BB80823FF8CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A4874F73-84FF-4633-925D-BB80823FF8CD}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {927270C0-4539-450F-B889-E8FF08FDAE54}
+ EndGlobalSection
+EndGlobal
diff --git a/src/Common/Common.csproj b/src/Common/Common.csproj
new file mode 100644
index 0000000..735a003
--- /dev/null
+++ b/src/Common/Common.csproj
@@ -0,0 +1,14 @@
+
+
+
+ netcoreapp2.1
+ Microsoft.GraphPlatform.Transpiler.Common
+
+
+
+
+
+
+
+
+
diff --git a/src/Common/Exceptions/TranspilerBindingException.cs b/src/Common/Exceptions/TranspilerBindingException.cs
new file mode 100644
index 0000000..a9d92fe
--- /dev/null
+++ b/src/Common/Exceptions/TranspilerBindingException.cs
@@ -0,0 +1,19 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+namespace openCypherTranspiler.Common.Exceptions
+{
+ ///
+ /// Exception during data source binding
+ ///
+ public class TranspilerBindingException : TranspilerException
+ {
+ public TranspilerBindingException(string bindingErrorMsg) :
+ base($"Data binding error error: {bindingErrorMsg}")
+ {
+
+ }
+ }
+}
diff --git a/src/Common/Exceptions/TranspilerException.cs b/src/Common/Exceptions/TranspilerException.cs
new file mode 100644
index 0000000..d10cd2e
--- /dev/null
+++ b/src/Common/Exceptions/TranspilerException.cs
@@ -0,0 +1,21 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+using System;
+
+namespace openCypherTranspiler.Common.Exceptions
+{
+ ///
+ /// General exception thrown by the Transpiler
+ ///
+ public abstract class TranspilerException : Exception
+ {
+ public TranspilerException(string exceptionMsg) :
+ base(exceptionMsg)
+ {
+
+ }
+ }
+}
diff --git a/src/Common/Exceptions/TranspilerInternalErrorException.cs b/src/Common/Exceptions/TranspilerInternalErrorException.cs
new file mode 100644
index 0000000..9e35da7
--- /dev/null
+++ b/src/Common/Exceptions/TranspilerInternalErrorException.cs
@@ -0,0 +1,18 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+
+namespace openCypherTranspiler.Common.Exceptions
+{
+ ///
+ /// Exception representing unexpected internal errors thrown by the transpiler
+ ///
+ public class TranspilerInternalErrorException : TranspilerException
+ {
+ public TranspilerInternalErrorException(string intErrMsg) : base($"Unexpected internal error: {intErrMsg}")
+ {
+ }
+ }
+}
diff --git a/src/Common/Exceptions/TranspilerNotSupportedException.cs b/src/Common/Exceptions/TranspilerNotSupportedException.cs
new file mode 100644
index 0000000..bb5f4f4
--- /dev/null
+++ b/src/Common/Exceptions/TranspilerNotSupportedException.cs
@@ -0,0 +1,21 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+using System;
+
+namespace openCypherTranspiler.Common.Exceptions
+{
+ ///
+ /// Specific exception thrown by scenarios that were explicitly blocked
+ ///
+ public class TranspilerNotSupportedException : Exception
+ {
+ public TranspilerNotSupportedException(string functionNotSupported) :
+ base($"{functionNotSupported} is not supported by the code generator.")
+ {
+
+ }
+ }
+}
diff --git a/src/Common/Exceptions/TranspilerSyntaxErrorException.cs b/src/Common/Exceptions/TranspilerSyntaxErrorException.cs
new file mode 100644
index 0000000..1667ef2
--- /dev/null
+++ b/src/Common/Exceptions/TranspilerSyntaxErrorException.cs
@@ -0,0 +1,21 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+using System;
+
+namespace openCypherTranspiler.Common.Exceptions
+{
+ ///
+ /// Exceptions raised from language syntax error
+ ///
+ public class TranspilerSyntaxErrorException : Exception
+ {
+ public TranspilerSyntaxErrorException(string syntaxErrorMsg) :
+ base($"Syntax error: {syntaxErrorMsg}")
+ {
+
+ }
+ }
+}
diff --git a/src/Common/GraphSchema/EdgeSchema.cs b/src/Common/GraphSchema/EdgeSchema.cs
new file mode 100644
index 0000000..06f15c3
--- /dev/null
+++ b/src/Common/GraphSchema/EdgeSchema.cs
@@ -0,0 +1,44 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+
+namespace openCypherTranspiler.Common.GraphSchema
+{
+ public class EdgeSchema : EntitySchema
+ {
+ public static char Separator = '@';
+
+ public static string GetEdgeId(string verb, string nodeFromName, string nodeToName)
+ {
+ return $"{nodeFromName}{Separator}{verb}{Separator}{nodeToName}";
+ }
+
+ ///
+ /// The identifier of edge
+ ///
+ public override string Id
+ {
+ get
+ {
+ return GetEdgeId(Name, SourceNodeId, SinkNodeId);
+ }
+ }
+
+ public EntityProperty SourceIdProperty { get; set; }
+
+ public EntityProperty SinkIdProperty { get; set; }
+
+ ///
+ /// The identifier of the source node schema this edge schema links to
+ ///
+ public string SourceNodeId { get; set; }
+
+ ///
+ /// The identifier of the sink node schema this edge schema links to
+ ///
+ public string SinkNodeId { get; set; }
+
+ }
+}
diff --git a/src/Common/GraphSchema/EntityProperty.cs b/src/Common/GraphSchema/EntityProperty.cs
new file mode 100644
index 0000000..9047b8d
--- /dev/null
+++ b/src/Common/GraphSchema/EntityProperty.cs
@@ -0,0 +1,32 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+
+using System;
+
+namespace openCypherTranspiler.Common.GraphSchema
+{
+ public class EntityProperty
+ {
+ public enum PropertyDefinitionType
+ {
+ NodeJoinKey,
+ SourceNodeJoinKey,
+ SinkNodeJoinKey,
+ RegularProperty,
+ Label,
+ SourceNodeLabel,
+ SinkNodeLabel,
+ NodeSampleKey,
+ SourceNodeSampleKey,
+ SinkNodeSampleKey
+ };
+
+ public string PropertyName { get; set; }
+ public Type DataType { get; set; }
+ public PropertyDefinitionType PropertyType { get; set; }
+
+ }
+}
diff --git a/src/Common/GraphSchema/EntitySchema.cs b/src/Common/GraphSchema/EntitySchema.cs
new file mode 100644
index 0000000..04c3c74
--- /dev/null
+++ b/src/Common/GraphSchema/EntitySchema.cs
@@ -0,0 +1,32 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+
+using System.Collections.Generic;
+
+namespace openCypherTranspiler.Common.GraphSchema
+{
+ ///
+ /// Base class to descript a graph schema entity (can be a node or edge)
+ ///
+ public abstract class EntitySchema
+ {
+ ///
+ /// Unique name to the node/edge. For node it is nodename, for edge it is unique
+ /// combination of node from/to plus edge verb
+ ///
+ public abstract string Id { get; }
+
+ ///
+ /// Name of the node or edge. In case of edge it is the 'verb' only
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// List of all properties on the node/edge (combining all parts)
+ ///
+ public IList Properties { get; set; }
+ }
+}
diff --git a/src/Common/GraphSchema/IGraphSchemaProvider.cs b/src/Common/GraphSchema/IGraphSchemaProvider.cs
new file mode 100644
index 0000000..df34b25
--- /dev/null
+++ b/src/Common/GraphSchema/IGraphSchemaProvider.cs
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace openCypherTranspiler.Common.GraphSchema
+{
+ ///
+ /// Implements by the Graph Schema provider to allow query the graph definition
+ ///
+ public interface IGraphSchemaProvider
+ {
+ ///
+ /// Return a NodeDefinition for the given node name.
+ ///
+ ///
+ /// if node name was not found
+ ///
+ NodeSchema GetNodeDefinition(string nodeName);
+
+ ///
+ /// Return a NodeDefinition for the given edge verb, source and sink entity name.
+ ///
+ ///
+ ///
+ ///
+ /// if node name was not found
+ ///
+ EdgeSchema GetEdgeDefinition(string edgeVerb, string fromNodeName, string toNodeName);
+ }
+}
diff --git a/src/Common/GraphSchema/NodeSchema.cs b/src/Common/GraphSchema/NodeSchema.cs
new file mode 100644
index 0000000..d4bc7bc
--- /dev/null
+++ b/src/Common/GraphSchema/NodeSchema.cs
@@ -0,0 +1,21 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+
+namespace openCypherTranspiler.Common.GraphSchema
+{
+ public class NodeSchema : EntitySchema
+ {
+ public override string Id
+ {
+ get
+ {
+ return Name;
+ }
+ }
+ public EntityProperty NodeIdProperty { get; set; }
+ }
+}
+
diff --git a/src/Common/Logging/BaseLogger.cs b/src/Common/Logging/BaseLogger.cs
new file mode 100644
index 0000000..caf0a5c
--- /dev/null
+++ b/src/Common/Logging/BaseLogger.cs
@@ -0,0 +1,99 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+
+namespace openCypherTranspiler.Common.Logging
+{
+ public abstract class BaseLogger : ILoggable
+ {
+ protected LoggingLevel _currentLogLevel = LoggingLevel.Normal;
+
+ public void SetLoggingLevel(LoggingLevel logLevel)
+ {
+ _currentLogLevel = logLevel;
+ }
+
+ public void Log(string msgFormat, params object[] msgArgs)
+ {
+ if (_currentLogLevel >= LoggingLevel.Normal)
+ {
+ LogMessage(msgFormat, msgArgs);
+ }
+ }
+
+ public void LogCritical(string msgFormat, params object[] msgArgs)
+ {
+ if (_currentLogLevel >= LoggingLevel.CriticalOnly)
+ {
+ LogMessage(msgFormat, msgArgs);
+ }
+ }
+
+ public void LogVerbose(string msgFormat, params object[] msgArgs)
+ {
+ if (_currentLogLevel >= LoggingLevel.Verbose)
+ {
+ LogMessage(msgFormat, msgArgs);
+ }
+ }
+
+ public void LogCritical(string msg)
+ {
+ LogCritical("{0}", msg);
+ }
+
+ public void Log(string msg)
+ {
+ Log("{0}", msg);
+ }
+
+ public void LogVerbose(string msg)
+ {
+ LogVerbose("{0}", msg);
+ }
+
+ public void LogFuncVerbose(
+ string msg,
+ [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
+ [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
+ [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0
+ )
+ {
+ if (_currentLogLevel >= LoggingLevel.Verbose)
+ {
+ LogMessage("{0}: {1}", memberName, msg);
+ }
+ }
+
+ public void LogFunc(
+ string msg,
+ [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
+ [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
+ [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0
+ )
+ {
+ if (_currentLogLevel >= LoggingLevel.Normal)
+ {
+ LogMessage("{0}: {1}", memberName, msg);
+ }
+ }
+
+ public void LogFuncCritical(string msg,
+ [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
+ [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
+ [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0
+ )
+ {
+ LogMessage("{0}: {1}", memberName, msg);
+ }
+
+ ///
+ /// This is the only method that child logger class need to implement
+ ///
+ ///
+ ///
+ abstract protected void LogMessage(string msgFormat, params object[] msgArgs);
+ }
+}
diff --git a/src/Common/Logging/ILoggable.cs b/src/Common/Logging/ILoggable.cs
new file mode 100644
index 0000000..8dea8e9
--- /dev/null
+++ b/src/Common/Logging/ILoggable.cs
@@ -0,0 +1,52 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+
+namespace openCypherTranspiler.Common.Logging
+{
+ public enum LoggingLevel
+ {
+ CriticalOnly,
+ Normal,
+ Verbose,
+ }
+
+ ///
+ /// Inteface for the logger used through out this project
+ ///
+ public interface ILoggable
+ {
+ void SetLoggingLevel(LoggingLevel logLevel);
+
+
+ // LogFunc* calls with log the msg with the function name as well
+
+ void LogFuncCritical(
+ string msg,
+ [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
+ [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
+ [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0
+ );
+ void LogFunc(
+ string msg,
+ [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
+ [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
+ [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0
+ );
+ void LogFuncVerbose(
+ string msg,
+ [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
+ [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
+ [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0
+ );
+
+ void LogCritical(string msg);
+ void LogCritical(string msgFormat, params object[] msgArgs);
+ void Log(string msg);
+ void Log(string msgFormat, params object[] msgArgs);
+ void LogVerbose(string msg);
+ void LogVerbose(string msgFormat, params object[] msgArgs);
+ }
+}
diff --git a/src/Common/Utils/FNVHash.cs b/src/Common/Utils/FNVHash.cs
new file mode 100644
index 0000000..0e971f1
--- /dev/null
+++ b/src/Common/Utils/FNVHash.cs
@@ -0,0 +1,63 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace openCypherTranspiler.Common.Utils
+{
+ public static class FNVHash
+ {
+ public static int GetFNVHash(params object[] objs)
+ {
+ // FNV hash
+ unchecked
+ {
+ int hash = (int)2166136261;
+ if (objs != null)
+ {
+ foreach (var o in objs)
+ {
+ hash = hash * 23 + (o?.GetHashCode() ?? 0);
+ }
+ return hash;
+ }
+ }
+ return 0;
+ }
+
+ public static int GetFNVHash(IEnumerable objs)
+ {
+ // FNV hash
+ unchecked
+ {
+ int hash = (int)2166136261;
+ if (objs != null)
+ {
+ foreach (var o in objs)
+ {
+ hash = hash * 23 + (o?.GetHashCode() ?? 0);
+ }
+ return hash;
+ }
+ }
+ return 0;
+ }
+
+ public static uint GetFNVHashUInt(params object[] objs)
+ {
+ int hash = GetFNVHash(objs);
+ return (uint)hash; // this will, for example, convert -1 to 4294967295
+ }
+
+ public static uint GetFNVHashUInt(IEnumerable objs)
+ {
+ int hash = GetFNVHash(objs);
+ return (uint)hash; // this will, for example, convert -1 to 4294967295
+ }
+
+ }
+}
diff --git a/src/Common/Utils/TextHelper.cs b/src/Common/Utils/TextHelper.cs
new file mode 100644
index 0000000..d2b36ca
--- /dev/null
+++ b/src/Common/Utils/TextHelper.cs
@@ -0,0 +1,63 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace openCypherTranspiler.Common.Utils
+{
+ public static class TextHelper
+ {
+ ///
+ /// Helper function to use a regex pattern to clean up a dirty string for
+ /// certain restriction by another system/api
+ ///
+ ///
+ ///
+ ///
+ public static string MakeCompliantString(string originalStr, string regexPat)
+ {
+ string compStr = "";
+ foreach (Match m in Regex.Matches(originalStr, regexPat))
+ {
+ compStr += m.Value;
+ }
+ return compStr;
+ }
+
+ ///
+ /// Add or remove indentation at line level
+ ///
+ ///
+ /// Positive means adding indentation of delta unit, Negative means remove indentation of delta unit
+ ///
+ ///
+ ///
+ public static string ChangeIndentation(this string text, int delta, char indentChar = ' ', int indentUnitCnt = 4)
+ {
+ var lines = Regex.Split(text, "\r\n|\r|\n");
+
+ if (delta > 0)
+ {
+ var indentation = new string(indentChar, delta * indentUnitCnt);
+ return string.Join("\r\n", lines.Select(l => $"{indentation}{l}"));
+ }
+ else if (delta < 0)
+ {
+ var indentationUnit = new string(indentChar, indentUnitCnt);
+ return string.Join("\r\n", lines.Select(l => Regex.Replace(l, $"^({indentationUnit}){{0,{delta}}}", string.Empty)));
+ }
+ else
+ {
+ return text;
+ }
+ }
+ }
+}
diff --git a/src/Common/Utils/TypeHelper.cs b/src/Common/Utils/TypeHelper.cs
new file mode 100644
index 0000000..856e0f0
--- /dev/null
+++ b/src/Common/Utils/TypeHelper.cs
@@ -0,0 +1,115 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace openCypherTranspiler.Common.Utils
+{
+ public class TypeHelper
+ {
+ ///
+ /// Check if null value can be assigned
+ /// E.g. IsNullableType(string) = true, IsNullableType(DateTime) = false
+ /// IsNullableType(
+ ///
+ ///
+ ///
+ public static bool CanAssignNullToType(Type type)
+ {
+ if (!type.IsValueType)
+ {
+ return true; // ref-type can assign null value
+ }
+ if (IsSystemNullableType(type))
+ {
+ return true; // Nullable can assign null value
+ }
+ return false;
+ }
+
+ ///
+ /// Check if a type is System.Nullable or derived from System.Nullable
+ /// E.g. IsNullableType(string) = false, IsNullableType(System.Nullable) = true
+ ///
+ ///
+ ///
+ public static bool IsSystemNullableType(Type type)
+ {
+ // NOTE: Nullable is sealed type, so no class can derive from it
+ if (type != default(Type) && Nullable.GetUnderlyingType(type) != null)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Given an non-nullable type, wrap it in a System.Nullable<> generic type
+ ///
+ /// Non nullable type
+ ///
+ /// Throws InvalidCastException, if type is already nullable.
+ public static Type GetNullableTypeForType(Type type)
+ {
+ if (CanAssignNullToType(type))
+ {
+ throw new InvalidCastException($"Type: {type} is already nullable");
+ }
+ return typeof(Nullable<>).MakeGenericType(type);
+ }
+
+ ///
+ /// Check if a type is derived from a generic type
+ /// E.g. class SubClass : Node {} ;
+ /// IsSubclassOfRawGeneric(typeof(SubClass), Node<>) == true
+ ///
+ ///
+ ///
+ ///
+ public static bool IsSubclassOfRawGeneric(Type toCheck, Type generic)
+ {
+ while (toCheck != null && toCheck != typeof(object))
+ {
+ var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
+ if (generic == cur)
+ {
+ return true;
+ }
+ toCheck = toCheck.BaseType;
+ }
+ return false;
+ }
+
+ ///
+ /// Return the type itself, or for a nullable type, its unboxed type
+ ///
+ ///
+ ///
+ public static Type GetUnderlyingTypeIfNullable(Type t)
+ {
+ var isNullable = IsSystemNullableType(t);
+ return isNullable ? Nullable.GetUnderlyingType(t) : t;
+ }
+
+ ///
+ /// Wrap unnullable type to nullable
+ ///
+ ///
+ ///
+ public static Type MakeNullableIfNotAlready(Type t)
+ {
+ if (!CanAssignNullToType(t))
+ {
+ return typeof(Nullable<>).MakeGenericType(t);
+ }
+ else
+ {
+ return t;
+ }
+ }
+ }
+}
diff --git a/src/LogicalPlanner/Logical/IBindable.cs b/src/LogicalPlanner/Logical/IBindable.cs
new file mode 100644
index 0000000..8f184ae
--- /dev/null
+++ b/src/LogicalPlanner/Logical/IBindable.cs
@@ -0,0 +1,14 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+using openCypherTranspiler.Common.GraphSchema;
+
+namespace openCypherTranspiler.LogicalPlanner
+{
+ interface IBindable
+ {
+ void Bind(IGraphSchemaProvider graphDefinition);
+ }
+}
diff --git a/src/LogicalPlanner/Logical/LogicalOperator.cs b/src/LogicalPlanner/Logical/LogicalOperator.cs
new file mode 100644
index 0000000..7ef4008
--- /dev/null
+++ b/src/LogicalPlanner/Logical/LogicalOperator.cs
@@ -0,0 +1,1137 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using openCypherTranspiler.Common.GraphSchema;
+using openCypherTranspiler.Common.Utils;
+using openCypherTranspiler.Common.Exceptions;
+using openCypherTranspiler.openCypherParser.AST;
+using openCypherTranspiler.openCypherParser.Common;
+
+namespace openCypherTranspiler.LogicalPlanner
+{
+ ///
+ /// Logical operator representing the relational algebra to evaluate graph queries
+ /// The logical plan is a DAG (directed acyclic graph) of logical operators
+ /// This is the base type
+ ///
+ public abstract class LogicalOperator
+ {
+ private List _inOperators = new List();
+ private List _outOperators = new List();
+
+ ///
+ /// The schema this operator takes in
+ ///
+ /// Note: we thought about making this a getter of fields from output from it's in operator
+ /// however, for certain operators, it is not always a combination of all output, making
+ /// it awkard in some cases to implement field reference propagate
+ /// hence for now, we keep this a separate instanced list to maintain
+ public Schema InputSchema { get; set; }
+
+ ///
+ /// The schema this operator outputs
+ ///
+ public Schema OutputSchema { get; set; }
+
+ ///
+ /// The level of this particular operator
+ ///
+ public abstract int Depth { get; }
+
+ ///
+ /// Debug field: the id of the operator during creation
+ ///
+ public int OperatorDebugId { get; set; }
+
+ ///
+ /// Downstream operators
+ ///
+ public IEnumerable InOperators
+ {
+ get
+ {
+ return _inOperators;
+ }
+ }
+
+ ///
+ /// Downstream operators
+ ///
+ public IEnumerable OutOperators
+ {
+ get
+ {
+ return _outOperators;
+ }
+ }
+
+ internal virtual void AddInOperator(LogicalOperator op)
+ {
+ if (_inOperators.Contains(op))
+ {
+ throw new TranspilerInternalErrorException($"Internal error: operator {op} has already been added");
+ }
+ _inOperators.Add(op);
+ }
+
+ internal virtual void AddOutOperator(LogicalOperator op)
+ {
+ if (_outOperators.Contains(op))
+ {
+ throw new TranspilerInternalErrorException($"Internal error: operator {op} has already been added");
+ }
+ _outOperators.Add(op);
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine($"{this.GetType().Name}: ");
+ sb.AppendLine($"In Schema: {string.Join(", ", InputSchema ?? Enumerable.Empty())};");
+ sb.AppendLine($"Out Schema: {string.Join(", ", OutputSchema ?? Enumerable.Empty())};");
+ return sb.ToString();
+ }
+
+ ///
+ /// Operator graph traverse: get all downstream operators of a particular type
+ ///
+ ///
+ ///
+ public IEnumerable GetAllDownstreamOperatorsOfType() where T : LogicalOperator
+ {
+ var opList = new List();
+
+ if (OutOperators.Count() > 0)
+ {
+ opList.AddRange(OutOperators.SelectMany(op => op.GetAllDownstreamOperatorsOfType()));
+ }
+
+ if (this is T)
+ {
+ opList.Add(this as T);
+ }
+
+ return opList;
+ }
+
+ ///
+ /// Operator graph traverse: get all upstream operators of a particular type
+ ///
+ ///
+ ///
+ public IEnumerable GetAllUpstreamOperatorsOfType() where T : LogicalOperator
+ {
+ var opList = new List();
+
+ if (InOperators.Count() > 0)
+ {
+ opList.AddRange(
+ InOperators
+ .SelectMany(op => op.GetAllUpstreamOperatorsOfType())
+ .Distinct() // we do have multiplexing (multiple output) for operators in some scenarios, so we need to distinct here
+ );
+ }
+
+ if (this is T)
+ {
+ opList.Add(this as T);
+ }
+
+ return opList;
+ }
+
+
+ ///
+ /// Used by logic planner to propagate input schema
+ ///
+ internal abstract void PropagateSchema();
+
+ ///
+ /// Used by logic planner to update the list of ReferencedFields
+ ///
+ internal virtual void PropagateReferencedPropertiesForEntityFields()
+ {
+ // lift the referenced fields from the next layer of operators back to this one's output entity fields
+ var referedFieldsInDownstreamOps = OutOperators.SelectMany(op => op.InputSchema)
+ .Where(f => f is EntityField)
+ .Cast()
+ .GroupBy(f => f.FieldAlias)
+ .ToDictionary(kv => kv.Key, kv => kv.SelectMany(fn => fn.ReferencedFieldAliases).Distinct().ToList());
+
+ foreach (var field in OutputSchema.Where(f => f is EntityField).Cast())
+ {
+ Debug.Assert(referedFieldsInDownstreamOps.ContainsKey(field.FieldAlias));
+ field.AddReferenceFieldAliases(referedFieldsInDownstreamOps[field.FieldAlias]);
+ }
+
+ // lift the referenced fields in the entity fields from output to input schema of this operator, if
+ // applicable
+ if ((InputSchema?.Count ?? 0) > 0)
+ {
+ // handle entity name renamed cases by creating a map from field alias before projection to after
+ // projection
+ var aliasMap = new Dictionary();
+
+ if (this is ProjectionOperator)
+ {
+ var aliasMap1 = (this as ProjectionOperator).ProjectionMap
+ .Where(u => OutputSchema.Where(n => n is EntityField).Any(k => k.FieldAlias == u.Key));
+ aliasMap = aliasMap1?.ToDictionary(n => n.Value.GetChildrenQueryExpressionType().First().VariableName, n => n.Key);
+ }
+ else
+ {
+ // create a dummy alias map ( A -> A, B -> B; not alias name modified) for non-projection operator
+ aliasMap = OutputSchema.Where(n => n is EntityField).ToDictionary(n => n.FieldAlias, n=> n.FieldAlias);
+
+ }
+
+ foreach (var field in InputSchema.Where(f => f is EntityField).Cast())
+ {
+ var mappedAlias = (aliasMap.ContainsKey(field.FieldAlias) ?aliasMap[field.FieldAlias]:null);
+
+ if (mappedAlias != null && referedFieldsInDownstreamOps.ContainsKey(mappedAlias))
+ {
+ field.AddReferenceFieldAliases(referedFieldsInDownstreamOps[mappedAlias]);
+ }
+ }
+
+ var referedFieldsForUpstream = InputSchema
+ .Where(f => f is EntityField).Cast()
+ .ToDictionary(kv => kv.FieldAlias, kv => kv);
+
+ // Some operators has additional fields may get referenced even they are not in output schema
+ // Such as in WHERE
+ // Child logical operator class implement this and does the addition
+ AppendReferencedProperties(referedFieldsForUpstream);
+ }
+ }
+
+ ///
+ /// Incremental addition of reference fields by the logical operator (E.g. WHERE/JOIN can add field references
+ /// in addition to those already in the outschema when propagating to inschema)
+ /// To be implemented by each Logical Operator class
+ ///
+ /// A list of avaialble entities (with bound fields) in the input for this operator
+ internal abstract void AppendReferencedProperties(IDictionary entityFields);
+
+ ///
+ /// Helper function to propagate content of fields
+ ///
+ ///
+ ///
+ internal static void CopyFieldInfoHelper(IEnumerable targetFields, IEnumerable srcFields)
+ {
+ // Select operator does not alter schema, so the output should map to input perfectly
+ Debug.Assert(targetFields.All(f => srcFields.Any(f2 => f2.FieldAlias == f.FieldAlias && f2.GetType() == f.GetType())));
+
+ var fieldMapping = new Dictionary();
+ foreach (var tarF in targetFields)
+ {
+ var matchSrcF = srcFields.Where(f => f.FieldAlias == tarF.FieldAlias);
+ Debug.Assert(matchSrcF.Count() == 1);
+ tarF.Copy(matchSrcF.First());
+ }
+ }
+ }
+
+ ///
+ /// Base type for starting operator in a Logical Plan, equivalent to source tuples in RelationAlgebra
+ ///
+ public abstract class StartLogicalOperator : LogicalOperator
+ {
+ internal override void AddInOperator(LogicalOperator op)
+ {
+ throw new TranspilerInternalErrorException("StartLogicalOperator derived types does not take any InOperator");
+ }
+
+ public override int Depth
+ {
+ get
+ {
+ return 0;
+ }
+ }
+
+ internal override void PropagateSchema()
+ {
+ // No input, so nothing need to be done
+ }
+
+ internal override void AppendReferencedProperties(IDictionary entityFields)
+ {
+ throw new TranspilerInternalErrorException("DataSourceOperator does not have a input source. Not expected to get called here");
+ }
+ }
+
+ ///
+ /// Base type for operator that takes just one input
+ ///
+ public abstract class UnaryLogicalOperator : LogicalOperator
+ {
+ public LogicalOperator InOperator
+ {
+ get
+ {
+ return InOperators.FirstOrDefault();
+ }
+ }
+
+ protected void SetInOperator(LogicalOperator op)
+ {
+ if (InOperators.Count() > 0)
+ {
+ throw new TranspilerInternalErrorException("InOperator is already set");
+ }
+ op.AddOutOperator(this);
+ AddInOperator(op);
+ }
+
+ public override int Depth
+ {
+ get
+ {
+ return InOperator.Depth + 1;
+ }
+ }
+
+ internal abstract void PropagateOutSchema();
+
+ internal override void PropagateSchema()
+ {
+ // For unary operator, the input schema is the output of its input operator's schema without any changes
+ var prevOutSchema = InOperator.OutputSchema;
+ var fieldMapping = new Dictionary();
+
+ foreach (var inF in InputSchema)
+ {
+ var matchOutFields = prevOutSchema.Where(f => f.FieldAlias == inF.FieldAlias);
+ if (matchOutFields.Count() > 1)
+ {
+ throw new TranspilerInternalErrorException($"Ambiguous match of field with alias '{inF.FieldAlias}'");
+ }
+ if (matchOutFields.Count() == 0)
+ {
+ throw new TranspilerInternalErrorException($"Failed to match field with alias '{inF.FieldAlias}'");
+ }
+ fieldMapping.Add(inF, matchOutFields.First());
+ }
+
+ CopyFieldInfoHelper(fieldMapping.Keys, fieldMapping.Values);
+
+ PropagateOutSchema();
+ }
+ }
+
+ ///
+ /// Base type for operator that takes two inputs
+ ///
+ public abstract class BinaryLogicalOperator : LogicalOperator
+ {
+ public LogicalOperator InOperatorLeft
+ {
+ get
+ {
+ return InOperators.First();
+ }
+ }
+ public LogicalOperator InOperatorRight
+ {
+ get
+ {
+ return InOperators.Last();
+ }
+ }
+
+ protected void SetInOperators(LogicalOperator opLeft, LogicalOperator opRight)
+ {
+ if (InOperators.Count() > 0)
+ {
+ throw new TranspilerInternalErrorException("InOperatorLeft or InOperatorRight is already set.");
+ }
+ opLeft.AddOutOperator(this);
+ opRight.AddOutOperator(this);
+ AddInOperator(opLeft);
+ AddInOperator(opRight);
+ }
+
+ public override int Depth
+ {
+ get
+ {
+ return Math.Max(InOperatorLeft.Depth, InOperatorRight.Depth) + 1;
+ }
+ }
+
+ internal abstract void PropagateInSchema();
+
+ internal virtual void PropagateOutSchema()
+ {
+ // Default implmementation is that output schema doesn't change
+ CopyFieldInfoHelper(OutputSchema, InputSchema);
+ }
+
+ internal override void PropagateSchema()
+ {
+ PropagateInSchema();
+ PropagateOutSchema();
+ }
+ }
+
+ ///
+ /// Starting operator representing an entity data source of the graph
+ ///
+ public sealed class DataSourceOperator : StartLogicalOperator, IBindable
+ {
+ public DataSourceOperator(Entity entity)
+ {
+ Entity = entity;
+ }
+
+ public Entity Entity { get; set; }
+
+ ///
+ /// This function bind the data source to a given graph definitions
+ ///
+ ///
+ public void Bind(IGraphSchemaProvider graphDefinition)
+ {
+ // During binding, we read graph definition of the entity
+ // and populate the EntityField object in the output
+ // with the list of fields that the node/edge definition can expose
+ var properties = new List();
+ string entityUniqueName;
+ string sourceEntityName = null;
+ string sinkEntityName = null;
+ SingleField nodeIdField = null;
+ SingleField edgeSrcIdField = null;
+ SingleField edgeSinkIdField = null;
+
+ try
+ {
+ if (Entity is NodeEntity)
+ {
+ NodeSchema nodeDef = graphDefinition.GetNodeDefinition(Entity.EntityName);
+ entityUniqueName = nodeDef.Id;
+ nodeIdField = new SingleField(nodeDef.NodeIdProperty.PropertyName, nodeDef.NodeIdProperty.DataType);
+
+ properties.AddRange(nodeDef.Properties.Select(p => new SingleField(p.PropertyName, p.DataType)));
+ properties.Add(nodeIdField);
+ }
+ else
+ {
+ var edgeEnt = Entity as RelationshipEntity;
+ EdgeSchema edgeDef = null;
+
+ switch (edgeEnt.RelationshipDirection)
+ {
+ case RelationshipEntity.Direction.Forward:
+ edgeDef = graphDefinition.GetEdgeDefinition(edgeEnt.EntityName, edgeEnt.LeftEntityName, edgeEnt.RightEntityName);
+ break;
+ case RelationshipEntity.Direction.Backward:
+ edgeDef = graphDefinition.GetEdgeDefinition(edgeEnt.EntityName, edgeEnt.RightEntityName, edgeEnt.LeftEntityName);
+ break;
+ default:
+ // either direction
+ // TODO: we don't handle 'both' direction yet
+ Debug.Assert(edgeEnt.RelationshipDirection == RelationshipEntity.Direction.Both);
+ edgeDef = graphDefinition.GetEdgeDefinition(edgeEnt.EntityName, edgeEnt.LeftEntityName, edgeEnt.RightEntityName);
+ if (edgeDef == null)
+ {
+ edgeDef = graphDefinition.GetEdgeDefinition(edgeEnt.EntityName, edgeEnt.RightEntityName, edgeEnt.LeftEntityName);
+ }
+ break;
+ }
+
+ entityUniqueName = edgeDef.Id;
+ sourceEntityName = edgeDef.SourceNodeId;
+ sinkEntityName = edgeDef.SinkNodeId;
+ edgeSrcIdField = new SingleField(edgeDef.SourceIdProperty.PropertyName, edgeDef.SourceIdProperty.DataType);
+ edgeSinkIdField = new SingleField(edgeDef.SinkIdProperty.PropertyName, edgeDef.SinkIdProperty.DataType);
+
+ properties.AddRange(edgeDef.Properties.Select(p => new SingleField(p.PropertyName, p.DataType)));
+ properties.Add(edgeSrcIdField);
+ properties.Add(edgeSinkIdField);
+ }
+ }
+ catch (KeyNotFoundException e)
+ {
+ throw new TranspilerBindingException($"Failed to binding entity with alias '{Entity.Alias}' of type '{Entity.EntityName}' to graph definition. Inner error: {e.GetType().Name}: {e.Message}");
+ }
+
+ Debug.Assert(OutputSchema.Count == 1
+ && OutputSchema.First() is EntityField
+ && (OutputSchema.First() as EntityField).EntityName == Entity.EntityName);
+
+ var field = OutputSchema.First() as EntityField;
+ field.BoundEntityName = entityUniqueName;
+ field.BoundSourceEntityName = sourceEntityName;
+ field.BoundSinkEntityName = sinkEntityName;
+ field.EncapsulatedFields = properties;
+ field.NodeJoinField = nodeIdField;
+ field.RelSourceJoinField = edgeSrcIdField;
+ field.RelSinkJoinField = edgeSinkIdField;
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder(base.ToString());
+ sb.AppendLine($"DataEntitySource: {Entity};");
+ return sb.ToString();
+ }
+ }
+
+ ///
+ /// Operator to satisfy WHERE
+ ///
+ public sealed class SelectionOperator : UnaryLogicalOperator
+ {
+ public SelectionOperator(LogicalOperator inOp, QueryExpression filterExpr)
+ {
+ FilterExpression = filterExpr;
+ UnexpandedEntityInequalityConditions = null;
+ SetInOperator(inOp);
+ }
+ public SelectionOperator(LogicalOperator inOp,IList orderExpr, IList limitExpr)
+ {
+ FilterExpression = null;
+ UnexpandedEntityInequalityConditions = null;
+ SetInOperator(inOp);
+ OrderByExpressions = orderExpr;
+ LimitExpressions = limitExpr;
+ }
+
+ public SelectionOperator(LogicalOperator inOp, IEnumerable<(string, string)> entityInquityConds)
+ {
+ FilterExpression = null;
+ UnexpandedEntityInequalityConditions = entityInquityConds.ToList();
+ SetInOperator(inOp);
+ }
+
+ public QueryExpression FilterExpression { get; private set; }
+
+ ///
+ /// use to store ORDER BY expression under WITH or RETURN clause
+ ///
+ public IList OrderByExpressions { get; private set; }
+
+ ///
+ /// use to store LIMIT expression under WITH or RETURN clause
+ ///
+ public IList LimitExpressions { get; private set; }
+
+ ///
+ /// Inequality conditions to be added after binding, specificially reserved for the implied inequality
+ /// conditions from MATCH clauses like MATCH (p:Person)-[a1:Acted_In]-(m:Movie)-[a2:Acted_In]-(p2:Person)
+ /// where implicity condition of a1 <> a2 must be added
+ ///
+ public IEnumerable<(string RelAlias1, string RelAlias2)> UnexpandedEntityInequalityConditions { get; private set; }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder(base.ToString());
+ sb.AppendLine($"Filtering Condition: {FilterExpression};");
+ return sb.ToString();
+ }
+
+ internal override void PropagateOutSchema()
+ {
+ // Select operator does not alter schema, so the output should map to input perfectly
+ Debug.Assert(OutputSchema.All(f => InputSchema.Any(f2 => f2.FieldAlias == f.FieldAlias && f2.GetType() == f.GetType())));
+ CopyFieldInfoHelper(OutputSchema, InputSchema);
+
+ // Additional step for Select operator is to check if additional conditions
+ // should be added if UnexpandedEntityInequalityConditions is specified
+ if (UnexpandedEntityInequalityConditions != null)
+ {
+ Debug.Assert(FilterExpression == null);
+ FilterExpression = UnexpandedEntityInequalityConditions.Aggregate(
+ FilterExpression,
+ (expr, c) =>
+ {
+ var relEnt1 = InputSchema.First(e => e.FieldAlias == c.RelAlias1) as EntityField;
+ var relEnt2 = InputSchema.First(e => e.FieldAlias == c.RelAlias2) as EntityField;
+ Debug.Assert(relEnt1 != null && relEnt2 != null);
+
+ var condExpr1 = new QueryExpressionBinary()
+ {
+ Operator = OperatorHelper.GetOperator(BinaryOperator.NEQ),
+ LeftExpression = new QueryExpressionProperty()
+ {
+ VariableName = c.RelAlias1,
+ PropertyName = relEnt1.RelSourceJoinField.FieldAlias
+ },
+ RightExpression = new QueryExpressionProperty()
+ {
+ VariableName = c.RelAlias2,
+ PropertyName = relEnt2.RelSourceJoinField.FieldAlias
+ }
+ };
+ var condExpr2 = new QueryExpressionBinary()
+ {
+ Operator = OperatorHelper.GetOperator(BinaryOperator.NEQ),
+ LeftExpression = new QueryExpressionProperty()
+ {
+ VariableName = c.RelAlias1,
+ PropertyName = relEnt1.RelSinkJoinField.FieldAlias
+ },
+ RightExpression = new QueryExpressionProperty()
+ {
+ VariableName = c.RelAlias2,
+ PropertyName = relEnt2.RelSinkJoinField.FieldAlias
+ }
+ };
+ var binExpr = new QueryExpressionBinary()
+ {
+ Operator = OperatorHelper.GetOperator(BinaryOperator.OR),
+ LeftExpression = condExpr1,
+ RightExpression = condExpr2
+ };
+
+ return expr == null ?
+ binExpr :
+ new QueryExpressionBinary()
+ {
+ Operator = OperatorHelper.GetOperator(BinaryOperator.AND),
+ LeftExpression = expr,
+ RightExpression = binExpr
+ };
+ });
+ }
+ }
+
+ internal override void AppendReferencedProperties(IDictionary entityFields)
+ {
+ // Selection operator may have additional field references in the where/order by conditions that were not referenced in output
+ // Add these as well to the input
+
+ Debug.Assert(FilterExpression != null || OrderByExpressions != null || LimitExpressions != null );
+ var allPropertyReferences = new List();
+
+ var propsFromFilterExprs = FilterExpression?.GetChildrenQueryExpressionType();
+ var propsFromOrderByExprs = OrderByExpressions?.SelectMany(n => n.GetChildrenQueryExpressionType());
+
+ if(propsFromOrderByExprs != null)
+ {
+ allPropertyReferences.AddRange(propsFromOrderByExprs);
+ }
+ if (propsFromFilterExprs != null)
+ {
+ allPropertyReferences.AddRange(propsFromFilterExprs);
+ }
+
+ foreach (var prop in allPropertyReferences)
+ {
+ var varName = prop.VariableName;
+ var fieldName = prop.PropertyName;
+
+ if (prop.Entity != null)
+ {
+ if(!entityFields.ContainsKey(varName))
+ {
+ throw new TranspilerInternalErrorException($"Entity field: '{varName}' does not exsit");
+ }
+
+ var entity = entityFields[varName];
+
+ // for entity reference, such as MATCH (d:device) RETURN count(d)
+ // we can by default, for node, does count(d.id) instead, and edge does count(d._vertexId) instead
+ if (entity.Type == EntityField.EntityType.Node)
+ {
+ entity.AddReferenceFieldAlias(entity.NodeJoinField.FieldAlias);
+ }
+ else
+ {
+ entity.AddReferenceFieldAlias(entity.RelSourceJoinField.FieldAlias);
+ }
+ }
+ else if (fieldName != null)
+ {
+ // field dereference, just append to list
+ var propName = prop.PropertyName;
+ if (!entityFields.ContainsKey(varName))
+ {
+ throw new TranspilerSyntaxErrorException($"entity field: \"{varName}\" not exsit");
+ }
+ var entity = entityFields[varName];
+ entity.AddReferenceFieldAlias(fieldName);
+ }
+ else
+ {
+ // reference to an existing field
+ // nothing need to be done
+ // add a check if expression alias did exist
+ if(OutOperators.First().OutputSchema.All(n => n.FieldAlias != varName))
+ {
+ throw new TranspilerSyntaxErrorException($"single field name: {varName} not existed in output schema of depth {Depth} selection operator.");
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Operator to satisfy SELECT
+ ///
+ public sealed class ProjectionOperator : UnaryLogicalOperator
+ {
+ public bool IsDistinct { get; private set; }
+ public bool HasAggregationField { get; private set; } = false;
+ public ProjectionOperator(LogicalOperator inOp, IDictionary projectionMap, bool isDistinct)
+ {
+ ProjectionMap = projectionMap;
+ IsDistinct = isDistinct;
+ SetInOperator(inOp);
+ }
+
+ // projection map, from source expression to projection result (indexed by its FieldName)
+ public IDictionary ProjectionMap { get; private set; }
+
+ internal override void PropagateOutSchema()
+ {
+ // projection alters the schema, we need to look into the query expressions
+ // to do type propagation
+
+ var exprToOutputMap = ProjectionMap.ToDictionary(
+ kv => kv.Key, // key is output alias
+ kv => new { Expr = kv.Value, Field = OutputSchema.First(f => f.FieldAlias == kv.Key) } // value is the corresponding field object and expression
+ );
+
+ foreach (var map in exprToOutputMap)
+ {
+ // if any aggregation function, set the flag to be true
+ var allPropertyReferences = map.Value.Expr.GetChildrenQueryExpressionType();
+ if(map.Value.Expr.GetChildrenQueryExpressionType().Count() > 0)
+ {
+ HasAggregationField = true;
+ }
+
+ if (map.Value.Field is EntityField)
+ {
+ // This can only be direct exposure of entity (as opposed to deference of a particular property)
+ // We just copy of the fields that the entity can potentially be deferenced
+ Debug.Assert(allPropertyReferences.Count() == 1);
+
+ var varName = allPropertyReferences.First().VariableName;
+ var matchInputField = InputSchema.First(f => f.FieldAlias == varName);
+ map.Value.Field.Copy(matchInputField);
+ }
+ else
+ {
+ // This can be a complex expression involve multiple field/column references
+ // We will compute the type of the expression
+ Debug.Assert(map.Value.Field is SingleField);
+
+ // first of all, bind the type to the variable references
+ foreach (var prop in allPropertyReferences)
+ {
+ var varName = prop.VariableName;
+ var propName = prop.PropertyName;
+ Debug.Assert(prop.VariableName != null);
+ var matchedField = InputSchema.FirstOrDefault(f => f.FieldAlias == varName);
+
+ if (matchedField == null)
+ {
+ throw new TranspilerBindingException($"Failed to find input matching field alias {varName}");
+ }
+
+ if (string.IsNullOrEmpty(propName))
+ {
+ // refer to a already aliased column exposed from piped data
+ if (matchedField is SingleField)
+ {
+ prop.DataType = (matchedField as SingleField).FieldType;
+ }
+ else
+ {
+ // entity field reference in a single field expression
+ // this is valid only in handful situaions, such as Count(d), Count(distinct(d))
+ // in such case, we populate the Entity object with correct entity type so that code generator can use it later
+ Debug.Assert(matchedField is EntityField);
+ var matchedEntity = matchedField as EntityField;
+ prop.Entity = matchedEntity.Type == EntityField.EntityType.Node ?
+ new NodeEntity() { EntityName = matchedEntity.EntityName, Alias = matchedEntity.FieldAlias } as Entity:
+ new RelationshipEntity() { EntityName = matchedEntity.EntityName, Alias = matchedEntity.FieldAlias } as Entity;
+ }
+ }
+ else
+ {
+ // property reference of an entity
+ if (!(matchedField is EntityField))
+ {
+ throw new TranspilerBindingException($"Failed to dereference property {propName} for alias {varName}, which is not an alias of entity type as expected");
+ }
+ var entField = matchedField as EntityField;
+ var entPropField = entField.EncapsulatedFields.FirstOrDefault(f => f.FieldAlias == propName);
+ if (entPropField == null)
+ {
+ throw new TranspilerBindingException($"Failed to dereference property {propName} for alias {varName}, Entity type {entField.BoundEntityName} does not have a property named {propName}");
+ }
+ entField.AddReferenceFieldAlias(propName);
+ prop.DataType = entPropField.FieldType;
+ }
+ }
+
+ // do data type evaluation
+ var evalutedType = map.Value.Expr.EvaluateType();
+ var outField = map.Value.Field as SingleField;
+ outField.FieldType = evalutedType;
+ }
+ }
+ }
+ internal override void AppendReferencedProperties(IDictionary entityFields)
+ {
+
+ // Adding more logic here, as some of upstream selection operator is selection operator, then the extra field in order
+ // by clause need to flow down to the referenced field.
+ if(InOperator is SelectionOperator)
+ {
+ var preSelectionOp = (InOperator as SelectionOperator);
+ var orderExpr = preSelectionOp?.OrderByExpressions;
+ if (orderExpr?.Count() > 0)
+ {
+ var propList = orderExpr
+ .SelectMany(n => n.GetChildrenQueryExpressionType())
+ .Where(p => p.PropertyName != null && p.VariableName != null)
+ .GroupBy(l => l.VariableName)
+ ?.ToDictionary(p => p.Key, p => p.Select(n => n.PropertyName));
+ //looking for new entity fields in the slection operrator and then add to the entity field reference dictionary
+ if (propList != null)
+ {
+ foreach(var entry in propList)
+ {
+ foreach(var propertyName in entry.Value)
+ {
+ if (entityFields.ContainsKey(entry.Key) && !entityFields[entry.Key].ReferencedFieldAliases.Contains(propertyName))
+ {
+ var entity = entityFields[entry.Key];
+ entity.AddReferenceFieldAlias(propertyName);
+ // update output schema reference field at the same time.
+ var outputEntity = OutputSchema.Where(n => n is EntityField && n.FieldAlias == entry.Key);
+ Debug.Assert(outputEntity.Count() == 1);
+ outputEntity.Cast().First().AddReferenceFieldAlias(propertyName);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Operator to satisfy UNION
+ ///
+ public sealed class SetOperator : BinaryLogicalOperator
+ {
+ public SetOperator(LogicalOperator leftOp, LogicalOperator rightOp, SetOperationType setOpType)
+ {
+ SetOperation = setOpType;
+ SetInOperators(leftOp, rightOp);
+ }
+
+ public enum SetOperationType
+ {
+ Union,
+ UnionAll,
+ }
+
+ public SetOperationType SetOperation { get; set; }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder(base.ToString());
+ sb.AppendLine($"Set Operation Type: {SetOperation};");
+ return sb.ToString();
+ }
+
+ internal override void PropagateInSchema()
+ {
+ // For SetOperator, out schema should be the same for union from 2 source of input
+
+ var leftSchema = InOperatorLeft.OutputSchema;
+ var rightSchema = InOperatorRight.OutputSchema;
+
+ if (leftSchema.Count != rightSchema.Count)
+ {
+ throw new TranspilerSyntaxErrorException("All sub queries in an UNION must have the same column names");
+ }
+
+ // validate left schema and see if it is compatible with right schema
+ foreach (var z in leftSchema.Zip(rightSchema, (l, r) => (LeftField: l, RightField: r)))
+ {
+ if (z.LeftField.FieldAlias != z.RightField.FieldAlias)
+ {
+ throw new TranspilerSyntaxErrorException($"Column name mismatch: left='{z.LeftField.FieldAlias}', right='{z.LeftField.FieldAlias}'");
+ }
+
+ if (z.LeftField.GetType() != z.RightField.GetType())
+ {
+ throw new TranspilerSyntaxErrorException($"Column type mismatch for column '{z.LeftField.FieldAlias}'");
+ }
+
+ if (z.LeftField is EntityField)
+ {
+ var l = z.LeftField as EntityField;
+ var r = z.RightField as EntityField;
+
+ if (l.BoundEntityName != r.BoundEntityName)
+ {
+ throw new TranspilerSyntaxErrorException($"Column type mismatch for column '{z.LeftField.FieldAlias}': left='{l.BoundEntityName}', right='{r.BoundEntityName}'");
+ }
+ }
+ else
+ {
+ Debug.Assert(z.LeftField is SingleField && z.RightField is SingleField);
+ var l = z.LeftField as SingleField;
+ var r = z.RightField as SingleField;
+ if (l.FieldType != r.FieldType)
+ {
+ throw new TranspilerSyntaxErrorException($"Column type mismatch for column '{z.LeftField.FieldAlias}': left='{l.FieldType}', right='{r.FieldType}'");
+ }
+ }
+ }
+
+ // Snap to the column names, type and order of the later sub-query
+ CopyFieldInfoHelper(OutputSchema, rightSchema);
+ }
+
+ internal override void AppendReferencedProperties(IDictionary entityFields)
+ {
+ // NOOP. Set operators does not have addition referenced properties than those already on the output
+ }
+ }
+
+ ///
+ /// Operator to satisfy JOIN
+ ///
+ public sealed class JoinOperator : BinaryLogicalOperator
+ {
+ ///
+ /// Keeps track of what entities are joint together
+ ///
+ private List _joinPairs = new List();
+
+ ///
+ /// Structure designate how two entity instance should be joined together
+ ///
+ public class JoinKeyPair
+ {
+ public enum JoinKeyPairType
+ {
+ None,
+ Source, // Node join to Rel's SourceId
+ Sink, // Node join to Rel's SinkId
+ Either, // Node allowed to join either Source or Sink, whichever is applicable
+ Both, // Node join to both source and sink
+ NodeId, // Node to node join
+ }
+
+ public string NodeAlias { get; set; }
+ public string RelationshipOrNodeAlias { get; set; }
+ public JoinKeyPairType Type { get; set; }
+
+ #region Equality comparison overrides
+ public override bool Equals(object obj)
+ {
+ var compObj = obj as JoinKeyPair;
+ return compObj?.Type == Type &&
+ compObj?.NodeAlias == NodeAlias &&
+ compObj?.RelationshipOrNodeAlias == RelationshipOrNodeAlias;
+ }
+
+ public override int GetHashCode()
+ {
+ return FNVHash.GetFNVHash(Type, NodeAlias, RelationshipOrNodeAlias);
+ }
+
+ public static bool operator ==(JoinKeyPair lhs, JoinKeyPair rhs) => lhs.Equals(rhs);
+ public static bool operator !=(JoinKeyPair lhs, JoinKeyPair rhs) => !lhs.Equals(rhs);
+
+ #endregion Equality comparison overrides
+
+ public override string ToString()
+ {
+ return $"JoinPair: Node={NodeAlias} RelOrNode={RelationshipOrNodeAlias} Type=({Type})";
+ }
+
+ }
+
+ ///
+ /// Type of join
+ ///
+ public enum JoinType
+ {
+ // ordered from least constrained to most
+ Cross = 0,
+ Left = 1,
+ Inner = 2,
+ }
+
+ public JoinOperator(LogicalOperator leftOp, LogicalOperator rightOp, JoinType joinType)
+ {
+ Type = joinType;
+ SetInOperators(leftOp, rightOp);
+ }
+
+ ///
+ /// Type of join to be performed for this JOIN operator for all the join key pairs
+ ///
+ public JoinType Type { get; set; }
+
+ ///
+ /// List of joins needed by this operator between entities from the input
+ ///
+ public IReadOnlyCollection JoinPairs { get { return _joinPairs.AsReadOnly(); } }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder(base.ToString());
+ sb.AppendLine($"Join Type: {Type};");
+ sb.AppendLine($"Join Operations: {string.Join(", ", JoinPairs.Select(kv => $"{kv.NodeAlias} |><|_{kv.Type.ToString().First()} {kv.RelationshipOrNodeAlias}"))};");
+ return sb.ToString();
+ }
+
+ internal override void PropagateInSchema()
+ {
+ // Input for JOIN is the combination of both sources
+
+ var prevLeftSchema = InOperatorLeft.OutputSchema;
+ var prevRightSchema = InOperatorRight.OutputSchema;
+ var prevOutFields = prevLeftSchema.Union(prevRightSchema);
+ var fieldMapping = new Dictionary();
+
+ foreach (var inF in InputSchema)
+ {
+ var matchOutFields = prevOutFields.Where(f => f.FieldAlias == inF.FieldAlias);
+ if (matchOutFields.Count() > 1)
+ {
+ // In case of join by node ids between left and right input (such as in MATCH .. OPTIONAL MATCH ... WHERE case)
+ // we will take the from left side and ignore dups
+ // otherwise throws exception
+ if (!JoinPairs.All(jp => jp.Type == JoinKeyPair.JoinKeyPairType.NodeId))
+ {
+ throw new TranspilerInternalErrorException($"Ambiguous match of field with alias '{inF.FieldAlias}'");
+ }
+ }
+ if (matchOutFields.Count() == 0)
+ {
+ throw new TranspilerInternalErrorException($"Failed to match field with alias '{inF.FieldAlias}'");
+ }
+ fieldMapping.Add(inF, matchOutFields.First());
+ }
+
+ CopyFieldInfoHelper(InputSchema, fieldMapping.Values);
+
+ // additional validation on if relationships can satisfy the joins if directional traversal is explicitly specified
+ foreach (var joinPair in JoinPairs)
+ {
+ var nodeEntity = InputSchema.First(s => s.FieldAlias == joinPair.NodeAlias) as EntityField;
+ var relEntity = InputSchema.First(s => s.FieldAlias == joinPair.RelationshipOrNodeAlias) as EntityField;
+ switch (joinPair.Type)
+ {
+ case JoinKeyPair.JoinKeyPairType.Source:
+ if (relEntity.BoundSourceEntityName != nodeEntity.BoundEntityName)
+ {
+ throw new TranspilerBindingException($"Cannot find relationship ({nodeEntity.BoundEntityName})-[{relEntity.BoundSourceEntityName}]->(*), for {joinPair.NodeAlias} and {joinPair.RelationshipOrNodeAlias}");
+ }
+ break;
+ case JoinKeyPair.JoinKeyPairType.Sink:
+ if (relEntity.BoundSinkEntityName != nodeEntity.BoundEntityName)
+ {
+ throw new TranspilerBindingException($"Cannot find relationship ({nodeEntity.BoundEntityName})<-[{relEntity.BoundSourceEntityName}]-(*), for {joinPair.NodeAlias} and {joinPair.RelationshipOrNodeAlias}");
+ }
+ break;
+ default:
+ // in .Either, or .Both case
+ // no validation need to be done as Binding should already enforce the existence of the relationships
+ break;
+ }
+ }
+ }
+
+ internal override void PropagateOutSchema()
+ {
+ // Output for JOIN depends on type of the join
+ // For LEFT join, attributes becomes non-nullable
+ // For CROSS join or INNER join, attributes type remains unchanged after join
+ if (Type == JoinType.Left)
+ {
+ var prevLeftSchema = InOperatorLeft.OutputSchema;
+ var prevRightSchema = InOperatorRight.OutputSchema;
+
+ var fieldMapping = new Dictionary();
+ foreach (var outField in OutputSchema)
+ {
+ var inFieldMatches = InputSchema.Where(f => f.FieldAlias == outField.FieldAlias);
+ Debug.Assert(InputSchema.Where(f => f.FieldAlias == outField.FieldAlias).Count() == 1); // must have match in IN for any OUT field
+ var inField = inFieldMatches.First();
+ // we made it that left schema LEFT OUTTER JOIN with right schema always
+ var isFromRight = !prevLeftSchema.Any(f => f.FieldAlias == inField.FieldAlias);
+
+ // copy over the schema first
+ outField.Copy(inField);
+
+ // make adjustment for outter join
+ if (inField is SingleField)
+ {
+ Debug.Assert(outField.GetType() == inField.GetType()); // join doesn't alter field types
+ var outFieldSingleField = outField as SingleField;
+
+ if (isFromRight && !TypeHelper.CanAssignNullToType(outFieldSingleField.FieldType))
+ {
+ // make it nullable variant of the original type
+ outFieldSingleField.FieldType = TypeHelper.GetNullableTypeForType(outFieldSingleField.FieldType);
+ }
+ }
+ else
+ {
+ Debug.Assert(outField is EntityField);
+ var outEntCapFields = (outField as EntityField).EncapsulatedFields;
+
+ if (isFromRight)
+ {
+ foreach (var outEntCapField in outEntCapFields)
+ {
+ if (!TypeHelper.CanAssignNullToType(outEntCapField.FieldType))
+ {
+ // make it nullable variant of the original type
+ outEntCapField.FieldType = TypeHelper.GetNullableTypeForType(outEntCapField.FieldType);
+ }
+ }
+ }
+ }
+ }
+
+ }
+ else
+ {
+ base.PropagateOutSchema();
+ }
+ }
+
+ internal override void AppendReferencedProperties(IDictionary entityFields)
+ {
+ // NOOP. Besides join keys, JOIN operator does not introduce any additionally referenced fields
+ // The handling of join key is left for later stage
+ }
+
+ ///
+ /// For adding to the maintained list of what entities are joint together by this operator
+ ///
+ /// the alias on the left side of the join
+ /// the alias on the right side of the join
+ internal void AddJoinPair(JoinKeyPair joinKeyPair)
+ {
+ Debug.Assert(InputSchema.Where(f => f is EntityField && f.FieldAlias == joinKeyPair.NodeAlias).Count() == 1);
+ Debug.Assert(InputSchema.Where(f => f is EntityField && f.FieldAlias == joinKeyPair.RelationshipOrNodeAlias).Count() == 1);
+ _joinPairs.Add(joinKeyPair);
+ }
+ }
+}
diff --git a/src/LogicalPlanner/Logical/Schema.cs b/src/LogicalPlanner/Logical/Schema.cs
new file mode 100644
index 0000000..a9c8769
--- /dev/null
+++ b/src/LogicalPlanner/Logical/Schema.cs
@@ -0,0 +1,237 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using openCypherTranspiler.Common.Exceptions;
+
+namespace openCypherTranspiler.LogicalPlanner
+{
+ ///
+ /// a field of a schema
+ ///
+ public abstract class Field
+ {
+ public Field(string alias)
+ {
+ FieldAlias = alias;
+ }
+
+ ///
+ /// The local unique name (in current operator's context) to refer this field
+ ///
+ public string FieldAlias { get; set; }
+
+ ///
+ /// Create a deep copy of this Field object
+ ///
+ ///
+ public abstract Field Clone();
+
+ ///
+ /// Copy the information from another field to itself (except for alias)
+ ///
+ public abstract void Copy(Field f);
+ }
+
+ ///
+ /// A field which is an entity (a set of columns forming a node or edge stream/stream group)
+ ///
+ public class EntityField : Field
+ {
+ private ISet _referencedAliases = new HashSet();
+
+ public EntityField(string alias, string entityName, EntityType type) : base(alias)
+ {
+ EntityName = entityName;
+ Type = type;
+ }
+
+ ///
+ /// Copy constructor
+ ///
+ ///
+ public EntityField(EntityField field) : base(field.FieldAlias)
+ {
+ Copy(field);
+ }
+
+ public override void Copy(Field field)
+ {
+ var fieldSrc = field as EntityField;
+ Debug.Assert(fieldSrc != null);
+ this.EntityName = fieldSrc.EntityName;
+ this.Type = fieldSrc.Type;
+ this.BoundEntityName = fieldSrc.BoundEntityName;
+ this.BoundSourceEntityName = fieldSrc.BoundSourceEntityName;
+ this.BoundSinkEntityName = fieldSrc.BoundSinkEntityName;
+ this.NodeJoinField = fieldSrc.NodeJoinField?.Clone() as SingleField;
+ this.RelSinkJoinField = fieldSrc.RelSinkJoinField?.Clone() as SingleField;
+ this.RelSourceJoinField = fieldSrc.RelSourceJoinField?.Clone() as SingleField;
+ this.EncapsulatedFields = fieldSrc.EncapsulatedFields?.Select(f => f.Clone() as SingleField).ToList();
+ _referencedAliases.Clear();
+ this.AddReferenceFieldAliases(fieldSrc.ReferencedFieldAliases?.ToList());
+ }
+
+ public enum EntityType
+ {
+ Node,
+ Relationship
+ }
+
+ ///
+ /// For node this is node name, for edge this is the edge verb
+ ///
+ public string EntityName { get; set; }
+
+ ///
+ /// This is the name of the entity that this is eventually bound to in the graph definition
+ /// For node, it is usually same as EntityName, but for edge, it is the unique entity name
+ /// from the graph definition which is in the format of node_in-verb-node_out
+ ///
+ public string BoundEntityName { get; set; }
+
+ ///
+ /// This is the name of the source entity name for this relationship (in the graph definition)
+ /// Only non null for edge type of entity
+ ///
+ public string BoundSourceEntityName { get; set; }
+
+ ///
+ /// This is the name of the sink entity name for this relationship (in the graph definition)
+ /// Only non null for edge type of entity
+ ///
+ public string BoundSinkEntityName { get; set; }
+
+ ///
+ /// If this entity is node or edge
+ ///
+ public EntityType Type { get; set; }
+
+ ///
+ /// List of fields that this entity can expand into (namely, property reference)
+ ///
+ public IList EncapsulatedFields { get; set; }
+
+ ///
+ /// Used for keep tracking what properties of this entity were actually accessed
+ ///
+ public IReadOnlyCollection ReferencedFieldAliases { get { return _referencedAliases.ToList().AsReadOnly(); } }
+
+ ///
+ /// The field name for the node's join key
+ ///
+ public SingleField NodeJoinField { get; set; }
+
+ ///
+ /// The field name for the edge's source join key
+ ///
+ public SingleField RelSourceJoinField { get; set; }
+
+ ///
+ /// The field name for the relationship's sink join key
+ ///
+ public SingleField RelSinkJoinField { get; set; }
+
+
+ public override string ToString()
+ {
+ return $"{Type}Field: {FieldAlias}({EntityName})";
+ }
+
+ public override Field Clone()
+ {
+ return new EntityField(this);
+ }
+
+ public void AddReferenceFieldAlias(string fieldAlias)
+ {
+ if(EncapsulatedFields.All(n => n.FieldAlias != fieldAlias))
+ {
+ throw new TranspilerSyntaxErrorException($"field alias {fieldAlias} not existed in entity:{EntityName}.");
+ }
+ else if(!_referencedAliases.Contains(fieldAlias))
+ {
+ _referencedAliases.Add(fieldAlias);
+ }
+ }
+
+ public void AddReferenceFieldAliases(IEnumerable fieldAliases)
+ {
+ fieldAliases.ToList().ForEach(a => AddReferenceFieldAlias(a));
+ }
+
+ }
+
+ ///
+ /// A field which is a column
+ ///
+ public class SingleField : Field
+ {
+ public SingleField(string alias) : base(alias) { }
+
+ ///
+ /// Copy constructor
+ ///
+ ///
+ public SingleField(SingleField field) : base(field.FieldAlias)
+ {
+ Copy(field);
+ }
+
+ public SingleField(string alias, Type fieldType) : base(alias)
+ {
+ FieldType = fieldType;
+ }
+
+ public override void Copy(Field field)
+ {
+ var fieldSrc = field as SingleField;
+ Debug.Assert(fieldSrc != null);
+ this.FieldType = fieldSrc.FieldType;
+ }
+
+ public Type FieldType { get; set; }
+
+
+ public override string ToString()
+ {
+ return $"Field: {FieldAlias}{(FieldType == default(Type) ? "(?)" : $"({FieldType.Name})")}";
+ }
+
+ public override Field Clone()
+ {
+ return new SingleField(this);
+ }
+ }
+
+
+ ///
+ /// a collection of fields represent the schema of input or output of a logical operator
+ ///
+ public class Schema : List
+ {
+ public Schema()
+ {
+ }
+
+ ///
+ /// Copy constructor
+ ///
+ ///
+ public Schema(Schema schema)
+ {
+ AddRange(schema.Select(f => f.Clone()).ToList());
+ }
+
+ public Schema(IEnumerable fields)
+ {
+ AddRange(fields.ToList());
+ }
+
+ }
+}
diff --git a/src/LogicalPlanner/LogicalPlan.cs b/src/LogicalPlanner/LogicalPlan.cs
new file mode 100644
index 0000000..0c356f7
--- /dev/null
+++ b/src/LogicalPlanner/LogicalPlan.cs
@@ -0,0 +1,1099 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using openCypherTranspiler.Common.Exceptions;
+using openCypherTranspiler.Common.GraphSchema;
+using openCypherTranspiler.Common.Logging;
+using openCypherTranspiler.Common.Utils;
+using openCypherTranspiler.openCypherParser.AST;
+using openCypherTranspiler.LogicalPlanner.Utils;
+
+namespace openCypherTranspiler.LogicalPlanner
+{
+ public class LogicalPlan
+ {
+ private ILoggable _logger;
+
+ public LogicalPlan(ILoggable logger)
+ {
+ _logger = logger;
+ }
+
+ ///
+ /// return a set of operators that are starting points of the logical plan
+ ///
+ public IEnumerable StartingOperators { get; private set; }
+
+ ///
+ /// return a set of operators that are terminal of the logical plan (representing the output)
+ ///
+ public IEnumerable TerminalOperators { get; private set; }
+
+ ///
+ /// Create an instance of LogicalPlanner with a given query AST tree
+ ///
+ ///
+ ///
+ ///
+ public static LogicalPlan ProcessQueryTree(QueryTreeNode treeRoot, IGraphSchemaProvider graphDef, ILoggable logger = null)
+ {
+ var logicalPlanner = new LogicalPlan(logger);
+ var allLogicalOps = new HashSet();
+
+ // from AST, create the logical plan
+ var logicalRoot = logicalPlanner.CreateLogicalTree(treeRoot, allLogicalOps);
+ logicalPlanner.StartingOperators = logicalRoot.GetAllUpstreamOperatorsOfType().ToList();
+ logicalPlanner.TerminalOperators = new List(1) { logicalRoot };
+
+ // bind logical tree with the graph schema
+ var bindableStartingOp = logicalPlanner.StartingOperators.Where(op => op is IBindable);
+ bindableStartingOp.ToList().ForEach(op => (op as IBindable).Bind(graphDef));
+
+ // propagate down the schema from starting operator and data types (top down)
+ logicalPlanner.PropagateDataTypes();
+
+ // expand and optimize out columns not used (bottom up)
+ logicalPlanner.UpdateActualFieldReferencesForEntityFields();
+
+ // note: in this function, we only deal with graph definition.
+ // later in code generatation phase, we deal with Cosmos storage descriptors
+ // which then worries the details of what streams to pull
+
+ // verify that no dangling operator exists
+ var i = 0;
+ foreach (var op in allLogicalOps)
+ {
+ op.OperatorDebugId = ++i;
+ }
+
+ var allOpsFromTraversal = logicalPlanner.StartingOperators.SelectMany(op => op.GetAllDownstreamOperatorsOfType()).Distinct().OrderBy(op => op.OperatorDebugId).ToList();
+ var allOpsFromBuildingTree = allLogicalOps.OrderBy(op => op.OperatorDebugId).ToList();
+ Debug.Assert(allOpsFromTraversal.Count == allOpsFromBuildingTree.Count);
+
+ return logicalPlanner;
+ }
+
+ ///
+ /// Dump textual format of the logical plan
+ ///
+ ///
+ public virtual string DumpGraph()
+ {
+ var sb = new StringBuilder();
+ var allOperators = StartingOperators
+ .SelectMany(op => op.GetAllDownstreamOperatorsOfType())
+ .Distinct()
+ .GroupBy(op => op.Depth)
+ .OrderBy(g => g.Key);
+
+ foreach (var opGrp in allOperators)
+ {
+ sb.AppendLine($"Level {opGrp.Key}:");
+ sb.AppendLine("--------------------------------------------------------------------------");
+ foreach (var op in opGrp)
+ {
+ sb.AppendLine($"OpId={op.OperatorDebugId} Op={op.GetType().Name}; InOpIds={string.Join(",", op.InOperators.Select(n => n.OperatorDebugId))}; OutOpIds={string.Join(",", op.OutOperators.Select(n => n.OperatorDebugId))};");
+ sb.AppendLine(op.ToString().ChangeIndentation(1));
+ sb.AppendLine("*");
+ }
+ sb.AppendLine("--------------------------------------------------------------------------");
+ }
+ return sb.ToString();
+ }
+
+ ///
+ /// Create logical operator tree for a given query AST tree
+ ///
+ ///
+ ///
+ private LogicalOperator CreateLogicalTree(QueryTreeNode treeNode, ISet allLogicalOps)
+ {
+ // Example of logical tree to be created:
+ // MATCH (a:n1)-[b:r1]-(c:n2)
+ // WITH a as c, c as a
+ // OPTIONAL MATCH (c)-[:r2]-(a)
+ // WHERE a.id = "some id"
+ // RETURN a.id as Id
+ //
+ // The logical tree looks like:
+ // DS(a:n1) DS(b:r1) DS(c:n2)
+ // \ / /
+ // \ / /
+ // InJoin(a:n1, b:r1) /
+ // \ /
+ // \ /
+ // InJoin(InJoin(...), c:n2)
+ // |
+ // Projection(a->c, c->a) DS(:r2)
+ // \ /
+ // \ /
+ // LfJoin(Proj, :r2)
+ // |
+ // Sel(a.id = 'some id')
+ // |
+ // Projection(a.id -> Id)
+
+
+ // inorder walk of the tree to evaulate individual queries
+ if (treeNode is SingleQueryTreeNode)
+ {
+ // single query
+ return CreateLogicalTree(treeNode as SingleQueryTreeNode, allLogicalOps);
+ }
+ else
+ {
+ Debug.Assert(treeNode is InfixQueryTreeNode);
+ return CreateLogicalTree(treeNode as InfixQueryTreeNode, allLogicalOps);
+ }
+ }
+
+ ///
+ /// Create logical oeprator tree for sub-tree node type InfixQueryTreeNode
+ ///
+ ///
+ ///
+ private BinaryLogicalOperator CreateLogicalTree(InfixQueryTreeNode treeNode, ISet allLogicalOps)
+ {
+ var left = CreateLogicalTree(treeNode.LeftQueryTreeNode, allLogicalOps);
+ var right = CreateLogicalTree(treeNode.RightQueryTreeNode, allLogicalOps);
+
+ // TODO: better way of ensure schema is the same
+ Debug.Assert(left.OutputSchema.Count == left.OutputSchema.Count);
+
+ SetOperator.SetOperationType op;
+ switch (treeNode.Operator)
+ {
+ case InfixQueryTreeNode.QueryOperator.Union:
+ op = SetOperator.SetOperationType.Union;
+ break;
+ case InfixQueryTreeNode.QueryOperator.UnionAll:
+ op = SetOperator.SetOperationType.UnionAll;
+ break;
+ default:
+ throw new TranspilerInternalErrorException($"Unimplemented operator type: {treeNode.Operator}");
+ }
+
+ var setOp = new SetOperator(left, right, op);
+ setOp.InputSchema = new Schema(left.InputSchema);
+ setOp.OutputSchema = new Schema(left.InputSchema); // the schema remains the same for setoperator
+ allLogicalOps.Add(setOp);
+
+ return setOp;
+ }
+
+ ///
+ /// Create logical operator for AST node type SingleQueryTreeNode
+ ///
+ ///
+ ///
+ private LogicalOperator CreateLogicalTree(SingleQueryTreeNode treeNode, ISet allLogicalOps)
+ {
+ var stack = new Stack();
+ PartialQueryTreeNode lastVisited = treeNode.PipedData;
+
+ while (lastVisited != null)
+ {
+ stack.Push(lastVisited);
+ lastVisited = lastVisited.PipedData;
+ }
+
+ // maintain a list of query parts that we may collapse for optimization
+ var pureMatchChainQueries = new List();
+ // maintain a pointer to the last logical operator that need to chained into next operator to be created
+ LogicalOperator lastOperator = null;
+ PartialQueryTreeNode curNode = null;
+ PartialQueryTreeNode prevNode = null;
+ var finalProjectedExprs = treeNode.EntityPropertySelected;
+ while (stack.Any())
+ {
+ prevNode = curNode;
+ curNode = stack.Pop();
+ lastOperator = CreateLogicalTree(curNode, lastOperator, allLogicalOps, prevNode);
+ }
+
+ // chain with another selection operator there are ORDER BY + LIMIT combination clause
+ // Single ORDER BY or Single LIMIT will not be compiled in SCOPE, skipped here
+ if (treeNode.EntityRowsLimit?.Count() > 0 && treeNode.EntityPropertyOrderBy?.Count() > 0)
+ {
+ var limitExpr = treeNode.EntityRowsLimit;
+ var orderExpr = treeNode.EntityPropertyOrderBy;
+ lastOperator = CreateLogicalTree(orderExpr, limitExpr, lastOperator, allLogicalOps);
+
+ // TODO: Add more expression that came from previous partial query tree node
+ var appendedProjExpr = treeNode.EntityPropertySelected;
+ var newProjExpr = curNode?.ProjectedExpressions?.Where(n => appendedProjExpr.All(p => p.Alias != n.Alias));
+
+ // flag true if any selected properties has aggregation function.
+ var hasAggregationExpression = false;
+ if (treeNode.EntityPropertySelected.Any(n => n.GetChildrenQueryExpressionType().Count() > 0))
+ {
+ hasAggregationExpression = true;
+ }
+
+ appendedProjExpr = (newProjExpr == null || treeNode.IsDistinct || hasAggregationExpression? appendedProjExpr : appendedProjExpr.Union(newProjExpr).ToList());
+ lastOperator = CreateLogicalTree(appendedProjExpr, treeNode.IsDistinct, lastOperator, allLogicalOps);
+
+ finalProjectedExprs = finalProjectedExprs.Select(n =>
+ {
+ var property = n.GetChildrenQueryExpressionType().FirstOrDefault();
+ if (property?.Entity == null)
+ {
+ return new QueryExpressionWithAlias
+ {
+ Alias = n.Alias,
+ InnerExpression = new QueryExpressionProperty()
+ {
+ VariableName = n.Alias,
+ DataType = null,
+ Entity = null,
+ PropertyName = null
+ }
+ };
+ }
+ else
+ {
+ return n;
+ }
+ }).ToList();
+
+ // if distinct case, we currently don't support entity field in the order by statement.
+ // For example:
+ // RETURN DISTINCT a.Name as Name, b.Address as Address
+ // ORDER BY a.Name
+ // LIMIT 10
+ if ((treeNode.IsDistinct || hasAggregationExpression) && orderExpr.Any(n =>
+ {
+ var properties = n.GetChildrenQueryExpressionType();
+ if(properties != null && properties.Any(k => k.PropertyName!=null && k.VariableName != null))
+ {
+ return true;
+ }
+ return false;
+ }))
+ {
+ throw new TranspilerNotSupportedException("ORDER BY X.XXX under RETURN DISTINCT clause/RETURN with aggregation function");
+ }
+
+ }
+
+ // add the final projection operator for the fields select in the wrapping Single Query tree node, which
+ // has only Projection and PipedData
+ Debug.Assert(lastOperator != null);
+ Debug.Assert((treeNode.EntityPropertySelected?.Count ?? 0) > 0);
+
+ // Block the attempt to return entities as we don't support this anywhere today
+ if (treeNode.EntityPropertySelected.Any(p => p.TryGetDirectlyExposedEntity() != null))
+ {
+ throw new TranspilerNotSupportedException
+ ($"Query final return body returns the whole entity {treeNode.EntityPropertySelected.First(p => p.TryGetDirectlyExposedEntity() != null).Alias} instead of its fields");
+ }
+
+ var finalProjOperator = CreateLogicalTree(finalProjectedExprs, treeNode.IsDistinct, lastOperator, allLogicalOps);
+ return finalProjOperator;
+ }
+
+ ///
+ /// Create filtering operator for query expressions
+ ///
+ ///
+ ///
+ ///
+ ///
+ private LogicalOperator CreateLogicalTree(QueryExpression exp, LogicalOperator pipedData, ISet allLogicalOps)
+ {
+ var selectionOp = new SelectionOperator(pipedData, exp);
+ selectionOp.InputSchema = new Schema(pipedData.OutputSchema);
+ selectionOp.OutputSchema = new Schema(pipedData.OutputSchema); // the schema remains the same for SelectionOperator
+ allLogicalOps.Add(selectionOp);
+ return selectionOp;
+ }
+
+ ///
+ /// creating selection operator for ORDER BY ... LIMIT clause
+ ///
+ ///
+ ///
+ ///
+ ///
+ private LogicalOperator CreateLogicalTree(
+ IList orderExp,
+ IList limitExp,
+ LogicalOperator pipedData,
+ ISet allLogicalOps)
+ {
+ var selectionOp = new SelectionOperator(pipedData, orderExp, limitExp);
+ var selectionSchema = new Schema(pipedData.OutputSchema);
+
+ // append new property from ORDER BY clause to the the input and output schema
+
+ selectionOp.InputSchema = new Schema(selectionSchema);
+ selectionOp.OutputSchema = new Schema(selectionSchema);
+ allLogicalOps.Add(selectionOp);
+ return selectionOp;
+ }
+
+ ///
+ /// Create filtering operator for unexpanded inequality expression
+ ///
+ ///
+ ///
+ ///
+ ///
+ private LogicalOperator CreateLogicalTree(IEnumerable<(string RelAlias1, string RelAlias2)> conds, LogicalOperator pipedData, ISet allLogicalOps)
+ {
+ var selectionOp = new SelectionOperator(pipedData, conds);
+ selectionOp.InputSchema = new Schema(pipedData.OutputSchema);
+ selectionOp.OutputSchema = new Schema(pipedData.OutputSchema); // the schema remains the same for SelectionOperator
+ allLogicalOps.Add(selectionOp);
+ return selectionOp;
+ }
+
+ ///
+ /// Create projection operator
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private LogicalOperator CreateLogicalTree(IList projExprs, bool isDistinct, LogicalOperator pipedData, ISet allLogicalOps)
+ {
+ // Do a projection
+ var outputSchemaFields = pipedData.OutputSchema.OrderBy(f => f.FieldAlias);
+ var expectedOutputSchemaFields = projExprs.Select(expr =>
+ {
+ var innerExpr = expr.InnerExpression;
+ var entity = innerExpr.TryGetDirectlyExposedEntity();
+ if (entity != null)
+ {
+ return new EntityField(
+ expr.Alias,
+ entity.EntityName,
+ entity is NodeEntity ? EntityField.EntityType.Node : EntityField.EntityType.Relationship
+ ) as Field;
+ }
+ else
+ {
+ return new SingleField(
+ expr.Alias
+ ) as Field;
+ }
+ }).ToList();
+
+ // TODO:
+ // verify that we are able to honor the projection by comparing In and OutFields
+
+ // construct the projection logical operator
+ var inSchema = new Schema(pipedData.OutputSchema.Select(f => f.Clone()));
+ var outSchema = new Schema(expectedOutputSchemaFields);
+ var projOperator = new ProjectionOperator(
+ pipedData,
+ projExprs.ToDictionary(exp => exp.Alias, exp => exp.InnerExpression),
+ isDistinct
+ );
+ projOperator.InputSchema = inSchema;
+ projOperator.OutputSchema = outSchema;
+ allLogicalOps.Add(projOperator);
+
+ return projOperator;
+ }
+
+ ///
+ /// Create join operator to join two set of operators with a list of entity aliases of instances of node which their id should be used in equi-join
+ ///
+ ///
+ ///
+ /// the list of aliases (in the output of left/right) that represents entity instances and should be used in nodeid equi-join
+ /// if false, the join type assumed to be would be inner instead
+ ///
+ ///
+ private LogicalOperator CreateLogicalTree(LogicalOperator left, LogicalOperator right, IEnumerable entityAliasesToJoin, bool isLeftOutterJoin, ISet allLogicalOps)
+ {
+ var joinPairs = entityAliasesToJoin
+ .Select(a => new JoinOperator.JoinKeyPair()
+ {
+ NodeAlias = a,
+ RelationshipOrNodeAlias = a,
+ Type = JoinOperator.JoinKeyPair.JoinKeyPairType.NodeId
+ })
+ .ToList();
+
+ if (joinPairs.Count > 0)
+ {
+ // left outter join or inner join (depending on the parameter)
+ var newOp = new JoinOperator(left, right, isLeftOutterJoin ? JoinOperator.JoinType.Left : JoinOperator.JoinType.Inner);
+ var fields = left.OutputSchema.Union(right.OutputSchema)
+ .Select(f => f.Clone())
+ .GroupBy(f => f.FieldAlias)
+ .Select(f => f.First());
+ newOp.InputSchema = new Schema(fields);
+ newOp.OutputSchema = new Schema(fields);
+ joinPairs.ForEach(jp => newOp.AddJoinPair(jp));
+ allLogicalOps.Add(newOp);
+ return newOp;
+ }
+ else
+ {
+ // cross join
+ var newOp = new JoinOperator(left, right, JoinOperator.JoinType.Cross);
+ var fields = left.OutputSchema.Union(right.OutputSchema)
+ .Select(f => f.Clone())
+ .GroupBy(f => f.FieldAlias)
+ .Select(f => f.First());
+ newOp.InputSchema = new Schema(fields);
+ newOp.OutputSchema = new Schema(fields);
+ allLogicalOps.Add(newOp);
+ return newOp;
+ }
+ }
+
+ ///
+ /// Create logical operators for a partial query part
+ ///
+ ///
+ ///
+ ///
+ ///
+ private LogicalOperator CreateLogicalTree(PartialQueryTreeNode treeNode, LogicalOperator pipedData, ISet allLogicalOps, PartialQueryTreeNode previousNode)
+ {
+ LogicalOperator lastOperator = pipedData;
+
+ if (treeNode.MatchData != null)
+ {
+ // comes with additional Match statements
+ var isOptionalMatch = treeNode.MatchData.MatchPatterns.Any(p => p.IsOptionalMatch);
+ Debug.Assert(treeNode.MatchData.MatchPatterns.All(p => p.IsOptionalMatch == isOptionalMatch));
+
+ if (treeNode.PostCondition != null)
+ {
+ // with filtering conditions, optional match requires a fork to apply filtering first and then rejoin with piped data,
+ // where non-optional doesn't require it
+ // e.g. MATCH ... OPTIONAL MATCH ... WHERE ... WITH/RETURN ...
+ if (isOptionalMatch)
+ {
+ // apply filtering conditions to the optional match pattern then left outter join back with the piped data
+ var modifiedMatch = new MatchDataSource()
+ {
+ MatchPatterns = treeNode.MatchData.MatchPatterns.Select(p => new MatchPattern(false, p)).ToList()
+ };
+ var omOperator = CreateLogicalTree(modifiedMatch, lastOperator, allLogicalOps);
+ var condOmOperator = CreateLogicalTree(treeNode.PostCondition, omOperator, allLogicalOps);
+
+ var leftEntityAliases = lastOperator.OutputSchema
+ .Where(f => f is EntityField).Select(f => (f as EntityField).FieldAlias);
+ var explicitlyMatchedAliasesToJoinWithLeft = modifiedMatch.AllEntitiesOrdered
+ .Select(e => e.Alias)
+ .Where(a => a != null)
+ .Intersect(leftEntityAliases);
+ lastOperator = CreateLogicalTree(lastOperator, condOmOperator, explicitlyMatchedAliasesToJoinWithLeft, true, allLogicalOps);
+ }
+ else
+ {
+ // join with match data
+ lastOperator = CreateLogicalTree(treeNode.MatchData, lastOperator, allLogicalOps);
+ // apply filtering
+ lastOperator = CreateLogicalTree(treeNode.PostCondition, lastOperator, allLogicalOps);
+ }
+ }
+ else
+ {
+ // without filtering conditions, optional and non-optional match are processed similarly (except for the join type)
+ // e.g. MATCH ... OPTIONAL MATCH ... WITH/RETURN ...
+ lastOperator = CreateLogicalTree(treeNode.MatchData, lastOperator, allLogicalOps);
+ }
+ // Do projection
+ lastOperator = CreateLogicalTree(treeNode.ProjectedExpressions, treeNode.IsDistinct, lastOperator, allLogicalOps);
+ }
+ else
+ {
+ // Order By need to pair with limit in order to compilable in scope
+ var isValidOrderByClausePair = (treeNode.LimitExpression?.Count() > 0 && treeNode.OrderByExpression?.Count() > 0);
+ var isExtraCollectionProjectionNeeded = (treeNode.PostCondition != null || isValidOrderByClausePair);
+ var appendedProjExpr = treeNode.ProjectedExpressions;
+ var hasAggregationExpression = false;
+
+ if (treeNode.ProjectedExpressions.SelectMany(n => n.GetChildrenQueryExpressionType()).Count() > 0)
+ {
+ hasAggregationExpression = true;
+ }
+
+ // "DISTINCT" acted like another "WITH" clause: downstream ORDER BY, WHERE can only use field from WITH clause
+ // if no DISTINCT, collect field information from previous tree node
+ // if has DISTINCT, no need to collect from previous one for the behavior of DISTINCT descrived above
+ // Example:
+ // WITH DISTINCT a.Name as Name, b.Title as Title
+ // ORDER BY Name, Title
+ // WHERE Name <> "Tom"
+ // RETURN Name, Title
+ // ** ORDER BY/WHERE can only refer Name, Title as there is a DISTINCT
+ //
+ // if no distinct
+ // WITH a.Name as Name, b.Title as Title
+ // ORDER BY a.Tagline, n.Price
+ // WHERE Name <> "Tom"
+ // RETURN Name, Title
+ // ** ORDER BY/WHERE can refer any field in a and b
+
+
+ if (!treeNode.IsDistinct && previousNode != null && !hasAggregationExpression)
+ {
+ var newProjExpr = previousNode.ProjectedExpressions.Where(n => appendedProjExpr.All(p =>p.Alias!= n.Alias));
+ appendedProjExpr = (newProjExpr== null? appendedProjExpr: appendedProjExpr.Union(newProjExpr).ToList());
+ lastOperator = CreateLogicalTree(appendedProjExpr, treeNode.IsDistinct, lastOperator, allLogicalOps);
+ }
+ else
+ {
+ lastOperator = CreateLogicalTree(treeNode.ProjectedExpressions, treeNode.IsDistinct, lastOperator, allLogicalOps);
+ }
+
+ // Do trimming the non-entity field by removing redundant calculation and changed it to Alias AS Alias
+ // The reason for trimming: related calculation was already done in previous steps. Only need to refer the alias and return.
+ var trimmedProjExpr = appendedProjExpr.Select(n =>
+ {
+ var property = n.GetChildrenQueryExpressionType().FirstOrDefault();
+ if (property?.Entity == null)
+ {
+ return new QueryExpressionWithAlias
+ {
+ Alias = n.Alias,
+ InnerExpression = new QueryExpressionProperty()
+ {
+ VariableName = n.Alias,
+ DataType = null,
+ Entity = null,
+ PropertyName = null
+ }
+ };
+ }
+ else
+ {
+ return n;
+ }
+ }).ToList();
+
+ // chain with another selection operator there are ORDER BY + LIMIT combination clause
+ // Single ORDER BY or Single LIMIT will not be compiled in SCOPE, skipped here
+ if (isValidOrderByClausePair)
+ {
+ var limitExpr = treeNode.LimitExpression;
+ var orderExpr = treeNode.OrderByExpression;
+ lastOperator = CreateLogicalTree(orderExpr, limitExpr, lastOperator, allLogicalOps);
+ // projection operator "after order by" selection operator
+ lastOperator = CreateLogicalTree(trimmedProjExpr, treeNode.IsDistinct, lastOperator, allLogicalOps);
+ }
+
+ if (treeNode.PostCondition != null)
+ {
+ // without match, we can just apply filtering if needed (e.g. WITH ... WHERE ...)
+ lastOperator = CreateLogicalTree(treeNode.PostCondition, lastOperator, allLogicalOps);
+ // projection operator after "where" selection operator
+ lastOperator = CreateLogicalTree(trimmedProjExpr, treeNode.IsDistinct, lastOperator, allLogicalOps);
+ }
+ // Do the last projection in the WITH statement.
+ // Since all the schema was already mapped to the target aliases in the previous projection operators, only direct mapping needed.
+ // target -> target
+ // eg. a.Name as Name : Name -> Name
+ // a as b: b -> b (at here 'a' can be a entity or can be a property.
+ var finalProjExpr = treeNode.ProjectedExpressions.Select(n =>
+ {
+ var property = n.GetChildrenQueryExpressionType().FirstOrDefault();
+ return new QueryExpressionWithAlias
+ {
+ Alias = n.Alias,
+ InnerExpression = new QueryExpressionProperty()
+ {
+ VariableName = n.Alias,
+ DataType = null,
+ Entity = property?.Entity,
+ PropertyName = null
+ }
+ };
+ }).ToList();
+
+ lastOperator = CreateLogicalTree(finalProjExpr, treeNode.IsDistinct, lastOperator, allLogicalOps);
+ }
+ return lastOperator;
+ }
+
+ ///
+ /// This function takes 2 entities and returns in a determinstic order of their aliases:
+ /// It guarantees that the order is that if node is the src of edge, then [node, edge]
+ /// otherwise [edge, node]
+ ///
+ ///
+ ///
+ ///
+ private JoinOperator.JoinKeyPair GetJoinKeyPairInProperOrder(Entity prevEnt, Entity ent)
+ {
+ Debug.Assert(prevEnt.GetType() != ent.GetType()); // must be one node and one relationship
+ var relEntity = (prevEnt is RelationshipEntity ? prevEnt : ent) as RelationshipEntity;
+ var nodeEntity = (prevEnt is RelationshipEntity ? ent : prevEnt) as NodeEntity;
+ JoinOperator.JoinKeyPair.JoinKeyPairType joinKeyType;
+
+ if (relEntity.RelationshipDirection == RelationshipEntity.Direction.Both)
+ {
+ if (relEntity.LeftEntityName == relEntity.RightEntityName)
+ {
+ // TODO: in future, we can support this and set the joinKeyType to .Both. This requires
+ // we add support in codegen to produce a data source that has src/sink key reversed unioned with itself
+ throw new TranspilerNotSupportedException("Consider specifying the direction of traversal <-[]- or -[]->. Directionless traversal for relationship with same type of source and sink entity");
+ }
+ joinKeyType = JoinOperator.JoinKeyPair.JoinKeyPairType.Either;
+ }
+ else
+ {
+ if (prevEnt == nodeEntity)
+ {
+ joinKeyType = relEntity.RelationshipDirection == RelationshipEntity.Direction.Forward ?
+ JoinOperator.JoinKeyPair.JoinKeyPairType.Source :
+ JoinOperator.JoinKeyPair.JoinKeyPairType.Sink;
+ }
+ else
+ {
+ joinKeyType = relEntity.RelationshipDirection == RelationshipEntity.Direction.Forward ?
+ JoinOperator.JoinKeyPair.JoinKeyPairType.Sink :
+ JoinOperator.JoinKeyPair.JoinKeyPairType.Source;
+ }
+ }
+
+ return new JoinOperator.JoinKeyPair()
+ {
+ NodeAlias = nodeEntity.Alias,
+ RelationshipOrNodeAlias = relEntity.Alias,
+ Type = joinKeyType
+ };
+ }
+
+ ///
+ /// Create logical operators for MatchDataSource
+ ///
+ ///
+ ///
+ ///
+ ///
+ private LogicalOperator CreateLogicalTree(MatchDataSource matchData, LogicalOperator pipedData, ISet allLogicalOps)
+ {
+ // turn linear match patterns into logical operators
+
+ var boolComparer = Comparer.Create((b1, b2) => b1 ? (b2 ? 0 : 1) : (!b2 ? 0 : -1)); // comparer is true > false
+ var anonVarPrefix = "__unnamed_";
+
+ // first build a table of alias to entity, and make sure that every entity
+ // reference has a previously given or assigned alias
+ var namedMatchData = matchData.AssignGeneratedEntityAliasToAnonEntities(anonVarPrefix);
+ var aliasGroups = namedMatchData
+ .AllEntitiesOrdered
+ .Select((p, i) => new { Order = i, Entity = p })
+ .GroupBy(p => p.Entity.Alias);
+ var firstDupRelAlias = aliasGroups
+ .Where(g => g.Any(e => e.Entity is RelationshipEntity))
+ .FirstOrDefault(g => g.Count() > 1);
+ if (firstDupRelAlias != null)
+ {
+ // relationship label should not be repeated in the same MATCH clause
+ throw new TranspilerNotSupportedException($"Cannot use the same relationship variable '{firstDupRelAlias.Key}' for multiple patterns");
+ }
+ var aliasTable = aliasGroups
+ .Select(p =>
+ {
+ // Assert that we only have one type of entity per alias
+ Debug.Assert(p.Select(e => e.Entity.EntityName).Distinct().Count() == 1);
+ Debug.Assert(p.Select(e => e.GetType()).Distinct().Count() == 1);
+ return p.First();
+ })
+ .OrderBy(p => p.Order)
+ .Select((p, i) => new { Order = i, Entity = p.Entity })
+ .ToDictionary(kv => kv.Entity.Alias, kv => kv);
+
+ // build an adjancency matrix represent the DAG for the join logical operator each alias in the form of an array[Dim1, Dim2]
+ // note that the list of DataSourceOperator are added to allLogicalOps later in the code after recociling with inherited entities
+ var logicOpSrcTable = aliasTable.ToDictionary(kv => kv.Key, kv => new DataSourceOperator(kv.Value.Entity) as LogicalOperator);
+
+ // build adjancency matrix with element being the join type between the two entities
+ // alias1, alias2, alias3, ... (Dim 1)
+ // alias1 NA INNER LEFT
+ // alias2 NA LEFT
+ // alias3 NA
+ // ...
+ // (Dim 2)
+ var entityJoinMatrix = new JoinOperator.JoinType[logicOpSrcTable.Count, logicOpSrcTable.Count];
+
+ // build join operator matrix to record the state if logical join operator already created or not
+ // between two alias (this will be used later to construct the logical join op creation)
+ var hasJoined = new bool[logicOpSrcTable.Count, logicOpSrcTable.Count];
+
+ for (int k = 0; k < entityJoinMatrix.GetLength(0); k++)
+ {
+ for (int l = 0; l < entityJoinMatrix.GetLength(1); l++)
+ {
+ entityJoinMatrix[k, l] = (k == l ? JoinOperator.JoinType.Inner : JoinOperator.JoinType.Cross);
+ hasJoined[k, l] = (k == l ? true : false);
+ }
+ }
+
+ // if some of the entity referenced are inherited from previous query, such as following overlapping case
+ // where some entities returned from previous query parts are referenced in current MATCH patterns
+ // MATCH (a)
+ // WITH a as b
+ // MATCH (b)-[c]->(d) ...
+ // we will
+ // - update the logical operator table to replace the DataSource with the inherited logical operator instead of
+ // the default DS(n)
+ // - update hasJoined table to indicate those entities piped in were already in the inherited logical operater
+ // then we will get the transitive closure of the hasJoined table to show what are the group of entities that
+ // are linked together
+ var additionalCrossJoinOp = new List();
+ var inheritedEntityAliases = pipedData?.OutputSchema
+ .Where(f => f is EntityField).Select(f => (f as EntityField).FieldAlias)
+ .Intersect(aliasTable.Keys).ToList();
+ if (pipedData != null)
+ {
+ if (inheritedEntityAliases.Count > 0)
+ {
+ string prevEntAlias = inheritedEntityAliases.First();
+ logicOpSrcTable[prevEntAlias] = pipedData;
+
+ foreach (var entAlias in inheritedEntityAliases.Skip(1))
+ {
+ logicOpSrcTable[entAlias] = pipedData;
+
+ var prevEntityAliasIdx = aliasTable[prevEntAlias].Order;
+ var entityAliasIdx = aliasTable[entAlias].Order;
+ hasJoined[prevEntityAliasIdx, entityAliasIdx] = true;
+ hasJoined[entityAliasIdx, prevEntityAliasIdx] = true;
+ }
+ }
+ else
+ {
+ // there's no overlapping, the piped data will be cross joined
+ additionalCrossJoinOp.Add(pipedData);
+ }
+ }
+ hasJoined = hasJoined.TransitiveClosure(boolComparer);
+ logicOpSrcTable.Values.Where(op => op is DataSourceOperator).Cast().ToList()
+ .ForEach(op =>
+ {
+ op.OutputSchema = new Schema
+ (
+ new List(1)
+ {
+ new EntityField(
+ op.Entity.Alias,
+ op.Entity.EntityName,
+ op.Entity is NodeEntity ? EntityField.EntityType.Node : EntityField.EntityType.Relationship
+ ) as Field
+ }
+ );
+ allLogicalOps.Add(op);
+ });
+
+ // process the matching patterns to update the adjancency matrix
+ var entitiesPartOfNonOptionalMatch = namedMatchData.MatchPatterns
+ .Where(p => p.IsOptionalMatch == false)
+ .SelectMany(p => p)
+ .Select(e => e.Alias)
+ .Union(inheritedEntityAliases ?? Enumerable.Empty())
+ .Distinct();
+ foreach (var matchPattern in namedMatchData.MatchPatterns)
+ {
+ Entity prevEnt = matchPattern.First();
+ int prevEntIdx = aliasTable[prevEnt.Alias].Order;
+
+ foreach (var ent in matchPattern.Skip(1))
+ {
+ var curEntIdx = aliasTable[ent.Alias].Order;
+ // left join only if current is pattern is optional match
+ // and one of the entity already appears in non-optional match
+ // pattern or is inhertied from previous query part
+ bool isLeftJoin =
+ matchPattern.IsOptionalMatch &&
+ (entitiesPartOfNonOptionalMatch.Contains(prevEnt.Alias) != entitiesPartOfNonOptionalMatch.Contains(ent.Alias));
+ switch (entityJoinMatrix[prevEntIdx, curEntIdx])
+ {
+ case JoinOperator.JoinType.Cross:
+ case JoinOperator.JoinType.Left:
+ // if CROSS or LEFT, left it up to LEFT or INNER, respectively
+ entityJoinMatrix[prevEntIdx, curEntIdx] = isLeftJoin ? JoinOperator.JoinType.Left : JoinOperator.JoinType.Inner;
+ entityJoinMatrix[curEntIdx, prevEntIdx] = entityJoinMatrix[prevEntIdx, curEntIdx];
+ break;
+ default:
+ // do nothing if it is INNER, already highest constrained join
+ break;
+ }
+
+ prevEnt = ent;
+ prevEntIdx = aliasTable[ent.Alias].Order;
+ }
+ }
+
+ // get the transitive closure of the DAG
+ var entityJoinMatrixClosure = entityJoinMatrix.TransitiveClosure();
+
+ // we update the logical operator table (logicOpSrcTable) based on the match patterns
+ // we do it in 3 passes:
+ // - inner join first
+ // - left join second
+ // - cross join last
+ //
+ // e.g. Match (a)-[b]-(c), (e), (a)-[d]-(c)
+ // Start:
+ // a, DataSource(a)
+ // b, DataSource(b)
+ // c, DataSource(c)
+ // d, DataSource(d)
+ // e, DataSource(e)
+ // After processing the first MatchPattern: (a)-[b]-(c):
+ // a, Join(Join(DataSource(a), DataSource(b)), DataSource(c))
+ // b, Join(Join(DataSource(a), DataSource(b)), DataSource(c))
+ // c, Join(Join(DataSource(a), DataSource(b)), DataSource(c))
+ // d, DataSource(d)
+ // e, DataSource(e)
+ // After processin the second MatchPattern: (e)
+ // // no change as (e) does not have
+ // After processing the third MatchPattern: (a)-[d]-(c)
+ // a, Join(Join(Join(DataSource(a), DataSource(b)), DataSource(c)), DataSource(d))
+ // b, Join(Join(Join(DataSource(a), DataSource(b)), DataSource(c)), DataSource(d))
+ // c, Join(Join(Join(DataSource(a), DataSource(b)), DataSource(c)), DataSource(d))
+ // d, Join(Join(Join(DataSource(a), DataSource(b)), DataSource(c)), DataSource(d))
+ // e, DataSource(e)
+ // Post processing by cross product any disjoint parts:
+ // a, CrossJoin(Join(Join(Join(DataSource(a), DataSource(b)), DataSource(c)), DataSource(d)), DataSource(e))
+ // b, CrossJoin(Join(Join(Join(DataSource(a), DataSource(b)), DataSource(c)), DataSource(d)), DataSource(e))
+ // c, CrossJoin(Join(Join(Join(DataSource(a), DataSource(b)), DataSource(c)), DataSource(d)), DataSource(e))
+ // d, CrossJoin(Join(Join(Join(DataSource(a), DataSource(b)), DataSource(c)), DataSource(d)), DataSource(e))
+ // e, CrossJoin(Join(Join(Join(DataSource(a), DataSource(b)), DataSource(c)), DataSource(d)), DataSource(e))
+
+ // get a list of unique field joins need to be performed from the traversals
+ var joinPairs = new Dictionary, JoinOperator.JoinKeyPair>();
+ foreach (var matchPattern in namedMatchData.MatchPatterns)
+ {
+ for (var patIdx = 1; patIdx < matchPattern.Count; patIdx++)
+ {
+ // the order is that if node is the src of edge, then [node, edge]
+ // otherwise [edge, node]
+ Entity prevEnt = matchPattern[patIdx-1];
+ var ent = matchPattern[patIdx];
+ var joinPair = GetJoinKeyPairInProperOrder(prevEnt, ent);
+ var key = new KeyValuePair(joinPair.NodeAlias, joinPair.RelationshipOrNodeAlias);
+ JoinOperator.JoinKeyPair existingJoinPair;
+ if (joinPairs.TryGetValue(key, out existingJoinPair))
+ {
+ // update/validate if already observed the same join and maybe of differen type
+ // update paths:
+ // either -> { left, right, both }
+ // left -> {both}
+ // right -> {both}
+ // all others are invalid if not equal
+ if (existingJoinPair.Type != joinPair.Type)
+ {
+ if (existingJoinPair.Type == JoinOperator.JoinKeyPair.JoinKeyPairType.Either ||
+ joinPair.Type == JoinOperator.JoinKeyPair.JoinKeyPairType.Both)
+ {
+ joinPairs[key] = joinPair;
+ }
+ else
+ {
+ // conflict
+ throw new TranspilerBindingException($"Conflicting traversal detected between '{joinPair.NodeAlias}' and '{joinPair.RelationshipOrNodeAlias}'");
+ }
+ }
+ }
+ else
+ {
+ joinPairs.Add(key, joinPair);
+ }
+ }
+ }
+
+ // pass 'inner join' then pass 'left join'
+ for (var passType = JoinOperator.JoinType.Inner; passType >= JoinOperator.JoinType.Left; passType--)
+ {
+ foreach (var matchPattern in namedMatchData.MatchPatterns)
+ {
+ Entity prevEnt = matchPattern.First();
+ int prevEntIdx = aliasTable[prevEnt.Alias].Order;
+
+ for (var patIdx = 1; patIdx < matchPattern.Count; patIdx++)
+ {
+ var ent = matchPattern[patIdx];
+ var curEntIdx = aliasTable[ent.Alias].Order;
+ var joinType = entityJoinMatrixClosure[prevEntIdx, curEntIdx];
+ var joinPair = GetJoinKeyPairInProperOrder(prevEnt, ent);
+
+ if (hasJoined[prevEntIdx, curEntIdx] || joinType < passType)
+ {
+ // skip if already joined or passtype delays the join to later pass
+ }
+ else
+ {
+ // create a new join operator, with the left side operator
+ // be the left side of the LEFT OUTTER JOIN if it is outter join (inner join order doesn't matter)
+ var leftOp = logicOpSrcTable[prevEnt.Alias];
+ var rightOp = logicOpSrcTable[ent.Alias];
+ var newOp = new JoinOperator(
+ leftOp,
+ rightOp,
+ entityJoinMatrixClosure[prevEntIdx, curEntIdx] // join type
+ );
+ var fields = leftOp.OutputSchema.Union(rightOp.OutputSchema)
+ .Select(f => f.Clone())
+ .GroupBy(f => f.FieldAlias)
+ .Select(f => f.First());
+ newOp.InputSchema = new Schema(fields);
+ newOp.OutputSchema = new Schema(fields);
+
+ // caculate join needed by this operator
+ var entAliasesJointTogether = leftOp.OutputSchema.Where(s => s is EntityField).Select(s => s.FieldAlias)
+ .Union(
+ rightOp.OutputSchema.Where(s => s is EntityField).Select(s => s.FieldAlias)
+ ).Distinct();
+ var joinKeysRequiredForThisJoin = joinPairs
+ .Where(kv => entAliasesJointTogether.Contains(kv.Key.Key) && entAliasesJointTogether.Contains(kv.Key.Value))
+ .ToList();
+ joinKeysRequiredForThisJoin.ForEach(kv =>
+ {
+ // add join to this particular operator
+ newOp.AddJoinPair(kv.Value);
+ // remove from unsatisified join key list
+ joinPairs.Remove(kv.Key);
+ });
+
+ // add newly created logical operator to the all logical operator list
+ allLogicalOps.Add(newOp);
+
+ // update the Logical Operator lookup table
+ var aliasToUpdateOp = logicOpSrcTable.Where(kv => kv.Value == leftOp || kv.Value == rightOp).Select(kv => kv.Key).ToList();
+ aliasToUpdateOp.ForEach(a => logicOpSrcTable[a] = newOp);
+
+ // update the fact if two table already jointed not
+ hasJoined[prevEntIdx, curEntIdx] = true;
+ hasJoined[curEntIdx, prevEntIdx] = true;
+ hasJoined = hasJoined.TransitiveClosure(boolComparer);
+ }
+
+ prevEnt = ent;
+ prevEntIdx = aliasTable[ent.Alias].Order;
+ }
+ }
+ }
+
+ // pass 'cross join': all the remaining segments to create the logical operators
+ var joiningSegments = logicOpSrcTable.Values.Union(additionalCrossJoinOp).Distinct();
+ LogicalOperator finalOp = joiningSegments.Count() > 1 ? joiningSegments.Aggregate(
+ (x, delta) =>
+ {
+ var newOp = new JoinOperator(x, delta, JoinOperator.JoinType.Cross);
+ var fields = x.OutputSchema.Union(delta.OutputSchema)
+ .Select(f => f.Clone())
+ .GroupBy(f => f.FieldAlias)
+ .Select(f => f.First());
+ newOp.InputSchema = new Schema(fields);
+ newOp.OutputSchema = new Schema(fields);
+ allLogicalOps.Add(newOp);
+ return newOp;
+ }
+ ) : joiningSegments.First();
+
+ // for any relationships of same type appearing in the same match statement, adding
+ // a condition that it should not be repeated. E.g., for
+ // MATCH (p:Person)-[a1:Acted_In]->(m:Movie)<-[a2:Acted_In]-(p2:Person) ...
+ // we will add a condition that
+ // a1.SrcId <> a2.SrcId AND a1.SinkId <> a2.SinkId
+ var relationshipTypeGroups = namedMatchData
+ .AllEntitiesOrdered
+ .Where(e => e is RelationshipEntity)
+ .Cast()
+ .SelectMany(e =>
+ {
+ switch (e.RelationshipDirection)
+ {
+ case RelationshipEntity.Direction.Forward:
+ return new(string Alias, string EntityFullName)[1] { (e.Alias, $"{e.LeftEntityName}@{e.EntityName}@{e.RightEntityName}") };
+ case RelationshipEntity.Direction.Backward:
+ return new(string Alias, string EntityFullName)[1] { (e.Alias, $"{e.RightEntityName}@{e.EntityName}@{e.LeftEntityName}") };
+ default:
+ Debug.Assert(e.RelationshipDirection == RelationshipEntity.Direction.Both);
+ return new(string Alias, string EntityFullName)[2]
+ {
+ (e.Alias, $"{e.LeftEntityName}@{e.EntityName}@{e.RightEntityName}"),
+ (e.Alias, $"{e.RightEntityName}@{e.EntityName}@{e.LeftEntityName}"),
+ };
+ }
+ })
+ .GroupBy(e => e.EntityFullName)
+ .Where(e => e.Count() > 1)
+ .ToList();
+
+ if (relationshipTypeGroups.Count > 0)
+ {
+ var unexpandedInequalityConditions = relationshipTypeGroups
+ .Aggregate(
+ new List<(string RelAlias1, string RelAlias2)>(),
+ (list, g) => {
+ var cond = g.ToList();
+ for (int i = 0; i < cond.Count-1; i++)
+ {
+ for (int j = i + 1; j < cond.Count; j++)
+ {
+ list.Add((cond[i].Alias, cond[j].Alias));
+ }
+ }
+ return list;
+ }
+ );
+ finalOp = CreateLogicalTree(unexpandedInequalityConditions, finalOp, allLogicalOps);
+ }
+
+ return finalOp;
+ }
+
+ ///
+ /// Evaluate field data types for each operators' output schema (after data schema is bound)
+ ///
+ private void PropagateDataTypes()
+ {
+ // we go top down for each layers in the logical plan to propagate field's data types
+
+ var allOperators = StartingOperators
+ .SelectMany(op => op.GetAllDownstreamOperatorsOfType())
+ .Distinct()
+ .GroupBy(op => op.Depth)
+ .OrderBy(g => g.Key);
+
+ Debug.Assert(allOperators.First().All(op => op is StartLogicalOperator));
+
+ // We assume that first level is taken care off in previous stage (data binding)
+ // We start from second level and down to propagate data types
+ foreach (var opGrp in allOperators.Skip(1))
+ {
+ opGrp.ToList().ForEach(op => op.PropagateSchema());
+ }
+ }
+
+ ///
+ /// Update the list of actual properties that got referenced for each entity field
+ ///
+ private void UpdateActualFieldReferencesForEntityFields()
+ {
+ // we go bottom up to propagate entity fields referenced in return body or condition clauses
+ // that each operator's input/ouput schema must carry
+
+ var allOperators = StartingOperators
+ .SelectMany(op => op.GetAllDownstreamOperatorsOfType())
+ .Distinct()
+ .GroupBy(op => op.Depth)
+ .OrderByDescending(g => g.Key);
+ foreach (var opGrp in allOperators)
+ {
+ opGrp.ToList().ForEach(op => op.PropagateReferencedPropertiesForEntityFields());
+ }
+ }
+ }
+}
diff --git a/src/LogicalPlanner/LogicalPlanner.csproj b/src/LogicalPlanner/LogicalPlanner.csproj
new file mode 100644
index 0000000..4ef5058
--- /dev/null
+++ b/src/LogicalPlanner/LogicalPlanner.csproj
@@ -0,0 +1,12 @@
+
+
+
+ netcoreapp2.1
+ Microsoft.GraphPlatform.Transpiler.LogicalPlanner
+
+
+
+
+
+
+
diff --git a/src/LogicalPlanner/Utils/ArrayExt.cs b/src/LogicalPlanner/Utils/ArrayExt.cs
new file mode 100644
index 0000000..f32e1c6
--- /dev/null
+++ b/src/LogicalPlanner/Utils/ArrayExt.cs
@@ -0,0 +1,90 @@
+// Copyright(c) Microsoft Corporation
+// All rights reserved.
+//
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace openCypherTranspiler.LogicalPlanner.Utils
+{
+ public static class ArrayExt
+ {
+ private static T Max(T x, T y, IComparer comparer)
+ {
+ if (comparer == null)
+ {
+ comparer = Comparer.Default;
+ }
+ return (comparer.Compare(x, y) > 0) ? x : y;
+ }
+
+ private static T Min(T x, T y, IComparer comparer)
+ {
+ if (comparer == null)
+ {
+ comparer = Comparer.Default;
+ }
+ return (comparer.Compare(x, y) < 0) ? x : y;
+ }
+
+ private static T[,] TransitiveClosureOnce(this T[,] graph, IComparer comparer = null) where T : IComparable
+ {
+ Debug.Assert(graph.GetLength(0) == graph.GetLength(1));
+ var dim = graph.GetLength(0);
+
+ var reach = new T[dim, dim];
+ int i, j, k;
+ for (i = 0; i < dim; i++)
+ {
+ for (j = 0; j < dim; j++)
+ {
+ reach[i, j] = graph[i, j];
+ }
+ }
+ for (k = 0; k < dim; k++)
+ {
+ for (i = 0; i < dim; i++)
+ {
+ for (j = 0; j < dim; j++)
+ {
+ reach[i, j] = Max(reach[i, j], Min(reach[i, k], reach[k, j], comparer), comparer);
+ }
+ }
+ }
+
+ return reach;
+ }
+
+ public static T[,] TransitiveClosure(this T[,] graph, IComparer comparer = null) where T : IComparable
+ {
+ return graph.TransitiveClosure(Math.Max(graph.GetLength(0), graph.GetLength(1)), comparer);
+ }
+
+ public static T[,] TransitiveClosure(this T[,] graph, int folds, IComparer comparer = null) where T: IComparable
+ {
+ var reach = graph;
+ for (var i = 0; i < folds; i++)
+ {
+ var newReach = reach.TransitiveClosureOnce(comparer);
+ var equal = Enumerable.Range(0, newReach.Rank).All(dimension => newReach.GetLength(dimension) == reach.GetLength(dimension))
+ && newReach.Cast().SequenceEqual(reach.Cast());
+ if (equal)
+ {
+ return newReach;
+ }
+ else
+ {
+ reach = newReach;
+ }
+ }
+ return reach;
+ }
+ }
+}
diff --git a/src/SQLRenderer/ISQLDBSchemaProvider.cs b/src/SQLRenderer/ISQLDBSchemaProvider.cs
new file mode 100644
index 0000000..84894c5
--- /dev/null
+++ b/src/SQLRenderer/ISQLDBSchemaProvider.cs
@@ -0,0 +1,15 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+using openCypherTranspiler.Common.GraphSchema;
+using System.Collections.Generic;
+
+namespace openCypherTranspiler.SQLRenderer
+{
+ public interface ISQLDBSchemaProvider : IGraphSchemaProvider
+ {
+ SQLTableDescriptor GetSQLTableDescriptors(string entityId);
+ }
+}
diff --git a/src/SQLRenderer/README.md b/src/SQLRenderer/README.md
new file mode 100644
index 0000000..3b50726
--- /dev/null
+++ b/src/SQLRenderer/README.md
@@ -0,0 +1,3 @@
+# SQL Renderer
+
+This project is an example code renderer that produces T-SQL code for parsed openCypher graph query logical plan.
diff --git a/src/SQLRenderer/SQLRenderer.cs b/src/SQLRenderer/SQLRenderer.cs
new file mode 100644
index 0000000..1aa386f
--- /dev/null
+++ b/src/SQLRenderer/SQLRenderer.cs
@@ -0,0 +1,1082 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using openCypherTranspiler.Common.Exceptions;
+using openCypherTranspiler.Common.Logging;
+using openCypherTranspiler.Common.Utils;
+using openCypherTranspiler.LogicalPlanner;
+using openCypherTranspiler.openCypherParser.AST;
+using openCypherTranspiler.openCypherParser.Common;
+
+namespace openCypherTranspiler.SQLRenderer
+{
+ public class SQLRenderer
+ {
+ // Cached graph definition object
+ private readonly ISQLDBSchemaProvider _graphDef;
+
+ // Cached logger
+ private readonly ILoggable _logger;
+
+ // Map from operator type to the pattern need to used to render valid SQL
+ private static readonly IDictionary OperatorRenderPattern = new Dictionary()
+ {
+ { BinaryOperator.Plus, "({0})+({1})" },
+ { BinaryOperator.Minus, "({0})-({1})" },
+ { BinaryOperator.Multiply, "({0})*({1})" },
+ { BinaryOperator.Divide, "({0})/({1})" },
+ { BinaryOperator.Modulo, "({0})%({1})" },
+ { BinaryOperator.Exponentiation, "CAST(POWER({0},{1}) AS float)" },
+
+ { BinaryOperator.AND, "({0}) AND ({1})" },
+ { BinaryOperator.OR, "({0}) OR ({1})" },
+ { BinaryOperator.XOR, "(({0}) AND NOT ({1})) OR (NOT ({0}) AND ({1}))" },
+
+ { BinaryOperator.LT, "({0})<({1})" },
+ { BinaryOperator.LEQ, "({0})<=({1})" },
+ { BinaryOperator.GT, "({0})>({1})" },
+ { BinaryOperator.GEQ, "({0})>=({1})" },
+ { BinaryOperator.EQ, "({0})=({1})" },
+ { BinaryOperator.NEQ, "({0})!=({1})" },
+ { BinaryOperator.REGMATCH, "PATINDEX('%{1}%', {0})" },
+ { BinaryOperator.IN, "({0}) IN {1}" },
+ };
+
+ // Map from operator type for CAST statement when need arises in some situlations such as CASE WHEN
+ private static readonly IDictionary TypeToSQLTypeMapping = new Dictionary()
+ {
+ // https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql-server-data-type-mappings
+ { typeof(int), SqlDbType.Int},
+ { typeof(short), SqlDbType.SmallInt},
+ { typeof(long), SqlDbType.BigInt},
+ { typeof(double), SqlDbType.Float},
+ { typeof(string), SqlDbType.NVarChar},
+ { typeof(float), SqlDbType.Float},
+ { typeof(DateTime), SqlDbType.DateTime2}, // https://docs.microsoft.com/en-us/sql/t-sql/data-types/datetime-transact-sql?view=sql-server-2017
+ { typeof(bool), SqlDbType.Bit},
+ { typeof(Guid), SqlDbType.UniqueIdentifier},
+ { typeof(uint), SqlDbType.Int},
+ { typeof(ushort), SqlDbType.SmallInt},
+ { typeof(ulong), SqlDbType.BigInt},
+ { typeof(byte), SqlDbType.TinyInt},
+ { typeof(byte[]), SqlDbType.Binary},
+ { typeof(decimal), SqlDbType.Decimal},
+ };
+
+ private static readonly IDictionary SQLTypeRenderingMapping = new Dictionary()
+ {
+ // Doc: https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql-server-data-type-mappings
+ { SqlDbType.Int, "int" },
+ { SqlDbType.SmallInt, "smallint" },
+ { SqlDbType.BigInt, "bigint" },
+ { SqlDbType.Float, "float" },
+ { SqlDbType.NVarChar, "nvarchar(MAX)" },
+ { SqlDbType.DateTime2, "datetime2" },
+ { SqlDbType.Bit, "bit"},
+ { SqlDbType.UniqueIdentifier, "uniqueidentifier" },
+ { SqlDbType.TinyInt, "tinyint" },
+ { SqlDbType.Binary, "binary" },
+ { SqlDbType.Decimal, "decimal" },
+ };
+
+ // Map from Aggregation Function to its equivalent in SQL
+ private static readonly IDictionary AggregationFunctionRenderPattern = new Dictionary()
+ {
+ { AggregationFunction.Avg, "AVG(CAST({0} AS float))"},
+ { AggregationFunction.Sum, "SUM({0})"},
+ { AggregationFunction.Min, "MIN({0})"},
+ { AggregationFunction.Max, "MAX({0})"},
+ { AggregationFunction.First, "MIN({0})"},
+ { AggregationFunction.Last, "MAX({0})"},
+ { AggregationFunction.StDev, "STDEV({0})" },
+ { AggregationFunction.StDevP, "STDEVP({0})" },
+ };
+
+ enum ConversionType
+ {
+ NoNeed,
+ Convert,
+ Cast,
+ Invalid
+ }
+
+ private static readonly IDictionary<(SqlDbType, SqlDbType), ConversionType> SQLTypeConversionType = new Dictionary<(SqlDbType, SqlDbType), ConversionType>()
+ {
+ // Doc: https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-2017
+ { (SqlDbType.Int, SqlDbType.Int), ConversionType.NoNeed },
+ { (SqlDbType.Int, SqlDbType.SmallInt), ConversionType.Cast },
+ { (SqlDbType.Int, SqlDbType.BigInt), ConversionType.Cast },
+ { (SqlDbType.Int, SqlDbType.Float), ConversionType.Cast },
+ { (SqlDbType.Int, SqlDbType.NVarChar), ConversionType.Cast },
+ { (SqlDbType.Int, SqlDbType.DateTime2), ConversionType.Invalid },
+ { (SqlDbType.Int, SqlDbType.Bit), ConversionType.Cast },
+ { (SqlDbType.Int, SqlDbType.UniqueIdentifier), ConversionType.Invalid },
+ { (SqlDbType.Int, SqlDbType.TinyInt), ConversionType.Cast },
+ { (SqlDbType.Int, SqlDbType.Binary), ConversionType.Cast },
+ { (SqlDbType.Int, SqlDbType.Decimal), ConversionType.Cast },
+ { (SqlDbType.SmallInt, SqlDbType.Int), ConversionType.Cast },
+ { (SqlDbType.SmallInt, SqlDbType.SmallInt), ConversionType.NoNeed },
+ { (SqlDbType.SmallInt, SqlDbType.BigInt), ConversionType.Cast },
+ { (SqlDbType.SmallInt, SqlDbType.Float), ConversionType.Cast },
+ { (SqlDbType.SmallInt, SqlDbType.NVarChar), ConversionType.Cast },
+ { (SqlDbType.SmallInt, SqlDbType.DateTime2), ConversionType.Invalid },
+ { (SqlDbType.SmallInt, SqlDbType.Bit), ConversionType.Cast },
+ { (SqlDbType.SmallInt, SqlDbType.UniqueIdentifier), ConversionType.Invalid },
+ { (SqlDbType.SmallInt, SqlDbType.TinyInt), ConversionType.Cast },
+ { (SqlDbType.SmallInt, SqlDbType.Binary), ConversionType.Cast },
+ { (SqlDbType.SmallInt, SqlDbType.Decimal), ConversionType.Cast },
+ { (SqlDbType.BigInt, SqlDbType.Int), ConversionType.Cast },
+ { (SqlDbType.BigInt, SqlDbType.SmallInt), ConversionType.Cast },
+ { (SqlDbType.BigInt, SqlDbType.BigInt), ConversionType.NoNeed },
+ { (SqlDbType.BigInt, SqlDbType.Float), ConversionType.Cast },
+ { (SqlDbType.BigInt, SqlDbType.NVarChar), ConversionType.Cast },
+ { (SqlDbType.BigInt, SqlDbType.DateTime2), ConversionType.Invalid },
+ { (SqlDbType.BigInt, SqlDbType.Bit), ConversionType.Cast },
+ { (SqlDbType.BigInt, SqlDbType.UniqueIdentifier), ConversionType.Invalid },
+ { (SqlDbType.BigInt, SqlDbType.TinyInt), ConversionType.Cast },
+ { (SqlDbType.BigInt, SqlDbType.Binary), ConversionType.Cast },
+ { (SqlDbType.BigInt, SqlDbType.Decimal), ConversionType.Cast },
+ { (SqlDbType.Float, SqlDbType.Int), ConversionType.Cast },
+ { (SqlDbType.Float, SqlDbType.SmallInt), ConversionType.Cast },
+ { (SqlDbType.Float, SqlDbType.BigInt), ConversionType.Cast },
+ { (SqlDbType.Float, SqlDbType.Float), ConversionType.NoNeed },
+ { (SqlDbType.Float, SqlDbType.NVarChar), ConversionType.Cast },
+ { (SqlDbType.Float, SqlDbType.DateTime2), ConversionType.Invalid },
+ { (SqlDbType.Float, SqlDbType.Bit), ConversionType.Cast },
+ { (SqlDbType.Float, SqlDbType.UniqueIdentifier), ConversionType.Invalid },
+ { (SqlDbType.Float, SqlDbType.TinyInt), ConversionType.Cast },
+ { (SqlDbType.Float, SqlDbType.Binary), ConversionType.Cast },
+ { (SqlDbType.Float, SqlDbType.Decimal), ConversionType.Cast },
+ { (SqlDbType.NVarChar, SqlDbType.Int), ConversionType.Cast },
+ { (SqlDbType.NVarChar, SqlDbType.SmallInt), ConversionType.Cast },
+ { (SqlDbType.NVarChar, SqlDbType.BigInt), ConversionType.Cast },
+ { (SqlDbType.NVarChar, SqlDbType.Float), ConversionType.Cast },
+ { (SqlDbType.NVarChar, SqlDbType.NVarChar), ConversionType.NoNeed },
+ { (SqlDbType.NVarChar, SqlDbType.DateTime2), ConversionType.Cast },
+ { (SqlDbType.NVarChar, SqlDbType.Bit), ConversionType.Cast },
+ { (SqlDbType.NVarChar, SqlDbType.UniqueIdentifier), ConversionType.Cast },
+ { (SqlDbType.NVarChar, SqlDbType.TinyInt), ConversionType.Cast },
+ { (SqlDbType.NVarChar, SqlDbType.Binary), ConversionType.Convert },
+ { (SqlDbType.NVarChar, SqlDbType.Decimal), ConversionType.Cast },
+ { (SqlDbType.DateTime2, SqlDbType.Int), ConversionType.Invalid },
+ { (SqlDbType.DateTime2, SqlDbType.SmallInt), ConversionType.Invalid },
+ { (SqlDbType.DateTime2, SqlDbType.BigInt), ConversionType.Invalid },
+ { (SqlDbType.DateTime2, SqlDbType.Float), ConversionType.Invalid },
+ { (SqlDbType.DateTime2, SqlDbType.NVarChar), ConversionType.Cast },
+ { (SqlDbType.DateTime2, SqlDbType.DateTime2), ConversionType.NoNeed },
+ { (SqlDbType.DateTime2, SqlDbType.Bit), ConversionType.Invalid },
+ { (SqlDbType.DateTime2, SqlDbType.UniqueIdentifier), ConversionType.Invalid },
+ { (SqlDbType.DateTime2, SqlDbType.TinyInt), ConversionType.Invalid },
+ { (SqlDbType.DateTime2, SqlDbType.Binary), ConversionType.Convert },
+ { (SqlDbType.DateTime2, SqlDbType.Decimal), ConversionType.Invalid },
+ { (SqlDbType.Bit, SqlDbType.Int), ConversionType.Cast },
+ { (SqlDbType.Bit, SqlDbType.SmallInt), ConversionType.Cast },
+ { (SqlDbType.Bit, SqlDbType.BigInt), ConversionType.Cast },
+ { (SqlDbType.Bit, SqlDbType.Float), ConversionType.Cast },
+ { (SqlDbType.Bit, SqlDbType.NVarChar), ConversionType.Cast },
+ { (SqlDbType.Bit, SqlDbType.DateTime2), ConversionType.Invalid },
+ { (SqlDbType.Bit, SqlDbType.Bit), ConversionType.NoNeed },
+ { (SqlDbType.Bit, SqlDbType.UniqueIdentifier), ConversionType.Invalid },
+ { (SqlDbType.Bit, SqlDbType.TinyInt), ConversionType.Cast },
+ { (SqlDbType.Bit, SqlDbType.Binary), ConversionType.Cast },
+ { (SqlDbType.Bit, SqlDbType.Decimal), ConversionType.Cast },
+ { (SqlDbType.UniqueIdentifier, SqlDbType.Int), ConversionType.Invalid },
+ { (SqlDbType.UniqueIdentifier, SqlDbType.SmallInt), ConversionType.Invalid },
+ { (SqlDbType.UniqueIdentifier, SqlDbType.BigInt), ConversionType.Invalid },
+ { (SqlDbType.UniqueIdentifier, SqlDbType.Float), ConversionType.Invalid },
+ { (SqlDbType.UniqueIdentifier, SqlDbType.NVarChar), ConversionType.Cast },
+ { (SqlDbType.UniqueIdentifier, SqlDbType.DateTime2), ConversionType.Invalid },
+ { (SqlDbType.UniqueIdentifier, SqlDbType.Bit), ConversionType.Invalid },
+ { (SqlDbType.UniqueIdentifier, SqlDbType.UniqueIdentifier), ConversionType.NoNeed },
+ { (SqlDbType.UniqueIdentifier, SqlDbType.TinyInt), ConversionType.Invalid },
+ { (SqlDbType.UniqueIdentifier, SqlDbType.Binary), ConversionType.Cast },
+ { (SqlDbType.UniqueIdentifier, SqlDbType.Decimal), ConversionType.Invalid },
+ { (SqlDbType.TinyInt, SqlDbType.Int), ConversionType.Cast },
+ { (SqlDbType.TinyInt, SqlDbType.SmallInt), ConversionType.Cast },
+ { (SqlDbType.TinyInt, SqlDbType.BigInt), ConversionType.Cast },
+ { (SqlDbType.TinyInt, SqlDbType.Float), ConversionType.Cast },
+ { (SqlDbType.TinyInt, SqlDbType.NVarChar), ConversionType.Cast },
+ { (SqlDbType.TinyInt, SqlDbType.DateTime2), ConversionType.Invalid },
+ { (SqlDbType.TinyInt, SqlDbType.Bit), ConversionType.Cast },
+ { (SqlDbType.TinyInt, SqlDbType.UniqueIdentifier), ConversionType.Invalid },
+ { (SqlDbType.TinyInt, SqlDbType.TinyInt), ConversionType.NoNeed },
+ { (SqlDbType.TinyInt, SqlDbType.Binary), ConversionType.Cast },
+ { (SqlDbType.TinyInt, SqlDbType.Decimal), ConversionType.Cast },
+ { (SqlDbType.Binary, SqlDbType.Int), ConversionType.Cast },
+ { (SqlDbType.Binary, SqlDbType.SmallInt), ConversionType.Cast },
+ { (SqlDbType.Binary, SqlDbType.BigInt), ConversionType.Cast },
+ { (SqlDbType.Binary, SqlDbType.Float), ConversionType.Invalid },
+ { (SqlDbType.Binary, SqlDbType.NVarChar), ConversionType.Cast },
+ { (SqlDbType.Binary, SqlDbType.DateTime2), ConversionType.Convert },
+ { (SqlDbType.Binary, SqlDbType.Bit), ConversionType.Cast },
+ { (SqlDbType.Binary, SqlDbType.UniqueIdentifier), ConversionType.Cast },
+ { (SqlDbType.Binary, SqlDbType.TinyInt), ConversionType.Cast },
+ { (SqlDbType.Binary, SqlDbType.Binary), ConversionType.NoNeed },
+ { (SqlDbType.Binary, SqlDbType.Decimal), ConversionType.Cast },
+ { (SqlDbType.Decimal, SqlDbType.Int), ConversionType.Cast },
+ { (SqlDbType.Decimal, SqlDbType.SmallInt), ConversionType.Cast },
+ { (SqlDbType.Decimal, SqlDbType.BigInt), ConversionType.Cast },
+ { (SqlDbType.Decimal, SqlDbType.Float), ConversionType.Cast },
+ { (SqlDbType.Decimal, SqlDbType.NVarChar), ConversionType.Cast },
+ { (SqlDbType.Decimal, SqlDbType.DateTime2), ConversionType.Invalid },
+ { (SqlDbType.Decimal, SqlDbType.Bit), ConversionType.Cast },
+ { (SqlDbType.Decimal, SqlDbType.UniqueIdentifier), ConversionType.Invalid },
+ { (SqlDbType.Decimal, SqlDbType.TinyInt), ConversionType.Cast },
+ { (SqlDbType.Decimal, SqlDbType.Binary), ConversionType.Cast },
+ { (SqlDbType.Decimal, SqlDbType.Decimal), ConversionType.Cast },
+ };
+
+ class ExpressionRenderingContext
+ {
+ public ExpressionRenderingContext()
+ {
+
+ }
+ public ExpressionRenderingContext(ExpressionRenderingContext ctx)
+ {
+ ExpectConditionExpression = ctx.ExpectConditionExpression;
+ EnclosingOperator = ctx.EnclosingOperator;
+ }
+ public bool ExpectConditionExpression { get; set; } = false;
+ public ExpressionRenderingContext ModifyExpectConditionExpression(bool newVal)
+ {
+ ExpectConditionExpression = newVal;
+ return this;
+ }
+ public LogicalOperator EnclosingOperator { get; set; }
+ }
+
+ ///
+ /// Constructor
+ ///
+ ///
+ /// A set of parameters and its default value
+ /// Optional. If provided, a true VC url is provided, or empty, for local testing
+ /// Optional. For logging
+ public SQLRenderer
+ (
+ ISQLDBSchemaProvider graphDef,
+ ILoggable logger = null
+ )
+ {
+ _graphDef = graphDef;
+ _logger = logger;
+ }
+
+ #region Helpers
+ private string GetFieldNameForEntityField(string prefix, string singleFieldName)
+ {
+ var prefixClean = TextHelper.MakeCompliantString(prefix, "[A-Za-z0-9_]+");
+ return $"__{prefixClean}_{singleFieldName}";
+ }
+
+ ///
+ /// Returns a list of fields that are
+ /// - stand alone single field
+ /// - single field that are part of entity and got referenced
+ ///
+ ///
+ private IEnumerable<(string EntityAlias, string PropertyName, Type FieldType, bool IsKeyfield)> ExpandSchema(Schema schema)
+ {
+ return schema
+ .Where(f => f is EntityField).Cast()
+ .SelectMany(ef => ef.ReferencedFieldAliases
+ .Select(fn => (
+ EntityAlias: ef.FieldAlias,
+ PropertyName: fn,
+ FieldType: ef.EncapsulatedFields.First(f2 => fn == f2.FieldAlias).FieldType,
+ IsKeyfield: fn == ef.NodeJoinField?.FieldAlias || fn == ef.RelSourceJoinField?.FieldAlias || fn == ef.RelSinkJoinField?.FieldAlias
+ )))
+ .Union(schema
+ .Where(f => f is SingleField).Cast()
+ .Select(fn => (EntityAlias: (string)null, PropertyName: fn.FieldAlias, FieldType: fn.FieldType, IsKeyfield: false)));
+ }
+
+ private string EscapeStringLiteral(string originalStr)
+ {
+ return originalStr.Replace("'", "''");
+ }
+
+ private string RenderTypeCastingForExpression(Type targetType, string expr)
+ {
+ var unboxedType = TypeHelper.GetUnderlyingTypeIfNullable(targetType);
+ var sqlTypeText = SQLTypeRenderingMapping[TypeToSQLTypeMapping[unboxedType]];
+ if (targetType != unboxedType)
+ {
+ return $"CAST({expr} AS {sqlTypeText})";
+ }
+ else
+ {
+ return $"ISNULL(CAST({expr} AS {sqlTypeText}), {expr})";
+ }
+ }
+
+ private string RenderTypeConversionForExpression(Type targetType, string expr)
+ {
+ var unboxedType = TypeHelper.GetUnderlyingTypeIfNullable(targetType);
+ var sqlTypeText = SQLTypeRenderingMapping[TypeToSQLTypeMapping[unboxedType]];
+ return $"CONVERT({sqlTypeText}, {expr})";
+ }
+
+ private string RenderCaseValueExpression(Type targetType, QueryExpression expr, ExpressionRenderingContext exprCtx)
+ {
+ /// References:
+ /// - https://sqlsunday.com/2019/06/05/cast-convert-makes-expressions-nullable/
+ ///
+ var renderedExpr = RenderExpression(expr, exprCtx);
+ var currentType = expr.EvaluateType();
+
+ // first check if the type conversion is allowed
+ var fromSQLType = TypeToSQLTypeMapping[TypeHelper.GetUnderlyingTypeIfNullable(currentType)];
+ var toSQLType = TypeToSQLTypeMapping[TypeHelper.GetUnderlyingTypeIfNullable(targetType)];
+ var conversionType = SQLTypeConversionType[(fromSQLType, toSQLType)];
+ switch (conversionType)
+ {
+ case ConversionType.Cast:
+ return RenderTypeCastingForExpression(targetType, renderedExpr);
+ case ConversionType.Convert:
+ return RenderTypeConversionForExpression(targetType, renderedExpr);
+ case ConversionType.NoNeed:
+ return renderedExpr;
+ case ConversionType.Invalid:
+ throw new TranspilerNotSupportedException($"Converting from type {fromSQLType} to type {toSQLType}");
+ default:
+ throw new TranspilerInternalErrorException($"Unexpected conversion type {conversionType}");
+ }
+ }
+ #endregion Helpers
+
+ #region Logical Operator Renderers
+
+ private string RenderDataSource(DataSourceOperator dataSourceOp, int depth)
+ {
+ var codeSnip = new StringBuilder();
+
+ // Currently we support only single source data source
+ Debug.Assert(
+ dataSourceOp.OutputSchema.First() is EntityField &&
+ dataSourceOp.OutputSchema.Count() == 1
+ );
+
+ var ent = dataSourceOp.OutputSchema.First() as EntityField;
+ var storDesc = _graphDef.GetSQLTableDescriptors(ent.BoundEntityName);
+ var allReferencedFieldsWithAliasPrefix = ent.ReferencedFieldAliases.Select(f => GetFieldNameForEntityField(ent.EntityName, f));
+ codeSnip.AppendLine(depth, $"SELECT");
+
+ // Render join key fields always and does it first
+ bool isFirstRow = true;
+ if (ent.Type == EntityField.EntityType.Node)
+ {
+ var nodeIdJoinKeyName = GetFieldNameForEntityField(ent.FieldAlias, ent.NodeJoinField.FieldAlias);
+ codeSnip.AppendLine(depth + 1, $"{(!isFirstRow ? ", " : " ")}{ent.NodeJoinField.FieldAlias} AS {nodeIdJoinKeyName}");
+ isFirstRow = false;
+ }
+ else
+ {
+ var edgeSrcIdJoinKeyName = GetFieldNameForEntityField(ent.FieldAlias, ent.RelSourceJoinField.FieldAlias);
+ var edgeSinkIdJoinKeyName = GetFieldNameForEntityField(ent.FieldAlias, ent.RelSinkJoinField.FieldAlias);
+ codeSnip.AppendLine(depth + 1, $"{(!isFirstRow ? ", " : " ")}{ent.RelSourceJoinField.FieldAlias} AS {edgeSrcIdJoinKeyName}");
+ codeSnip.AppendLine(depth + 1, $", {ent.RelSinkJoinField.FieldAlias} AS {edgeSinkIdJoinKeyName}");
+ isFirstRow = false;
+ }
+
+ // Render other non-joinkey fields
+ foreach (var field in ent.ReferencedFieldAliases
+ .Where(f => (ent.NodeJoinField?.FieldAlias != f && ent.RelSourceJoinField?.FieldAlias != f && ent.RelSinkJoinField?.FieldAlias != f)))
+ {
+ var fieldAlias = GetFieldNameForEntityField(ent.FieldAlias, field);
+ codeSnip.AppendLine(depth + 1, $", {field} AS {fieldAlias}");
+ }
+
+ codeSnip.AppendLine(depth, $"FROM");
+ codeSnip.AppendLine(depth + 1, $"{ storDesc.TableOrViewName}");
+
+ return codeSnip.ToString();
+ }
+
+ private string RenderJoin(JoinOperator joinOp, int depth)
+ {
+ var codeSnip = new StringBuilder();
+
+ var leftOp = joinOp.InOperatorLeft;
+ var rightOp = joinOp.InOperatorRight;
+
+ var leftVar = "_left";
+ var rightVar = "_right";
+
+ // expand output schema
+ var allColsToOutput = ExpandSchema(joinOp.OutputSchema);
+ var allOutputEntities = joinOp.OutputSchema.Where(f => f is EntityField).Cast();
+
+ // expand left input schema
+ var leftInCols = ExpandSchema(leftOp.OutputSchema);
+ var leftInEntityAliases = leftOp.OutputSchema.Where(f => f is EntityField).Select(e => e.FieldAlias);
+
+ // expand right input schema
+ var rightInCols = ExpandSchema(rightOp.OutputSchema);
+ var rightInEntityAliases = rightOp.OutputSchema.Where(f => f is EntityField).Select(e => e.FieldAlias);
+
+ codeSnip.AppendLine(depth, $"SELECT");
+
+ // render join key fields always and does it first
+ bool isFirstRow = true;
+ Debug.Assert(allOutputEntities.Count() > 0);
+ foreach (var ent in allOutputEntities)
+ {
+ var isFromLeft = leftInEntityAliases.Contains(ent.FieldAlias); // prefer left side first (sometimes an alias would be in both left and right, such as in OPTIONAL MATCH case)
+ Debug.Assert(isFromLeft || rightInEntityAliases.Contains(ent.FieldAlias));
+ if (ent.Type == EntityField.EntityType.Node)
+ {
+ var nodeIdJoinKeyName = GetFieldNameForEntityField(ent.FieldAlias, ent.NodeJoinField.FieldAlias);
+ codeSnip.AppendLine(depth + 1, $"{(!isFirstRow ? ", " : " ")}{(isFromLeft ? leftVar : rightVar)}.{nodeIdJoinKeyName} AS {nodeIdJoinKeyName}");
+ isFirstRow = false;
+ }
+ else
+ {
+ var edgeSrcIdJoinKeyName = GetFieldNameForEntityField(ent.FieldAlias, ent.RelSourceJoinField.FieldAlias);
+ var edgeSinkIdJoinKeyName = GetFieldNameForEntityField(ent.FieldAlias, ent.RelSinkJoinField.FieldAlias);
+ codeSnip.AppendLine(depth + 1, $"{(!isFirstRow ? ", " : " ")}{(isFromLeft ? leftVar : rightVar)}.{edgeSrcIdJoinKeyName} AS {edgeSrcIdJoinKeyName}");
+ codeSnip.AppendLine(depth + 1, $", {(isFromLeft ? leftVar : rightVar)}.{edgeSinkIdJoinKeyName} AS {edgeSinkIdJoinKeyName}");
+ isFirstRow = false;
+ }
+ }
+ // render selected properties (except key fields, which will always be rendered above)
+ foreach (var col in allColsToOutput.Where(c => !c.IsKeyfield))
+ {
+ var isFromLeft = leftInCols.Any(f => (!string.IsNullOrEmpty(col.EntityAlias) ? f.EntityAlias == col.EntityAlias : true) && f.PropertyName == col.PropertyName);
+ Debug.Assert(isFromLeft || rightInCols.Any(f => (!string.IsNullOrEmpty(col.EntityAlias) ? f.EntityAlias == col.EntityAlias : true) && f.PropertyName == col.PropertyName));
+ if (!string.IsNullOrEmpty(col.EntityAlias))
+ {
+ // entity member (not yet expanded into a single column)
+ var fieldAliasWrappedInEntity = GetFieldNameForEntityField(col.EntityAlias, col.PropertyName);
+ codeSnip.AppendLine(depth + 1, $", {(isFromLeft ? leftVar : rightVar)}.{fieldAliasWrappedInEntity} AS {fieldAliasWrappedInEntity}");
+ }
+ else
+ {
+ // single column reference
+ codeSnip.AppendLine(depth + 1, $", {(isFromLeft ? leftVar : rightVar)}.{col.PropertyName} AS {col.PropertyName}");
+ }
+ }
+
+ codeSnip.AppendLine(depth, $"FROM (");
+ codeSnip.AppendLine(RenderLogicalOperator(leftOp, depth+1));
+ codeSnip.AppendLine(depth, $") AS {leftVar}");
+
+ if (joinOp.Type == JoinOperator.JoinType.Cross)
+ {
+ Debug.Assert(joinOp.JoinPairs.Count == 0);
+ codeSnip.AppendLine(depth, $"CROSS JOIN (");
+ codeSnip.AppendLine(RenderLogicalOperator(rightOp, depth+1));
+ codeSnip.AppendLine(depth, $") AS {rightVar}");
+ }
+ else
+ {
+ Debug.Assert(joinOp.Type == JoinOperator.JoinType.Left || joinOp.Type == JoinOperator.JoinType.Inner);
+ Debug.Assert(joinOp.JoinPairs.Count > 0);
+ codeSnip.AppendLine(depth, $"{(joinOp.Type == JoinOperator.JoinType.Inner ? "INNER JOIN" : "LEFT JOIN")} (");
+ codeSnip.AppendLine(RenderLogicalOperator(rightOp, depth+1));
+ codeSnip.AppendLine(depth, $") AS {rightVar} ON");
+
+ bool isFirstJoinCond = true;
+ foreach (var joinKeyPair in joinOp.JoinPairs)
+ {
+ var isNodeFromLeft = leftInEntityAliases.Contains(joinKeyPair.NodeAlias);
+ Debug.Assert(isNodeFromLeft ?
+ rightInEntityAliases.Contains(joinKeyPair.RelationshipOrNodeAlias) :
+ leftInEntityAliases.Contains(joinKeyPair.RelationshipOrNodeAlias) && rightInEntityAliases.Contains(joinKeyPair.NodeAlias));
+ var varWithNode = isNodeFromLeft ? leftVar : rightVar;
+ var varWithNodeEntity = joinOp.OutputSchema.First(n => n.FieldAlias == joinKeyPair.NodeAlias) as EntityField;
+ var nodeJoinKey = GetFieldNameForEntityField(joinKeyPair.NodeAlias, varWithNodeEntity.NodeJoinField.FieldAlias);
+
+ string nodeOrRelJoinKey;
+ var varWithRelOrNode = isNodeFromLeft ? rightVar : leftVar;
+ var varWithRelOrNodeEntity = joinOp.OutputSchema.First(n => n.FieldAlias == joinKeyPair.RelationshipOrNodeAlias) as EntityField;
+
+ switch (joinKeyPair.Type)
+ {
+ case JoinOperator.JoinKeyPair.JoinKeyPairType.Source:
+ nodeOrRelJoinKey = GetFieldNameForEntityField(joinKeyPair.RelationshipOrNodeAlias, varWithRelOrNodeEntity.RelSourceJoinField.FieldAlias);
+ codeSnip.AppendLine(depth + 1, $"{(isFirstJoinCond ? "" : "AND ")}{varWithNode}.{nodeJoinKey} = {varWithRelOrNode}.{nodeOrRelJoinKey}");
+ break;
+ case JoinOperator.JoinKeyPair.JoinKeyPairType.Sink:
+ nodeOrRelJoinKey = GetFieldNameForEntityField(joinKeyPair.RelationshipOrNodeAlias, varWithRelOrNodeEntity.RelSinkJoinField.FieldAlias);
+ codeSnip.AppendLine(depth + 1, $"{(isFirstJoinCond ? "" : "AND ")}{varWithNode}.{nodeJoinKey} = {varWithRelOrNode}.{nodeOrRelJoinKey}");
+ break;
+ case JoinOperator.JoinKeyPair.JoinKeyPairType.Both:
+ nodeOrRelJoinKey = GetFieldNameForEntityField(joinKeyPair.RelationshipOrNodeAlias, varWithRelOrNodeEntity.RelSourceJoinField.FieldAlias);
+ codeSnip.AppendLine(depth + 1, $"{(isFirstJoinCond ? "" : "AND ")}{varWithNode}.{nodeJoinKey} = {varWithRelOrNode}.{nodeOrRelJoinKey}");
+ nodeOrRelJoinKey = GetFieldNameForEntityField(joinKeyPair.RelationshipOrNodeAlias, varWithRelOrNodeEntity.RelSinkJoinField.FieldAlias);
+ codeSnip.AppendLine(depth + 1, $"{"AND "}{varWithNode}.{nodeJoinKey} == {varWithRelOrNode}.{nodeOrRelJoinKey}");
+ break;
+ case JoinOperator.JoinKeyPair.JoinKeyPairType.NodeId:
+ nodeOrRelJoinKey = GetFieldNameForEntityField(joinKeyPair.RelationshipOrNodeAlias, varWithRelOrNodeEntity.NodeJoinField.FieldAlias);
+ codeSnip.AppendLine(depth + 1, $"{(isFirstJoinCond ? "" : "AND ")}{varWithNode}.{nodeJoinKey} = {varWithRelOrNode}.{nodeOrRelJoinKey}");
+ break;
+ default:
+ Debug.Assert(joinKeyPair.Type == JoinOperator.JoinKeyPair.JoinKeyPairType.Either);
+ var nodeField = joinOp.InputSchema.First(f => f.FieldAlias == joinKeyPair.NodeAlias) as EntityField;
+ var relField = joinOp.InputSchema.First(f => f.FieldAlias == joinKeyPair.RelationshipOrNodeAlias) as EntityField;
+ var isSrc = relField.BoundSourceEntityName == nodeField.BoundEntityName;
+ Debug.Assert(isSrc || relField.BoundSinkEntityName == nodeField.BoundEntityName);
+ nodeOrRelJoinKey = GetFieldNameForEntityField(joinKeyPair.RelationshipOrNodeAlias, isSrc ? varWithRelOrNodeEntity.RelSourceJoinField.FieldAlias : varWithRelOrNodeEntity.RelSinkJoinField.FieldAlias);
+ codeSnip.AppendLine(depth + 1, $"{(isFirstJoinCond ? "" : "AND ")}{varWithNode}.{nodeJoinKey} = {varWithRelOrNode}.{nodeOrRelJoinKey}");
+ break;
+ }
+
+ isFirstJoinCond = false;
+ }
+ }
+
+ return codeSnip.ToString();
+ }
+
+ private string RenderBinaryOperator(BinaryOperatorInfo op, QueryExpression left, QueryExpression right, ExpressionRenderingContext exprCtx)
+ {
+ if (op.Name == BinaryOperator.Invalid)
+ {
+ throw new TranspilerInternalErrorException("Encountered an invalid operator");
+ }
+ var pattern = OperatorRenderPattern[op.Name] ??
+ throw new TranspilerNotSupportedException($"Operator {op.Name}");
+ var leftExpr = RenderExpression(left, exprCtx);
+ var rightExpr = RenderExpression(right, exprCtx);
+ return string.Format(pattern, leftExpr, rightExpr);
+ }
+
+ private string RenderFunction(FunctionInfo func, IEnumerable parameters, ExpressionRenderingContext exprCtx)
+ {
+ var exprRenderResult = parameters.Select(p => RenderExpression(p, exprCtx));
+ switch (func.FunctionName)
+ {
+ case Function.ToFloat:
+ Debug.Assert(parameters.Count() == 1);
+ return RenderTypeCastingForExpression(typeof(float), exprRenderResult.First());
+ case Function.ToString:
+ Debug.Assert(parameters.Count() == 1);
+ return RenderTypeCastingForExpression(typeof(string), exprRenderResult.First());
+ case Function.ToBoolean:
+ Debug.Assert(parameters.Count() == 1);
+ return RenderTypeCastingForExpression(typeof(bool), exprRenderResult.First());
+ case Function.ToInteger:
+ Debug.Assert(parameters.Count() == 1);
+ return RenderTypeCastingForExpression(typeof(int), exprRenderResult.First());
+ case Function.ToDouble:
+ Debug.Assert(parameters.Count() == 1);
+ return RenderTypeCastingForExpression(typeof(double), exprRenderResult.First());
+ case Function.ToLong:
+ Debug.Assert(parameters.Count() == 1);
+ return RenderTypeCastingForExpression(typeof(long), exprRenderResult.First());
+ case Function.Not:
+ Debug.Assert(parameters.Count() == 1);
+ return $"NOT ({string.Join(", ", exprRenderResult)})";
+ case Function.StringStartsWith:
+ Debug.Assert(parameters.Count() == 2);
+ return $"LEFT({exprRenderResult.First()}, LEN({exprRenderResult.Skip(1).First()})) = {exprRenderResult.Skip(1).First()}";
+ case Function.StringEndsWith:
+ Debug.Assert(parameters.Count() == 2);
+ return $"RIGHT({exprRenderResult.First()}, LEN({exprRenderResult.Skip(1).First()})) = {exprRenderResult.Skip(1).First()}";
+ case Function.StringContains:
+ Debug.Assert(parameters.Count() == 2);
+ return $"charindex({exprRenderResult.Skip(1).First()}, {exprRenderResult.First()}) >= 1";
+ case Function.StringLeft:
+ Debug.Assert(parameters.Count() == 2);
+ return $"LEFT({exprRenderResult.First()}, {exprRenderResult.Skip(1).First()})";
+ case Function.StringRight:
+ Debug.Assert(parameters.Count() == 2);
+ return $"RIGHT({exprRenderResult.First()}, {exprRenderResult.Skip(1).First()})";
+ case Function.StringTrim:
+ Debug.Assert(parameters.Count() == 1);
+ return $"TRIM({exprRenderResult.First()})";
+ case Function.StringLTrim:
+ Debug.Assert(parameters.Count() == 1);
+ return $"LTRIM({exprRenderResult.First()})";
+ case Function.StringRTrim:
+ Debug.Assert(parameters.Count() == 1);
+ return $"RTRIM({exprRenderResult.First()})";
+ case Function.StringToUpper:
+ Debug.Assert(parameters.Count() == 1);
+ return $"UPPER({exprRenderResult.First()})";
+ case Function.StringToLower:
+ Debug.Assert(parameters.Count() == 1);
+ return $"LOWER({exprRenderResult.First()})";
+ case Function.StringSize:
+ Debug.Assert(parameters.Count() == 1);
+ return $"LEN({exprRenderResult.First()})";
+ case Function.IsNull:
+ return $"({string.Join(", ", exprRenderResult)}) IS NULL";
+ case Function.IsNotNull:
+ return $"({string.Join(", ", exprRenderResult)}) IS NOT NULL";
+ default:
+ throw new TranspilerNotSupportedException($"Function '{func.FunctionName}'");
+ }
+ }
+
+ private string RenderExpression(QueryExpression expr, ExpressionRenderingContext exprCtx)
+ {
+ if (expr is QueryExpressionBinary)
+ {
+ var exprTyped = expr as QueryExpressionBinary;
+ var opType = exprTyped.Operator.Type;
+ var exprCtxChild = new ExpressionRenderingContext(exprCtx)
+ .ModifyExpectConditionExpression(opType == BinaryOperatorType.Logical);
+ string exprText = RenderBinaryOperator(
+ exprTyped.Operator,
+ exprTyped.LeftExpression,
+ exprTyped.RightExpression,
+ exprCtxChild
+ );
+ if (!exprCtx.ExpectConditionExpression &&
+ (opType == BinaryOperatorType.Comparison || opType == BinaryOperatorType.Logical))
+ {
+ return $"(CASE WHEN {exprText} THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END)";
+ }
+ else
+ {
+ return exprText;
+ }
+ }
+ else if (expr is QueryExpressionAggregationFunction)
+ {
+ var exprTyped = expr as QueryExpressionAggregationFunction;
+
+ // TODO: temporary block for some yet to be supported aggregation functions
+ if (exprTyped.AggregationFunction == AggregationFunction.PercentileCont ||
+ exprTyped.AggregationFunction == AggregationFunction.PercentileDisc)
+ {
+ throw new TranspilerNotSupportedException($"Yet to implemented aggregation function {exprTyped.AggregationFunction}");
+ }
+
+ // special handling for count
+ if (exprTyped.AggregationFunction == AggregationFunction.Count)
+ {
+ if (exprTyped.InnerExpression is QueryExpressionProperty &&
+ (exprTyped.InnerExpression as QueryExpressionProperty).Entity != null)
+ {
+ // special handling for Count(entity) or Count(distinct(entity))
+ var innerPropExpr = exprTyped.InnerExpression as QueryExpressionProperty;
+ var entity = innerPropExpr.Entity;
+ if (entity is RelationshipEntity && exprTyped.IsDistinct)
+ {
+ // block a scenario we currently cannot support
+ throw new TranspilerNotSupportedException("COUNT DISTINCT applied to relationship entity");
+ }
+ var entityField = exprCtx.EnclosingOperator.InputSchema.First(f => f.FieldAlias == entity.Alias) as EntityField;
+ Debug.Assert(entityField != null);
+
+ // we use the key field as surrogate for counting entities
+ var surrogateFieldForCounting = GetFieldNameForEntityField(
+ innerPropExpr.VariableName,
+ entity is RelationshipEntity ? entityField.RelSourceJoinField.FieldAlias : entityField.NodeJoinField.FieldAlias
+ );
+ return $"COUNT({(exprTyped.IsDistinct ? "DISTINCT(" : "")}{surrogateFieldForCounting}{(exprTyped.IsDistinct ? ")" : "")})";
+ }
+ else
+ {
+ // default handling of count
+ if (exprTyped.InnerExpression.GetChildrenQueryExpressionType().Count() > 0)
+ {
+ throw new TranspilerNotSupportedException("Aggregation function inside aggregate function");
+ }
+ var innerExprStr = RenderExpression(exprTyped.InnerExpression, exprCtx);
+ return $"COUNT({(exprTyped.IsDistinct ? "DISTINCT(" : "")}{innerExprStr}{(exprTyped.IsDistinct ? ")" : "")})";
+ }
+ }
+ else
+ {
+ // default handling
+ if (exprTyped.InnerExpression.GetChildrenQueryExpressionType().Count() > 0)
+ {
+ throw new TranspilerNotSupportedException("Aggregation function inside aggregate function");
+ }
+ if (exprTyped.IsDistinct)
+ {
+ throw new TranspilerNotSupportedException("Distinct applied to aggregation functions other than COUNT");
+ }
+ var innerExprStr = RenderExpression(exprTyped.InnerExpression, exprCtx);
+ return string.Format(AggregationFunctionRenderPattern[exprTyped.AggregationFunction], innerExprStr);
+ }
+ }
+ else if (expr is QueryExpressionFunction)
+ {
+ var exprTyped = expr as QueryExpressionFunction;
+ var allExprs = (new List() { exprTyped.InnerExpression })
+ .Union(exprTyped.AdditionalExpressions ?? Enumerable.Empty());
+ return RenderFunction(exprTyped.Function, allExprs, exprCtx);
+ }
+ else if (expr is QueryExpressionProperty)
+ {
+ var exprTyped = expr as QueryExpressionProperty;
+ var expressionText = string.IsNullOrEmpty(exprTyped.PropertyName) ?
+ exprTyped.VariableName :
+ GetFieldNameForEntityField(exprTyped.VariableName, exprTyped.PropertyName);
+ return expressionText;
+ }
+ else if (expr is QueryExpressionList)
+ {
+ var exprTyped = expr as QueryExpressionList;
+ var exprRendered = exprTyped.ExpressionList.Select(e => RenderExpression(e, exprCtx)).ToList();
+ return $"({string.Join(", ", exprRendered)})";
+ }
+ else if (expr is QueryExpressionValue)
+ {
+ var exprTyped = expr as QueryExpressionValue;
+ if (exprTyped.ValueType == typeof(string))
+ {
+ // add double quote for string value, and escape if needed
+ return $"'{EscapeStringLiteral(exprTyped.StringValue)}'";
+ }
+ if (exprTyped.ValueType == typeof(bool))
+ {
+ return exprTyped.StringValue.ToLower();
+ }
+ else
+ {
+ return $"{exprTyped.StringValue}";
+ }
+ }
+ else if (expr is QueryExpressionWithAlias)
+ {
+ throw new NotSupportedException("Does not support aliased expression at non-root level");
+ }
+ else if (expr is QueryExpressionCaseExpression)
+ {
+ var caseExprText = RenderCaseExpression(expr as QueryExpressionCaseExpression, exprCtx);
+ return caseExprText;
+ }
+ else
+ {
+ throw new NotSupportedException($"Unsupported expression type: {expr.GetType().ToString()}");
+ }
+ }
+
+ private string RenderCaseExpression(QueryExpressionCaseExpression caseExpression, ExpressionRenderingContext exprCtx)
+ {
+ var caseAlternatives = caseExpression.CaseAlternatives;
+ var elseCondition = caseExpression.ElseExpression;
+ var targetType = caseExpression.EvaluateType();
+
+ var codeSnip = new StringBuilder();
+
+
+ // Note: right now the code renderer does not support casing on an expression. The
+ // expression has to be put into the WHEN / ELSE statement like SQL. In future,
+ // we may support this by embed CASE expr into the WHEN exprs
+ if (caseExpression.InitialCaseExpression != null)
+ {
+ throw new TranspilerNotSupportedException("Please use CASE WHEN ... instead of CASE WHEN . The latter is");
+ }
+
+ if (caseAlternatives.Count == 0)
+ {
+ throw new TranspilerInternalErrorException("No casing statements provided for CASE");
+ }
+
+ codeSnip.Append("CASE ");
+ Debug.Assert(exprCtx.ExpectConditionExpression == false);
+ foreach (var alterExpr in caseAlternatives)
+ {
+ codeSnip.Append($"WHEN {RenderExpression(alterExpr.WhenExpression, new ExpressionRenderingContext(exprCtx).ModifyExpectConditionExpression(true))} ");
+ codeSnip.Append($"THEN {RenderCaseValueExpression(targetType, alterExpr.ThenExpression, exprCtx)}");
+ }
+ if (elseCondition != null)
+ {
+ codeSnip.Append($" ELSE {RenderCaseValueExpression(targetType, elseCondition, exprCtx)} END");
+ }
+ else
+ {
+ codeSnip.Append($" END");
+ }
+
+ return codeSnip.ToString();
+ }
+
+ private string RenderProjection(ProjectionOperator prjOp, int depth)
+ {
+ var codeSnip = new StringBuilder();
+
+ // expand output schema
+ var allColsToOutput = ExpandSchema(prjOp.OutputSchema);
+ var allOutputEntities = prjOp.OutputSchema.Where(f => f is EntityField).Cast();
+ var allOutputSingleFields = prjOp.OutputSchema.Where(f => f is SingleField).Cast();
+ var preCond = prjOp.InOperator as SelectionOperator;
+ var topXVal = preCond?.LimitExpressions?.FirstOrDefault()?.RowCount;
+
+ codeSnip.AppendLine(depth, $"SELECT{(prjOp.IsDistinct ? " DISTINCT" : "")}{(topXVal.HasValue ? $" TOP {topXVal.Value}" : "")}");
+
+ // project entities and flow the join keys
+ // do group by if any aggregation functions used
+ var nonAggFieldExprs = new List();
+
+ bool isFirstRow = true;
+ foreach (var ent in allOutputEntities)
+ {
+ var entExpr = prjOp.ProjectionMap[ent.FieldAlias];
+ Debug.Assert(entExpr is QueryExpressionProperty);
+ var inSchemaAlias = (entExpr as QueryExpressionProperty).VariableName;
+
+ // project keys for entities that are exposed by the projection
+ if (ent.Type == EntityField.EntityType.Node)
+ {
+ var nodeIdJoinKeyName = GetFieldNameForEntityField(ent.FieldAlias, ent.NodeJoinField.FieldAlias);
+ var nodeIdInSchemaJoinKeyName = GetFieldNameForEntityField(inSchemaAlias, ent.NodeJoinField.FieldAlias);
+ codeSnip.AppendLine(depth+1, $"{(!isFirstRow ? ", " : " ")}{nodeIdInSchemaJoinKeyName} AS {nodeIdJoinKeyName}");
+ nonAggFieldExprs.Add(nodeIdInSchemaJoinKeyName);
+ isFirstRow = false;
+ }
+ else
+ {
+ var edgeSrcIdJoinKeyName = GetFieldNameForEntityField(ent.FieldAlias, ent.RelSourceJoinField.FieldAlias);
+ var edgeSinkIdJoinKeyName = GetFieldNameForEntityField(ent.FieldAlias, ent.RelSinkJoinField.FieldAlias);
+ var edgeSrcIdInSchemaJoinKeyName = GetFieldNameForEntityField(inSchemaAlias, ent.RelSourceJoinField.FieldAlias);
+ var edgeSinkIdInSchemaJoinKeyName = GetFieldNameForEntityField(inSchemaAlias, ent.RelSinkJoinField.FieldAlias);
+ codeSnip.AppendLine(depth + 1, $"{(!isFirstRow ? ", " : " ")}{edgeSrcIdInSchemaJoinKeyName} AS {edgeSrcIdJoinKeyName}");
+ codeSnip.AppendLine(depth + 1, $", {edgeSinkIdInSchemaJoinKeyName} AS {edgeSinkIdJoinKeyName}");
+ nonAggFieldExprs.Add(edgeSrcIdInSchemaJoinKeyName);
+ nonAggFieldExprs.Add(edgeSinkIdInSchemaJoinKeyName);
+ isFirstRow = false;
+ }
+
+ // referenced non-joinkey fields in the wrapped entities
+ var nonNullJoinKeyValues = new string[] { ent.NodeJoinField?.FieldAlias, ent.RelSourceJoinField?.FieldAlias, ent.RelSinkJoinField?.FieldAlias }.Where(a => !string.IsNullOrEmpty(a));
+ foreach (var field in ent.ReferencedFieldAliases.Except(nonNullJoinKeyValues))
+ {
+ var inSchemaFieldName = GetFieldNameForEntityField(inSchemaAlias, field);
+ var outSchemaFieldName = GetFieldNameForEntityField(ent.FieldAlias, field);
+ codeSnip.AppendLine(depth + 1, $", {inSchemaFieldName} AS {outSchemaFieldName}");
+ nonAggFieldExprs.Add(inSchemaFieldName);
+ }
+ }
+
+ // project single fields
+ foreach (var field in allOutputSingleFields)
+ {
+ // find corresponding expression
+ var expr = prjOp.ProjectionMap[field.FieldAlias];
+ var exprText = RenderExpression(
+ expr,
+ new ExpressionRenderingContext()
+ {
+ ExpectConditionExpression = false,
+ EnclosingOperator = prjOp
+ });
+ codeSnip.AppendLine(depth + 1, $"{(!isFirstRow ? ", " : " ")}{exprText} AS {field.FieldAlias}");
+ if (expr.GetChildrenQueryExpressionType().Count() == 0)
+ {
+ nonAggFieldExprs.Add(exprText);
+ }
+ isFirstRow = false;
+ }
+
+ codeSnip.AppendLine(depth, $"FROM (");
+
+ if (preCond != null)
+ {
+ var prevOp = preCond.InOperator;
+ codeSnip.AppendLine(RenderLogicalOperator(prevOp, depth + 1));
+ codeSnip.AppendLine(depth, $") AS _proj");
+
+ // if there's precondition, render it too
+ // WHERE clause specifically
+ if (preCond?.FilterExpression != null)
+ {
+ // Assert that schema has not changed and matches
+ Debug.Assert(preCond.InputSchema.Count == preCond.OutputSchema.Count && preCond.OutputSchema.Count == prjOp.InputSchema.Count);
+ var condText = RenderExpression(
+ preCond.FilterExpression,
+ new ExpressionRenderingContext()
+ {
+ ExpectConditionExpression = true, // SQL does not allow value type to be in WHERE condition
+ EnclosingOperator = preCond
+ });
+ codeSnip.AppendLine(depth, $"WHERE");
+ codeSnip.AppendLine(depth+1, $"{condText}");
+ }
+ }
+ else
+ {
+ codeSnip.AppendLine(RenderLogicalOperator(prjOp.InOperator, depth + 1));
+ codeSnip.AppendLine(depth, $") AS _proj");
+ }
+
+ // add group by if aggregation is used and there are non aggregation columns
+ if (prjOp.HasAggregationField && nonAggFieldExprs.Count > 0)
+ {
+ codeSnip.AppendLine(depth, $"GROUP BY");
+ var isFirstRowGrpBy = true;
+ foreach (var field in nonAggFieldExprs)
+ {
+ codeSnip.AppendLine(depth+1, $"{(isFirstRowGrpBy ? "" : ", ")}{field}");
+ isFirstRowGrpBy = false;
+ }
+ }
+
+ // order by clause must have limit statement, otherwise, won't compile in scope
+ if (topXVal.HasValue && preCond.OrderByExpressions?.Count() > 0)
+ {
+ // Assert that schema has not changed and matches
+ Debug.Assert(preCond.InputSchema.Count == preCond.OutputSchema.Count && preCond.OutputSchema.Count == prjOp.InputSchema.Count);
+
+ codeSnip.AppendLine(depth, $"ORDER BY");
+ var isFirstRowOrderBy = true;
+ foreach (var orderExpr in preCond.OrderByExpressions)
+ {
+ bool isDescending = orderExpr.IsDescending;
+ var childrenProperty = orderExpr.GetChildrenQueryExpressionType();
+ foreach (var prop in childrenProperty)
+ {
+ var exprText = RenderExpression(
+ prop,
+ new ExpressionRenderingContext()
+ {
+ ExpectConditionExpression = false,
+ EnclosingOperator = preCond
+ });
+ codeSnip.AppendLine(depth+1, $"{(isFirstRowOrderBy ? "" : ", ")}{exprText} {(isDescending ? "DESC" : "ASC")}");
+ isFirstRowOrderBy = false;
+ }
+ }
+ }
+
+ return codeSnip.ToString();
+ }
+
+ private string RenderSelection(SelectionOperator condOp, int depth)
+ {
+ var codeSnip = new StringBuilder();
+
+ // Assert that schema has not changed and matches
+ Debug.Assert(condOp.InputSchema.Count == condOp.OutputSchema.Count);
+
+ // expand output schema
+ var allColsToOutput = ExpandSchema(condOp.OutputSchema);
+ var allOutputEntities = condOp.OutputSchema.Where(f => f is EntityField).Cast();
+ var allOutputSingleFields = condOp.OutputSchema.Where(f => f is SingleField).Cast();
+ bool isFirstRow = true;
+
+ codeSnip.AppendLine(depth, $"SELECT");
+
+ // project entities and join keys need to be flowed
+ foreach (var ent in allOutputEntities)
+ {
+ // project keys for entities that are exposed by the projection
+ if (ent.Type == EntityField.EntityType.Node)
+ {
+ var nodeIdJoinKeyName = GetFieldNameForEntityField(ent.FieldAlias, ent.NodeJoinField.FieldAlias);
+ codeSnip.AppendLine(depth+1, $"{(!isFirstRow ? ", " : " ")}{nodeIdJoinKeyName} AS {nodeIdJoinKeyName}");
+ isFirstRow = false;
+ }
+ else
+ {
+ var edgeSrcIdJoinKeyName = GetFieldNameForEntityField(ent.FieldAlias, ent.RelSourceJoinField.FieldAlias);
+ var edgeSinkIdJoinKeyName = GetFieldNameForEntityField(ent.FieldAlias, ent.RelSinkJoinField.FieldAlias);
+ codeSnip.AppendLine(depth + 1, $"{(!isFirstRow ? ", " : " ")}{edgeSrcIdJoinKeyName} AS {edgeSrcIdJoinKeyName}");
+ codeSnip.AppendLine(depth + 1, $", {edgeSinkIdJoinKeyName} AS {edgeSinkIdJoinKeyName}");
+ isFirstRow = false;
+ }
+
+ // referenced fields in the wrapped entities
+ var nonNullJoinKeyValues = new string[] { ent.NodeJoinField?.FieldAlias, ent.RelSourceJoinField?.FieldAlias, ent.RelSinkJoinField?.FieldAlias }.Where(a => !string.IsNullOrEmpty(a));
+ foreach (var field in ent.ReferencedFieldAliases.Except(nonNullJoinKeyValues))
+ {
+ var fieldName = GetFieldNameForEntityField(ent.FieldAlias, field);
+ codeSnip.AppendLine(depth + 1, $", {fieldName} AS {fieldName}");
+ }
+ }
+
+ // project single properties (columns)
+ foreach (var field in allOutputSingleFields)
+ {
+ // find corresponding expression
+ codeSnip.AppendLine(depth + 1, $"{(!isFirstRow ? ", " : " ")}{field.FieldAlias} AS {field.FieldAlias}");
+ isFirstRow = false;
+ }
+
+ codeSnip.AppendLine(depth, $"FROM (");
+ codeSnip.AppendLine(RenderLogicalOperator(condOp.InOperator, depth+1));
+ codeSnip.AppendLine(depth, $") AS _select");
+
+ var condText = RenderExpression(
+ condOp.FilterExpression,
+ new ExpressionRenderingContext()
+ {
+ ExpectConditionExpression = true, // SQL does not allow value type to be in WHERE condition
+ EnclosingOperator = condOp
+ });
+ codeSnip.AppendLine(depth, $"WHERE");
+ codeSnip.AppendLine(depth+1, $"{condText}");
+
+ return codeSnip.ToString();
+ }
+
+ private string RenderSet(SetOperator opSel, int depth)
+ {
+ var codeSnip = new StringBuilder();
+ codeSnip.AppendLine(depth, "(");
+ codeSnip.AppendLine(RenderLogicalOperator(opSel.InOperatorLeft, depth + 1));
+ codeSnip.AppendLine(depth, $") {(opSel.SetOperation == SetOperator.SetOperationType.UnionAll ? "UNION ALL" : "UNION")}");
+ codeSnip.AppendLine(depth, "(");
+ codeSnip.AppendLine(RenderLogicalOperator(opSel.InOperatorRight, depth + 1));
+ codeSnip.AppendLine(depth, ")");
+
+ return codeSnip.ToString();
+ }
+
+
+ ///
+ /// Called by subclass to render main portion of the code
+ ///
+ ///
+ ///
+ private string RenderLogicalOperator(LogicalOperator op, int depth)
+ {
+ switch (op)
+ {
+ case DataSourceOperator opDs:
+ return RenderDataSource(opDs, depth);
+ case ProjectionOperator opProj:
+ return RenderProjection(opProj, depth);
+ case SelectionOperator opSel:
+ return RenderSelection(opSel, depth);
+ case JoinOperator opSel:
+ return RenderJoin(opSel, depth);
+ case SetOperator opSet:
+ return RenderSet(opSet, depth);
+ default:
+ throw new TranspilerInternalErrorException($"Unexpected operator type: {op.GetType().Name}");
+ }
+ }
+
+ #endregion Logical Operator Renderers
+
+ ///
+ /// Render the Scope code for a given plan and return the output variable names
+ ///
+ ///
+ ///
+ ///
+ public string RenderPlan(LogicalPlan logicalPlan)
+ {
+ var codeScript = new StringBuilder();
+
+ if (logicalPlan.TerminalOperators.Count() != 1)
+ {
+ throw new TranspilerNotSupportedException("Multi-output query");
+ }
+
+ var terminatingOperator = logicalPlan.TerminalOperators.First();
+
+ var queryCodeText = RenderLogicalOperator(terminatingOperator, 0);
+ return queryCodeText;
+ }
+
+ }
+}
diff --git a/src/SQLRenderer/SQLRenderer.csproj b/src/SQLRenderer/SQLRenderer.csproj
new file mode 100644
index 0000000..65402a7
--- /dev/null
+++ b/src/SQLRenderer/SQLRenderer.csproj
@@ -0,0 +1,12 @@
+
+
+
+ netcoreapp2.1
+ Microsoft.GraphPlatform.Transpiler.SQLRenderer
+
+
+
+
+
+
+
diff --git a/src/SQLRenderer/SQLRendererHelper.cs b/src/SQLRenderer/SQLRendererHelper.cs
new file mode 100644
index 0000000..cc5d222
--- /dev/null
+++ b/src/SQLRenderer/SQLRendererHelper.cs
@@ -0,0 +1,18 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+
+using System.Text;
+
+namespace openCypherTranspiler.SQLRenderer
+{
+ static class SQLRendererHelpers
+ {
+ public static StringBuilder AppendLine(this StringBuilder sb, int indentUnits, string line)
+ {
+ return sb.AppendLine($"{new string(' ', indentUnits * 4)}{line}");
+ }
+ }
+}
diff --git a/src/SQLRenderer/SQLTableDescriptor.cs b/src/SQLRenderer/SQLTableDescriptor.cs
new file mode 100644
index 0000000..5a1e8a6
--- /dev/null
+++ b/src/SQLRenderer/SQLTableDescriptor.cs
@@ -0,0 +1,14 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+namespace openCypherTranspiler.SQLRenderer
+{
+ public class SQLTableDescriptor
+ {
+ public string EntityId { get; set; }
+
+ public string TableOrViewName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/openCypherParser/AST/AggregationFunctionReturnTypeTable.cs b/src/openCypherParser/AST/AggregationFunctionReturnTypeTable.cs
new file mode 100644
index 0000000..d4297df
--- /dev/null
+++ b/src/openCypherParser/AST/AggregationFunctionReturnTypeTable.cs
@@ -0,0 +1,60 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+using System;
+using System.Collections.Generic;
+using openCypherTranspiler.openCypherParser.Common;
+
+namespace openCypherTranspiler.openCypherParser.AST
+{
+ public static class AggregationFunctionReturnTypeTable
+ {
+ public static readonly IDictionary<(AggregationFunction, Type), Type> TypeMapTable = new Dictionary<(AggregationFunction, Type), Type>()
+ {
+ { (AggregationFunction.Count, Type.GetType("System.Int32")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.Double")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.Int64")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.Int16")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.UInt16")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.UInt64")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.Byte")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.Single")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.UInt32")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.SByte")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.Decimal")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.String")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.Boolean")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.DateTime")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.Byte[]")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, Type.GetType("System.Object")), Type.GetType("System.Int64")},
+ { (AggregationFunction.Count, default(Type)), Type.GetType("System.Int64")},
+
+ { (AggregationFunction.Avg, Type.GetType("System.Int32")), Type.GetType("System.Nullable`1[System.Double]")},
+ { (AggregationFunction.Avg, Type.GetType("System.Double")), Type.GetType("System.Nullable`1[System.Double]")},
+ { (AggregationFunction.Avg, Type.GetType("System.Int64")), Type.GetType("System.Nullable`1[System.Double]")},
+ { (AggregationFunction.Avg, Type.GetType("System.Int16")), Type.GetType("System.Nullable`1[System.Double]")},
+ { (AggregationFunction.Avg, Type.GetType("System.UInt16")), Type.GetType("System.Nullable`1[System.Double]")},
+ { (AggregationFunction.Avg, Type.GetType("System.UInt64")), Type.GetType("System.Nullable`1[System.Double]")},
+ { (AggregationFunction.Avg, Type.GetType("System.Byte")), Type.GetType("System.Nullable`1[System.Double]")},
+ { (AggregationFunction.Avg, Type.GetType("System.Single")), Type.GetType("System.Nullable`1[System.Double]")},
+ { (AggregationFunction.Avg, Type.GetType("System.UInt32")), Type.GetType("System.Nullable`1[System.Double]")},
+ { (AggregationFunction.Avg, Type.GetType("System.SByte")), Type.GetType("System.Nullable`1[System.Double]")},
+ { (AggregationFunction.Avg, Type.GetType("System.Decimal")), Type.GetType("System.Nullable`1[System.Decimal]")},
+
+ { (AggregationFunction.Sum, Type.GetType("System.Int32")),Type.GetType("System.Int64") },
+ { (AggregationFunction.Sum, Type.GetType("System.Double")),Type.GetType("System.Double")},
+ { (AggregationFunction.Sum, Type.GetType("System.Int64")),Type.GetType("System.Int64") },
+ { (AggregationFunction.Sum, Type.GetType("System.Int16")),Type.GetType("System.Int64") },
+ { (AggregationFunction.Sum, Type.GetType("System.UInt16")),Type.GetType("System.Int64") },
+ { (AggregationFunction.Sum, Type.GetType("System.UInt64")),Type.GetType("System.Double")},
+ { (AggregationFunction.Sum, Type.GetType("System.Byte")),Type.GetType("System.Int64") },
+ { (AggregationFunction.Sum, Type.GetType("System.Single")),Type.GetType("System.Double")},
+ { (AggregationFunction.Sum, Type.GetType("System.UInt32")),Type.GetType("System.Int64") },
+ { (AggregationFunction.Sum, Type.GetType("System.SByte")),Type.GetType("System.Int64") },
+ { (AggregationFunction.Sum, Type.GetType("System.Decimal")),Type.GetType("System.Decimal")},
+
+ };
+ }
+}
diff --git a/src/openCypherParser/AST/BinaryOperatorCoersionTable.cs b/src/openCypherParser/AST/BinaryOperatorCoersionTable.cs
new file mode 100644
index 0000000..cbb87cc
--- /dev/null
+++ b/src/openCypherParser/AST/BinaryOperatorCoersionTable.cs
@@ -0,0 +1,1598 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+using System;
+using System.Collections.Generic;
+using openCypherTranspiler.openCypherParser.Common;
+
+namespace openCypherTranspiler.openCypherParser.AST
+{
+ ///
+ /// Lookup table for implicit conversions between primitive types
+ ///
+ public static class CoersionTables
+ {
+ public static readonly IDictionary<(BinaryOperator OpType, Type Op1Type, Type Op2Type), Type> CoersionTableForValueType = new Dictionary<(BinaryOperator OpType, Type Op1Type, Type Op2Type), Type>()
+ {
+ { (BinaryOperator.Plus, typeof(int), typeof(int)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(int), typeof(double)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(int), typeof(long)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(int), typeof(short)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(int), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(int), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Plus, typeof(int), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(int), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(int), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(int), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(int), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(int), typeof(string)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(int), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Plus, typeof(int), typeof(bool)), default(Type) },
+ { (BinaryOperator.Plus, typeof(double), typeof(int)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(double), typeof(double)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(double), typeof(long)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(double), typeof(short)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(double), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(double), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(double), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(double), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(double), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(double), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(double), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Plus, typeof(double), typeof(string)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(double), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Plus, typeof(double), typeof(bool)), default(Type) },
+ { (BinaryOperator.Plus, typeof(long), typeof(int)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(long), typeof(double)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(long), typeof(long)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(long), typeof(short)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(long), typeof(ushort)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(long), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Plus, typeof(long), typeof(byte)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(long), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(long), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(long), typeof(SByte)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(long), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(long), typeof(string)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(long), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Plus, typeof(long), typeof(bool)), default(Type) },
+ { (BinaryOperator.Plus, typeof(short), typeof(int)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(short), typeof(double)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(short), typeof(long)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(short), typeof(short)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(short), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(short), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Plus, typeof(short), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(short), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(short), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(short), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(short), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(short), typeof(string)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(short), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Plus, typeof(short), typeof(bool)), default(Type) },
+ { (BinaryOperator.Plus, typeof(ushort), typeof(int)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(ushort), typeof(double)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(ushort), typeof(long)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(ushort), typeof(short)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(ushort), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(ushort), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Plus, typeof(ushort), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(ushort), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(ushort), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Plus, typeof(ushort), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(ushort), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(ushort), typeof(string)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(ushort), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Plus, typeof(ushort), typeof(bool)), default(Type) },
+ { (BinaryOperator.Plus, typeof(ulong), typeof(int)), default(Type) },
+ { (BinaryOperator.Plus, typeof(ulong), typeof(double)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(ulong), typeof(long)), default(Type) },
+ { (BinaryOperator.Plus, typeof(ulong), typeof(short)), default(Type) },
+ { (BinaryOperator.Plus, typeof(ulong), typeof(ushort)), typeof(ulong) },
+ { (BinaryOperator.Plus, typeof(ulong), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Plus, typeof(ulong), typeof(byte)), typeof(ulong) },
+ { (BinaryOperator.Plus, typeof(ulong), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(ulong), typeof(uint)), typeof(ulong) },
+ { (BinaryOperator.Plus, typeof(ulong), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Plus, typeof(ulong), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(ulong), typeof(string)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(ulong), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Plus, typeof(ulong), typeof(bool)), default(Type) },
+ { (BinaryOperator.Plus, typeof(byte), typeof(int)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(byte), typeof(double)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(byte), typeof(long)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(byte), typeof(short)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(byte), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(byte), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Plus, typeof(byte), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(byte), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(byte), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Plus, typeof(byte), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(byte), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(byte), typeof(string)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(byte), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Plus, typeof(byte), typeof(bool)), default(Type) },
+ { (BinaryOperator.Plus, typeof(Single), typeof(int)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(Single), typeof(double)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(Single), typeof(long)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(Single), typeof(short)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(Single), typeof(ushort)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(Single), typeof(ulong)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(Single), typeof(byte)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(Single), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(Single), typeof(uint)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(Single), typeof(SByte)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(Single), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Plus, typeof(Single), typeof(string)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(Single), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Plus, typeof(Single), typeof(bool)), default(Type) },
+ { (BinaryOperator.Plus, typeof(uint), typeof(int)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(uint), typeof(double)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(uint), typeof(long)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(uint), typeof(short)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(uint), typeof(ushort)), typeof(uint) },
+ { (BinaryOperator.Plus, typeof(uint), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Plus, typeof(uint), typeof(byte)), typeof(uint) },
+ { (BinaryOperator.Plus, typeof(uint), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(uint), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Plus, typeof(uint), typeof(SByte)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(uint), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(uint), typeof(string)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(uint), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Plus, typeof(uint), typeof(bool)), default(Type) },
+ { (BinaryOperator.Plus, typeof(SByte), typeof(int)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(SByte), typeof(double)), typeof(double) },
+ { (BinaryOperator.Plus, typeof(SByte), typeof(long)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(SByte), typeof(short)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(SByte), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(SByte), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Plus, typeof(SByte), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(SByte), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Plus, typeof(SByte), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Plus, typeof(SByte), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Plus, typeof(SByte), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(SByte), typeof(string)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(SByte), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Plus, typeof(SByte), typeof(bool)), default(Type) },
+ { (BinaryOperator.Plus, typeof(decimal), typeof(int)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(decimal), typeof(double)), default(Type) },
+ { (BinaryOperator.Plus, typeof(decimal), typeof(long)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(decimal), typeof(short)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(decimal), typeof(ushort)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(decimal), typeof(ulong)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(decimal), typeof(byte)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(decimal), typeof(Single)), default(Type) },
+ { (BinaryOperator.Plus, typeof(decimal), typeof(uint)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(decimal), typeof(SByte)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(decimal), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Plus, typeof(decimal), typeof(string)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(decimal), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Plus, typeof(decimal), typeof(bool)), default(Type) },
+ { (BinaryOperator.Plus, typeof(string), typeof(int)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(string), typeof(double)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(string), typeof(long)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(string), typeof(short)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(string), typeof(ushort)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(string), typeof(ulong)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(string), typeof(byte)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(string), typeof(Single)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(string), typeof(uint)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(string), typeof(SByte)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(string), typeof(decimal)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(string), typeof(string)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(string), typeof(DateTime)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(string), typeof(bool)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(DateTime), typeof(int)), default(Type) },
+ { (BinaryOperator.Plus, typeof(DateTime), typeof(double)), default(Type) },
+ { (BinaryOperator.Plus, typeof(DateTime), typeof(long)), default(Type) },
+ { (BinaryOperator.Plus, typeof(DateTime), typeof(short)), default(Type) },
+ { (BinaryOperator.Plus, typeof(DateTime), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Plus, typeof(DateTime), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Plus, typeof(DateTime), typeof(byte)), default(Type) },
+ { (BinaryOperator.Plus, typeof(DateTime), typeof(Single)), default(Type) },
+ { (BinaryOperator.Plus, typeof(DateTime), typeof(uint)), default(Type) },
+ { (BinaryOperator.Plus, typeof(DateTime), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Plus, typeof(DateTime), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Plus, typeof(DateTime), typeof(string)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(DateTime), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Plus, typeof(DateTime), typeof(bool)), default(Type) },
+ { (BinaryOperator.Plus, typeof(bool), typeof(int)), default(Type) },
+ { (BinaryOperator.Plus, typeof(bool), typeof(double)), default(Type) },
+ { (BinaryOperator.Plus, typeof(bool), typeof(long)), default(Type) },
+ { (BinaryOperator.Plus, typeof(bool), typeof(short)), default(Type) },
+ { (BinaryOperator.Plus, typeof(bool), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Plus, typeof(bool), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Plus, typeof(bool), typeof(byte)), default(Type) },
+ { (BinaryOperator.Plus, typeof(bool), typeof(Single)), default(Type) },
+ { (BinaryOperator.Plus, typeof(bool), typeof(uint)), default(Type) },
+ { (BinaryOperator.Plus, typeof(bool), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Plus, typeof(bool), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Plus, typeof(bool), typeof(string)), typeof(string) },
+ { (BinaryOperator.Plus, typeof(bool), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Plus, typeof(bool), typeof(bool)), default(Type) },
+ { (BinaryOperator.Minus, typeof(int), typeof(int)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(int), typeof(double)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(int), typeof(long)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(int), typeof(short)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(int), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(int), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Minus, typeof(int), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(int), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(int), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(int), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(int), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(int), typeof(string)), default(Type) },
+ { (BinaryOperator.Minus, typeof(int), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Minus, typeof(int), typeof(bool)), default(Type) },
+ { (BinaryOperator.Minus, typeof(double), typeof(int)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(double), typeof(double)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(double), typeof(long)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(double), typeof(short)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(double), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(double), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(double), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(double), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(double), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(double), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(double), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Minus, typeof(double), typeof(string)), default(Type) },
+ { (BinaryOperator.Minus, typeof(double), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Minus, typeof(double), typeof(bool)), default(Type) },
+ { (BinaryOperator.Minus, typeof(long), typeof(int)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(long), typeof(double)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(long), typeof(long)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(long), typeof(short)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(long), typeof(ushort)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(long), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Minus, typeof(long), typeof(byte)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(long), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(long), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(long), typeof(SByte)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(long), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(long), typeof(string)), default(Type) },
+ { (BinaryOperator.Minus, typeof(long), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Minus, typeof(long), typeof(bool)), default(Type) },
+ { (BinaryOperator.Minus, typeof(short), typeof(int)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(short), typeof(double)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(short), typeof(long)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(short), typeof(short)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(short), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(short), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Minus, typeof(short), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(short), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(short), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(short), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(short), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(short), typeof(string)), default(Type) },
+ { (BinaryOperator.Minus, typeof(short), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Minus, typeof(short), typeof(bool)), default(Type) },
+ { (BinaryOperator.Minus, typeof(ushort), typeof(int)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(ushort), typeof(double)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(ushort), typeof(long)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(ushort), typeof(short)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(ushort), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(ushort), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Minus, typeof(ushort), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(ushort), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(ushort), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Minus, typeof(ushort), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(ushort), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(ushort), typeof(string)), default(Type) },
+ { (BinaryOperator.Minus, typeof(ushort), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Minus, typeof(ushort), typeof(bool)), default(Type) },
+ { (BinaryOperator.Minus, typeof(ulong), typeof(int)), default(Type) },
+ { (BinaryOperator.Minus, typeof(ulong), typeof(double)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(ulong), typeof(long)), default(Type) },
+ { (BinaryOperator.Minus, typeof(ulong), typeof(short)), default(Type) },
+ { (BinaryOperator.Minus, typeof(ulong), typeof(ushort)), typeof(ulong) },
+ { (BinaryOperator.Minus, typeof(ulong), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Minus, typeof(ulong), typeof(byte)), typeof(ulong) },
+ { (BinaryOperator.Minus, typeof(ulong), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(ulong), typeof(uint)), typeof(ulong) },
+ { (BinaryOperator.Minus, typeof(ulong), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Minus, typeof(ulong), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(ulong), typeof(string)), default(Type) },
+ { (BinaryOperator.Minus, typeof(ulong), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Minus, typeof(ulong), typeof(bool)), default(Type) },
+ { (BinaryOperator.Minus, typeof(byte), typeof(int)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(byte), typeof(double)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(byte), typeof(long)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(byte), typeof(short)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(byte), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(byte), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Minus, typeof(byte), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(byte), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(byte), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Minus, typeof(byte), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(byte), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(byte), typeof(string)), default(Type) },
+ { (BinaryOperator.Minus, typeof(byte), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Minus, typeof(byte), typeof(bool)), default(Type) },
+ { (BinaryOperator.Minus, typeof(Single), typeof(int)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(Single), typeof(double)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(Single), typeof(long)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(Single), typeof(short)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(Single), typeof(ushort)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(Single), typeof(ulong)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(Single), typeof(byte)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(Single), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(Single), typeof(uint)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(Single), typeof(SByte)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(Single), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Minus, typeof(Single), typeof(string)), default(Type) },
+ { (BinaryOperator.Minus, typeof(Single), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Minus, typeof(Single), typeof(bool)), default(Type) },
+ { (BinaryOperator.Minus, typeof(uint), typeof(int)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(uint), typeof(double)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(uint), typeof(long)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(uint), typeof(short)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(uint), typeof(ushort)), typeof(uint) },
+ { (BinaryOperator.Minus, typeof(uint), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Minus, typeof(uint), typeof(byte)), typeof(uint) },
+ { (BinaryOperator.Minus, typeof(uint), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(uint), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Minus, typeof(uint), typeof(SByte)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(uint), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(uint), typeof(string)), default(Type) },
+ { (BinaryOperator.Minus, typeof(uint), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Minus, typeof(uint), typeof(bool)), default(Type) },
+ { (BinaryOperator.Minus, typeof(SByte), typeof(int)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(SByte), typeof(double)), typeof(double) },
+ { (BinaryOperator.Minus, typeof(SByte), typeof(long)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(SByte), typeof(short)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(SByte), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(SByte), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Minus, typeof(SByte), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(SByte), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Minus, typeof(SByte), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Minus, typeof(SByte), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Minus, typeof(SByte), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(SByte), typeof(string)), default(Type) },
+ { (BinaryOperator.Minus, typeof(SByte), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Minus, typeof(SByte), typeof(bool)), default(Type) },
+ { (BinaryOperator.Minus, typeof(decimal), typeof(int)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(decimal), typeof(double)), default(Type) },
+ { (BinaryOperator.Minus, typeof(decimal), typeof(long)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(decimal), typeof(short)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(decimal), typeof(ushort)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(decimal), typeof(ulong)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(decimal), typeof(byte)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(decimal), typeof(Single)), default(Type) },
+ { (BinaryOperator.Minus, typeof(decimal), typeof(uint)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(decimal), typeof(SByte)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(decimal), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Minus, typeof(decimal), typeof(string)), default(Type) },
+ { (BinaryOperator.Minus, typeof(decimal), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Minus, typeof(decimal), typeof(bool)), default(Type) },
+ { (BinaryOperator.Minus, typeof(string), typeof(int)), default(Type) },
+ { (BinaryOperator.Minus, typeof(string), typeof(double)), default(Type) },
+ { (BinaryOperator.Minus, typeof(string), typeof(long)), default(Type) },
+ { (BinaryOperator.Minus, typeof(string), typeof(short)), default(Type) },
+ { (BinaryOperator.Minus, typeof(string), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Minus, typeof(string), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Minus, typeof(string), typeof(byte)), default(Type) },
+ { (BinaryOperator.Minus, typeof(string), typeof(Single)), default(Type) },
+ { (BinaryOperator.Minus, typeof(string), typeof(uint)), default(Type) },
+ { (BinaryOperator.Minus, typeof(string), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Minus, typeof(string), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Minus, typeof(string), typeof(string)), default(Type) },
+ { (BinaryOperator.Minus, typeof(string), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Minus, typeof(string), typeof(bool)), default(Type) },
+ { (BinaryOperator.Minus, typeof(DateTime), typeof(int)), default(Type) },
+ { (BinaryOperator.Minus, typeof(DateTime), typeof(double)), default(Type) },
+ { (BinaryOperator.Minus, typeof(DateTime), typeof(long)), default(Type) },
+ { (BinaryOperator.Minus, typeof(DateTime), typeof(short)), default(Type) },
+ { (BinaryOperator.Minus, typeof(DateTime), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Minus, typeof(DateTime), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Minus, typeof(DateTime), typeof(byte)), default(Type) },
+ { (BinaryOperator.Minus, typeof(DateTime), typeof(Single)), default(Type) },
+ { (BinaryOperator.Minus, typeof(DateTime), typeof(uint)), default(Type) },
+ { (BinaryOperator.Minus, typeof(DateTime), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Minus, typeof(DateTime), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Minus, typeof(DateTime), typeof(string)), default(Type) },
+ { (BinaryOperator.Minus, typeof(DateTime), typeof(DateTime)), Type.GetType("System.TimeSpan") },
+ { (BinaryOperator.Minus, typeof(DateTime), typeof(bool)), default(Type) },
+ { (BinaryOperator.Minus, typeof(bool), typeof(int)), default(Type) },
+ { (BinaryOperator.Minus, typeof(bool), typeof(double)), default(Type) },
+ { (BinaryOperator.Minus, typeof(bool), typeof(long)), default(Type) },
+ { (BinaryOperator.Minus, typeof(bool), typeof(short)), default(Type) },
+ { (BinaryOperator.Minus, typeof(bool), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Minus, typeof(bool), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Minus, typeof(bool), typeof(byte)), default(Type) },
+ { (BinaryOperator.Minus, typeof(bool), typeof(Single)), default(Type) },
+ { (BinaryOperator.Minus, typeof(bool), typeof(uint)), default(Type) },
+ { (BinaryOperator.Minus, typeof(bool), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Minus, typeof(bool), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Minus, typeof(bool), typeof(string)), default(Type) },
+ { (BinaryOperator.Minus, typeof(bool), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Minus, typeof(bool), typeof(bool)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(int), typeof(int)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(int), typeof(double)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(int), typeof(long)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(int), typeof(short)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(int), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(int), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(int), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(int), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(int), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(int), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(int), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(int), typeof(string)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(int), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(int), typeof(bool)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(double), typeof(int)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(double), typeof(double)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(double), typeof(long)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(double), typeof(short)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(double), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(double), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(double), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(double), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(double), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(double), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(double), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(double), typeof(string)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(double), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(double), typeof(bool)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(long), typeof(int)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(long), typeof(double)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(long), typeof(long)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(long), typeof(short)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(long), typeof(ushort)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(long), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(long), typeof(byte)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(long), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(long), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(long), typeof(SByte)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(long), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(long), typeof(string)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(long), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(long), typeof(bool)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(short), typeof(int)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(short), typeof(double)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(short), typeof(long)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(short), typeof(short)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(short), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(short), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(short), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(short), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(short), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(short), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(short), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(short), typeof(string)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(short), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(short), typeof(bool)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(ushort), typeof(int)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(ushort), typeof(double)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(ushort), typeof(long)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(ushort), typeof(short)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(ushort), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(ushort), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Multiply, typeof(ushort), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(ushort), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(ushort), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Multiply, typeof(ushort), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(ushort), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(ushort), typeof(string)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(ushort), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(ushort), typeof(bool)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(ulong), typeof(int)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(ulong), typeof(double)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(ulong), typeof(long)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(ulong), typeof(short)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(ulong), typeof(ushort)), typeof(ulong) },
+ { (BinaryOperator.Multiply, typeof(ulong), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Multiply, typeof(ulong), typeof(byte)), typeof(ulong) },
+ { (BinaryOperator.Multiply, typeof(ulong), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(ulong), typeof(uint)), typeof(ulong) },
+ { (BinaryOperator.Multiply, typeof(ulong), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(ulong), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(ulong), typeof(string)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(ulong), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(ulong), typeof(bool)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(byte), typeof(int)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(byte), typeof(double)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(byte), typeof(long)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(byte), typeof(short)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(byte), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(byte), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Multiply, typeof(byte), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(byte), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(byte), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Multiply, typeof(byte), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(byte), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(byte), typeof(string)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(byte), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(byte), typeof(bool)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(Single), typeof(int)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(Single), typeof(double)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(Single), typeof(long)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(Single), typeof(short)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(Single), typeof(ushort)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(Single), typeof(ulong)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(Single), typeof(byte)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(Single), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(Single), typeof(uint)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(Single), typeof(SByte)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(Single), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(Single), typeof(string)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(Single), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(Single), typeof(bool)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(uint), typeof(int)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(uint), typeof(double)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(uint), typeof(long)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(uint), typeof(short)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(uint), typeof(ushort)), typeof(uint) },
+ { (BinaryOperator.Multiply, typeof(uint), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Multiply, typeof(uint), typeof(byte)), typeof(uint) },
+ { (BinaryOperator.Multiply, typeof(uint), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(uint), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Multiply, typeof(uint), typeof(SByte)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(uint), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(uint), typeof(string)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(uint), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(uint), typeof(bool)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(SByte), typeof(int)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(SByte), typeof(double)), typeof(double) },
+ { (BinaryOperator.Multiply, typeof(SByte), typeof(long)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(SByte), typeof(short)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(SByte), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(SByte), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(SByte), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(SByte), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Multiply, typeof(SByte), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Multiply, typeof(SByte), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Multiply, typeof(SByte), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(SByte), typeof(string)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(SByte), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(SByte), typeof(bool)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(decimal), typeof(int)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(decimal), typeof(double)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(decimal), typeof(long)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(decimal), typeof(short)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(decimal), typeof(ushort)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(decimal), typeof(ulong)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(decimal), typeof(byte)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(decimal), typeof(Single)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(decimal), typeof(uint)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(decimal), typeof(SByte)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(decimal), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Multiply, typeof(decimal), typeof(string)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(decimal), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(decimal), typeof(bool)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(string), typeof(int)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(string), typeof(double)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(string), typeof(long)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(string), typeof(short)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(string), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(string), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(string), typeof(byte)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(string), typeof(Single)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(string), typeof(uint)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(string), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(string), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(string), typeof(string)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(string), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(string), typeof(bool)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(DateTime), typeof(int)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(DateTime), typeof(double)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(DateTime), typeof(long)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(DateTime), typeof(short)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(DateTime), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(DateTime), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(DateTime), typeof(byte)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(DateTime), typeof(Single)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(DateTime), typeof(uint)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(DateTime), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(DateTime), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(DateTime), typeof(string)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(DateTime), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(DateTime), typeof(bool)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(bool), typeof(int)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(bool), typeof(double)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(bool), typeof(long)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(bool), typeof(short)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(bool), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(bool), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(bool), typeof(byte)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(bool), typeof(Single)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(bool), typeof(uint)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(bool), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(bool), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(bool), typeof(string)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(bool), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Multiply, typeof(bool), typeof(bool)), default(Type) },
+ { (BinaryOperator.Divide, typeof(int), typeof(int)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(int), typeof(double)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(int), typeof(long)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(int), typeof(short)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(int), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(int), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Divide, typeof(int), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(int), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(int), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(int), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(int), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(int), typeof(string)), default(Type) },
+ { (BinaryOperator.Divide, typeof(int), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Divide, typeof(int), typeof(bool)), default(Type) },
+ { (BinaryOperator.Divide, typeof(double), typeof(int)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(double), typeof(double)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(double), typeof(long)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(double), typeof(short)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(double), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(double), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(double), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(double), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(double), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(double), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(double), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Divide, typeof(double), typeof(string)), default(Type) },
+ { (BinaryOperator.Divide, typeof(double), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Divide, typeof(double), typeof(bool)), default(Type) },
+ { (BinaryOperator.Divide, typeof(long), typeof(int)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(long), typeof(double)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(long), typeof(long)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(long), typeof(short)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(long), typeof(ushort)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(long), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Divide, typeof(long), typeof(byte)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(long), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(long), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(long), typeof(SByte)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(long), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(long), typeof(string)), default(Type) },
+ { (BinaryOperator.Divide, typeof(long), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Divide, typeof(long), typeof(bool)), default(Type) },
+ { (BinaryOperator.Divide, typeof(short), typeof(int)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(short), typeof(double)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(short), typeof(long)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(short), typeof(short)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(short), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(short), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Divide, typeof(short), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(short), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(short), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(short), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(short), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(short), typeof(string)), default(Type) },
+ { (BinaryOperator.Divide, typeof(short), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Divide, typeof(short), typeof(bool)), default(Type) },
+ { (BinaryOperator.Divide, typeof(ushort), typeof(int)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(ushort), typeof(double)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(ushort), typeof(long)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(ushort), typeof(short)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(ushort), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(ushort), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Divide, typeof(ushort), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(ushort), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(ushort), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Divide, typeof(ushort), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(ushort), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(ushort), typeof(string)), default(Type) },
+ { (BinaryOperator.Divide, typeof(ushort), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Divide, typeof(ushort), typeof(bool)), default(Type) },
+ { (BinaryOperator.Divide, typeof(ulong), typeof(int)), default(Type) },
+ { (BinaryOperator.Divide, typeof(ulong), typeof(double)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(ulong), typeof(long)), default(Type) },
+ { (BinaryOperator.Divide, typeof(ulong), typeof(short)), default(Type) },
+ { (BinaryOperator.Divide, typeof(ulong), typeof(ushort)), typeof(ulong) },
+ { (BinaryOperator.Divide, typeof(ulong), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Divide, typeof(ulong), typeof(byte)), typeof(ulong) },
+ { (BinaryOperator.Divide, typeof(ulong), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(ulong), typeof(uint)), typeof(ulong) },
+ { (BinaryOperator.Divide, typeof(ulong), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Divide, typeof(ulong), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(ulong), typeof(string)), default(Type) },
+ { (BinaryOperator.Divide, typeof(ulong), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Divide, typeof(ulong), typeof(bool)), default(Type) },
+ { (BinaryOperator.Divide, typeof(byte), typeof(int)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(byte), typeof(double)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(byte), typeof(long)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(byte), typeof(short)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(byte), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(byte), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Divide, typeof(byte), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(byte), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(byte), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Divide, typeof(byte), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(byte), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(byte), typeof(string)), default(Type) },
+ { (BinaryOperator.Divide, typeof(byte), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Divide, typeof(byte), typeof(bool)), default(Type) },
+ { (BinaryOperator.Divide, typeof(Single), typeof(int)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(Single), typeof(double)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(Single), typeof(long)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(Single), typeof(short)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(Single), typeof(ushort)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(Single), typeof(ulong)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(Single), typeof(byte)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(Single), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(Single), typeof(uint)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(Single), typeof(SByte)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(Single), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Divide, typeof(Single), typeof(string)), default(Type) },
+ { (BinaryOperator.Divide, typeof(Single), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Divide, typeof(Single), typeof(bool)), default(Type) },
+ { (BinaryOperator.Divide, typeof(uint), typeof(int)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(uint), typeof(double)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(uint), typeof(long)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(uint), typeof(short)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(uint), typeof(ushort)), typeof(uint) },
+ { (BinaryOperator.Divide, typeof(uint), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Divide, typeof(uint), typeof(byte)), typeof(uint) },
+ { (BinaryOperator.Divide, typeof(uint), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(uint), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Divide, typeof(uint), typeof(SByte)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(uint), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(uint), typeof(string)), default(Type) },
+ { (BinaryOperator.Divide, typeof(uint), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Divide, typeof(uint), typeof(bool)), default(Type) },
+ { (BinaryOperator.Divide, typeof(SByte), typeof(int)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(SByte), typeof(double)), typeof(double) },
+ { (BinaryOperator.Divide, typeof(SByte), typeof(long)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(SByte), typeof(short)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(SByte), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(SByte), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Divide, typeof(SByte), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(SByte), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Divide, typeof(SByte), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Divide, typeof(SByte), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Divide, typeof(SByte), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(SByte), typeof(string)), default(Type) },
+ { (BinaryOperator.Divide, typeof(SByte), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Divide, typeof(SByte), typeof(bool)), default(Type) },
+ { (BinaryOperator.Divide, typeof(decimal), typeof(int)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(decimal), typeof(double)), default(Type) },
+ { (BinaryOperator.Divide, typeof(decimal), typeof(long)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(decimal), typeof(short)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(decimal), typeof(ushort)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(decimal), typeof(ulong)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(decimal), typeof(byte)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(decimal), typeof(Single)), default(Type) },
+ { (BinaryOperator.Divide, typeof(decimal), typeof(uint)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(decimal), typeof(SByte)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(decimal), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Divide, typeof(decimal), typeof(string)), default(Type) },
+ { (BinaryOperator.Divide, typeof(decimal), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Divide, typeof(decimal), typeof(bool)), default(Type) },
+ { (BinaryOperator.Divide, typeof(string), typeof(int)), default(Type) },
+ { (BinaryOperator.Divide, typeof(string), typeof(double)), default(Type) },
+ { (BinaryOperator.Divide, typeof(string), typeof(long)), default(Type) },
+ { (BinaryOperator.Divide, typeof(string), typeof(short)), default(Type) },
+ { (BinaryOperator.Divide, typeof(string), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Divide, typeof(string), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Divide, typeof(string), typeof(byte)), default(Type) },
+ { (BinaryOperator.Divide, typeof(string), typeof(Single)), default(Type) },
+ { (BinaryOperator.Divide, typeof(string), typeof(uint)), default(Type) },
+ { (BinaryOperator.Divide, typeof(string), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Divide, typeof(string), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Divide, typeof(string), typeof(string)), default(Type) },
+ { (BinaryOperator.Divide, typeof(string), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Divide, typeof(string), typeof(bool)), default(Type) },
+ { (BinaryOperator.Divide, typeof(DateTime), typeof(int)), default(Type) },
+ { (BinaryOperator.Divide, typeof(DateTime), typeof(double)), default(Type) },
+ { (BinaryOperator.Divide, typeof(DateTime), typeof(long)), default(Type) },
+ { (BinaryOperator.Divide, typeof(DateTime), typeof(short)), default(Type) },
+ { (BinaryOperator.Divide, typeof(DateTime), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Divide, typeof(DateTime), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Divide, typeof(DateTime), typeof(byte)), default(Type) },
+ { (BinaryOperator.Divide, typeof(DateTime), typeof(Single)), default(Type) },
+ { (BinaryOperator.Divide, typeof(DateTime), typeof(uint)), default(Type) },
+ { (BinaryOperator.Divide, typeof(DateTime), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Divide, typeof(DateTime), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Divide, typeof(DateTime), typeof(string)), default(Type) },
+ { (BinaryOperator.Divide, typeof(DateTime), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Divide, typeof(DateTime), typeof(bool)), default(Type) },
+ { (BinaryOperator.Divide, typeof(bool), typeof(int)), default(Type) },
+ { (BinaryOperator.Divide, typeof(bool), typeof(double)), default(Type) },
+ { (BinaryOperator.Divide, typeof(bool), typeof(long)), default(Type) },
+ { (BinaryOperator.Divide, typeof(bool), typeof(short)), default(Type) },
+ { (BinaryOperator.Divide, typeof(bool), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Divide, typeof(bool), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Divide, typeof(bool), typeof(byte)), default(Type) },
+ { (BinaryOperator.Divide, typeof(bool), typeof(Single)), default(Type) },
+ { (BinaryOperator.Divide, typeof(bool), typeof(uint)), default(Type) },
+ { (BinaryOperator.Divide, typeof(bool), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Divide, typeof(bool), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Divide, typeof(bool), typeof(string)), default(Type) },
+ { (BinaryOperator.Divide, typeof(bool), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Divide, typeof(bool), typeof(bool)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(int), typeof(int)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(int), typeof(double)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(int), typeof(long)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(int), typeof(short)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(int), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(int), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(int), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(int), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(int), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(int), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(int), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(int), typeof(string)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(int), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(int), typeof(bool)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(double), typeof(int)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(double), typeof(double)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(double), typeof(long)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(double), typeof(short)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(double), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(double), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(double), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(double), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(double), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(double), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(double), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(double), typeof(string)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(double), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(double), typeof(bool)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(long), typeof(int)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(long), typeof(double)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(long), typeof(long)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(long), typeof(short)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(long), typeof(ushort)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(long), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(long), typeof(byte)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(long), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(long), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(long), typeof(SByte)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(long), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(long), typeof(string)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(long), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(long), typeof(bool)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(short), typeof(int)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(short), typeof(double)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(short), typeof(long)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(short), typeof(short)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(short), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(short), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(short), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(short), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(short), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(short), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(short), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(short), typeof(string)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(short), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(short), typeof(bool)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(ushort), typeof(int)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(ushort), typeof(double)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(ushort), typeof(long)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(ushort), typeof(short)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(ushort), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(ushort), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Modulo, typeof(ushort), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(ushort), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(ushort), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Modulo, typeof(ushort), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(ushort), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(ushort), typeof(string)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(ushort), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(ushort), typeof(bool)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(ulong), typeof(int)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(ulong), typeof(double)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(ulong), typeof(long)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(ulong), typeof(short)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(ulong), typeof(ushort)), typeof(ulong) },
+ { (BinaryOperator.Modulo, typeof(ulong), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Modulo, typeof(ulong), typeof(byte)), typeof(ulong) },
+ { (BinaryOperator.Modulo, typeof(ulong), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(ulong), typeof(uint)), typeof(ulong) },
+ { (BinaryOperator.Modulo, typeof(ulong), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(ulong), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(ulong), typeof(string)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(ulong), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(ulong), typeof(bool)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(byte), typeof(int)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(byte), typeof(double)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(byte), typeof(long)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(byte), typeof(short)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(byte), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(byte), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Modulo, typeof(byte), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(byte), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(byte), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Modulo, typeof(byte), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(byte), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(byte), typeof(string)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(byte), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(byte), typeof(bool)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(Single), typeof(int)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(Single), typeof(double)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(Single), typeof(long)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(Single), typeof(short)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(Single), typeof(ushort)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(Single), typeof(ulong)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(Single), typeof(byte)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(Single), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(Single), typeof(uint)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(Single), typeof(SByte)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(Single), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(Single), typeof(string)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(Single), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(Single), typeof(bool)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(uint), typeof(int)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(uint), typeof(double)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(uint), typeof(long)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(uint), typeof(short)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(uint), typeof(ushort)), typeof(uint) },
+ { (BinaryOperator.Modulo, typeof(uint), typeof(ulong)), typeof(ulong) },
+ { (BinaryOperator.Modulo, typeof(uint), typeof(byte)), typeof(uint) },
+ { (BinaryOperator.Modulo, typeof(uint), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(uint), typeof(uint)), typeof(uint) },
+ { (BinaryOperator.Modulo, typeof(uint), typeof(SByte)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(uint), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(uint), typeof(string)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(uint), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(uint), typeof(bool)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(SByte), typeof(int)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(SByte), typeof(double)), typeof(double) },
+ { (BinaryOperator.Modulo, typeof(SByte), typeof(long)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(SByte), typeof(short)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(SByte), typeof(ushort)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(SByte), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(SByte), typeof(byte)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(SByte), typeof(Single)), typeof(Single) },
+ { (BinaryOperator.Modulo, typeof(SByte), typeof(uint)), typeof(long) },
+ { (BinaryOperator.Modulo, typeof(SByte), typeof(SByte)), typeof(int) },
+ { (BinaryOperator.Modulo, typeof(SByte), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(SByte), typeof(string)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(SByte), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(SByte), typeof(bool)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(decimal), typeof(int)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(decimal), typeof(double)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(decimal), typeof(long)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(decimal), typeof(short)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(decimal), typeof(ushort)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(decimal), typeof(ulong)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(decimal), typeof(byte)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(decimal), typeof(Single)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(decimal), typeof(uint)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(decimal), typeof(SByte)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(decimal), typeof(decimal)), typeof(decimal) },
+ { (BinaryOperator.Modulo, typeof(decimal), typeof(string)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(decimal), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(decimal), typeof(bool)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(string), typeof(int)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(string), typeof(double)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(string), typeof(long)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(string), typeof(short)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(string), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(string), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(string), typeof(byte)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(string), typeof(Single)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(string), typeof(uint)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(string), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(string), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(string), typeof(string)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(string), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(string), typeof(bool)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(DateTime), typeof(int)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(DateTime), typeof(double)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(DateTime), typeof(long)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(DateTime), typeof(short)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(DateTime), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(DateTime), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(DateTime), typeof(byte)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(DateTime), typeof(Single)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(DateTime), typeof(uint)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(DateTime), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(DateTime), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(DateTime), typeof(string)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(DateTime), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(DateTime), typeof(bool)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(bool), typeof(int)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(bool), typeof(double)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(bool), typeof(long)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(bool), typeof(short)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(bool), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(bool), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(bool), typeof(byte)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(bool), typeof(Single)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(bool), typeof(uint)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(bool), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(bool), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(bool), typeof(string)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(bool), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Modulo, typeof(bool), typeof(bool)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(int), typeof(int)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(int), typeof(double)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(int), typeof(long)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(int), typeof(short)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(int), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(int), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(int), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(int), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(int), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(int), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(int), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(int), typeof(string)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(int), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(int), typeof(bool)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(double), typeof(int)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(double), typeof(double)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(double), typeof(long)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(double), typeof(short)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(double), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(double), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(double), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(double), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(double), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(double), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(double), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(double), typeof(string)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(double), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(double), typeof(bool)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(long), typeof(int)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(long), typeof(double)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(long), typeof(long)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(long), typeof(short)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(long), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(long), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(long), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(long), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(long), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(long), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(long), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(long), typeof(string)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(long), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(long), typeof(bool)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(short), typeof(int)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(short), typeof(double)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(short), typeof(long)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(short), typeof(short)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(short), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(short), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(short), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(short), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(short), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(short), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(short), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(short), typeof(string)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(short), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(short), typeof(bool)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(ushort), typeof(int)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ushort), typeof(double)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ushort), typeof(long)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ushort), typeof(short)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ushort), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ushort), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ushort), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ushort), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ushort), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ushort), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ushort), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(ushort), typeof(string)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(ushort), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(ushort), typeof(bool)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(ulong), typeof(int)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ulong), typeof(double)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ulong), typeof(long)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ulong), typeof(short)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ulong), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ulong), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ulong), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ulong), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ulong), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ulong), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(ulong), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(ulong), typeof(string)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(ulong), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(ulong), typeof(bool)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(byte), typeof(int)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(byte), typeof(double)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(byte), typeof(long)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(byte), typeof(short)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(byte), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(byte), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(byte), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(byte), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(byte), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(byte), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(byte), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(byte), typeof(string)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(byte), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(byte), typeof(bool)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(Single), typeof(int)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(Single), typeof(double)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(Single), typeof(long)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(Single), typeof(short)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(Single), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(Single), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(Single), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(Single), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(Single), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(Single), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(Single), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(Single), typeof(string)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(Single), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(Single), typeof(bool)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(uint), typeof(int)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(uint), typeof(double)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(uint), typeof(long)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(uint), typeof(short)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(uint), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(uint), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(uint), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(uint), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(uint), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(uint), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(uint), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(uint), typeof(string)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(uint), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(uint), typeof(bool)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(SByte), typeof(int)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(SByte), typeof(double)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(SByte), typeof(long)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(SByte), typeof(short)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(SByte), typeof(ushort)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(SByte), typeof(ulong)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(SByte), typeof(byte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(SByte), typeof(Single)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(SByte), typeof(uint)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(SByte), typeof(SByte)), typeof(double) },
+ { (BinaryOperator.Exponentiation, typeof(SByte), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(SByte), typeof(string)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(SByte), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(SByte), typeof(bool)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(decimal), typeof(int)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(decimal), typeof(double)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(decimal), typeof(long)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(decimal), typeof(short)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(decimal), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(decimal), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(decimal), typeof(byte)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(decimal), typeof(Single)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(decimal), typeof(uint)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(decimal), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(decimal), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(decimal), typeof(string)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(decimal), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(decimal), typeof(bool)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(string), typeof(int)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(string), typeof(double)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(string), typeof(long)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(string), typeof(short)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(string), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(string), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(string), typeof(byte)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(string), typeof(Single)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(string), typeof(uint)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(string), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(string), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(string), typeof(string)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(string), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(string), typeof(bool)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(DateTime), typeof(int)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(DateTime), typeof(double)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(DateTime), typeof(long)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(DateTime), typeof(short)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(DateTime), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(DateTime), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(DateTime), typeof(byte)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(DateTime), typeof(Single)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(DateTime), typeof(uint)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(DateTime), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(DateTime), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(DateTime), typeof(string)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(DateTime), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(DateTime), typeof(bool)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(bool), typeof(int)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(bool), typeof(double)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(bool), typeof(long)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(bool), typeof(short)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(bool), typeof(ushort)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(bool), typeof(ulong)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(bool), typeof(byte)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(bool), typeof(Single)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(bool), typeof(uint)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(bool), typeof(SByte)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(bool), typeof(decimal)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(bool), typeof(string)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(bool), typeof(DateTime)), default(Type) },
+ { (BinaryOperator.Exponentiation, typeof(bool), typeof(bool)), default(Type) },
+ };
+
+ public static readonly IDictionary<(Type O1Type, Type O2Type), Type> CoersionTableEqualityComparison = new Dictionary<(Type Op1Type, Type Op2Type), Type>()
+ {
+ { (typeof(int), typeof(int)), typeof(bool) },
+ { (typeof(int), typeof(double)), typeof(bool) },
+ { (typeof(int), typeof(long)), typeof(bool) },
+ { (typeof(int), typeof(short)), typeof(bool) },
+ { (typeof(int), typeof(ushort)), typeof(bool) },
+ { (typeof(int), typeof(ulong)), default(Type) },
+ { (typeof(int), typeof(byte)), typeof(bool) },
+ { (typeof(int), typeof(Single)), typeof(bool) },
+ { (typeof(int), typeof(uint)), typeof(bool) },
+ { (typeof(int), typeof(SByte)), typeof(bool) },
+ { (typeof(int), typeof(decimal)), typeof(bool) },
+ { (typeof(int), typeof(string)), default(Type) },
+ { (typeof(int), typeof(DateTime)), default(Type) },
+ { (typeof(int), typeof(bool)), default(Type) },
+ { (typeof(double), typeof(int)), typeof(bool) },
+ { (typeof(double), typeof(double)), typeof(bool) },
+ { (typeof(double), typeof(long)), typeof(bool) },
+ { (typeof(double), typeof(short)), typeof(bool) },
+ { (typeof(double), typeof(ushort)), typeof(bool) },
+ { (typeof(double), typeof(ulong)), typeof(bool) },
+ { (typeof(double), typeof(byte)), typeof(bool) },
+ { (typeof(double), typeof(Single)), typeof(bool) },
+ { (typeof(double), typeof(uint)), typeof(bool) },
+ { (typeof(double), typeof(SByte)), typeof(bool) },
+ { (typeof(double), typeof(decimal)), default(Type) },
+ { (typeof(double), typeof(string)), default(Type) },
+ { (typeof(double), typeof(DateTime)), default(Type) },
+ { (typeof(double), typeof(bool)), default(Type) },
+ { (typeof(long), typeof(int)), typeof(bool) },
+ { (typeof(long), typeof(double)), typeof(bool) },
+ { (typeof(long), typeof(long)), typeof(bool) },
+ { (typeof(long), typeof(short)), typeof(bool) },
+ { (typeof(long), typeof(ushort)), typeof(bool) },
+ { (typeof(long), typeof(ulong)), default(Type) },
+ { (typeof(long), typeof(byte)), typeof(bool) },
+ { (typeof(long), typeof(Single)), typeof(bool) },
+ { (typeof(long), typeof(uint)), typeof(bool) },
+ { (typeof(long), typeof(SByte)), typeof(bool) },
+ { (typeof(long), typeof(decimal)), typeof(bool) },
+ { (typeof(long), typeof(string)), default(Type) },
+ { (typeof(long), typeof(DateTime)), default(Type) },
+ { (typeof(long), typeof(bool)), default(Type) },
+ { (typeof(short), typeof(int)), typeof(bool) },
+ { (typeof(short), typeof(double)), typeof(bool) },
+ { (typeof(short), typeof(long)), typeof(bool) },
+ { (typeof(short), typeof(short)), typeof(bool) },
+ { (typeof(short), typeof(ushort)), typeof(bool) },
+ { (typeof(short), typeof(ulong)), default(Type) },
+ { (typeof(short), typeof(byte)), typeof(bool) },
+ { (typeof(short), typeof(Single)), typeof(bool) },
+ { (typeof(short), typeof(uint)), typeof(bool) },
+ { (typeof(short), typeof(SByte)), typeof(bool) },
+ { (typeof(short), typeof(decimal)), typeof(bool) },
+ { (typeof(short), typeof(string)), default(Type) },
+ { (typeof(short), typeof(DateTime)), default(Type) },
+ { (typeof(short), typeof(bool)), default(Type) },
+ { (typeof(ushort), typeof(int)), typeof(bool) },
+ { (typeof(ushort), typeof(double)), typeof(bool) },
+ { (typeof(ushort), typeof(long)), typeof(bool) },
+ { (typeof(ushort), typeof(short)), typeof(bool) },
+ { (typeof(ushort), typeof(ushort)), typeof(bool) },
+ { (typeof(ushort), typeof(ulong)), typeof(bool) },
+ { (typeof(ushort), typeof(byte)), typeof(bool) },
+ { (typeof(ushort), typeof(Single)), typeof(bool) },
+ { (typeof(ushort), typeof(uint)), typeof(bool) },
+ { (typeof(ushort), typeof(SByte)), typeof(bool) },
+ { (typeof(ushort), typeof(decimal)), typeof(bool) },
+ { (typeof(ushort), typeof(string)), default(Type) },
+ { (typeof(ushort), typeof(DateTime)), default(Type) },
+ { (typeof(ushort), typeof(bool)), default(Type) },
+ { (typeof(ulong), typeof(int)), default(Type) },
+ { (typeof(ulong), typeof(double)), typeof(bool) },
+ { (typeof(ulong), typeof(long)), default(Type) },
+ { (typeof(ulong), typeof(short)), default(Type) },
+ { (typeof(ulong), typeof(ushort)), typeof(bool) },
+ { (typeof(ulong), typeof(ulong)), typeof(bool) },
+ { (typeof(ulong), typeof(byte)), typeof(bool) },
+ { (typeof(ulong), typeof(Single)), typeof(bool) },
+ { (typeof(ulong), typeof(uint)), typeof(bool) },
+ { (typeof(ulong), typeof(SByte)), default(Type) },
+ { (typeof(ulong), typeof(decimal)), typeof(bool) },
+ { (typeof(ulong), typeof(string)), default(Type) },
+ { (typeof(ulong), typeof(DateTime)), default(Type) },
+ { (typeof(ulong), typeof(bool)), default(Type) },
+ { (typeof(byte), typeof(int)), typeof(bool) },
+ { (typeof(byte), typeof(double)), typeof(bool) },
+ { (typeof(byte), typeof(long)), typeof(bool) },
+ { (typeof(byte), typeof(short)), typeof(bool) },
+ { (typeof(byte), typeof(ushort)), typeof(bool) },
+ { (typeof(byte), typeof(ulong)), typeof(bool) },
+ { (typeof(byte), typeof(byte)), typeof(bool) },
+ { (typeof(byte), typeof(Single)), typeof(bool) },
+ { (typeof(byte), typeof(uint)), typeof(bool) },
+ { (typeof(byte), typeof(SByte)), typeof(bool) },
+ { (typeof(byte), typeof(decimal)), typeof(bool) },
+ { (typeof(byte), typeof(string)), default(Type) },
+ { (typeof(byte), typeof(DateTime)), default(Type) },
+ { (typeof(byte), typeof(bool)), default(Type) },
+ { (typeof(Single), typeof(int)), typeof(bool) },
+ { (typeof(Single), typeof(double)), typeof(bool) },
+ { (typeof(Single), typeof(long)), typeof(bool) },
+ { (typeof(Single), typeof(short)), typeof(bool) },
+ { (typeof(Single), typeof(ushort)), typeof(bool) },
+ { (typeof(Single), typeof(ulong)), typeof(bool) },
+ { (typeof(Single), typeof(byte)), typeof(bool) },
+ { (typeof(Single), typeof(Single)), typeof(bool) },
+ { (typeof(Single), typeof(uint)), typeof(bool) },
+ { (typeof(Single), typeof(SByte)), typeof(bool) },
+ { (typeof(Single), typeof(decimal)), default(Type) },
+ { (typeof(Single), typeof(string)), default(Type) },
+ { (typeof(Single), typeof(DateTime)), default(Type) },
+ { (typeof(Single), typeof(bool)), default(Type) },
+ { (typeof(uint), typeof(int)), typeof(bool) },
+ { (typeof(uint), typeof(double)), typeof(bool) },
+ { (typeof(uint), typeof(long)), typeof(bool) },
+ { (typeof(uint), typeof(short)), typeof(bool) },
+ { (typeof(uint), typeof(ushort)), typeof(bool) },
+ { (typeof(uint), typeof(ulong)), typeof(bool) },
+ { (typeof(uint), typeof(byte)), typeof(bool) },
+ { (typeof(uint), typeof(Single)), typeof(bool) },
+ { (typeof(uint), typeof(uint)), typeof(bool) },
+ { (typeof(uint), typeof(SByte)), typeof(bool) },
+ { (typeof(uint), typeof(decimal)), typeof(bool) },
+ { (typeof(uint), typeof(string)), default(Type) },
+ { (typeof(uint), typeof(DateTime)), default(Type) },
+ { (typeof(uint), typeof(bool)), default(Type) },
+ { (typeof(SByte), typeof(int)), typeof(bool) },
+ { (typeof(SByte), typeof(double)), typeof(bool) },
+ { (typeof(SByte), typeof(long)), typeof(bool) },
+ { (typeof(SByte), typeof(short)), typeof(bool) },
+ { (typeof(SByte), typeof(ushort)), typeof(bool) },
+ { (typeof(SByte), typeof(ulong)), default(Type) },
+ { (typeof(SByte), typeof(byte)), typeof(bool) },
+ { (typeof(SByte), typeof(Single)), typeof(bool) },
+ { (typeof(SByte), typeof(uint)), typeof(bool) },
+ { (typeof(SByte), typeof(SByte)), typeof(bool) },
+ { (typeof(SByte), typeof(decimal)), typeof(bool) },
+ { (typeof(SByte), typeof(string)), default(Type) },
+ { (typeof(SByte), typeof(DateTime)), default(Type) },
+ { (typeof(SByte), typeof(bool)), default(Type) },
+ { (typeof(decimal), typeof(int)), typeof(bool) },
+ { (typeof(decimal), typeof(double)), default(Type) },
+ { (typeof(decimal), typeof(long)), typeof(bool) },
+ { (typeof(decimal), typeof(short)), typeof(bool) },
+ { (typeof(decimal), typeof(ushort)), typeof(bool) },
+ { (typeof(decimal), typeof(ulong)), typeof(bool) },
+ { (typeof(decimal), typeof(byte)), typeof(bool) },
+ { (typeof(decimal), typeof(Single)), default(Type) },
+ { (typeof(decimal), typeof(uint)), typeof(bool) },
+ { (typeof(decimal), typeof(SByte)), typeof(bool) },
+ { (typeof(decimal), typeof(decimal)), typeof(bool) },
+ { (typeof(decimal), typeof(string)), default(Type) },
+ { (typeof(decimal), typeof(DateTime)), default(Type) },
+ { (typeof(decimal), typeof(bool)), default(Type) },
+ { (typeof(string), typeof(int)), default(Type) },
+ { (typeof(string), typeof(double)), default(Type) },
+ { (typeof(string), typeof(long)), default(Type) },
+ { (typeof(string), typeof(short)), default(Type) },
+ { (typeof(string), typeof(ushort)), default(Type) },
+ { (typeof(string), typeof(ulong)), default(Type) },
+ { (typeof(string), typeof(byte)), default(Type) },
+ { (typeof(string), typeof(Single)), default(Type) },
+ { (typeof(string), typeof(uint)), default(Type) },
+ { (typeof(string), typeof(SByte)), default(Type) },
+ { (typeof(string), typeof(decimal)), default(Type) },
+ { (typeof(string), typeof(string)), typeof(bool) },
+ { (typeof(string), typeof(DateTime)), default(Type) },
+ { (typeof(string), typeof(bool)), default(Type) },
+ { (typeof(DateTime), typeof(int)), default(Type) },
+ { (typeof(DateTime), typeof(double)), default(Type) },
+ { (typeof(DateTime), typeof(long)), default(Type) },
+ { (typeof(DateTime), typeof(short)), default(Type) },
+ { (typeof(DateTime), typeof(ushort)), default(Type) },
+ { (typeof(DateTime), typeof(ulong)), default(Type) },
+ { (typeof(DateTime), typeof(byte)), default(Type) },
+ { (typeof(DateTime), typeof(Single)), default(Type) },
+ { (typeof(DateTime), typeof(uint)), default(Type) },
+ { (typeof(DateTime), typeof(SByte)), default(Type) },
+ { (typeof(DateTime), typeof(decimal)), default(Type) },
+ { (typeof(DateTime), typeof(string)), default(Type) },
+ { (typeof(DateTime), typeof(DateTime)), typeof(bool) },
+ { (typeof(DateTime), typeof(bool)), default(Type) },
+ { (typeof(bool), typeof(int)), default(Type) },
+ { (typeof(bool), typeof(double)), default(Type) },
+ { (typeof(bool), typeof(long)), default(Type) },
+ { (typeof(bool), typeof(short)), default(Type) },
+ { (typeof(bool), typeof(ushort)), default(Type) },
+ { (typeof(bool), typeof(ulong)), default(Type) },
+ { (typeof(bool), typeof(byte)), default(Type) },
+ { (typeof(bool), typeof(Single)), default(Type) },
+ { (typeof(bool), typeof(uint)), default(Type) },
+ { (typeof(bool), typeof(SByte)), default(Type) },
+ { (typeof(bool), typeof(decimal)), default(Type) },
+ { (typeof(bool), typeof(string)), default(Type) },
+ { (typeof(bool), typeof(DateTime)), default(Type) },
+ { (typeof(bool), typeof(bool)), typeof(bool) },
+ };
+
+ public static readonly IDictionary<(Type O1Type, Type O2Type), Type> CoersionTableInequalityComparison = new Dictionary<(Type Op1Type, Type Op2Type), Type>()
+ {
+ { (typeof(int), typeof(int)), typeof(bool) },
+ { (typeof(int), typeof(double)), typeof(bool) },
+ { (typeof(int), typeof(long)), typeof(bool) },
+ { (typeof(int), typeof(short)), typeof(bool) },
+ { (typeof(int), typeof(ushort)), typeof(bool) },
+ { (typeof(int), typeof(ulong)), default(Type) },
+ { (typeof(int), typeof(byte)), typeof(bool) },
+ { (typeof(int), typeof(Single)), typeof(bool) },
+ { (typeof(int), typeof(uint)), typeof(bool) },
+ { (typeof(int), typeof(SByte)), typeof(bool) },
+ { (typeof(int), typeof(decimal)), typeof(bool) },
+ { (typeof(int), typeof(string)), default(Type) },
+ { (typeof(int), typeof(DateTime)), default(Type) },
+ { (typeof(int), typeof(bool)), default(Type) },
+ { (typeof(double), typeof(int)), typeof(bool) },
+ { (typeof(double), typeof(double)), typeof(bool) },
+ { (typeof(double), typeof(long)), typeof(bool) },
+ { (typeof(double), typeof(short)), typeof(bool) },
+ { (typeof(double), typeof(ushort)), typeof(bool) },
+ { (typeof(double), typeof(ulong)), typeof(bool) },
+ { (typeof(double), typeof(byte)), typeof(bool) },
+ { (typeof(double), typeof(Single)), typeof(bool) },
+ { (typeof(double), typeof(uint)), typeof(bool) },
+ { (typeof(double), typeof(SByte)), typeof(bool) },
+ { (typeof(double), typeof(decimal)), default(Type) },
+ { (typeof(double), typeof(string)), default(Type) },
+ { (typeof(double), typeof(DateTime)), default(Type) },
+ { (typeof(double), typeof(bool)), default(Type) },
+ { (typeof(long), typeof(int)), typeof(bool) },
+ { (typeof(long), typeof(double)), typeof(bool) },
+ { (typeof(long), typeof(long)), typeof(bool) },
+ { (typeof(long), typeof(short)), typeof(bool) },
+ { (typeof(long), typeof(ushort)), typeof(bool) },
+ { (typeof(long), typeof(ulong)), default(Type) },
+ { (typeof(long), typeof(byte)), typeof(bool) },
+ { (typeof(long), typeof(Single)), typeof(bool) },
+ { (typeof(long), typeof(uint)), typeof(bool) },
+ { (typeof(long), typeof(SByte)), typeof(bool) },
+ { (typeof(long), typeof(decimal)), typeof(bool) },
+ { (typeof(long), typeof(string)), default(Type) },
+ { (typeof(long), typeof(DateTime)), default(Type) },
+ { (typeof(long), typeof(bool)), default(Type) },
+ { (typeof(short), typeof(int)), typeof(bool) },
+ { (typeof(short), typeof(double)), typeof(bool) },
+ { (typeof(short), typeof(long)), typeof(bool) },
+ { (typeof(short), typeof(short)), typeof(bool) },
+ { (typeof(short), typeof(ushort)), typeof(bool) },
+ { (typeof(short), typeof(ulong)), default(Type) },
+ { (typeof(short), typeof(byte)), typeof(bool) },
+ { (typeof(short), typeof(Single)), typeof(bool) },
+ { (typeof(short), typeof(uint)), typeof(bool) },
+ { (typeof(short), typeof(SByte)), typeof(bool) },
+ { (typeof(short), typeof(decimal)), typeof(bool) },
+ { (typeof(short), typeof(string)), default(Type) },
+ { (typeof(short), typeof(DateTime)), default(Type) },
+ { (typeof(short), typeof(bool)), default(Type) },
+ { (typeof(ushort), typeof(int)), typeof(bool) },
+ { (typeof(ushort), typeof(double)), typeof(bool) },
+ { (typeof(ushort), typeof(long)), typeof(bool) },
+ { (typeof(ushort), typeof(short)), typeof(bool) },
+ { (typeof(ushort), typeof(ushort)), typeof(bool) },
+ { (typeof(ushort), typeof(ulong)), typeof(bool) },
+ { (typeof(ushort), typeof(byte)), typeof(bool) },
+ { (typeof(ushort), typeof(Single)), typeof(bool) },
+ { (typeof(ushort), typeof(uint)), typeof(bool) },
+ { (typeof(ushort), typeof(SByte)), typeof(bool) },
+ { (typeof(ushort), typeof(decimal)), typeof(bool) },
+ { (typeof(ushort), typeof(string)), default(Type) },
+ { (typeof(ushort), typeof(DateTime)), default(Type) },
+ { (typeof(ushort), typeof(bool)), default(Type) },
+ { (typeof(ulong), typeof(int)), default(Type) },
+ { (typeof(ulong), typeof(double)), typeof(bool) },
+ { (typeof(ulong), typeof(long)), default(Type) },
+ { (typeof(ulong), typeof(short)), default(Type) },
+ { (typeof(ulong), typeof(ushort)), typeof(bool) },
+ { (typeof(ulong), typeof(ulong)), typeof(bool) },
+ { (typeof(ulong), typeof(byte)), typeof(bool) },
+ { (typeof(ulong), typeof(Single)), typeof(bool) },
+ { (typeof(ulong), typeof(uint)), typeof(bool) },
+ { (typeof(ulong), typeof(SByte)), default(Type) },
+ { (typeof(ulong), typeof(decimal)), typeof(bool) },
+ { (typeof(ulong), typeof(string)), default(Type) },
+ { (typeof(ulong), typeof(DateTime)), default(Type) },
+ { (typeof(ulong), typeof(bool)), default(Type) },
+ { (typeof(byte), typeof(int)), typeof(bool) },
+ { (typeof(byte), typeof(double)), typeof(bool) },
+ { (typeof(byte), typeof(long)), typeof(bool) },
+ { (typeof(byte), typeof(short)), typeof(bool) },
+ { (typeof(byte), typeof(ushort)), typeof(bool) },
+ { (typeof(byte), typeof(ulong)), typeof(bool) },
+ { (typeof(byte), typeof(byte)), typeof(bool) },
+ { (typeof(byte), typeof(Single)), typeof(bool) },
+ { (typeof(byte), typeof(uint)), typeof(bool) },
+ { (typeof(byte), typeof(SByte)), typeof(bool) },
+ { (typeof(byte), typeof(decimal)), typeof(bool) },
+ { (typeof(byte), typeof(string)), default(Type) },
+ { (typeof(byte), typeof(DateTime)), default(Type) },
+ { (typeof(byte), typeof(bool)), default(Type) },
+ { (typeof(Single), typeof(int)), typeof(bool) },
+ { (typeof(Single), typeof(double)), typeof(bool) },
+ { (typeof(Single), typeof(long)), typeof(bool) },
+ { (typeof(Single), typeof(short)), typeof(bool) },
+ { (typeof(Single), typeof(ushort)), typeof(bool) },
+ { (typeof(Single), typeof(ulong)), typeof(bool) },
+ { (typeof(Single), typeof(byte)), typeof(bool) },
+ { (typeof(Single), typeof(Single)), typeof(bool) },
+ { (typeof(Single), typeof(uint)), typeof(bool) },
+ { (typeof(Single), typeof(SByte)), typeof(bool) },
+ { (typeof(Single), typeof(decimal)), default(Type) },
+ { (typeof(Single), typeof(string)), default(Type) },
+ { (typeof(Single), typeof(DateTime)), default(Type) },
+ { (typeof(Single), typeof(bool)), default(Type) },
+ { (typeof(uint), typeof(int)), typeof(bool) },
+ { (typeof(uint), typeof(double)), typeof(bool) },
+ { (typeof(uint), typeof(long)), typeof(bool) },
+ { (typeof(uint), typeof(short)), typeof(bool) },
+ { (typeof(uint), typeof(ushort)), typeof(bool) },
+ { (typeof(uint), typeof(ulong)), typeof(bool) },
+ { (typeof(uint), typeof(byte)), typeof(bool) },
+ { (typeof(uint), typeof(Single)), typeof(bool) },
+ { (typeof(uint), typeof(uint)), typeof(bool) },
+ { (typeof(uint), typeof(SByte)), typeof(bool) },
+ { (typeof(uint), typeof(decimal)), typeof(bool) },
+ { (typeof(uint), typeof(string)), default(Type) },
+ { (typeof(uint), typeof(DateTime)), default(Type) },
+ { (typeof(uint), typeof(bool)), default(Type) },
+ { (typeof(SByte), typeof(int)), typeof(bool) },
+ { (typeof(SByte), typeof(double)), typeof(bool) },
+ { (typeof(SByte), typeof(long)), typeof(bool) },
+ { (typeof(SByte), typeof(short)), typeof(bool) },
+ { (typeof(SByte), typeof(ushort)), typeof(bool) },
+ { (typeof(SByte), typeof(ulong)), default(Type) },
+ { (typeof(SByte), typeof(byte)), typeof(bool) },
+ { (typeof(SByte), typeof(Single)), typeof(bool) },
+ { (typeof(SByte), typeof(uint)), typeof(bool) },
+ { (typeof(SByte), typeof(SByte)), typeof(bool) },
+ { (typeof(SByte), typeof(decimal)), typeof(bool) },
+ { (typeof(SByte), typeof(string)), default(Type) },
+ { (typeof(SByte), typeof(DateTime)), default(Type) },
+ { (typeof(SByte), typeof(bool)), default(Type) },
+ { (typeof(decimal), typeof(int)), typeof(bool) },
+ { (typeof(decimal), typeof(double)), default(Type) },
+ { (typeof(decimal), typeof(long)), typeof(bool) },
+ { (typeof(decimal), typeof(short)), typeof(bool) },
+ { (typeof(decimal), typeof(ushort)), typeof(bool) },
+ { (typeof(decimal), typeof(ulong)), typeof(bool) },
+ { (typeof(decimal), typeof(byte)), typeof(bool) },
+ { (typeof(decimal), typeof(Single)), default(Type) },
+ { (typeof(decimal), typeof(uint)), typeof(bool) },
+ { (typeof(decimal), typeof(SByte)), typeof(bool) },
+ { (typeof(decimal), typeof(decimal)), typeof(bool) },
+ { (typeof(decimal), typeof(string)), default(Type) },
+ { (typeof(decimal), typeof(DateTime)), default(Type) },
+ { (typeof(decimal), typeof(bool)), default(Type) },
+ { (typeof(string), typeof(int)), default(Type) },
+ { (typeof(string), typeof(double)), default(Type) },
+ { (typeof(string), typeof(long)), default(Type) },
+ { (typeof(string), typeof(short)), default(Type) },
+ { (typeof(string), typeof(ushort)), default(Type) },
+ { (typeof(string), typeof(ulong)), default(Type) },
+ { (typeof(string), typeof(byte)), default(Type) },
+ { (typeof(string), typeof(Single)), default(Type) },
+ { (typeof(string), typeof(uint)), default(Type) },
+ { (typeof(string), typeof(SByte)), default(Type) },
+ { (typeof(string), typeof(decimal)), default(Type) },
+ { (typeof(string), typeof(string)), typeof(bool) },
+ { (typeof(string), typeof(DateTime)), default(Type) },
+ { (typeof(string), typeof(bool)), default(Type) },
+ { (typeof(DateTime), typeof(int)), default(Type) },
+ { (typeof(DateTime), typeof(double)), default(Type) },
+ { (typeof(DateTime), typeof(long)), default(Type) },
+ { (typeof(DateTime), typeof(short)), default(Type) },
+ { (typeof(DateTime), typeof(ushort)), default(Type) },
+ { (typeof(DateTime), typeof(ulong)), default(Type) },
+ { (typeof(DateTime), typeof(byte)), default(Type) },
+ { (typeof(DateTime), typeof(Single)), default(Type) },
+ { (typeof(DateTime), typeof(uint)), default(Type) },
+ { (typeof(DateTime), typeof(SByte)), default(Type) },
+ { (typeof(DateTime), typeof(decimal)), default(Type) },
+ { (typeof(DateTime), typeof(string)), default(Type) },
+ { (typeof(DateTime), typeof(DateTime)), typeof(bool) },
+ { (typeof(DateTime), typeof(bool)), default(Type) },
+ { (typeof(bool), typeof(int)), default(Type) },
+ { (typeof(bool), typeof(double)), default(Type) },
+ { (typeof(bool), typeof(long)), default(Type) },
+ { (typeof(bool), typeof(short)), default(Type) },
+ { (typeof(bool), typeof(ushort)), default(Type) },
+ { (typeof(bool), typeof(ulong)), default(Type) },
+ { (typeof(bool), typeof(byte)), default(Type) },
+ { (typeof(bool), typeof(Single)), default(Type) },
+ { (typeof(bool), typeof(uint)), default(Type) },
+ { (typeof(bool), typeof(SByte)), default(Type) },
+ { (typeof(bool), typeof(decimal)), default(Type) },
+ { (typeof(bool), typeof(string)), default(Type) },
+ { (typeof(bool), typeof(DateTime)), default(Type) },
+ { (typeof(bool), typeof(bool)), typeof(Type) },
+ };
+ }
+
+}
diff --git a/src/openCypherParser/AST/CypherVisitor.cs b/src/openCypherParser/AST/CypherVisitor.cs
new file mode 100644
index 0000000..a1637e9
--- /dev/null
+++ b/src/openCypherParser/AST/CypherVisitor.cs
@@ -0,0 +1,2055 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Antlr4.Runtime;
+using Antlr4.Runtime.Misc;
+using Antlr4.Runtime.Tree;
+using System.Text.RegularExpressions;
+using openCypherTranspiler.Common.Exceptions;
+using openCypherTranspiler.Common.Logging;
+using openCypherTranspiler.openCypherParser.ANTLR;
+using openCypherTranspiler.openCypherParser.AST;
+using openCypherTranspiler.openCypherParser.Common;
+using openCypherTranspiler.Common.Utils;
+
+namespace openCypherTranspiler.openCypherParser.AST
+{
+ ///
+ /// openCypher's ANTLR4 visitor to turn parsing result CST to AST
+ ///
+ internal class CypherVisitor : CypherBaseVisitor
+ {
+ private ILoggable _logger;
+
+ private class Optional where T : ParserRuleContext { }
+
+ #region Helper functions
+
+ // check existence of parent nodes
+ private bool IsInsideContextType(Type type, RuleContext ctx)
+ {
+ var parentObj = ctx?.Parent;
+ while (parentObj != null)
+ {
+ if (parentObj.GetType() == type)
+ {
+ return true;
+ }
+ parentObj = parentObj?.Parent;
+ }
+ return false;
+ }
+
+ private bool IsContextContainsTextToken(IParseTree context, string textToken)
+ {
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+ if (child is ITerminalNode && string.Compare(child.GetText(), textToken, true) == 0)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private bool IsContextSP(IParseTree context)
+ {
+ return (context is ITerminalNode && (context as ITerminalNode).Symbol.Type == CypherParser.SP);
+ }
+
+ // 'take' the current state variable as a value but at same time reset it
+ private T TakeAndResetStateVariable(ref T variable)
+ {
+ T tempValue = variable;
+ variable = default(T);
+ return tempValue;
+ }
+
+ // helper function to see if a context node's children match specfic pattern (useful when it supports more than one type of patterns)
+ private bool IsMatchSequence(ParserRuleContext ctx, params object[] seq)
+ {
+ List ctxChildren = new List();
+ for (int i = 0; i < ctx.ChildCount; i++)
+ {
+ var ctxChild = ctx.GetChild(i);
+ if (ctxChild is ITerminalNode)
+ {
+ if (!IsContextSP(ctxChild))
+ {
+ ctxChildren.Add(ctxChild.GetText());
+ }
+ }
+ else
+ {
+ ctxChildren.Add(ctxChild.GetType());
+ }
+ }
+ string matchSrc = string.Join("|", ctxChildren.Select(o => o is string ? (string)o : ((Type)o).Name)) + "|";
+ string matchPat = string.Join("",
+ seq.Select(o => o is string ? $"{Regex.Escape((string)o)}\\|" :
+ TypeHelper.IsSubclassOfRawGeneric((Type)o, typeof(Optional<>)) ? $"(?:{Regex.Escape(((Type)o).Name)}\\|)?" : $"{Regex.Escape(((Type)o).Name)}\\|")
+ );
+ return Regex.IsMatch(matchSrc, matchPat);
+ }
+
+ private QueryExpressionBinary HandleChainedBinaryExpressions(IParseTree context)
+ {
+ var parentExpression = new QueryExpressionBinary();
+ var supportedOperators = new string[] { "+", "-", "%", "*", "/" };
+
+ bool visitedLeft = false;
+ bool visitedOperator = false;
+ bool visitedRight = false;
+
+
+ // get left expression
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (!visitedLeft && !(child is ITerminalNode))
+ {
+ Debug.Assert(
+ // list of supported expressions in this form today:
+ child is CypherParser.OC_AddOrSubtractExpressionContext ||
+ child is CypherParser.OC_MultiplyDivideModuloExpressionContext ||
+ child is CypherParser.OC_PowerOfExpressionContext
+ );
+
+ var childExpr = Visit(child) as QueryExpression ?? throw new TranspilerSyntaxErrorException($"Expect valid left expression {child.GetText()}");
+ parentExpression.LeftExpression = childExpr;
+ visitedLeft = true;
+ continue;
+ }
+
+ if (child is ITerminalNode && supportedOperators.Contains(context.GetChild(i).GetText()))
+ {
+ var op = child.GetText();
+
+ if (parentExpression.Operator != null)
+ {
+ Debug.Assert(parentExpression.RightExpression != null);
+ // not the first right operator/expr pair. do chaining
+ var newParentExpression = new QueryExpressionBinary()
+ {
+ LeftExpression = parentExpression
+ };
+ parentExpression = newParentExpression;
+ }
+
+ var opEnum = OperatorHelper.TryGetOperator(op)
+ ?? throw new TranspilerSyntaxErrorException($"Unknown or unsupported operator {op}");
+ parentExpression.Operator = opEnum;
+ visitedOperator = true;
+ // reset visited right as soon as we see another new operator
+ visitedRight = false;
+ continue;
+ }
+
+ if (!visitedRight && !(child is ITerminalNode))
+ {
+ Debug.Assert(visitedOperator);
+ var childExpr = Visit(child) as QueryExpression ?? throw new TranspilerSyntaxErrorException($"Expect valid right expression {child.GetText()}");
+ parentExpression.RightExpression = childExpr;
+ visitedRight = true;
+ continue;
+ }
+ }
+
+ Debug.Assert(visitedLeft && visitedOperator && visitedRight);
+ return parentExpression;
+ }
+
+ private QueryExpressionBinary HandlesBinaryExpression(IParseTree context, string op)
+ {
+ var opEnum = OperatorHelper.TryGetOperator(op)
+ ?? throw new TranspilerSyntaxErrorException($"Unknown or unsupported operator {op}");
+
+ QueryExpression finalExp = null;
+ bool visitedLeft = false;
+
+ // get left expression
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (!visitedLeft && !(child is ITerminalNode))
+ {
+ var childExpr = Visit(child) as QueryExpression
+ ?? throw new TranspilerSyntaxErrorException($"Expect valid left side expression {child.GetText()}");
+ finalExp = childExpr;
+ visitedLeft = true;
+ continue;
+ }
+
+ if (!(child is ITerminalNode))
+ {
+ var childExpr = Visit(child) as QueryExpression
+ ?? throw new TranspilerSyntaxErrorException($"Expect valid right side expression {child.GetText()}");
+ finalExp = new QueryExpressionBinary()
+ {
+ Operator = opEnum,
+ LeftExpression = finalExp,
+ RightExpression = childExpr
+ };
+ continue;
+ }
+ }
+
+ return finalExp as QueryExpressionBinary
+ ?? throw new TranspilerSyntaxErrorException($"Expecting at least 2 sides of a binary operator: {context.GetText()}");
+ }
+
+ private QueryExpressionFunction HandlesUnaryExpression(IParseTree context)
+ {
+ // We treat function and unary expression the same:
+ // e.g. +expr or -expr, or NOT expr
+ QueryExpression finalExpr = null;
+ var supportedOperators = new string[] { "+", "-'" };
+ var unaryOpChain = new Stack();
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (child is ITerminalNode && supportedOperators.Any(op => op == child.GetText()))
+ {
+ Debug.Assert(finalExpr == null); // operator should only appear before the expression
+ var fi = FunctionHelper.TryGetFunctionInfo(child.GetText().Trim())
+ ?? throw new TranspilerInternalErrorException("Parsing function info"); // unexpected operator. should have stopped by caller
+ unaryOpChain.Push(fi);
+ continue;
+ }
+
+ if (!(child is ITerminalNode))
+ {
+ Debug.Assert(finalExpr == null); // expr should only appear once in unary operator
+ finalExpr = Visit(child) as QueryExpression
+ ?? throw new TranspilerSyntaxErrorException($"Expect valid right expression {child.GetText()}");
+ }
+ }
+
+ Debug.Assert(unaryOpChain.Count > 0);
+ Debug.Assert(finalExpr != null);
+
+ while (unaryOpChain.Count > 0)
+ {
+ finalExpr = new QueryExpressionFunction()
+ {
+ Function = unaryOpChain.Pop(),
+ InnerExpression = finalExpr
+ };
+ }
+
+ return finalExpr as QueryExpressionFunction
+ ?? throw new TranspilerSyntaxErrorException($"Expect valid expression for an unary operator at least once: {context.GetText()}");
+ }
+
+ private QueryExpressionFunction HandlesUnaryFuncExpression(IParseTree context, string funcName)
+ {
+ // We treat function and unary expression the same:
+ // e.g. +expr or -expr, or NOT expr
+ var fi = FunctionHelper.TryGetFunctionInfo(funcName)
+ // unexpected function name: should have been stopped by the caller to HandlesUnaryFuncExpression
+ ?? throw new TranspilerInternalErrorException("Parsing function info");
+
+ var parentExpression = new QueryExpressionFunction()
+ {
+ Function = fi
+ };
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (!(child is ITerminalNode))
+ {
+ var childExpr = Visit(child) as QueryExpression ?? throw new TranspilerSyntaxErrorException($"Expect valid right expression {child.GetText()}");
+ Debug.Assert(parentExpression.InnerExpression == null);
+ parentExpression.InnerExpression = childExpr;
+ }
+ }
+
+ return parentExpression;
+ }
+
+ ///
+ /// Helper function to get all direct entity reference from a return body item list ( [some expr] AS [alias] )
+ /// E.g. MATCH (p:person) WITH p as q, p.id as Id, this will return just Entity with alias = p and type = erpson
+ ///
+ ///
+ /// A dictionary of the reference to the Entity object keyed by its alias (in AS [alias] expr)
+ private IDictionary GetEntitiesFromReturnBodyByRef(IEnumerable exprs)
+ {
+ return exprs
+ .Select(expr => new { Alias = expr.Alias, Entity = expr.InnerExpression.TryGetDirectlyExposedEntity() })
+ .Where(e => e.Entity != null)
+ .ToDictionary(expr => expr.Alias, expr => expr.Entity);
+ }
+
+ ///
+ /// Helper function to call GetEntitiesFromReturnBodyByRef but then replace the alias with the QueryExpressionWithAlias's alias
+ /// E.g. MATCH (p:person) WITH p as q. This will return entity type person with alias = q (instead of p)
+ ///
+ ///
+ ///
+ private IEnumerable GetEntitiesFromReturnBodyWithAliasApplied(IEnumerable exprs)
+ {
+ return GetEntitiesFromReturnBodyByRef(exprs)
+ .Select(kv =>
+ {
+ var ent = kv.Value.Clone();
+ ent.Alias = kv.Key;
+ return ent;
+ });
+ }
+
+ ///
+ /// Helper function to get all non entity fields from a return body item list ( [some expr] AS [alias] )
+ /// E.g. ... WITH avg+1 as shift_avg. This will return QueryExprWithAlias: alias = shift_avg, expr = avg+1
+ ///
+ ///
+ ///
+ private IEnumerable GetNonEntitiesAliasesFromReturnBody(IEnumerable exprs)
+ {
+ return exprs
+ .Where(expr => expr.InnerExpression.TryGetDirectlyExposedEntity() == null)
+ .Select(expr => expr.Alias);
+ }
+
+
+ private PartialQueryTreeNode CreateQueryPartFromReadingClauses(
+ CypherParser.OC_ReadingClauseContext readingClause,
+ PartialQueryTreeNode prevQueryPart
+ )
+ {
+ // this function consolidates consolidate multiple matches, each is a list of MatchPattern, and
+ // enforce that optional match cannot be the first one
+ var matchParts = new List(); // consolidate multiple matches, each is a list of MatchPattern
+
+ // returns valuetuple type (pass through):
+ // { MatchPatterns(IList), Condition(QueryExpression) }
+ var result = ((IList MatchPatterns, QueryExpression Condition))Visit(readingClause);
+ IList matchPatterns = result.MatchPatterns ?? throw new TranspilerInternalErrorException("Parsing oC_ReadingClause");
+
+ var isOptionalMatch = matchPatterns.Any(p => p.IsOptionalMatch);
+ Debug.Assert(matchPatterns.All(p => p.IsOptionalMatch == isOptionalMatch));
+
+ if (isOptionalMatch)
+ {
+ // if optional match, then it cannot be the first reading clause
+ if (prevQueryPart == null)
+ {
+ throw new TranspilerSyntaxErrorException("First MATCH cannot be OPTIONAL");
+ }
+ }
+ else
+ {
+ // not optional match, then we cannot follow immediately after optional match
+ if (prevQueryPart != null && !prevQueryPart.CanChainNonOptionalMatch)
+ {
+ throw new TranspilerSyntaxErrorException("MATCH cannot follow OPTIONAL MATCH (perhaps use a WITH clause between them)");
+ }
+ }
+
+ matchParts.AddRange(matchPatterns);
+
+ // construct the match data source (representing a group of matches)
+ MatchDataSource matchDs = null;
+ if (matchParts.Count > 0)
+ {
+ matchDs = new MatchDataSource()
+ {
+ MatchPatterns = matchParts
+ };
+ }
+
+ // A Match statment (not RET or WITH) has implied projection expressions as returned items of this partial query node:
+ // - includes all named entities in the match pattern (later, we can optimize out if some are not used at all)
+ // - includes all inherited returns
+ var allReturnedEntities = matchParts.SelectMany(l => l)
+ .Union(prevQueryPart != null ? GetEntitiesFromReturnBodyWithAliasApplied(prevQueryPart.ProjectedExpressions) : Enumerable.Empty())
+ .Where(e => !string.IsNullOrEmpty(e.Alias))
+ .GroupBy(e => e.Alias);
+ // TODO: missing alias duplicacy check here with entity
+ var allInheritedProperties = prevQueryPart != null ? GetNonEntitiesAliasesFromReturnBody(prevQueryPart.ProjectedExpressions) : Enumerable.Empty();
+
+ var returnedExprs = allReturnedEntities
+ .Select(g =>
+ {
+ if (g.Select(e => e.EntityName).Where(en => !string.IsNullOrEmpty(en)).Distinct().Count() > 1)
+ {
+ throw new TranspilerSyntaxErrorException($"Multiple labels assigned to same alias {g.Key}: {string.Join(",", g.Select(e => e.EntityName).Where(en => !string.IsNullOrEmpty(en)))}");
+ }
+ return new QueryExpressionWithAlias()
+ {
+ Alias = g.Key,
+ InnerExpression = new QueryExpressionProperty()
+ {
+ Entity = g.FirstOrDefault(en => !string.IsNullOrEmpty(en.EntityName)).Clone(),
+ VariableName = g.Key
+ }
+ };
+ })
+ .Union(
+ allInheritedProperties.Select(alias =>
+ new QueryExpressionWithAlias()
+ {
+ Alias = alias,
+ InnerExpression = new QueryExpressionProperty()
+ {
+ VariableName = alias
+ }
+ }
+ )
+ )
+ .ToList();
+
+ return new PartialQueryTreeNode()
+ {
+ MatchData = matchDs,
+ PostCondition = result.Condition,
+ ProjectedExpressions = returnedExprs,
+ PipedData = prevQueryPart,
+ IsDistinct = false, // MATCH reading clause will not be come with DISTINCT keyword
+ CanChainNonOptionalMatch = !isOptionalMatch,
+ IsImplicitProjection = true, // MATCH's projection is always implied (returning all aliased nodes)
+ };
+ }
+
+ private SingleQueryTreeNode ConstructSingleQuery([NotNull] CypherParser.OC_SinglePartQueryContext context, PartialQueryTreeNode prevQueryPart)
+ {
+ Debug.Assert(context.oC_Return() != null);
+
+ SingleQueryTreeNode queryNode = null;
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (child is CypherParser.OC_ReadingClauseContext) // MATCH (WHERE)
+ {
+ var partQueryTree = CreateQueryPartFromReadingClauses(
+ child as CypherParser.OC_ReadingClauseContext,
+ prevQueryPart
+ );
+
+ // set state for next clause
+ prevQueryPart = partQueryTree;
+ }
+ else if (child is CypherParser.OC_ReturnContext) // RETURN
+ {
+ // when we see an WITH, we know that we we are done with one query part of a multi-query part
+ // we will construct a new query part object, and if applicable, chain with previous query part
+ // and construct the MATCH patterns
+ if (prevQueryPart == null)
+ {
+ throw new TranspilerNotSupportedException("RETURN projection without reading clauses (a.k.a. Match)");
+ }
+
+ // returns valuetuple type:
+ // { IsDistinct(bool), ReturnItems(IList), OrderByItems(IList) }
+ var returnResult = ((
+ bool IsDistinct,
+ IList ReturnItems,
+ IList OrderByItems,
+ IList LimitItems))Visit(child);
+ var projectionExprs = returnResult.ReturnItems ?? throw new TranspilerSyntaxErrorException($"Expecting a valid list of items to be returned: {child.GetText()}");
+ var orderedExprs = returnResult.OrderByItems;
+ var limitExprs = returnResult.LimitItems;
+
+ bool isDistinct = returnResult.IsDistinct;
+ var singleQueryNode = new SingleQueryTreeNode()
+ {
+ PipedData = prevQueryPart,
+ EntityPropertySelected = projectionExprs,
+ IsDistinct = isDistinct,
+ EntityPropertyOrderBy = orderedExprs,
+ EntityRowsLimit = limitExprs,
+ };
+
+ // Check that return has no conflict
+ var firstDup = projectionExprs.GroupBy(expr => expr.Alias).FirstOrDefault(g => g.Count() > 1);
+ if (firstDup != null)
+ {
+ throw new TranspilerSyntaxErrorException($"Multiple result columns with the same name are not supported: {firstDup.Key}");
+ }
+
+ // propagate the entity types to the list of return items that are direct exposure of entities
+ // NOTE: currently we assume direct reference. Any function manipulation will cause it become
+ // a non-node reference so that it cannot be used as entity variables in next MATCH statement
+ // e.g. u as u or (u) as u is okay , AVG(u) as u is no longer an enity
+ Debug.Assert(projectionExprs.All(p => p is QueryExpressionWithAlias));
+ UpdateReturnBodyWithEntityTypes(
+ projectionExprs.Cast(),
+ GetEntitiesFromReturnBodyWithAliasApplied(prevQueryPart.ProjectedExpressions)
+ );
+
+ queryNode = singleQueryNode;
+ }
+ else if (child is CypherParser.OC_UpdatingClauseContext)
+ {
+ throw new TranspilerNotSupportedException("Updaing clause");
+ }
+ else
+ {
+ // skip the rest type nodes (e.g. SP)
+ }
+ }
+
+ return queryNode;
+ }
+
+ private void UpdateReturnBodyWithEntityTypes(IEnumerable exprsToUpdate, IEnumerable entities)
+ {
+ var projectionExprsReferingEntities = exprsToUpdate
+ .Where(p => p.InnerExpression is QueryExpressionProperty &&
+ string.IsNullOrEmpty(((QueryExpressionProperty)p.InnerExpression).PropertyName))
+ .Select(p => p.InnerExpression as QueryExpressionProperty)
+ .ToList();
+ projectionExprsReferingEntities.ForEach(e =>
+ {
+ var entityMatchedAlias = entities.FirstOrDefault(e2 => e2.Alias == e.VariableName);
+ if (entityMatchedAlias != null)
+ {
+ e.Entity = entityMatchedAlias.Clone();
+ }
+ else
+ {
+ // if we cannot match alias, we treat is not as entity reference for now and ignore it
+ // later part of parsing should fail for any dangling reference
+ }
+ });
+ }
+
+ #endregion Helper functions
+
+ public CypherVisitor(ILoggable logger)
+ {
+ _logger = logger;
+ }
+
+ // this method returns the final tree
+ public override object VisitOC_Cypher([NotNull] CypherParser.OC_CypherContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_Cypher", context.GetText());
+ // return the final parsed tree of type QueryTreeNode (pass through)
+ Debug.Assert(context.oC_Statement() != null);
+ return Visit(context.oC_Statement()) as QueryTreeNode ?? throw new TranspilerInternalErrorException("Parsing oC_Statement");
+ }
+
+ public override object VisitOC_Query([NotNull] CypherParser.OC_QueryContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_Query", context.GetText());
+ if (context.oC_RegularQuery() != null)
+ {
+ // return the final parsed tree of type QueryTreeNode (pass through)
+ return Visit(context.oC_RegularQuery()) as QueryTreeNode ?? throw new TranspilerInternalErrorException("Parsing oc_RegularQuery");
+ }
+ else
+ {
+ throw new TranspilerNotSupportedException("CALL statement");
+ }
+ }
+
+ public override object VisitOC_RegularQuery([NotNull] CypherParser.OC_RegularQueryContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_RegularQuery", context.GetText());
+ // returns QueryTreeNode, can be a passed through SingleQueryTreeNode, or InfixQueryTreeNode
+
+ Debug.Assert(context.oC_SingleQuery() != null);
+ var rootNode = Visit(context.oC_SingleQuery()) as QueryTreeNode ?? throw new TranspilerInternalErrorException("Parsing oC_SingleQuery");
+
+ if ((context.oC_Union()?.Length ?? 0) > 0)
+ {
+ foreach (var childCtx in context.oC_Union())
+ {
+ Debug.Assert(childCtx.UNION() != null);
+ var unioned = new InfixQueryTreeNode()
+ {
+ LeftQueryTreeNode = rootNode,
+ RightQueryTreeNode = Visit(childCtx.oC_SingleQuery()) as QueryTreeNode ?? throw new TranspilerInternalErrorException("Parsing oC_SingleQuery of UNION"),
+ Operator = childCtx.ALL() != null ? InfixQueryTreeNode.QueryOperator.UnionAll : InfixQueryTreeNode.QueryOperator.Union
+ };
+ rootNode = unioned;
+ }
+ }
+
+ return Visit(context.oC_SingleQuery()) as QueryTreeNode ?? throw new TranspilerInternalErrorException("Parsing oC_SingleQuery");
+ }
+
+ public override object VisitOC_SingleQuery([NotNull] CypherParser.OC_SingleQueryContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_SingleQuery", context.GetText());
+ // returns SingleQueryTreeNode
+
+ // In this function, we fix up the chained query parts to populate all the inferred labels
+ // to do this, we walk down to the bottom (leaf node) of the tree and walk back, and each step we
+ // fix up the current query parts and update the returned entity types
+ // For any types we cannot infer, we throw exception
+ var singleQuery = VisitChildren(context) as SingleQueryTreeNode ?? throw new TranspilerInternalErrorException("Parsing oC_SinglePartQuery or oC_MultiPartQuery");
+
+ var queryChainTreeNode = singleQuery.PipedData as PartialQueryTreeNode;
+ Debug.Assert(queryChainTreeNode != null); // should be guaranteed by child visitor already
+ var queryChain = new Stack();
+ queryChain.Push(queryChainTreeNode);
+
+ while (queryChainTreeNode.PipedData != null)
+ {
+ queryChainTreeNode = queryChainTreeNode.PipedData;
+ queryChain.Push(queryChainTreeNode);
+ }
+
+ // propagate entity names for all entity objects with same alias for each query part, from
+ // bottom up of the query tree
+ // for any conflict type (e.g. single alias assigned to 2 types), raise error
+ var inheritedEntities = Enumerable.Empty();
+ while (queryChain.Count > 0)
+ {
+ var queryTreeNode = queryChain.Pop();
+ var entitiesInReturn = GetEntitiesFromReturnBodyByRef(queryTreeNode.ProjectedExpressions);
+ var entitiesInMatchPatterns = queryTreeNode.MatchData?.AllEntitiesOrdered ?? Enumerable.Empty().ToList();
+
+ // first pass, propagate all node and edge labels for those sharing aliases
+ entitiesInReturn
+ .Values
+ .Union(entitiesInMatchPatterns)
+ .Where(e => !string.IsNullOrEmpty(e.Alias)) // skip over all anonymous node/edge. we later assert that they all have types already specified explictly
+ .GroupBy(e => e.Alias)
+ .ToList()
+ .ForEach(g =>
+ {
+ // Ensure that aliases are assigned type and label consistently
+ var entityNames = g.Select(e => e.EntityName).Where(en => !string.IsNullOrEmpty(en)).Distinct();
+ var entityTypes = g.Select(e => e.GetType()).Distinct();
+
+ if (entityTypes.Count() != 1)
+ {
+ throw new TranspilerSyntaxErrorException($"The same alias '{g.Key}' is assigned to both NODE and RELATIONSHIPS");
+ }
+ if (entityNames.Count() > 1)
+ {
+ throw new TranspilerSyntaxErrorException($"The alias '{g.Key}' assigned to more than one labels {string.Join(", ", entityNames)}");
+ }
+ if (entityNames.Count() == 0)
+ {
+ throw new TranspilerSyntaxErrorException($"Type label for alias '{g.Key}' cannot be deduced. Did you not give a label to it?");
+ }
+
+ g.ToList().ForEach(e =>
+ {
+ e.EntityName = entityNames.First();
+ });
+
+ });
+
+ // second pass, loop through all relationships and update the the source and sink entity name
+ for (int i = 0; i < entitiesInMatchPatterns.Count; i++)
+ {
+ var relEnt = entitiesInMatchPatterns[i] as RelationshipEntity;
+
+ if (relEnt != null)
+ {
+ var prevEnt = entitiesInMatchPatterns[i - 1] as NodeEntity;
+ var nextEnt = entitiesInMatchPatterns[i + 1] as NodeEntity;
+ Debug.Assert(prevEnt != null && nextEnt != null);
+ relEnt.LeftEntityName = prevEnt.EntityName;
+ relEnt.RightEntityName = nextEnt.EntityName;
+ }
+ }
+
+ // third pass, update all relationship entities' in/out node names
+ entitiesInReturn
+ .Values
+ .Union(inheritedEntities)
+ .Where(e => e is RelationshipEntity)
+ .Union(entitiesInMatchPatterns.Where(e => e is RelationshipEntity))
+ .Where(e => !string.IsNullOrEmpty(e.Alias)) // skip over anonymous edges . we later assert that they all have types already specified explictly
+ .GroupBy(e => e.Alias)
+ .ToList()
+ .ForEach(g =>
+ {
+ var entityTypes = g.Select(e => e.GetType()).Distinct();
+ Debug.Assert(entityTypes.Count() == 1);
+
+ if (entityTypes.First() == typeof(RelationshipEntity))
+ {
+ var entityFromNames = g.Select(e => (e as RelationshipEntity).LeftEntityName).Where(en => !string.IsNullOrEmpty(en)).Distinct();
+ var entityToNames = g.Select(e => (e as RelationshipEntity).RightEntityName).Where(en => !string.IsNullOrEmpty(en)).Distinct();
+
+ if (entityFromNames.Count() > 1 || entityToNames.Count() > 1)
+ {
+ var entNames = g.Select(e => $"({(e as RelationshipEntity).LeftEntityName})-[{e.EntityName}]-({(e as RelationshipEntity).RightEntityName})").Distinct();
+ throw new TranspilerSyntaxErrorException($"The alias {g.Key} assigned to a different type of relationships: {string.Join(", ", entNames)}");
+ }
+
+ g.Cast().ToList().ForEach(e =>
+ {
+ e.LeftEntityName = entityFromNames.First();
+ e.RightEntityName = entityToNames.First();
+ });
+ }
+ });
+
+ // final pass: verify that all types are inferred
+ var firstUnknownTypeEntity = entitiesInReturn.Values
+ .FirstOrDefault(e => e is NodeEntity ?
+ string.IsNullOrEmpty((e as NodeEntity).EntityName) :
+ string.IsNullOrEmpty((e as RelationshipEntity).EntityName) || string.IsNullOrEmpty((e as RelationshipEntity).LeftEntityName) || string.IsNullOrEmpty((e as RelationshipEntity).RightEntityName)
+ );
+ if (firstUnknownTypeEntity != null)
+ {
+ throw new TranspilerSyntaxErrorException($"Type label for alias '{firstUnknownTypeEntity.Alias}' cannot be deduced. Did you not give a label to it?");
+ }
+
+ // before moving to next query part, update inheritedEntities to the current query part's returned list of entities
+ inheritedEntities = GetEntitiesFromReturnBodyWithAliasApplied(queryTreeNode.ProjectedExpressions);
+ }
+
+ // in the final return statement, we currently do not support return entities, so we fail any attempt to do so
+ var entitiesInFinalReturn = GetEntitiesFromReturnBodyWithAliasApplied(singleQuery.EntityPropertySelected);
+ if (entitiesInFinalReturn.Count() > 0)
+ {
+ throw new TranspilerNotSupportedException($"Entites ({string.Join(", ", entitiesInFinalReturn.Select(e => e.Alias))}) in return statement");
+ }
+
+ return singleQuery;
+ }
+
+ public override object VisitOC_MultiPartQuery([NotNull] CypherParser.OC_MultiPartQueryContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_MultiPartQuery", context.GetText());
+ // return SingleQueryTreeNode
+ // multipart query has several situations we need to handle
+ // - MATCH not followed by any with but termination (MATCH RETURN)
+ // - with immediately follows a MATCH (MATCH WITH RETURN)
+ // - with follows other WITH (MATCH WITH WITH RETURN)
+ // - standalone WITH (not supported for now)
+
+ PartialQueryTreeNode prevQueryPart = null;
+ SingleQueryTreeNode queryNode = null;
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (child is CypherParser.OC_ReadingClauseContext) // MATCH (WHERE)
+ {
+ var partQueryTree = CreateQueryPartFromReadingClauses(
+ child as CypherParser.OC_ReadingClauseContext,
+ prevQueryPart
+ );
+
+ // set state for next clause
+ prevQueryPart = partQueryTree;
+ }
+ else if (child is CypherParser.OC_WithContext) // WITH (WHERE)
+ {
+ // when we see an WITH, we know that we we are done with one query part of a multi-query part
+ // we will construct a new query part object, and if applicable, chain with previous query part
+ // and construct the MATCH patterns
+ if (prevQueryPart == null)
+ {
+ throw new TranspilerNotSupportedException("WITH projection without reading clauses (a.k.a. Match)");
+ }
+
+ // child visit of OC_WITH returns valuetuple type representing the return body and flags:
+ // { IsDistinct(bool), ReturnItems(IList), Condition(QueryExpression) }
+ var withResult = ((
+ bool IsDistinct,
+ IList
+ ReturnItems,
+ QueryExpression Condition,
+ IList OrderByItems,
+ IList LimitItems))Visit(child);
+
+ // WITH will mask out any entities from MATCH that was not explicity returned
+ // get a list of entities exposed from current query part and leave it for next query part (if any)
+ // for constructing the match pattern that refers to any of these entities
+ // If we have a previous query part, we get a list of variables from previous query that
+ // directly exposes node/relationship entities. E.g.
+ // MATCH (u:user) <-- prev query part
+ // WITH u as u2
+ // MATCH (u)-[:some_rel]-(:some_node) <-- current query part, u is not the same u in previous MATCH but a new node
+ // ...
+ var projectionExprs = withResult.ReturnItems ?? throw new TranspilerSyntaxErrorException($"Expecting a valid list of items to be returned: {child.GetText()}");
+ var partQueryTree = new PartialQueryTreeNode()
+ {
+ PipedData = prevQueryPart,
+ ProjectedExpressions = projectionExprs,
+ PostCondition = withResult.Condition,
+ IsDistinct = withResult.IsDistinct,
+ MatchData = null,
+ CanChainNonOptionalMatch = true,
+ IsImplicitProjection = false,
+ LimitExpression = withResult.LimitItems,
+ OrderByExpression = withResult.OrderByItems,
+ };
+
+
+ // propagate the entity types (node/edge, if possible EntityName as well) to the list of return items that
+ // are direct exposure of entities
+ // NOTE: currently we assume direct reference. Any function manipulation will cause it become
+ // a non-node reference so that it cannot be used as entity variables in next MATCH statement
+ // e.g. u as u or (u) as u is okay , AVG(u) as u is no longer an enity
+ Debug.Assert(projectionExprs.All(p => p is QueryExpressionWithAlias));
+ UpdateReturnBodyWithEntityTypes(
+ projectionExprs.Cast(),
+ GetEntitiesFromReturnBodyWithAliasApplied(prevQueryPart.ProjectedExpressions)
+ );
+
+ // set state for next clause
+ prevQueryPart = partQueryTree;
+ }
+ else if (child is CypherParser.OC_SinglePartQueryContext) // (ReadingClauses)* RETURN
+ {
+ Debug.Assert(prevQueryPart != null);
+ queryNode = ConstructSingleQuery(child as CypherParser.OC_SinglePartQueryContext, prevQueryPart);
+ }
+ else if (child is CypherParser.OC_UpdatingClauseContext)
+ {
+ throw new TranspilerNotSupportedException("Updaing clause");
+ }
+ else
+ {
+ // skip the rest type nodes (e.g. SP)
+ }
+ }
+
+ Debug.Assert(queryNode != null);
+ return queryNode;
+ }
+
+ public override object VisitOC_SinglePartQuery([NotNull] CypherParser.OC_SinglePartQueryContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_SinglePartQuery", context.GetText());
+ // return SingleQueryTreeNode
+ // single part query is the terminating part that contains RETURN
+ // it is either MATCH RETURN, MATCH MATCH ... RETURN, or just RETURN
+ var queryNode = ConstructSingleQuery(context as CypherParser.OC_SinglePartQueryContext, null);
+ return queryNode;
+ }
+
+ public override object VisitOC_ReadingClause([NotNull] CypherParser.OC_ReadingClauseContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_ReadingClause", context.GetText());
+ // reading clause lead to MATCH, which is supported, and Unwind/InQueryCall, which are not supported
+ // returns valuetuple type (pass through):
+ // { MatchPatterns(IList), Condition(QueryExpression) }
+
+ if (context.oC_Unwind() != null || context.oC_InQueryCall() != null)
+ {
+ throw new TranspilerNotSupportedException("Unwind or Call");
+ }
+
+ if (context.oC_Match() != null)
+ {
+ // returns valuetuple type (MatchPatterns, Condition) (pass through):
+ return Visit(context.oC_Match());
+ }
+ else
+ {
+ throw new TranspilerSyntaxErrorException("Match is required");
+ }
+ }
+
+ public override object VisitOC_Match([NotNull] CypherParser.OC_MatchContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_Match", context.GetText());
+ // match has MATCH (WHERE)
+ // returns valuetuple type:
+ // ( MatchPatterns(IList), Condition(QueryExpression) )
+
+ var isOptional = IsContextContainsTextToken(context, "OPTIONAL");
+ var patterns = Visit(context.oC_Pattern()) as IList> ?? throw new TranspilerInternalErrorException("Parsing oc_Pattern");
+ QueryExpression condExpr = null;
+
+ if (context.oC_Where() != null)
+ {
+ condExpr = Visit(context.oC_Where()) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oc_Where");
+ }
+
+ return (
+ MatchPatterns: patterns.Select(l => new MatchPattern(isOptional, l)).ToList() as IList,
+ Condition: condExpr // Condition
+ );
+ }
+
+ public override object VisitOC_Pattern([NotNull] CypherParser.OC_PatternContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_Pattern", context.GetText());
+ // returns IList> type
+
+ var patterns = new List>();
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (child is CypherParser.OC_PatternPartContext)
+ {
+ var result = Visit(child) as IList;
+ Debug.Assert(result != null);
+ patterns.Add(result);
+ }
+ else
+ {
+ // skip the rest type nodes (e.g. SP)
+ }
+ }
+ return patterns;
+ }
+
+ public override object VisitOC_PatternPart([NotNull] CypherParser.OC_PatternPartContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_PatternPart", context.GetText());
+ // return IList (pass trhough)
+ IList matchPatternPart = null;
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (child is CypherParser.OC_AnonymousPatternPartContext)
+ {
+ var result = Visit(child) as IList;
+ Debug.Assert(result != null);
+ Debug.Assert(matchPatternPart == null);
+ matchPatternPart = result;
+ }
+ else if (child is CypherParser.OC_VariableContext)
+ {
+ var text = child.GetText();
+ throw new TranspilerNotSupportedException($"Variable on the match pattern: {text}");
+ }
+ else
+ {
+ // skip the rest type nodes (e.g. SP)
+ }
+ }
+ return matchPatternPart;
+ }
+
+ public override object VisitOC_PatternElement([NotNull] CypherParser.OC_PatternElementContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_PatternElement", context.GetText());
+ // returns IList
+ // an pattern element is node - chain or just node, or ( itself )
+ var matchPatternPart = new List();
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (child is CypherParser.OC_NodePatternContext)
+ {
+ var result = Visit(child) as NodeEntity;
+ Debug.Assert(result != null);
+ matchPatternPart.Add(result);
+ }
+ else if (child is CypherParser.OC_PatternElementChainContext || child is CypherParser.OC_PatternElementContext)
+ {
+ var result = Visit(child) as IList;
+ Debug.Assert(result != null);
+ Debug.Assert(matchPatternPart.Count % 2 == 1); // number of entity should always be odd, e.g. 1 node, 1 node + 1 rel + 1 node, ...
+ Debug.Assert(result.First() != null && result.First() is RelationshipEntity);
+ (result.First() as RelationshipEntity).LeftEntityName = matchPatternPart.Last().EntityName;
+ matchPatternPart.AddRange(result);
+ }
+ else
+ {
+ // skip the rest type nodes (e.g. SP)
+ }
+ }
+ return matchPatternPart;
+ }
+ public override object VisitOC_PatternElementChain([NotNull] CypherParser.OC_PatternElementChainContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_PatternElementChain", context.GetText());
+ // returns IList
+ // a chain is the -rel-node part
+ RelationshipEntity rel = null;
+ NodeEntity sinkNode = null;
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (child is CypherParser.OC_RelationshipPatternContext)
+ {
+ var result = Visit(child) as RelationshipEntity;
+ Debug.Assert(rel == null);
+ rel = result;
+ }
+ else if (child is CypherParser.OC_NodePatternContext)
+ {
+ var result = Visit(child) as NodeEntity;
+ Debug.Assert(sinkNode == null);
+ Debug.Assert(rel != null);
+ sinkNode = result;
+ rel.RightEntityName = result.EntityName;
+ }
+ else
+ {
+ // skip the rest type nodes (e.g. SP)
+ }
+ }
+
+ Debug.Assert(rel != null && sinkNode != null);
+ return new List() { rel, sinkNode };
+ }
+
+ public override object VisitOC_NodePattern([NotNull] CypherParser.OC_NodePatternContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_NodePattern", context.GetText());
+ // returns a NodeEntity object
+
+ NodeEntity node = new NodeEntity();
+
+ if (context.oC_NodeLabels() != null)
+ {
+ var result = (string)Visit(context.oC_NodeLabels());
+ Debug.Assert(result != null);
+ node.EntityName = result;
+ }
+
+ if (context.oC_Properties() != null)
+ {
+ throw new TranspilerNotSupportedException("Please consider WHERE. Properties set on the node match pattern");
+ }
+
+ if (context.oC_Variable() != null)
+ {
+ // variable for match pattern in node is optional
+ var result = (string)Visit(context.oC_Variable());
+ Debug.Assert(result != null);
+ node.Alias = result;
+ }
+
+ return node;
+ }
+
+ public override object VisitOC_NodeLabels([NotNull] CypherParser.OC_NodeLabelsContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_NodeLabels", context.GetText());
+ string labelName = null;
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (child is CypherParser.OC_NodeLabelContext)
+ {
+ var nextLabelName = (child as CypherParser.OC_NodeLabelContext).oC_LabelName().GetText();
+ if (labelName != null)
+ {
+ throw new TranspilerNotSupportedException($"Multiple nodel labels: {labelName} {nextLabelName}");
+ }
+ labelName = nextLabelName;
+ }
+ else
+ {
+
+ }
+ }
+ return labelName;
+ }
+
+ public override object VisitOC_RelationshipPattern([NotNull] CypherParser.OC_RelationshipPatternContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_RelationshipPattern", context.GetText());
+ // returns a RelationshipEntity object
+
+ RelationshipEntity relationship = new RelationshipEntity()
+ {
+ RelationshipDirection =
+ (context.oC_LeftArrowHead() != null ?
+ (context.oC_RightArrowHead() != null ? RelationshipEntity.Direction.Both : RelationshipEntity.Direction.Backward) :
+ (context.oC_RightArrowHead() != null ? RelationshipEntity.Direction.Forward : RelationshipEntity.Direction.Both))
+ };
+
+ var result = ((string VarName, string RelName))Visit(context.oC_RelationshipDetail());
+ relationship.EntityName = result.RelName;
+ relationship.Alias = result.VarName;
+ return relationship;
+ }
+
+ public override object VisitOC_RelationshipDetail([NotNull] CypherParser.OC_RelationshipDetailContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_RelationshipDetail", context.GetText());
+ // returns valuetuple type {VarName(string), RelName(string)}
+
+ string varName = null;
+ string relationshipName = null;
+
+ if (context.oC_Variable() != null)
+ {
+ varName = (string)Visit(context.oC_Variable());
+ }
+
+ if (context.oC_RelationshipTypes() != null)
+ {
+ relationshipName = (string)Visit(context.oC_RelationshipTypes());
+ }
+
+ if (context.oC_Properties() != null)
+ {
+ throw new TranspilerNotSupportedException("Please consider WHERE. Properties set on the relationship match pattern");
+ }
+
+ return (VarName: varName, RelName: relationshipName);
+ }
+
+ public override object VisitOC_RelationshipTypes([NotNull] CypherParser.OC_RelationshipTypesContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_RelationshipTypes", context.GetText());
+ string relationshipName = null;
+
+ // the relationship is the type specified inside [] bracket
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (child is CypherParser.OC_RelTypeNameContext)
+ {
+ var relName = child.GetText();
+ // Does not support multiple re
+ if (relationshipName != null)
+ {
+ throw new TranspilerNotSupportedException($"Multiple relationship: {relationshipName}|{relName}");
+ }
+ relationshipName = relName;
+ }
+ else
+ {
+ // ignore the rest of things
+ }
+ }
+ return relationshipName;
+ }
+
+ public override object VisitOC_With([NotNull] CypherParser.OC_WithContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_With", context.GetText());
+ // with can come with WITH , ... or WITH WHERE
+ // return valuetuple type:
+ // ( IsDistinct(bool), ReturnItems(IList), Condition(QueryExpression) )
+
+ var isDistinct = IsContextContainsTextToken(context, "DISTINCT");
+
+ Debug.Assert(context.oC_ReturnBody() != null);
+ var returnBodyResult = ((
+ IList ReturnItems,
+ IList OrderByItems,
+ IList LimitItems))Visit(context.oC_ReturnBody());
+
+ var queryExprs = returnBodyResult.ReturnItems ?? throw new TranspilerInternalErrorException("Parsing oC_ReturnBody");
+ var orderByExprs = returnBodyResult.OrderByItems;
+ var limitExprs = returnBodyResult.LimitItems;
+
+ QueryExpression condExpr = null;
+ if (context.oC_Where() != null)
+ {
+ condExpr = Visit(context.oC_Where()) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oC_Where");
+ }
+
+ return (IsDistinct: isDistinct, ReturnItems: queryExprs, Conditions: condExpr, OrderByItems: orderByExprs, LimitItems: limitExprs);
+ }
+
+ public override object VisitOC_Return([NotNull] CypherParser.OC_ReturnContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_Return", context.GetText());
+ // returns valuetuple type:
+ // ( IsDistinct(bool), ReturnItems(IList) )
+ var returnBody = context.oC_ReturnBody();
+
+ bool isDistinct = IsContextContainsTextToken(context, "DISTINCT");
+
+ var returnBodyResult = ((
+ IList ReturnItems,
+ IList OrderByItems,
+ IList LimitItems))Visit(returnBody);
+
+ return (IsDistinct: isDistinct,
+ ReturnItems: returnBodyResult.ReturnItems,
+ OrderByItems: returnBodyResult.OrderByItems,
+ LimitItems: returnBodyResult.LimitItems);
+ }
+
+ public override object VisitOC_ReturnItems([NotNull] CypherParser.OC_ReturnItemsContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_ReturnItems", context.GetText());
+ // returns IList()
+ var expressions = new List();
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+ if (child is CypherParser.OC_ReturnItemContext)
+ {
+ // child returns expression
+ var result = Visit(child) as QueryExpressionWithAlias ?? throw new TranspilerSyntaxErrorException($"{child.GetText()}. Expecting query expressions.");
+ expressions.Add(result);
+ }
+ }
+ return expressions;
+ }
+
+ public override object VisitOC_ReturnItem([NotNull] CypherParser.OC_ReturnItemContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_ReturnItem", context.GetText());
+
+ var selectedProperty = new QueryExpressionWithAlias();
+ bool visitedExpr = false;
+ bool visitedAlias = false;
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ if (!visitedExpr && context.GetChild(i) is CypherParser.OC_ExpressionContext)
+ {
+ var expr = Visit(context.GetChild(i)) as QueryExpression;
+ Debug.Assert(expr != null);
+ selectedProperty.InnerExpression = expr;
+ visitedExpr = true;
+ continue;
+ }
+
+ if (!visitedAlias && context.GetChild(i) is CypherParser.OC_VariableContext)
+ {
+ var varName = (string)Visit(context.GetChild(i));
+ selectedProperty.Alias = varName;
+ visitedAlias = true;
+ continue;
+ }
+ }
+
+ // if no alias is specified, then we infer from the property name / variable itself, or, if ambiguous, throw
+ // syntax exception
+ if (!visitedAlias)
+ {
+ var props = selectedProperty.InnerExpression.GetChildrenQueryExpressionType();
+ if (props.Count() != 1)
+ {
+ throw new TranspilerSyntaxErrorException($"You must specify alias name for expression {context.GetText()}");
+ }
+ else
+ {
+ selectedProperty.Alias = props.First().PropertyName ?? props.First().VariableName;
+ }
+ }
+
+ Debug.Assert(visitedExpr);
+ return selectedProperty;
+ }
+
+ public override object VisitOC_Where([NotNull] CypherParser.OC_WhereContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_Where", context.GetText());
+ // where returns single expression object (pass through)
+ return VisitChildren(context) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oc_Expression");
+ }
+
+ public override object VisitOC_Properties([NotNull] CypherParser.OC_PropertiesContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_Properties", context.GetText());
+ // Properties are JSON attached to Node or Edge match patterns. e.g.
+ // MATCH (n:device { id: 'someid'})
+ // not supported for now
+ throw new TranspilerNotSupportedException("Consider use WITH/WHERE. Properties specification");
+ }
+
+ public override object VisitOC_Expression([NotNull] CypherParser.OC_ExpressionContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_Expression", context.GetText());
+ // returns single expression (pass through)
+ return VisitChildren(context) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oC_OrExpression");
+ }
+ public override object VisitOC_OrExpression([NotNull] CypherParser.OC_OrExpressionContext context)
+ {
+ // OR is a binary operator, so if we see more than 1 children, the actual statement is seem than a pass-thru
+ if ((context.OR()?.Length ?? 0) > 0)
+ {
+ Debug.Assert(context.oC_XorExpression().Length >= 2);
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_OrExpression", context.GetText());
+ return HandlesBinaryExpression(context, "OR") ?? throw new TranspilerInternalErrorException("Parsing two oC_XorExpression of OR");
+ }
+ else
+ {
+ // pass through. this is not an OR expression
+ return VisitChildren(context) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oC_XorExpression");
+ }
+ }
+ public override object VisitOC_XorExpression([NotNull] CypherParser.OC_XorExpressionContext context)
+ {
+ // XOR is a binary operator, so if we see more than 1 children, the actual statement is seem than a pass-thru
+ if ((context.XOR()?.Length ?? 0) > 0)
+ {
+ Debug.Assert(context.oC_AndExpression().Length >= 2);
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_XorExpression", context.GetText());
+ return HandlesBinaryExpression(context, "XOR") ?? throw new TranspilerInternalErrorException("Parsing two oC_AndExpression of XOR");
+ }
+ else
+ {
+ // pass through. this is not an XOR expression
+ return VisitChildren(context) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oC_AndExpression");
+ }
+ }
+ public override object VisitOC_AndExpression([NotNull] CypherParser.OC_AndExpressionContext context)
+ {
+ // AND is a binary operator, so if we see more than 1 children, the actual statement is seem than a pass-thru
+ if ((context.AND()?.Length ?? 0) > 0)
+ {
+ Debug.Assert(context.oC_NotExpression().Length >= 2);
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_AndExpression", context.GetText());
+ return HandlesBinaryExpression(context, "AND") ?? throw new TranspilerInternalErrorException("Parsing two oC_NotExpression inside AND");
+ }
+ else
+ {
+ // pass through. this is not an AND expression
+ return VisitChildren(context) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oC_NotExpression");
+ }
+ }
+ public override object VisitOC_NotExpression([NotNull] CypherParser.OC_NotExpressionContext context)
+ {
+ if ((context.NOT()?.Length ?? 0) > 0)
+ {
+ Debug.Assert(context.NOT().Length == 1);
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_NotExpression", context.GetText());
+ return HandlesUnaryFuncExpression(context, "NOT") ?? throw new TranspilerInternalErrorException("Parsing oC_ComparisonExpression inside NOT");
+ }
+ else
+ {
+ // pass through. this is not an Not expression
+ return VisitChildren(context) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oC_ComparisonExpression");
+ }
+ }
+ public override object VisitOC_ComparisonExpression([NotNull] CypherParser.OC_ComparisonExpressionContext context)
+ {
+ // If we see PartialComparison then it is a real comparison expr, otherwise pass through
+ if ((context.oC_PartialComparisonExpression()?.Length ?? 0) > 0)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_ComparisonExpression", context.GetText());
+ Debug.Assert(context.oC_PartialComparisonExpression().Length == 1);
+
+ var parentExpression = new QueryExpressionBinary();
+
+ bool visitedLeft = false;
+ bool visitedRight = false;
+
+ // get left expression
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (!visitedLeft && !(child is ITerminalNode))
+ {
+ Debug.Assert(child is CypherParser.OC_AddOrSubtractExpressionContext);
+
+ var childExpr = Visit(child) as QueryExpression ?? throw new TranspilerSyntaxErrorException($"Expect valid left expression {child.GetText()}");
+ parentExpression.LeftExpression = childExpr;
+ visitedLeft = true;
+ continue;
+ }
+
+ if (!visitedRight && !(context.GetChild(i) is ITerminalNode))
+ {
+ var result = ((string OP, QueryExpression Expr))Visit(child);
+ var childExpr = result.Expr as QueryExpression ?? throw new TranspilerSyntaxErrorException($"Expect valid right expression {child.GetText()}");
+ var op = (string)result.OP ?? throw new TranspilerSyntaxErrorException($"Expect valid comparison operator {child.GetText()}");
+ parentExpression.RightExpression = childExpr;
+
+ var opEnum = OperatorHelper.TryGetOperator(op)
+ ?? throw new TranspilerSyntaxErrorException($"Unknown or unsupported operator {op}");
+ parentExpression.Operator = opEnum;
+ visitedRight = true;
+ continue;
+ }
+ }
+
+ Debug.Assert(parentExpression.LeftExpression != null && parentExpression.RightExpression != null && parentExpression.Operator != null);
+ return parentExpression;
+ }
+ else
+ {
+ // pass through. this is not a comparison expression
+ return VisitChildren(context) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oC_AddOrSubtractExpression");
+ }
+ }
+
+ public override object VisitOC_PartialComparisonExpression([NotNull] CypherParser.OC_PartialComparisonExpressionContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_PartialComparisonExpression", context.GetText());
+ // returns valuetuple object that (OP(string), Expr(QueryExpression))
+
+ bool visitedOperator = false;
+ bool visitedExpr = false;
+ string op = null;
+ QueryExpression expr = null;
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (!visitedOperator && child is ITerminalNode && !IsContextSP(context))
+ {
+ op = context.GetChild(i).GetText();
+ visitedOperator = true;
+ continue;
+ }
+
+ if (!visitedExpr && !(child is ITerminalNode))
+ {
+ expr = Visit(child) as QueryExpression ?? throw new TranspilerSyntaxErrorException($"Expect valid right expression {child.GetText()}");
+ visitedExpr = true;
+ continue;
+ }
+ }
+
+ Debug.Assert(visitedOperator && visitedExpr);
+ return (OP: op, Expr: expr);
+ }
+
+ public override object VisitOC_AddOrSubtractExpression([NotNull] CypherParser.OC_AddOrSubtractExpressionContext context)
+ {
+ // + or - is a binary operator and can chain up, so if we see more than 1 children, the actual statement is seem than a pass-thru
+ // MultipleDeviceModulo is immediately nested inside AddOrSub due to operator priority
+ if ((context.oC_MultiplyDivideModuloExpression()?.Length ?? 0) > 1)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_AddOrSubtractExpression", context.GetText());
+ return HandleChainedBinaryExpressions(context) ?? throw new TranspilerInternalErrorException("Parsing multiple oC_MultiplyDivideModuloExpression inside +/-");
+ }
+ else
+ {
+ // pass through. this is not an AddOrSub chain expression
+ return VisitChildren(context) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oC_MultiplyDivideModuloExpression");
+ }
+ }
+ public override object VisitOC_MultiplyDivideModuloExpression([NotNull] CypherParser.OC_MultiplyDivideModuloExpressionContext context)
+ {
+ if ((context.oC_PowerOfExpression()?.Length ?? 0) > 1)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_MultiplyDivideModuloExpression", context.GetText());
+ return HandleChainedBinaryExpressions(context) ?? throw new TranspilerInternalErrorException("Parsing multiple oC_PowerOfExpression inside +/-");
+ }
+ else
+ {
+ // pass through. this is not an MultiDeviceMod chain expression
+ return VisitChildren(context) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oC_PowerOfExpression");
+ }
+ }
+ public override object VisitOC_PowerOfExpression([NotNull] CypherParser.OC_PowerOfExpressionContext context)
+ {
+ if ((context.oC_UnaryAddOrSubtractExpression()?.Length ?? 0) > 1)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_PowerOfExpression", context.GetText());
+ return HandlesBinaryExpression(context, "^") ?? throw new TranspilerInternalErrorException("Parsing multiple oC_UnaryAddOrSubtractExpression inside +/-");
+ }
+ else
+ {
+ // pass through. this is not a Power chain expression
+ return VisitChildren(context) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oC_UnaryAddOrSubtractExpression");
+ }
+ }
+ public override object VisitOC_UnaryAddOrSubtractExpression([NotNull] CypherParser.OC_UnaryAddOrSubtractExpressionContext context)
+ {
+ var supportedOperators = new string[] { "+", "-" };
+ bool isUnaryAddSub = false;
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+ if (child is ITerminalNode && supportedOperators.Contains(child.GetText()))
+ {
+ isUnaryAddSub = true;
+ break;
+ }
+ }
+
+ if (isUnaryAddSub)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_UnaryAddOrSubtractExpression", context.GetText());
+ return HandlesUnaryExpression(context) ?? throw new TranspilerInternalErrorException("Parsing oC_StringListNullOperatorExpression inside unary +/-");
+ }
+ else
+ {
+ // pass through. this is not an MultiDeviceMod chain expression
+ return VisitChildren(context) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oC_StringListNullOperatorExpression");
+ }
+ }
+
+ public override object VisitOC_StringListNullOperatorExpression([NotNull] CypherParser.OC_StringListNullOperatorExpressionContext context)
+ {
+ // Handles PropertyOrLabelsExpression:
+ //
+ // oC_PropertyOrLabelsExpression
+ // (
+ // ( SP? '[' oC_Expression ']' ) | <---- Label list or range is not supported today
+ // ( SP? '[' oC_Expression? '..' oC_Expression? ']' ) | <---- Label list or range is not supported today
+ // ( ( ( SP IN ) | ( SP STARTS SP WITH ) | ( SP ENDS SP WITH ) | ( SP CONTAINS ) ) SP? oC_PropertyOrLabelsExpression ) |
+ // ( SP IS SP NULL ) | ( SP IS SP NOT SP NULL )
+ // )*
+
+ if (IsMatchSequence(context, "[", typeof(CypherParser.OC_ExpressionContext), "]") ||
+ IsMatchSequence(context, "[", typeof(Optional), "..", typeof(Optional), "]"))
+ {
+ throw new TranspilerNotSupportedException($"Label list or range: {context.GetText()}");
+ }
+ else if (context.ChildCount > 1)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_StringListNullOperatorExpression", context.GetText());
+
+ QueryExpression funcOrOp = null;
+
+ // handles string operators
+ if ((context.IN()?.Length ?? 0) > 0)
+ {
+ // operator type for 'IN'
+ funcOrOp = HandlesBinaryExpression(context, "in") ?? throw new TranspilerInternalErrorException("Parsing IN statement");
+ }
+ else
+ {
+ // other alternatives such as STARTS WITH / ENDS WITH ... , we construct as function types
+ var strFunc = new QueryExpressionFunction();
+
+ if ((context.STARTS()?.Length ?? 0) > 0 && (context.WITH()?.Length ?? 0) > 0)
+ {
+ strFunc.Function = FunctionHelper.GetFunctionInfo(Function.StringStartsWith);
+ }
+ else if ((context.ENDS()?.Length ?? 0) > 0 && (context.WITH()?.Length ?? 0) > 0)
+ {
+ strFunc.Function = FunctionHelper.GetFunctionInfo(Function.StringEndsWith);
+ }
+ else if ((context.CONTAINS()?.Length ?? 0) > 0)
+ {
+ strFunc.Function = FunctionHelper.GetFunctionInfo(Function.StringContains);
+ }
+ else if ((context.IS()?.Length ?? 0) > 0 && (context.NULL()?.Length ?? 0) > 0 && (context.NOT()?.Length ?? 0) == 0)
+ {
+ // IS NULL
+ strFunc.Function = FunctionHelper.GetFunctionInfo(Function.IsNull);
+ }
+ else if ((context.IS()?.Length ?? 0) > 0 && (context.NULL()?.Length ?? 0) > 0 && (context.NOT()?.Length ?? 0) > 0)
+ {
+ // IS NOT NULL
+ strFunc.Function = FunctionHelper.GetFunctionInfo(Function.IsNotNull);
+ }
+ else
+ {
+ throw new TranspilerNotSupportedException($"String manipulation function/operator used in {context.GetText()}");
+ }
+
+ if (strFunc.Function.RequiredParameters + strFunc.Function.OptionalParameters > 0)
+ {
+ var parameters = new List();
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ if (!(context.GetChild(i) is ITerminalNode))
+ {
+ parameters.Add(Visit(context.GetChild(i)) as QueryExpression
+ ?? throw new TranspilerInternalErrorException($"Parsing oC_PropertyOrLabelsExpression inside '{strFunc.Function.FunctionName}'"));
+ }
+ }
+
+ if (strFunc.Function.RequiredParameters > parameters.Count)
+ {
+ throw new TranspilerSyntaxErrorException($"Function {strFunc.Function.FunctionName} expects at least {strFunc.Function.RequiredParameters} parameters");
+ }
+ if (strFunc.Function.RequiredParameters + strFunc.Function.OptionalParameters > parameters.Count)
+ {
+ throw new TranspilerSyntaxErrorException($"Function {strFunc.Function.FunctionName} expects at most {strFunc.Function.RequiredParameters + strFunc.Function.OptionalParameters} parameters");
+ }
+
+ strFunc.InnerExpression = parameters.First();
+ strFunc.AdditionalExpressions = parameters.Skip(1);
+ }
+
+ funcOrOp = strFunc;
+ }
+
+ return funcOrOp;
+ }
+ else
+ {
+ // pass through if this is not a stringlistnulloperator expression
+ return VisitChildren(context) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oC_PropertyOrLabelsExpression");
+ }
+ }
+ public override object VisitOC_PropertyOrLabelsExpression([NotNull] CypherParser.OC_PropertyOrLabelsExpressionContext context)
+ {
+ // this can be a Atom.PropertyLookup or just Atom, where Atom will be some lterial/param or bunch of other operations
+ // we will report unhandled case if NodeLabels appears (Atom NodeLabels or Atom.PropertyLookup.NodeLabels)
+ // supported Atom can be Literal, Parameter, ParenthesizedExpression, Variable and FunctionInvocationm or COUNT(*)
+ // currently, PatternComprehension, CaseExpression, Parameter (as in Printf style of writting OC query and literals)
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_PropertyOrLabelsExpression", context.GetText());
+
+ if (context.oC_NodeLabels() != null)
+ {
+ throw new TranspilerNotSupportedException($"Node labels in the expression: {context.GetText()}");
+ }
+
+ if ((context.oC_PropertyLookup()?.Length ?? 0) > 0)
+ {
+ // this is a Atom.Property look up
+ if (context.oC_PropertyLookup().Length > 1)
+ {
+ throw new TranspilerNotSupportedException($"Nested property lookup in '{context.GetText()}'");
+ }
+
+ // we only support direct reference to variable name for now
+ var propertyObj = Visit(context.oC_Atom()) as QueryExpressionProperty ?? throw new TranspilerInternalErrorException("Parsing oC_Atom");
+ var propName = context.oC_PropertyLookup().First().oC_PropertyKeyName().GetText() ?? throw new TranspilerInternalErrorException("Parsing oC_PropertyLookup");
+ propertyObj.PropertyName = propName;
+
+ return propertyObj;
+ }
+ else
+ {
+ // pass through (assuming it is an expression)
+ return Visit(context.oC_Atom()) as QueryExpression;
+ }
+ }
+ public override object VisitOC_FunctionInvocation([NotNull] CypherParser.OC_FunctionInvocationContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_FunctionInvocation", context.GetText());
+
+ QueryExpression funcExpr = null;
+ bool hasDistinct = IsContextContainsTextToken(context, "DISTINCT");
+ var innerExprs = new List();
+
+ var funcName = context.oC_FunctionName().GetText();
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+ if (child is CypherParser.OC_ExpressionContext)
+ {
+ var result = Visit(child) as QueryExpression;
+ Debug.Assert(result != null);
+ innerExprs.Add(result);
+ }
+ }
+
+ Debug.Assert(innerExprs.Count > 0);
+
+ AggregationFunction func;
+ if (AggregationFunctionHelper.TryParse(funcName, out func))
+ {
+ // aggregation function
+ funcExpr = new QueryExpressionAggregationFunction()
+ {
+ AggregationFunction = func,
+ InnerExpression = innerExprs.First(),
+ IsDistinct = hasDistinct,
+ };
+
+ if (innerExprs.Count > 1)
+ {
+ throw new TranspilerSyntaxErrorException($"Aggregation function '{func}' with more than 1 parameters");
+ }
+ }
+ else
+ {
+ // non aggregaton function
+ if (hasDistinct)
+ {
+ throw new TranspilerSyntaxErrorException($"Function '{funcName}' does not support 'DISTINCT' modifier");
+ }
+
+ var funcInfo = FunctionHelper.TryGetFunctionInfo(funcName)
+ ?? throw new TranspilerNotSupportedException($"Function: {funcName}");
+
+ funcExpr = new QueryExpressionFunction()
+ {
+ Function = funcInfo,
+ InnerExpression = innerExprs.First(),
+ AdditionalExpressions = innerExprs.Count > 1 ? innerExprs.Skip(1) : null
+ };
+ }
+
+ return funcExpr;
+ }
+
+ public override object VisitOC_CaseExpression([NotNull] CypherParser.OC_CaseExpressionContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_CaseExpression", context.GetText());
+ var caseNodeVisited = false;
+ var elseNodeVisited = false;
+ var childCount = context.ChildCount;
+ var caseExpression = new QueryExpressionCaseExpression();
+ var caseAlterExprs = new List();
+ if (childCount > 0)
+ {
+ for (int i = 0; i < childCount; i++)
+ {
+ // TODO: Add logic to handle case expresion
+
+ var child = context.GetChild(i);
+ if (child.GetText().ToUpper() == "CASE")
+ {
+ caseNodeVisited = true;
+ }
+ else if (child.GetText().ToUpper() == "ELSE")
+ {
+ elseNodeVisited = true;
+ }
+ else if (child is CypherParser.OC_ExpressionContext)
+ {
+ if (caseNodeVisited && !elseNodeVisited)
+ {
+ var initialCondition = Visit(child) as QueryExpression;
+ throw new TranspilerNotSupportedException($"Please use Case When then format.{context.GetText()} ...");
+ }
+ else if (elseNodeVisited)
+ {
+ var elseCondition = Visit(child) as QueryExpression;
+ caseExpression.ElseExpression = elseCondition;
+ }
+ else
+ {
+ throw new TranspilerInternalErrorException($"Unexpected blob in CASE expression: {child.GetText()}");
+ }
+ }
+ else if (child is CypherParser.OC_CaseAlternativesContext)
+ {
+ caseAlterExprs.AddRange(Visit(child) as List);
+ }
+ else
+ {
+ // passing through other cases
+ }
+ }
+ }
+ caseExpression.CaseAlternatives = caseAlterExprs;
+
+
+ return caseExpression;
+
+ }
+ public override object VisitOC_CaseAlternatives([NotNull] CypherParser.OC_CaseAlternativesContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_CaseAlternatives", context.GetText());
+
+ var childCount = context.ChildCount;
+ var caseAlternatives = new List();
+ var isWhenState = false;
+ var isThenState = false;
+
+ if (childCount > 0)
+ {
+ var caseAlterExpr = new QueryExpressionCaseAlternative();
+ for (int i = 0; i < childCount; i++)
+ {
+ var child = context.GetChild(i);
+ if (child.GetText().ToUpper() == "WHEN")
+ {
+ isWhenState = true;
+ isThenState = false;
+ }
+ else if (child.GetText().ToUpper() == "THEN")
+ {
+ isWhenState = false;
+ isThenState = true;
+ }
+ else if (child is CypherParser.OC_ExpressionContext)
+ {
+ var expr = Visit(child) as QueryExpression;
+ if (isWhenState && !isThenState)
+ {
+ caseAlterExpr.WhenExpression = expr;
+ }
+ else if (!isWhenState && isThenState)
+ {
+ caseAlterExpr.ThenExpression = expr;
+ caseAlternatives.Add(caseAlterExpr);
+ caseAlterExpr = new QueryExpressionCaseAlternative();
+ }
+ }
+ }
+ }
+
+ return caseAlternatives;
+ }
+
+ public override object VisitOC_Variable([NotNull] CypherParser.OC_VariableContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_Variable", context.GetText());
+ // directly return text inside as variable name
+ return context.GetText();
+ }
+
+ public override object VisitOC_SchemaName([NotNull] CypherParser.OC_SchemaNameContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_SchemaName", context.GetText());
+ // directly return text inside as schema name
+ return context.GetText();
+ }
+
+ public override object VisitOC_FunctionName([NotNull] CypherParser.OC_FunctionNameContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_FunctionName", context.GetText());
+ // TODO: verify if a function is supported or not
+ // directly return text inside as function name
+ return context.GetText();
+ }
+
+ public override object VisitOC_Atom([NotNull] CypherParser.OC_AtomContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_Atom", context.GetText());
+
+ // returns a QueryExpression type
+
+ if (context.oC_Variable() != null)
+ {
+ // this is variable reference. We treat it as either property or alias
+ return new QueryExpressionProperty()
+ {
+ VariableName = context.GetText(),
+ };
+ }
+ else if (
+ context.oC_Literal() != null ||
+ context.oC_FunctionInvocation() != null ||
+ context.oC_ParenthesizedExpression() != null ||
+ context.oC_CaseExpression() != null
+
+ )
+ {
+ Debug.Assert(context.ChildCount == 1);
+ var result = Visit(context.GetChild(0)) as QueryExpression;
+ Debug.Assert(result != null);
+ return result;
+ }
+ else
+ {
+ // currently not supported:
+ // - oC_ListComprehension
+ // - oC_PatternComprehension
+ // - oC_RelationshipsPattern
+ // - COUNT *
+ // - FILTER / EXTRACT / ALL / ANY / NONE / SINGLE
+ throw new TranspilerNotSupportedException($"Expression '{context.GetText()}'");
+ }
+ }
+
+ public override object VisitOC_ParenthesizedExpression([NotNull] CypherParser.OC_ParenthesizedExpressionContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_ParenthesizedExpression", context.GetText());
+ Debug.Assert(context.oC_Expression() != null);
+ return Visit(context.oC_Expression()) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oC_Expression");
+ }
+
+ public override object VisitOC_Literal([NotNull] CypherParser.OC_LiteralContext context)
+ {
+ if (context.StringLiteral() != null)
+ {
+ _logger?.LogVerbose("{0}: {1}: {2}", "VisitOC_Literal", "STRING", context.GetText());
+
+ // cypher literal has single quote around it, removing it
+ var quotedText = context.StringLiteral().GetText();
+ var quoteChar = quotedText[0];
+ string unescapedRawText;
+
+ if (quotedText.Last() != quoteChar || quotedText.Length < 2)
+ {
+ throw new TranspilerSyntaxErrorException($"Unrecognized string literal format: {quotedText}, expecting ' or \".");
+ }
+
+ if (quoteChar == '\'')
+ {
+ unescapedRawText = quotedText.Substring(1, quotedText.Length - 2).Replace("''", "'").Replace("\\\\", "\\");
+ }
+ else if (quoteChar == '"')
+ {
+ unescapedRawText = quotedText.Substring(1, quotedText.Length - 2).Replace("\"\"", "\"").Replace("\\\\", "\\");
+ }
+ else
+ {
+ throw new TranspilerSyntaxErrorException($"Unrecognized string literal format: {quotedText}, expecting ' or \".");
+ }
+
+ return new QueryExpressionValue()
+ {
+ Value = unescapedRawText
+ };
+ }
+ else if (context.oC_BooleanLiteral() != null || context.oC_NumberLiteral() != null || context.oC_ListLiteral() != null)
+ {
+ // pass through for oC_* literals that are supported
+ return VisitChildren(context);
+ }
+ else
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_Literal", context.GetText());
+ throw new TranspilerNotSupportedException($"Literal: {context.GetText()}");
+ }
+ }
+ public override object VisitOC_BooleanLiteral([NotNull] CypherParser.OC_BooleanLiteralContext context)
+ {
+ if (context.TRUE() != null || context.FALSE() != null)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_BooleanLiteral", context.GetText());
+ var booleanText = context.GetText();
+ return new QueryExpressionValue()
+ {
+ Value = bool.Parse(booleanText)
+ };
+ }
+ else
+ {
+ // pass through: it is not boolean literal but some other type of literal
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_BooleanLiteral", context.GetText());
+ return VisitChildren(context);
+ }
+ }
+ public override object VisitOC_ListLiteral([NotNull] CypherParser.OC_ListLiteralContext context)
+ {
+ if (context.oC_Expression() != null)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_ListLiteral", context.GetText());
+ var childExps = new List();
+ foreach (var child in context.oC_Expression())
+ {
+ var exp = Visit(child) as QueryExpression ?? throw new TranspilerInternalErrorException("Parsing oC_Expression");
+ childExps.Add(exp);
+ }
+ return new QueryExpressionList()
+ {
+ ExpressionList = childExps
+ };
+ }
+ else
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_ListLiteral", context.GetText());
+ // pass through if current type is not a list literal
+ return VisitChildren(context) as QueryExpressionValue;
+ }
+ }
+ public override object VisitOC_NumberLiteral([NotNull] CypherParser.OC_NumberLiteralContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_NumberLiteral", context.GetText());
+ // return QueryExpressionLiteral (pass through)
+ return VisitChildren(context) as QueryExpressionValue;
+ }
+ public override object VisitOC_IntegerLiteral([NotNull] CypherParser.OC_IntegerLiteralContext context)
+ {
+ // one of the Integer rep has to be non-null as guaranteed by the Cypher g4 syntax
+ Debug.Assert(context.HexInteger() != null || context.OctalInteger() != null || context.DecimalInteger() != null);
+
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_IntegerLiteral", context.GetText());
+ var intText = context.GetChild(0).GetText();
+ return new QueryExpressionValue()
+ {
+ // TODO: improve the parsing for int number intead of using the C# built int default parser
+ Value = long.Parse(intText)
+ };
+ }
+ public override object VisitOC_DoubleLiteral([NotNull] CypherParser.OC_DoubleLiteralContext context)
+ {
+ // one of the Real rep has to be non-null as guaranteed by the Cypher g4 syntax
+ Debug.Assert(context.ExponentDecimalReal() != null || context.RegularDecimalReal() != null);
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_DoubleLiteral", context.GetText());
+ var numberText = context.GetChild(0).GetText();
+ return new QueryExpressionValue()
+ {
+ // TODO: improve the parsing for real number intead of using the C# built int default parser
+ Value = double.Parse(numberText)
+ };
+ }
+ public override object VisitOC_RangeLiteral([NotNull] CypherParser.OC_RangeLiteralContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_RangeLiteral", context.GetText());
+ throw new TranspilerNotSupportedException("Range literal");
+ }
+ public override object VisitOC_MapLiteral([NotNull] CypherParser.OC_MapLiteralContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_MapLiteral", context.GetText());
+ throw new TranspilerNotSupportedException("Map literal");
+ }
+ public override object VisitOC_Order([NotNull] CypherParser.OC_OrderContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_Order", context.GetText());
+
+ Debug.Assert(context.oC_SortItem() != null);
+ var queryExprsOrderBy = new List();
+
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+ if (child is CypherParser.OC_SortItemContext && child != null)
+ {
+ var queryExpr = Visit(child) as QueryExpressionOrderBy;
+ queryExprsOrderBy.Add(queryExpr);
+ }
+ }
+
+ return queryExprsOrderBy;
+ }
+ public override object VisitOC_SortItem([NotNull] CypherParser.OC_SortItemContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_SortItem", context.GetText());
+
+ var expr = Visit(context.oC_Expression()) as QueryExpression;
+ bool isDescending = IsContextContainsTextToken(context, "DESC") || IsContextContainsTextToken(context, "DESCENDING");
+ string alias = expr.GetChildrenQueryExpressionType().First().VariableName;
+ return new QueryExpressionOrderBy()
+ {
+ IsDescending = isDescending,
+ InnerExpression = expr,
+ Alias = alias,
+ };
+ }
+ public override object VisitOC_Limit([NotNull] CypherParser.OC_LimitContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_Limit", context.GetText());
+ var exprLimit = new QueryExpressionLimit();
+ if (context.oC_Expression() != null)
+ {
+ var expr = Visit(context.oC_Expression()) as QueryExpression;
+ exprLimit.RowCount = expr.GetChildrenQueryExpressionType().First().IntValue;
+ }
+ return exprLimit;
+ }
+ public override object VisitOC_ReturnBody([NotNull] CypherParser.OC_ReturnBodyContext context)
+ {
+ _logger?.LogVerbose("{0}: {1}", "VisitOC_ReturnBody", context.GetText());
+ // add specific implementation about visiting return body
+
+ var aliasList = new List();
+ var orderList = new List();
+ var limitList = new List();
+ for (int i = 0; i < context.ChildCount; i++)
+ {
+ var child = context.GetChild(i);
+
+ if (child is CypherParser.OC_ReturnItemsContext)
+ {
+ var aliasResult = Visit(child) as IList;
+ aliasList.AddRange(aliasResult);
+ }
+ else if (child is CypherParser.OC_OrderContext)
+ {
+ var orderResult = Visit(child) as IList;
+ orderList.AddRange(orderResult);
+ }
+ else if (child is CypherParser.OC_LimitContext)
+ {
+ var limitResult = Visit(child) as QueryExpressionLimit;
+ limitList.Add(limitResult);
+ }
+ }
+ return (
+ ReturnItems: aliasList as IList,
+ OrderByItems: orderList as IList,
+ LimitItems: limitList as IList);
+ }
+
+ }
+
+}
diff --git a/src/openCypherParser/AST/MatchDataSource.cs b/src/openCypherParser/AST/MatchDataSource.cs
new file mode 100644
index 0000000..a873dc2
--- /dev/null
+++ b/src/openCypherParser/AST/MatchDataSource.cs
@@ -0,0 +1,190 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+using System.Collections.Generic;
+using System.Linq;
+using openCypherTranspiler.Common.Exceptions;
+
+namespace openCypherTranspiler.openCypherParser.AST
+{
+ ///
+ /// represents the parsed group of matched patterns
+ ///
+ public class MatchDataSource : TreeNode
+ {
+ #region Implements TreeNode
+ protected override IEnumerable Children
+ {
+ get
+ {
+ return Enumerable.Empty();
+ }
+ }
+ #endregion Implements TreeNode
+
+ ///
+ /// Return all the entities refered by the Match statement
+ /// with the same order it appears
+ ///
+ public IList AllEntitiesOrdered
+ {
+ get
+ {
+ // can't use SelectMany if it doesn't guarantee order
+ // MatchPatterns?.SelectMany(p => p) ?? Enumerable.Empty();
+ return MatchPatterns?.Aggregate(new List(), (p, l) => { p.AddRange(l); return p; }) ?? new List(0);
+ }
+ }
+
+ ///
+ /// A list of match patterns:
+ /// e.g. (a:device)-[]->(
+ ///
+ public IList MatchPatterns { get; set; }
+
+ ///
+ /// Return a new MatchDataSource mirrors the old one but with anonymous entities filled with
+ /// a place holder alias
+ ///
+ ///
+ ///
+ public MatchDataSource AssignGeneratedEntityAliasToAnonEntities(string anonVarPrefix)
+ {
+ var varIdx = 0;
+ var existingAliases = new HashSet(MatchPatterns.SelectMany(p => p.Select(e => e.Alias)).Where(a => !string.IsNullOrEmpty(a)).Distinct());
+
+ if (existingAliases.Any(p => p.StartsWith(anonVarPrefix)))
+ {
+ throw new TranspilerSyntaxErrorException($"Banned prefix for variable aliases: {anonVarPrefix}. Please consider a different prefix.");
+ }
+
+ // creating a new DS with implied labels fixed up
+ var matchDsNew = new MatchDataSource()
+ {
+ MatchPatterns = MatchPatterns.Select(p => new MatchPattern(p.IsOptionalMatch, p.Select(e =>
+ {
+ var alias = e.Alias ?? $"{anonVarPrefix}{varIdx++}";
+ var entity = e.Clone();
+ entity.Alias = alias;
+ return entity;
+ }))).ToList()
+ };
+
+ return matchDsNew;
+ }
+
+
+ public override string ToString()
+ {
+ return $"Matches: Count={MatchPatterns?.Count ?? 0}, Patterns={string.Join(" ", MatchPatterns)}";
+ }
+ }
+
+ ///
+ /// A single match pattern is a traversal of entities (node and relationships)
+ ///
+ public class MatchPattern : List, IList
+ {
+ public MatchPattern(bool isOptional, IEnumerable list) : base(list)
+ {
+ IsOptionalMatch = isOptional;
+ }
+
+ public bool IsOptionalMatch { get; set; }
+
+ public override string ToString()
+ {
+ return $"MatchPat: IsOptional={IsOptionalMatch}; Pattern={string.Join(",", this)}";
+ }
+ }
+
+ ///
+ /// Represents an entity of either node or edge
+ ///
+ public abstract class Entity
+ {
+ ///
+ /// Name of the entity, for edge this is just verb of the edge
+ ///
+ public string EntityName { get; set; }
+
+ ///
+ /// Alias used for refering the entity
+ ///
+ public string Alias { get; set; }
+
+ ///
+ /// Make a deep copy of the derived class of this type
+ ///
+ ///
+ public abstract Entity Clone();
+ }
+
+ public class NodeEntity : Entity
+ {
+ public override Entity Clone()
+ {
+ return new NodeEntity()
+ {
+ Alias = this.Alias,
+ EntityName = this.EntityName
+ };
+ }
+
+ public override string ToString()
+ {
+ return $"({Alias}:{EntityName})";
+ }
+ }
+
+ ///
+ /// Edge is not unique by its verb during binding but depends on the node it connects
+ /// E.g. (:device)-[:runs]-(:app)
+ /// so we capture the in node (left) and out node of this edge (right)
+ ///
+ public class RelationshipEntity : Entity
+ {
+ public enum Direction
+ {
+ Both, // -[]-
+ Forward, // -[]->
+ Backward // <-[]-
+ }
+
+
+ ///
+ /// Left side node entity's name
+ /// Note that 'left' here the lexical order and not refelcting the direction
+ ///
+ public string LeftEntityName { get; set; }
+
+ ///
+ /// Right side node entity's name
+ /// Note that 'right' here the lexical order and not refelcting the direction
+ ///
+ public string RightEntityName { get; set; }
+
+ public override Entity Clone()
+ {
+ return new RelationshipEntity()
+ {
+ Alias = this.Alias,
+ EntityName = this.EntityName,
+ RelationshipDirection = this.RelationshipDirection,
+ LeftEntityName = this.LeftEntityName,
+ RightEntityName = this.RightEntityName,
+ };
+ }
+ ///
+ /// Name of the entity, for edge this is just verb of the edge
+ ///
+ public Direction RelationshipDirection { get; set; }
+
+ public override string ToString()
+ {
+ return $"{LeftEntityName}{(RelationshipDirection == Direction.Backward ? "<" : "")}-[{Alias}:{EntityName}]-{(RelationshipDirection == Direction.Forward ? ">" : "")}{RightEntityName}";
+ }
+ }
+}
diff --git a/src/openCypherParser/AST/QueryExpression.cs b/src/openCypherParser/AST/QueryExpression.cs
new file mode 100644
index 0000000..92326d2
--- /dev/null
+++ b/src/openCypherParser/AST/QueryExpression.cs
@@ -0,0 +1,638 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using openCypherTranspiler.Common.Exceptions;
+using openCypherTranspiler.Common.Utils;
+using openCypherTranspiler.openCypherParser.Common;
+
+namespace openCypherTranspiler.openCypherParser.AST
+{
+ ///
+ /// represents an expression appears in Graph Query language
+ ///
+ public abstract class QueryExpression : TreeNode
+ {
+ ///
+ /// Traversal helper to retrieve query expression of certain type
+ ///
+ ///
+ ///
+ public IEnumerable GetChildrenQueryExpressionType() where T : QueryExpression
+ {
+ return GetChildrenOfType();
+ }
+
+ ///
+ /// Compute the result's data type of the expression
+ ///
+ ///
+ abstract public Type EvaluateType();
+ }
+
+ ///
+ /// represents a expression with an explicit alias, e.g. a AS b
+ ///
+ public class QueryExpressionWithAlias : QueryExpression
+ {
+ #region Implements TreeNode
+ protected override IEnumerable Children
+ {
+ get
+ {
+ return new List() { InnerExpression };
+ }
+ }
+ #endregion Implements TreeNode
+
+ public QueryExpression InnerExpression { get; set; }
+ public string Alias { get; set; }
+
+ public override string ToString()
+ {
+ return $"ExprWithAlias: {Alias}";
+ }
+
+ public override Type EvaluateType()
+ {
+ return InnerExpression.EvaluateType();
+ }
+ }
+
+ ///
+ /// represents a binary (a + b, or a = b, or a <= b) operation
+ ///
+ public partial class QueryExpressionBinary : QueryExpression
+ {
+ #region Implements TreeNode
+ protected override IEnumerable Children
+ {
+ get
+ {
+ return new List() { LeftExpression, RightExpression };
+ }
+ }
+ #endregion Implements TreeNode
+
+ public BinaryOperatorInfo Operator { get; set; }
+ public QueryExpression LeftExpression { get; set; }
+ public QueryExpression RightExpression { get; set; }
+
+ public override string ToString()
+ {
+ return $"ExprBinary: Op='{Operator}'";
+ }
+
+ public override Type EvaluateType()
+ {
+ var leftType = LeftExpression.EvaluateType();
+ var rightType = RightExpression.EvaluateType();
+ var leftTypeUnboxed = TypeHelper.GetUnderlyingTypeIfNullable(leftType);
+ var rightTypeUnboxed = TypeHelper.GetUnderlyingTypeIfNullable(rightType);
+ var anyNullable = TypeHelper.IsSystemNullableType(leftType) || TypeHelper.IsSystemNullableType(rightType);
+ Type resultedTypeRaw;
+
+ switch (Operator.Type)
+ {
+ case BinaryOperatorType.Logical:
+ // For logical comparison, we ensure that all operands' type are logical already
+ // The return type is always boolean (logical)
+ if (leftType != typeof(bool) || leftType != typeof(bool?) &&
+ rightType != typeof(bool) || rightType != typeof(bool?))
+ {
+ throw new TranspilerNotSupportedException($"Logical binary operator {Operator} operating must operate on bool types. Actual types: {leftType}, {rightType}");
+ }
+ return (anyNullable ? TypeHelper.MakeNullableIfNotAlready(typeof(bool)) : typeof(bool));
+
+ case BinaryOperatorType.Value:
+ // For value type operator, use the value type coercion table
+ if (!CoersionTables.CoersionTableForValueType.TryGetValue((Operator.Name, leftTypeUnboxed, rightTypeUnboxed), out resultedTypeRaw))
+ {
+ throw new TranspilerInternalErrorException($"Unexpected use of binary operator {Operator.Name} operating between types {leftTypeUnboxed} and {rightTypeUnboxed}");
+ }
+ if (resultedTypeRaw == default(Type))
+ {
+ throw new TranspilerNotSupportedException($"Binary operator {Operator.Name} operating between types {leftTypeUnboxed} and {rightTypeUnboxed}");
+ }
+ return (anyNullable ? TypeHelper.MakeNullableIfNotAlready(resultedTypeRaw) : resultedTypeRaw);
+
+ case BinaryOperatorType.Comparison:
+ // For comparison operators, use the equality/inequality type coercion table
+ if (Operator.Name == BinaryOperator.EQ || Operator.Name == BinaryOperator.NEQ)
+ {
+ if (!CoersionTables.CoersionTableEqualityComparison.TryGetValue((leftTypeUnboxed, rightTypeUnboxed), out resultedTypeRaw))
+ {
+ throw new TranspilerInternalErrorException($"Unexpected use of binary operator {Operator.Name} operating between types {leftTypeUnboxed} and {rightTypeUnboxed}");
+ }
+ }
+ else
+ {
+ if (!CoersionTables.CoersionTableInequalityComparison.TryGetValue((leftTypeUnboxed, rightTypeUnboxed), out resultedTypeRaw))
+ {
+ throw new TranspilerInternalErrorException($"Unexpected use of binary operator {Operator.Name} operating between types {leftTypeUnboxed} and {rightTypeUnboxed}");
+ }
+ }
+ if (resultedTypeRaw == default(Type))
+ {
+ throw new TranspilerNotSupportedException($"Binary operator {Operator.Name} operating between types {leftTypeUnboxed} and {rightTypeUnboxed}");
+ }
+ return (anyNullable ? TypeHelper.MakeNullableIfNotAlready(resultedTypeRaw) : resultedTypeRaw);
+
+ case BinaryOperatorType.Invalid:
+ default:
+ throw new TranspilerInternalErrorException($"Unexpected operator type: {Operator.Type}");
+ }
+ }
+
+ }
+
+ ///
+ /// represents a function call, like toFloat(expr)
+ ///
+ public class QueryExpressionFunction : QueryExpression
+ {
+ #region Implements TreeNode
+ protected override IEnumerable Children
+ {
+ get
+ {
+ return new List() { InnerExpression };
+ }
+ }
+ #endregion Implements TreeNode
+
+ public FunctionInfo Function { get; set; }
+
+ public QueryExpression InnerExpression { get; set; }
+
+ public IEnumerable AdditionalExpressions { get; set; }
+
+ public override string ToString()
+ {
+ return $"ExprFunc: {Function}(a)";
+ }
+
+ public override Type EvaluateType()
+ {
+ var innerType = InnerExpression.EvaluateType();
+ var isWrappedinNullable = TypeHelper.IsSystemNullableType(innerType);
+ switch (Function.FunctionName)
+ {
+ case Common.Function.ToFloat:
+ return isWrappedinNullable ? typeof(float?) : typeof(float);
+ case Common.Function.ToString:
+ return typeof(string);
+ case Common.Function.ToBoolean:
+ return isWrappedinNullable ? typeof(bool?) : typeof(bool);
+ case Common.Function.ToInteger:
+ return isWrappedinNullable ? typeof(int?) : typeof(int);
+ case Common.Function.ToDouble:
+ return isWrappedinNullable ? typeof(long?) : typeof(long);
+ case Common.Function.ToLong:
+ return isWrappedinNullable ? typeof(double?) : typeof(double);
+ case Common.Function.Not:
+ return isWrappedinNullable ? typeof(bool?) : typeof(bool);
+ case Common.Function.StringContains:
+ case Common.Function.StringStartsWith:
+ case Common.Function.StringEndsWith:
+ case Common.Function.IsNull:
+ case Common.Function.IsNotNull:
+ return typeof(bool);
+ case Common.Function.StringSize:
+ return typeof(int);
+ default:
+ // treat all the rest as type preserving, e.g.
+ // trim, ltrim ....
+ return InnerExpression.EvaluateType();
+ }
+ }
+
+ }
+
+ ///
+ /// represents a aggregation function call, like avg(expr)
+ ///
+ public partial class QueryExpressionAggregationFunction : QueryExpression
+ {
+ #region Implements TreeNode
+ protected override IEnumerable Children
+ {
+ get
+ {
+ return new List() { InnerExpression };
+ }
+ }
+ #endregion Implements TreeNode
+
+ public AggregationFunction AggregationFunction { get; set; }
+ public bool IsDistinct { get; set; }
+ public QueryExpression InnerExpression { get; set; }
+
+ public override string ToString()
+ {
+ return $"ExprAggFunc: {AggregationFunction}(a)";
+ }
+
+ public override Type EvaluateType()
+ {
+ var innerType = InnerExpression.EvaluateType();
+ var innerTypeUnboxed = TypeHelper.GetUnderlyingTypeIfNullable(innerType);
+
+ Type resultedType;
+ if (!AggregationFunctionReturnTypeTable.TypeMapTable.TryGetValue((AggregationFunction, innerTypeUnboxed), out resultedType))
+ {
+ // for any aggregation function that were not having specially handling, then it is considered to preserve the original type
+ return innerType;
+ }
+
+ return resultedType;
+ }
+ }
+
+ ///
+ /// represents a reference to a property (column), e.g. r.Score
+ ///
+ public class QueryExpressionProperty : QueryExpression
+ {
+ #region Implements TreeNode
+ protected override IEnumerable Children
+ {
+ get
+ {
+ return Enumerable.Empty();
+ }
+ }
+ #endregion Implements TreeNode
+
+ ///
+ /// For a property reference, this is the variable part, namely alias of alias.field
+ ///
+ public string VariableName { get; set; }
+
+ ///
+ /// For a property reference, this is the property part, namely alias of alias.field
+ ///
+ public string PropertyName { get; set; }
+
+
+ ///
+ /// For a node/relationship, this is the entity type reference
+ ///
+ ///
+ ///
+ public Entity Entity { get; set; }
+
+ ///
+ /// For a single field, this is the data type
+ ///
+ ///
+ ///
+ public Type DataType { get; set; }
+
+ public override string ToString()
+ {
+ if (Entity != null)
+ {
+ return $"ExprProperty: {VariableName} {Entity}";
+ }
+ else
+ {
+ return $"ExprProperty: {VariableName}.{PropertyName}";
+ }
+ }
+ public override Type EvaluateType()
+ {
+ return DataType;
+ }
+
+ }
+
+ ///
+ /// represent a list of expressions
+ ///
+ public class QueryExpressionList : QueryExpression
+ {
+ #region Implements TreeNode
+ protected override IEnumerable Children
+ {
+ get
+ {
+ return ExpressionList.Cast