hydra-0.15.0: src/main/haskell/Hydra/Sources/Cypher/Features.hs
module Hydra.Sources.Cypher.Features where
-- Standard imports for type-level sources outside of the kernel
import Hydra.Kernel
import Hydra.Dsl.Annotations
import Hydra.Dsl.Bootstrap
import Hydra.Dsl.Types ((>:))
import qualified Hydra.Dsl.Types as T
import qualified Hydra.Sources.Kernel.Types.Core as Core
import qualified Data.List as L
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Maybe as Y
-- Additional imports
import Hydra.Sources.Cypher.Functions
ns :: Namespace
ns = Namespace "hydra.cypher.features"
cypherFeatures :: String -> Type
cypherFeatures = typeref ns
data FeatureSet = FeatureSet {
featureSetName :: String,
featureSetDescription :: String,
featureSetChildren :: [FeatureSet]}
module_ :: Module
module_ = Module {
moduleNamespace = ns,
moduleDefinitions = (map toTypeDef definitions),
moduleTermDependencies = [Core.ns],
moduleTypeDependencies = [Core.ns],
moduleDescription = Just ("A model for characterizing OpenCypher queries and implementations in terms of included features."
++ "Based on the OpenCypher grammar and the list of standard Cypher functions at "
++ "https://neo4j.com/docs/cypher-manual/current/functions."
++ " Current as of August 2024.")}
where
definitions = featureSetToType <$> flatten openCypherFeatures
where
flatten fs = if L.null children then [] else (fs:(L.concat (flatten <$> children)))
where
children = featureSetChildren fs
featureSetToType (FeatureSet name desc children) = datatype ns (featureSetNameToTypeName name) $
doc (featureSetDesc desc) $ T.record (toField <$> children)
where
toField (FeatureSet name1 desc1 children1) = (dotsToCamel name1)>: if L.null children1
then doc (featureSetDesc desc1) T.boolean
else doc (featureSetDesc desc1) $ cypherFeatures $ featureSetNameToTypeName name1
where
fieldDesc = "Whether to expect " ++ desc1
-- Convert dotted names like "date.realtime" to camelCase "dateRealtime"
dotsToCamel name = case break (== '.') name of
(first, []) -> decapitalize first
(first, _:rest) -> decapitalize first ++ capitalize (dotsToCamel rest)
featureSetNameToTypeName name = capitalize name ++ "Features"
featureSetDesc desc = capitalize desc
openCypherFeatures :: FeatureSet
openCypherFeatures = FeatureSet "Cypher"
("A set of features which characterize an OpenCypher query or implementation. "
++ "Any features which are omitted from the set are assumed to be unsupported or nonrequired.") [
FeatureSet "Arithmetic" "arithmetic operations" [
feature "plus" "the + operator",
feature "minus" "the - operator",
feature "multiply" "the * operator",
feature "divide" "the / operator",
feature "modulus" "the % operator",
feature "powerOf" "the ^ operator"],
FeatureSet "Atom" "various kinds of atomic expressions" [
feature "caseExpression" "CASE expressions",
feature "count" "the COUNT (*) expression",
feature "existentialSubquery" "existential subqueries",
feature "functionInvocation" "function invocation",
feature "parameter" "parameter expressions",
feature "patternComprehension" "pattern comprehensions",
feature "patternPredicate" "relationship patterns as subexpressions",
fixed $ feature "variable" "variable expressions"],
FeatureSet "Comparison" "comparison operators and functions" [
feature "equal" "the = comparison operator",
feature "greaterThan" "the > comparison operator",
feature "greaterThanOrEqual" "the >= comparison operator",
feature "lessThan" "the < comparison operator",
feature "lessThanOrEqual" "the <= comparison operator",
feature "notEqual" "the <> comparison operator"],
FeatureSet "Delete" "delete operations" [
feature "delete" "the basic DELETE clause",
feature "detachDelete" "the DETACH DELETE clause"],
FeatureSet "Function" "standard Cypher functions" (libraryToFeatureSet <$> cypherLibraries),
FeatureSet "List" "list functionality" [
feature "listComprehension" "basic list comprehensions",
feature "listRange" "list range comprehensions (e.g. [1..10])"],
FeatureSet "Literal" "various types of literal values" [
fixed $ feature "boolean" "boolean literals",
feature "double" "double-precision floating-point literals",
feature "integer" "integer literals",
feature "list" "list literals",
feature "map" "map literals",
feature "null" "the NULL literal",
fixed $ feature "string" "string literals"],
FeatureSet "Logical" "logical operations" [
feature "and" "the AND operator",
feature "not" "the NOT operator",
feature "or" "the OR operator",
feature "xor" "the XOR operator"],
FeatureSet "Match" "match queries" [
feature "match" "the basic (non-optional) MATCH clause",
feature "optionalMatch" "OPTIONAL MATCH"],
FeatureSet "Merge" "merge operations" [
feature "merge" "the basic MERGE clause",
feature "mergeOnCreate" "MERGE with the ON CREATE action",
feature "mergeOnMatch" "MERGE with the ON MATCH action"],
FeatureSet "NodePattern" "node patterns" [
feature "multipleLabels" "specifying multiple labels in a node pattern",
feature "parameter" "specifying a parameter as part of a node pattern",
feature "propertyMap" "specifying a key/value map of properties in a node pattern",
fixed $ feature "variableNode" "binding a variable to a node in a node pattern",
feature "wildcardLabel" "omitting labels from a node pattern"],
FeatureSet "Null" "IS NULL / IS NOT NULL checks" [
feature "isNull" "the IS NULL operator",
feature "isNotNull" "the IS NOT NULL operator"],
FeatureSet "Path" "path functions only found in OpenCypher" [
function "shortestPath"],
FeatureSet "ProcedureCall" "procedure calls" [
feature "inQueryCall" "CALL within a query",
feature "standaloneCall" "standalone / top-level CALL",
-- Note: additional features are possible around YIELD
feature "yield" "the YIELD clause in CALL"],
FeatureSet "Projection" "projections" [
feature "limit" "the LIMIT clause",
feature "orderBy" "the ORDER BY clause",
feature "projectDistinct" "the DISTINCT keyword",
feature "projectAll" "the * projection",
feature "projectAs" "the AS keyword",
feature "skip" "the SKIP clause",
feature "sortOrder" "the ASC/ASCENDING and DESC/DESCENDING keywords"],
FeatureSet "Quantifier" "quantifier expressions" [
feature "all" "the ALL quantifier",
feature "any" "the ANY quantifier",
feature "none" "the NONE quantifier",
feature "single" "the SINGLE quantifier"],
FeatureSet "RangeLiteral" "range literals within relationship patterns" [
feature "bounds" "range literals with both lower and upper bounds",
feature "exactRange" "range literals providing an exact number of repetitions",
feature "lowerBound" "range literals with a lower bound (only)",
feature "starRange" "the * range literal",
feature "upperBound" "range literals with an upper bound (only)"],
FeatureSet "Reading" "specific syntax related to reading data from the graph." [
feature "union" "the UNION operator",
feature "unionAll" "the UNION ALL operator",
feature "unwind" "the UNWIND clause"],
FeatureSet "RelationshipDirection" "relationship directions / arrow patterns" [
feature "both" "the two-headed arrow (<-[]->) relationship direction",
feature "left" "the left arrow (<-[]-) relationship direction",
feature "neither" "the headless arrow (-[]-) relationship direction",
feature "right" "the right arrow (-[]->) relationship direction"],
FeatureSet "RelationshipPattern" "relationship patterns" [
feature "multipleTypes" "specifying a disjunction of multiple types in a relationship pattern",
fixed $ feature "variableRelationship" "binding a variable to a relationship in a relationship pattern",
feature "wildcardType" "omitting types from a relationship pattern"],
FeatureSet "Remove" "REMOVE operations" [
feature "byLabel" "REMOVE Variable:NodeLabels",
feature "byProperty" "REMOVE PropertyExpression"],
FeatureSet "Set" "set definitions" [
feature "propertyEquals" "defining a set using PropertyExpression = Expression",
feature "variableEquals" "defining a set using Variable = Expression",
feature "variablePlusEquals" "defining a set using Variable += Expression",
feature "variableWithNodeLabels" "defining a set using Variable:NodeLabels"],
FeatureSet "String" "string functions/keywords only found in OpenCypher" [
functionWithKeyword "contains" "CONTAINS",
functionWithKeyword "endsWith" "ENDS WITH",
functionWithKeyword "in" "IN",
functionWithKeyword "startsWith" "STARTS WITH"],
FeatureSet "Updating" "specific syntax related to updating data in the graph" [
feature "create" "the CREATE clause",
feature "set" "the SET clause",
feature "with" "multi-part queries using WITH"]]
where
feature name desc = FeatureSet name desc []
fixed (FeatureSet name desc children)
= FeatureSet name (desc ++ " (note: included by most if not all implementations).") children
function name = FeatureSet name (funDesc name Nothing Nothing) []
funDesc name mkeyword mdesc = "the " ++ name ++ "() function" ++ keyword ++ desc
where
keyword = Y.maybe "" (\k -> " / " ++ k) mkeyword
desc = Y.maybe "" (\d -> ". " ++ d) mdesc
functionWithKeyword name keyword = FeatureSet name (funDesc name (Just keyword) Nothing) []
libraryToFeatureSet (CypherLibrary name desc funs) = FeatureSet (capitalize name ++ "Function") desc (toFeature <$> funs)
where
toFeature (CypherFunction name keyword forms) = FeatureSet name (funDesc name keyword $ Just desc) []
where
-- Note: signatures are currently not used
desc = L.intercalate "; " (cypherFunctionFormDescription <$> forms)
-- | An alternative model of (Open)Cypher features, flattened into an enumeration.
-- Usage:
-- writeProtobuf "/tmp/proto" [openCypherFeaturesEnumModule]
openCypherFeaturesEnumModule :: Module
openCypherFeaturesEnumModule = Module {
moduleNamespace = ns2,
moduleDefinitions = (map toTypeDef definitions),
moduleTermDependencies = [Core.ns],
moduleTypeDependencies = [Core.ns],
moduleDescription = Just ("A model with an enumeration of (Open)Cypher features.")}
where
ns2 = Namespace "hydra.org/opencypher/features"
def = datatype ns2
definitions = [
def "CypherFeature" $
doc "An enumeration of (Open)Cypher features."
openCypherFeaturesEnum]
openCypherFeaturesEnum :: Type
openCypherFeaturesEnum = T.union $ gatherFields True "" openCypherFeatures
where
gatherFields root prefix (FeatureSet name desc children) = if L.null children
then [FieldType (Name selfName) $ doc (capitalize desc) T.unit]
else L.concat (gatherFields False selfName <$> children)
where
--selfName = capitalize name
selfName = if root
then ""
else prefix ++ stripFunctionSuffix (capitalize name)
stripFunctionSuffix name = if "Function" `L.isSuffixOf` name && L.length name > flen
then removeLastN flen name
else name
where
removeLastN n xs = L.take (L.length xs - n) xs
flen = L.length ("Function" :: String)