Package: graphql-compiler
graphql-compilerTurn complex GraphQL queries into optimized database queries.pip install graphql-compilerQuick OverviewThrough the GraphQL compiler, users can write powerful queries that uncover
deep relationships in the data while not having to worry about the underlying database query
language. The GraphQL compiler turns read-only queries written in GraphQL syntax to different
query languages.Furthermore, the GraphQL compiler validates queries through the use of a GraphQL schema
that specifies the underlying schema of the database. We can currently autogenerate a
GraphQL schema by introspecting an OrientDB database, (seeEnd to End Example).In the near future, we plan to add schema autogeneration from SQLAlchemy metadata as well.For a more detailed overview and getting started guide, please seeour blog post.Table of contentsFeaturesEnd to End ExampleDefinitionsDirectives@optional@output@fold@tag@filter@recurse@output_sourceSupported filtering operationsComparison operatorsname_or_aliasbetweenin_collectionnot_in_collectionhas_substringcontainsnot_containsintersectshas_edge_degreeType coercionsMeta fields__typename_x_countThe GraphQL schemaExecution modelSQLConfiguring SQLAlchemyEnd-To-End SQL ExampleConfiguring the SQL Database to Match the GraphQL SchemaMiscellaneousPretty-Printing GraphQL QueriesExpanding@optionalvertex fieldsOptionaltype_equivalence_hintscompilation parameterSchemaGraphCypher query parametersFAQLicenseFeaturesDatabases and Query Languages:We currently support a single database, OrientDB version 2.2.28+, and two query languages that OrientDB supports: the OrientDB dialect of gremlin, and OrientDB's own custom SQL-like query language that we refer to as MATCH, after the name of its graph traversal operator. With OrientDB, MATCH should be the preferred choice for most users, since it tends to run faster than gremlin, and has other desirable properties. See the Execution model section for more details.Support for relational databases including PostgreSQL, MySQL, SQLite,
and Microsoft SQL Server is a work in progress. A subset of compiler features are available for
these databases. See theSQLsection for more details.GraphQL Language Features:We prioritized and implemented a subset of all functionality supported by the GraphQL language. We hope to add more functionality over time.End-to-End ExampleEven though this example specifically targets an OrientDB database, it is meant as a generic
end-to-end example of how to use the GraphQL compiler.fromgraphql.utils.schema_printerimportprint_schemafromgraphql_compilerimport(get_graphql_schema_from_orientdb_schema_data,graphql_to_match)fromgraphql_compiler.schema_generation.orientdb.utilsimportORIENTDB_SCHEMA_RECORDS_QUERY# Step 1: Get schema metadata from hypothetical Animals database.client=your_function_that_returns_an_orientdb_client()schema_records=client.command(ORIENTDB_SCHEMA_RECORDS_QUERY)schema_data=[record.oRecordDataforrecordinschema_records]# Step 2: Generate GraphQL schema from metadata.schema,type_equivalence_hints=get_graphql_schema_from_orientdb_schema_data(schema_data)print(print_schema(schema))# schema {#    query: RootSchemaQuery# }## directive @filter(op_name: String!, value: [String!]!) on FIELD | INLINE_FRAGMENT## directive @tag(tag_name: String!) on FIELD## directive @output(out_name: String!) on FIELD## directive @output_source on FIELD## directive @optional on FIELD## directive @recurse(depth: Int!) on FIELD## directive @fold on FIELD## type Animal {#     name: String#     net_worth: Int#     limbs: Int# }## type RootSchemaQuery{#     Animal: [Animal]# }# Step 3: Write GraphQL query that returns the names of all animals with a certain net worth.# Note that we prefix net_worth with '$' and surround it with quotes to indicate it's a parameter.graphql_query='''{Animal {name @output(out_name: "animal_name")net_worth @filter(op_name: "=", value: ["$net_worth"])}}'''parameters={'net_worth':'100',}# Step 4: Use autogenerated GraphQL schema to compile query into the target database language.compilation_result=graphql_to_match(schema,graphql_query,parameters,type_equivalence_hints)print(compilation_result.query)# SELECT Animal___1.name AS `animal_name`# FROM  ( MATCH  { class: Animal, where: ((net_worth = decimal("100"))), as: Animal___1 }# RETURN $matches)DefinitionsVertex field: A field corresponding to a vertex in the graph. In the below example,Animalandout_Entity_Relatedare vertex fields. TheAnimalfield is the field at which querying
starts, and is therefore theroot vertex field. In any scope, fields with the prefixout_denote vertex fields connected by an outbound edge, whereas ones with the prefixin_denote
vertex fields connected by an inbound edge.{Animal{name@output(out_name:"name")out_Entity_Related{...onSpecies{description@output(out_name:"description")}}}}Property field: A field corresponding to a property of a vertex in the graph. In the
above example, thenameanddescriptionfields are property fields. In any given scope,property fields must appear before vertex fields.Result set: An assignment of vertices in the graph to scopes (locations) in the query.
As the database processes the query, new result sets may be created (e.g. when traversing edges),
and result sets may be discarded when they do not satisfy filters or type coercions. After all
parts of the query are processed by the database, all remaining result sets are used to form the
query result, by taking their values at all properties marked for output.Scope: The part of a query between any pair of curly braces. The compiler infers the type
of each scope. For example, in the above query, the scope beginning withAnimal {is of
typeAnimal, the one beginning without_Entity_Related {is of typeEntity, and the one
beginning with... on Species {is of typeSpecies.Type coercion: An operation that produces a new scope of narrower type than the
scope in which it exists. Any result sets that cannot satisfy the narrower type are filtered out
and not returned. In the above query,... on Speciesis a type coercion which takes
its enclosing scope of typeEntity, and coerces it into a narrower scope of
typeSpecies. This is possible sinceEntityis an interface, andSpeciesis a type
that implements theEntityinterface.Directives@optionalWithout this directive, when a query includes a vertex field, any results matching that query
must be able to produce a value for that vertex field. Applied to a vertex field,
this directive prevents result sets that are unable to produce a value for that field from
being discarded, and allowed to continue processing the remainder of the query.Example Use{Animal{name@output(out_name:"name")out_Animal_ParentOf@optional{name@output(out_name:"child_name")}}}For eachAnimal:if it is a parent of another animal, at least one row containing the
parent and child animal's names, in thenameandchild_namecolumns respectively;if it is not a parent of another animal, a row with its name in thenamecolumn,
and anullvalue in thechild_namecolumn.Constraints and Rules@optionalcan only be applied to vertex fields, except the root vertex field.It is allowed to expand vertex fields within an@optionalscope.
However, doing so is currently associated with a performance penalty inMATCH.
For more detail, see:Expanding@optionalvertex fields.@recurse,@fold, or@output_sourcemay not be used at the same vertex field as@optional.@output_sourceand@foldmay not be used anywhere within a scope
marked@optional.If a given result set is unable to produce a value for a vertex field marked@optional,
any fields marked@outputwithin that vertex field return thenullvalue.When filtering (via@filter) or type coercion (via e.g.... on Animal) are applied
at or within a vertex field marked@optional, the@optionalis given precedence:If a given result set cannot produce a value for the optional vertex field, it is preserved:
the@optionaldirective is applied first, and no filtering or type coercion can happen.If a given result set is able to produce a value for the optional vertex field,
the@optionaldoes not apply, and that value is then checked against the filtering or type
coercion. These subsequent operations may then cause the result set to be discarded if it does
not match.For example, suppose we have twoPersonvertices with namesAlbertandBettysuch that there is aPerson_Knowsedge fromAlberttoBetty.Then the following query:{Person{out_Person_Knows@optional{name@filter(op_name:"=",value:["$name"])}name@output(out_name:"person_name")}}with runtime parameter{"name":"Charles"}would output an empty list because thePerson_Knowsedge fromAlberttoBettysatisfies the@optionaldirective, butBettydoesn't match the filter checking for a node with nameCharles.However, if no suchPerson_Knowsedge existed fromAlbert, then the output would be{name:'Albert'}because no such edge can satisfy the@optionaldirective, and no filtering happens.@outputDenotes that the value of a property field should be included in the output.
Itsout_nameargument specifies the name of the column in which the
output value should be returned.Example Use{Animal{name@output(out_name:"animal_name")}}This query returns the name of eachAnimalin the graph, in a column namedanimal_name.Constraints and Rules@outputcan only be applied to property fields.The value provided forout_namemay only consist of upper or lower case letters
(A-Z,a-z), or underscores (_).The value provided forout_namecannot be prefixed with___(three underscores). This
namespace is reserved for compiler internal use.For any given query, allout_namevalues must be unique. In other words, output columns must
have unique names.If the property field marked@outputexists within a scope marked@optional, result sets that
are unable to assign a value to the optional scope return the valuenullas the output
of that property field.@foldApplying@foldon a scope "folds" all outputs from within that scope: rather than appearing
on separate rows in the query result, the folded outputs are coalesced into lists starting
at the scope marked@fold.Example Use{Animal{name@output(out_name:"animal_name")out_Animal_ParentOf@fold{name@output(out_name:"child_names")}}}Each returned row has two columns:animal_namewith the name of eachAnimalin the graph,
andchild_nameswith a list of the names of all children of theAnimalnamedanimal_name.
If a givenAnimalhas no children, itschild_nameslist is empty.Constraints and Rules@foldcan only be applied to vertex fields, except the root vertex field.May not exist at the same vertex field as@recurse,@optional, or@output_source.Any scope that is either marked with@foldor is nested within a@foldmarked scope,
may expand at most one vertex field.There must be at least one@outputfield within a@foldscope.All@outputfields within a@foldtraversal must be present at the innermost scope.
It is invalid to expand vertex fields within a@foldafter encountering an@outputdirective.@tag,@recurse,@optional,@output_sourceand@foldmay not be used anywhere
within a scope marked@fold.Use of type coercions or@filterat or within the vertex field marked@foldis allowed.
Only data that satisfies the given type coercions and filters is returned by the@fold.If the compiler is able to prove that the type coercion in the@foldscope is actually a no-op,
it may optimize it away. See theOptionaltype_equivalence_hintscompilation parametersection for more details.ExampleThe following GraphQL isnot allowedand will produce aGraphQLCompilationError.
This query isinvalidfor two separate reasons:It expands vertex fields after an@outputdirective (outputtinganimal_name)Thein_Animal_ParentOfscope, which is within a scope marked@fold,
expands two vertex fields instead of at most one.{Animal{out_Animal_ParentOf@fold{name@output(out_name:"animal_name")in_Animal_ParentOf{out_Animal_OfSpecies{uuid@output(out_name:"species_id")}out_Entity_Related{...onAnimal{name@output(out_name:"relative_name")}}}}}}The following is a valid use of@fold:{Animal{out_Animal_ParentOf@fold{in_Animal_ParentOf{in_Animal_ParentOf{out_Entity_Related{...onAnimal{name@output(out_name:"final_name")}}}}}}}@tagThe@tagdirective enables filtering based on values encountered elsewhere in the same query.
Applied on a property field, it assigns a name to the value of that property field, allowing that
value to then be used as part of a@filterdirective.To supply a tagged value to a@filterdirective, place the tag name (prefixed with a%symbol)
in the@filter'svaluearray. SeePassing parametersfor more details.Example Use{Animal{name@tag(tag_name:"parent_name")out_Animal_ParentOf{name@filter(op_name:"<",value:["%parent_name"])@output(out_name:"child_name")}}}Each row returned by this query contains, in thechild_namecolumn, the name of anAnimalthat is the child of anotherAnimal, and has a name that is lexicographically smaller than
the name of its parent.Constraints and Rules@tagcan only be applied to property fields.The value provided fortag_namemay only consist of upper or lower case letters
(A-Z,a-z), or underscores (_).For any given query, alltag_namevalues must be unique.Cannot be applied to property fields within a scope marked@fold.Using a@tagand a@filterthat references the tag within the same vertex is allowed,
so long as the two do not appear on the exact same property field.@filterAllows filtering of the data to be returned, based on any of a set of filtering operations.
Conceptually, it is the GraphQL equivalent of the SQLWHEREkeyword.SeeSupported filtering operationsfor details on the various types of filtering that the compiler currently supports.
These operations are currently hardcoded in the compiler; in the future,
we may enable the addition of custom filtering operations via compiler plugins.Multiple@filterdirectives may be applied to the same field at once. Conceptually,
it is as if the different@filterdirectives were joined by SQLANDkeywords.Using a@tagand a@filterthat references the tag within the same vertex is allowed,
so long as the two do not appear on the exact same property field.Passing ParametersThe@filterdirective accepts two types of parameters: runtime parameters and tagged parameters.Runtime parametersare represented with a$prefix (e.g.$foo), and denote parameters
whose values will be known at runtime. The compiler will compile the GraphQL query leaving a
spot for the value to fill at runtime. After compilation, the user will have to supply values for
all runtime parameters, and their values will be inserted into the final query before it can be
executed against the database.Consider the following query:{Animal{name@output(out_name:"animal_name")color@filter(op_name:"=",value:["$animal_color"])}}It returns one row for everyAnimalvertex that has a color equal to$animal_color. Each row
contains the animal's name in a column namedanimal_name. The parameter$animal_coloris
a runtime parameter -- the user must pass in a value (e.g.{"animal_color": "blue"}) that
will be inserted into the query before querying the database.Tagged parametersare represented with a%prefix (e.g.%foo) and denote parameters
whose values are derived from a property field encountered elsewhere in the query.
If the user marks a property field with a@tagdirective and a suitable name,
that value becomes available to use as a tagged parameter in all subsequent@filterdirectives.Consider the following query:{Animal{name@tag(out_name:"parent_name")out_Animal_ParentOf{name@filter(op_name:"has_substring",value:["%parent_name"])@output(out_name:"child_name")}}}It returns the names of animals that contain their parent's name as a substring of their own.
The database captures the value of the parent animal's name as theparent_nametag, and this
value is then used as the%parent_nametagged parameter in the child animal's@filter.We considered andrejectedthe idea of allowing literal values (e.g.123)
as@filterparameters, for several reasons:The GraphQL type of the@filterdirective'svaluefield cannot reasonably encompass
all the different types of arguments that people might supply. Even counting scalar types only,
there's alreadyID, Int, Float, Boolean, String, Date, DateTime...-- way too many to include.Literal values would be used when the parameter's value is known to be fixed. We can just as
easily accomplish the same thing by using a runtime parameter with a fixed value. That approach
has the added benefit of potentially reducing the number of different queries that have to be
compiled: two queries with different literal values would have to be compiled twice, whereas
using two different sets of runtime arguments only requires the compilation of one query.We were concerned about the potential for accidental misuse of literal values. SQL systems have
supported stored procedures and parameterized queries for decades, and yet ad-hoc SQL query
construction via simple string interpolation is still a serious problem and is the source of
many SQL injection vulnerabilities. We felt that disallowing literal values in the query will
drastically reduce both the use and the risks of unsafe string interpolation,
at an acceptable cost.Constraints and RulesThe value provided forop_namemay only consist of upper or lower case letters
(A-Z,a-z), or underscores (_).Values provided in thevaluelist must start with either$(denoting a runtime parameter) or%(denoting a tagged parameter),
followed by exclusively upper or lower case letters (A-Z,a-z) or underscores (_).The@tagdirectives corresponding to any tagged parameters in a given@filterquery
must be applied to fields that appear either at the same vertex as the one with the@filter,
or strictly before the field with the@filterdirective."Can't compare apples and oranges" -- the GraphQL type of the parameters supplied to the@filtermust match the GraphQL types the compiler infers based on the field the@filteris applied to.If the@tagcorresponding to a tagged parameter originates from within a vertex field
marked@optional, the emitted code for the@filterchecks if the@optionalfield was
assigned a value. If no value was assigned to the@optionalfield, comparisons against the
tagged parameter from within that field returnTrue.For example, assuming%from_optionaloriginates from an@optionalscope, when no value is
assigned to the@optionalfield:using@filter(op_name: "=", value: ["%from_optional"])is equivalent to not
having the filter at all;using@filter(op_name: "between", value: ["$lower", "%from_optional"])is equivalent to@filter(op_name: ">=", value: ["$lower"]).Using a@tagand a@filterthat references the tag within the same vertex is allowed,
so long as the two do not appear on the exact same property field.@recurseApplied to a vertex field, specifies that the edge connecting that vertex field to the current
vertex should be visited repeatedly, up todepthtimes. The recursion always starts
atdepth = 0, i.e. the current vertex -- see the below sections for a more thorough explanation.Example UseSay the user wants to fetch the names of the children and grandchildren of eachAnimal.
That could be accomplished by running the following two queries and concatenating their results:{Animal{name@output(out_name:"ancestor")out_Animal_ParentOf{name@output(out_name:"descendant")}}}{Animal{name@output(out_name:"ancestor")out_Animal_ParentOf{out_Animal_ParentOf{name@output(out_name:"descendant")}}}}If the user then wanted to also add great-grandchildren to thedescendantsoutput, that would
require yet another query, and so on. Instead of concatenating the results of multiple queries,
the user can simply use the@recursedirective. The following query returns the child and
grandchild descendants:{Animal{name@output(out_name:"ancestor")out_Animal_ParentOf{out_Animal_ParentOf@recurse(depth:1){name@output(out_name:"descendant")}}}}Each row returned by this query contains the name of anAnimalin theancestorcolumn
and the name of its child or grandchild in thedescendantcolumn.
Theout_Animal_ParentOfvertex field marked@recurseis already enclosed within
anotherout_Animal_ParentOfvertex field, so the recursion starts at the
"child" level (theout_Animal_ParentOfnot marked with@recurse).
Therefore, thedescendantcolumn contains the names of anancestor's
children (fromdepth = 0of the recursion) and the names of its grandchildren (fromdepth = 1).Recursion using this directive is possible since the types of the enclosing scope and the recursion
scope work out: the@recursedirective is applied to a vertex field of typeAnimaland
its vertex field is enclosed within a scope of typeAnimal.
Additional cases where recursion is allowed are described in detail below.Thedescendantcolumn cannot have the name of theancestoranimal since the@recurseis already within oneout_Animal_ParentOfand not at the rootAnimalvertex field.
Similarly, it cannot have descendants that are more than two steps removed
(e.g., great-grandchildren), since thedepthparameter of@recurseis set to1.Now, let's see what happens when we eliminate the outerout_Animal_ParentOfvertex field
and simply have the@recurseapplied on theout_Animal_ParentOfin the root vertex field scope:{Animal{name@output(out_name:"ancestor")out_Animal_ParentOf@recurse(depth:1){name@output(out_name:"self_or_descendant")}}}In this case, when the recursion starts atdepth = 0, theAnimalwithin the recursion scope
will be the sameAnimalat the root vertex field, and therefore, in thedepth = 0step of
the recursion, the value of theself_or_descendantfield will be equal to the value of
theancestorfield.Constraints and Rules"The types must work out" -- when applied within a scope of typeA,
to a vertex field of typeB, at least one of the following must be true:Ais a GraphQL union;Bis a GraphQL interface, andAis a type that implements that interface;AandBare the same type.@recursecan only be applied to vertex fields other than the root vertex field of a query.Cannot be used within a scope marked@optionalor@fold.Thedepthparameter of the recursion must always have a value greater than or equal to 1.
Usingdepth = 1produces the current vertex and its neighboring vertices along the
specified edge.Type coercions and@filterdirectives within a scope marked@recursedo not limit the
recursion depth. Conceptually, recursion to the specified depth happens first,
and then type coercions and@filterdirectives eliminate some of the locations reached
by the recursion.As demonstrated by the examples above, the recursion always starts at depth 0,
so the recursion scope always includes the vertex at the scope that encloses
the vertex field marked@recurse.@output_sourceSee theCompleteness of returned resultssection
for a description of the directive and examples.Constraints and RulesMay exist at most once in any given GraphQL query.Can exist only on a vertex field, and only on the last vertex field used in the query.Cannot be used within a scope marked@optionalor@fold.Supported filtering operationsComparison operatorsSupported comparison operators:Equal to:=Not equal to:!=Greater than:>Less than:<Greater than or equal to:>=Less than or equal to:<=Example UseEqual to (=):{Species{name@filter(op_name:"=",value:["$species_name"])uuid@output(out_name:"species_uuid")}}This returns one row for everySpecieswhose name is equal to the value of the$species_nameparameter. Each row contains theuuidof theSpeciesin a column namedspecies_uuid.Greater than or equal to (>=):{
    Animal {
        name @output(out_name: "name")
        birthday @output(out_name: "birthday")
                 @filter(op_name: ">=", value: ["$point_in_time"])
    }
}This returns one row for everyAnimalvertex that was born after or on a$point_in_time.
Each row contains the animal's name and birthday in columns namednameandbirthday, respectively.Constraints and RulesAll comparison operators must be on a property field.name_or_aliasAllows you to filter on vertices which contain the exact string$wanted_name_or_aliasin theirnameoraliasfields.Example Use{Animal@filter(op_name:"name_or_alias",value:["$wanted_name_or_alias"]){name@output(out_name:"name")}}This returns one row for everyAnimalvertex whose name and/or alias is equal to$wanted_name_or_alias.
Each row contains the animal's name in a column namedname.The value provided for$wanted_name_or_aliasmust be the full name and/or alias of theAnimal.
Substrings will not be matched.Constraints and RulesMust be on a vertex field that hasnameandaliasproperties.betweenExample Use{Animal{name@output(out_name:"name")birthday@filter(op_name:"between",value:["$lower","$upper"])@output(out_name:"birthday")}}This returns:One row for everyAnimalvertex whose birthday is in between$lowerand$upperdates (inclusive).
Each row contains the animal's name in a column namedname.Constraints and RulesMust be on a property field.The lower and upper bounds represent an inclusive interval, which means that the output may
contain values that match them exactly.in_collectionExample Use{Animal{name@output(out_name:"animal_name")color@output(out_name:"color")@filter(op_name:"in_collection",value:["$colors"])}}This returns one row for everyAnimalvertex which has a color contained in a list of colors.
Each row contains theAnimal's name and color in columns namedanimal_nameandcolor, respectively.Constraints and RulesMust be on a property field that is not of list type.not_in_collectionExample Use{Animal{name@output(out_name:"animal_name")color@output(out_name:"color")@filter(op_name:"not_in_collection",value:["$colors"])}}This returns one row for everyAnimalvertex which has a color not contained in a list of colors.
Each row contains theAnimal's name and color in columns namedanimal_nameandcolor, respectively.Constraints and RulesMust be on a property field that is not of list type.has_substringExample Use{Animal{name@filter(op_name:"has_substring",value:["$substring"])@output(out_name:"animal_name")}}This returns one row for everyAnimalvertex whose name contains the value supplied
for the$substringparameter. Each row contains the matchingAnimal's name
in a column namedanimal_name.Constraints and RulesMust be on a property field of string type.containsExample Use{Animal{alias@filter(op_name:"contains",value:["$wanted"])name@output(out_name:"animal_name")}}This returns one row for everyAnimalvertex whose list of aliases contains the value supplied
for the$wantedparameter. Each row contains the matchingAnimal's name
in a column namedanimal_name.Constraints and RulesMust be on a property field of list type.not_containsExample Use{Animal{alias@filter(op_name:"not_contains",value:["$wanted"])name@output(out_name:"animal_name")}}This returns one row for everyAnimalvertex whose list of aliases does not contain the value supplied
for the$wantedparameter. Each row contains the matchingAnimal's name
in a column namedanimal_name.Constraints and RulesMust be on a property field of list type.intersectsExample Use{Animal{alias@filter(op_name:"intersects",value:["$wanted"])name@output(out_name:"animal_name")}}This returns one row for everyAnimalvertex whose list of aliases has a non-empty intersection
with the list of values supplied for the$wantedparameter.
Each row contains the matchingAnimal's name in a column namedanimal_name.Constraints and RulesMust be on a property field of list type.has_edge_degreeExample Use{Animal{name@output(out_name:"animal_name")out_Animal_ParentOf@filter(op_name:"has_edge_degree",value:["$child_count"])@optional{uuid}}}This returns one row for everyAnimalvertex that has exactly$child_countchildren
(i.e. where theout_Animal_ParentOfedge appears exactly$child_counttimes).
Each row contains the matchingAnimal's name, in a column namedanimal_name.Theuuidfield within theout_Animal_ParentOfvertex field is added simply to satisfy
the GraphQL syntax rule that requires at least one field to exist within any{}.
Since this field is not marked with any directive, it has no effect on the query.N.B.:Please note the@optionaldirective on the vertex field being filtered above.
If in your use case you expect to set$child_countto 0, you must also mark that
vertex field@optional. Recall that absence of@optionalimplies that at least one
such edge must exist. If thehas_edge_degreefilter is used with a parameter set to 0,
that requires the edge to not exist. Therefore, if the@optionalis not present in this situation,
no valid result sets can be produced, and the resulting query will return no results.Constraints and RulesMust be on a vertex field that is not the root vertex of the query.Tagged values are not supported as parameters for this filter.If the runtime parameter for this operator can be0, it isstrongly recommendedto also apply@optionalto the vertex field being filtered (see N.B. above for details).Type coercionsType coercions are operations that create a new scope whose type is different than the type of the
enclosing scope of the coercion -- they coerce the enclosing scope into a different type.
Type coercions are represented with GraphQL inline fragments.Example Use{Species{name@output(out_name:"species_name")out_Species_Eats{...onFood{name@output(out_name:"food_name")}}}}Here, theout_Species_Eatsvertex field is of theUnion__Food__FoodOrSpecies__Speciesunion type. To proceed
with the query, the user must choose which of the types in theUnion__Food__FoodOrSpecies__Speciesunion to use.
In this example,... on Foodindicates that theFoodtype was chosen, and any vertices
at that scope that are not of typeFoodare filtered out and discarded.{Species{name@output(out_name:"species_name")out_Entity_Related{...onSpecies{name@output(out_name:"food_name")}}}}In this query, theout_Entity_Relatedis ofEntitytype. However, the query only wants to
return results where the related entity is aSpecies, which... on Speciesensures is the case.Meta fields__typenameThe compiler supports the standard GraphQL meta field__typename, which returns the runtime type
of the scope where the field is found. Assuming the GraphQL schema matches the database's schema,
the runtime type will always be a subtype of (or exactly equal to) the static type of the scope
determined by the GraphQL type system. Below, we provide an example query in which
the runtime type is a subtype of the static type, but is not equal to it.The__typenamefield is treated as a property field of typeString, and supports
all directives that can be applied to any other property field.Example Use{Entity{__typename@output(out_name:"entity_type")name@output(out_name:"entity_name")}}This query returns one row for eachEntityvertex. The scope in which__typenameappears is
of static typeEntity. However,Animalis a type ofEntity, as areSpecies,Food,
and others. Vertices of all subtypes ofEntitywill therefore be returned, and theentity_typecolumn that outputs the__typenamefield will show their runtime type:Animal,Species,Food, etc._x_countThe_x_countmeta field is a non-standard meta field defined by the GraphQL compiler that makes it
possible to interact with thenumberof elements in a scope marked@fold. By applying directives
like@outputand@filterto this meta field, queries can output the number of elements captured
in the@foldand filter down results to select only those with the desired fold sizes.We use the_x_prefix to signify that this is an extension meta field introduced by the compiler,
and not part of the canonical set of GraphQL meta fields defined by the GraphQL specification.
We do not use the GraphQL standard double-underscore (__) prefix for meta fields,
since all names with that prefix areexplicitly reserved and prohibited from being usedin directives, fields, or any other artifacts.Adding the_x_countmeta field to your schemaSince the_x_countmeta field is not currently part of the GraphQL standard, it has to be
explicitly added to all interfaces and types in your schema. There are two ways to do this.The preferred way to do this is to use theEXTENDED_META_FIELD_DEFINITIONSconstant as
a starting point for building your interfaces' and types' field descriptions:from graphql import GraphQLInt, GraphQLField, GraphQLObjectType, GraphQLString
from graphql_compiler import EXTENDED_META_FIELD_DEFINITIONS

fields = EXTENDED_META_FIELD_DEFINITIONS.copy()
fields.update({
    'foo': GraphQLField(GraphQLString),
    'bar': GraphQLField(GraphQLInt),
    # etc.
})
graphql_type = GraphQLObjectType('MyType', fields)
# etc.If you are not able to programmatically define the schema, and instead simply have a pre-made
GraphQL schema object that you are able to mutate, the alternative approach is via theinsert_meta_fields_into_existing_schema()helper function defined by the compiler:# assuming that existing_schema is your GraphQL schema object
insert_meta_fields_into_existing_schema(existing_schema)
# existing_schema was mutated in-place and all custom meta-fields were addedExample Use{Animal{name@output(out_name:"name")out_Animal_ParentOf@fold{_x_count@output(out_name:"number_of_children")name@output(out_name:"child_names")}}}This query returns one row for eachAnimalvertex. Each row contains its name, and the number and names
of its children. While the output type of thechild_namesselection is a list of strings,
the output type of thenumber_of_childrenselection is an integer.{Animal{name@output(out_name:"name")out_Animal_ParentOf@fold{_x_count@filter(op_name:">=",value:["$min_children"])@output(out_name:"number_of_children")name@filter(op_name:"has_substring",value:["$substr"])@output(out_name:"child_names")}}}Here, we've modified the above query to add two more filtering constraints to the returned rows:childAnimalvertices must contain the value of$substras a substring in their name, andAnimalvertices must have at least$min_childrenchildren that satisfy the above filter.Importantly, any filtering on_x_countis appliedafterany other filters and type coercions
that are present in the@foldin question. This order of operations matters a lot: selectingAnimalvertices with 3+ children, then filtering the children based on their names is not the same
as filtering the children first, and then selectingAnimalvertices that have 3+ children that
matched the earlier filter.Constraints and RulesThe_x_countfield is only allowed to appear within a vertex field marked@fold.Filtering on_x_countis always appliedafterany other filters and type coercions present
in that@fold.Filtering or outputting the value of the_x_countfield must always be done at the innermost
scope of the@fold. It is invalid to expand vertex fields within a@foldafter filtering
or outputting the value of the_x_countmeta field.How is filtering on_x_countdifferent from@filterwithhas_edge_degree?Thehas_edge_degreefilter allows filtering based on the number of edges of a particular type.
There are situations in which filtering withhas_edge_degreeand filtering using=on_x_countproduce equivalent queries. Here is one such pair of queries:{Species{name@output(out_name:"name")in_Animal_OfSpecies@filter(op_name:"has_edge_degree",value:["$num_animals"]){uuid}}}and{Species{name@output(out_name:"name")in_Animal_OfSpecies@fold{_x_count@filter(op_name:"=",value:["$num_animals"])}}}In both of these queries, we ask for the names of theSpeciesvertices that have precisely$num_animalsmembers. However, we have expressed this question in two different ways: once
as a property of theSpeciesvertex ("the degree of thein_Animal_OfSpeciesis$num_animals"),
and once as a property of the list ofAnimalvertices produced by the@fold("the number of
elements in the@foldis$num_animals").When we add additional filtering within theAnimalvertices of thein_Animal_OfSpeciesvertex
field, this distinction becomes very important. Compare the following two queries:{Species{name@output(out_name:"name")in_Animal_OfSpecies@filter(op_name:"has_edge_degree",value:["$num_animals"]){out_Animal_LivesIn{name@filter(op_name:"=",value:["$location"])}}}}versus{Species{name@output(out_name:"name")in_Animal_OfSpecies@fold{out_Animal_LivesIn{_x_count@filter(op_name:"=",value:["$num_animals"])name@filter(op_name:"=",value:["$location"])}}}}In the first, for the purposes of thehas_edge_degreefiltering, the location where the animals
live is irrelevant: thehas_edge_degreeonly makes sure that theSpeciesvertex has the
correct number of edges of typein_Animal_OfSpecies, and that's it. In contrast, the second query
ensures that onlySpeciesvertices that have$num_animalsanimals that live in the selected
location are returned -- the location matters since the@filteron the_x_countfield applies
to the number of elements in the@foldscope.The GraphQL schemaThis section assumes that the reader is familiar with the way schemas work in thereference implementation of GraphQL.The GraphQL schema used with the compiler must contain the custom directives and customDateandDateTimescalar types defined by the compiler:directive @recurse(depth: Int!) on FIELD

directive @filter(value: [String!]!, op_name: String!) on FIELD | INLINE_FRAGMENT

directive @tag(tag_name: String!) on FIELD

directive @output(out_name: String!) on FIELD

directive @output_source on FIELD

directive @optional on FIELD

directive @fold on FIELD

scalar DateTime

scalar DateIf constructing the schema programmatically, one can simply import the the Python object
representations of the custom directives and the custom types:from graphql_compiler import DIRECTIVES  # the list of custom directives
from graphql_compiler import GraphQLDate, GraphQLDateTime  # the custom typesSince the GraphQL and OrientDB type systems have different rules, there is no one-size-fits-all
solution to writing the GraphQL schema for a given database schema.
However, the following rules of thumb are useful to keep in mind:Generally, represent OrientDB abstract classes as GraphQL interfaces. In GraphQL's type system,
GraphQL interfaces cannot inherit from other GraphQL interfaces.Generally, represent OrientDB non-abstract classes as GraphQL types,
listing the GraphQL interfaces that they implement. In GraphQL's type system, GraphQL types
cannot inherit from other GraphQL types.Inheritance relationships between two OrientDB non-abstract classes,
or between two OrientDB abstract classes, introduce some difficulties in GraphQL.
When modelling your data in OrientDB, it's best to avoid such inheritance if possible.If it is impossible to avoid having two non-abstract OrientDB classesAandBsuch thatBinherits fromA, you have two options:You may choose to represent theAOrientDB class as a GraphQL interface,
which the GraphQL type corresponding toBcan implement.
In this case, the GraphQL schema preserves the inheritance relationship
betweenAandB, but sacrifices the representation of any inheritance relationshipsAmay have with any OrientDB superclasses.You may choose to represent bothAandBas GraphQL types. The tradeoff in this case is
exactly the opposite from the previous case: the GraphQL schema
sacrifices the inheritance relationship betweenAandB, but preserves the
inheritance relationships ofAwith its superclasses.
In this case, it is recommended to create a GraphQL union typeA | B,
and to use that GraphQL union type for any vertex fields that
in OrientDB would be of typeA.If it is impossible to avoid having two abstract OrientDB classesAandBsuch thatBinherits fromA, you similarly have two options:You may choose to representBas a GraphQL type that can implement the GraphQL interface
corresponding toA. This makes the GraphQL schema preserve the inheritance relationship
betweenAandB, but sacrifices the ability for other GraphQL types to inherit fromB.You may choose to represent bothAandBas GraphQL interfaces, sacrificing the schema's
representation of the inheritance betweenAandB, but allowing GraphQL types
to inherit from bothAandB. If necessary, you can then create a GraphQL
union typeA | Band use it for any vertex fields that in OrientDB would be of typeA.It is legal to fully omit classes and fields that are not representable in GraphQL. The compiler
currently does not support OrientDB'sEmbeddedMaptype nor embedded non-primitive typed fields,
so such fields can simply be omitted in the GraphQL representation of their classes.
Alternatively, the entire OrientDB class and all edges that may point to it may be omitted
entirely from the GraphQL schema.Execution modelSince the GraphQL compiler can target multiple different query languages, each with its own
behaviors and limitations, the execution model must also be defined as a function of the
compilation target language. While we strive to minimize the differences between
compilation targets, some differences are unavoidable.The compiler abides by the following principles:When the database is queried with a compiled query string, its response must always be in the
form of a list of results.The precise format of each such result is defined by each compilation target separately.gremlin,MATCHandSQLreturn data in a tabular format, where each result is
a row of the table, and fields marked for output are columns.However, future compilation targets may have a different format. For example, each result
may appear in the nested tree format used by the standard GraphQL specification.Each such result must satisfy all directives and types in its corresponding GraphQL query.The returned list of results isnotguaranteed to be complete!In other words, there may have been additional result sets that satisfy all directives and
types in the corresponding GraphQL query, but were not returned by the database.However, compilation target implementations are encouraged to return complete results
if at all practical. TheMATCHcompilation target is guaranteed to produce complete results.Completeness of returned resultsTo explain the completeness of returned results in more detail, assume the database contains
the following example graph:a  ---->_ x
|____   /|
    _|_/
   / |____
  /      \/
b  ----> yLeta, b, x, ybe the values of thenameproperty field of four vertices.
Let the vertices namedaandbbe of typeS, and letxandybe of typeT.
Let vertexabe connected to bothxandyvia directed edges of typeE.
Similarly, let vertexbalso be connected to bothxandyvia directed edges of typeE.Consider the GraphQL query:{
    S {
        name @output(out_name: "s_name")
        out_E {
            name @output(out_name: "t_name")
        }
    }
}Between the data in the database and the query's structure, it is clear that combining any ofaorbwith any ofxorywould produce a valid result. Therefore,
the complete result list, shown here in JSON format, would be:[
    {"s_name": "a", "t_name": "x"},
    {"s_name": "a", "t_name": "y"},
    {"s_name": "b", "t_name": "x"},
    {"s_name": "b", "t_name": "y"},
]This is precisely what theMATCHcompilation target is guaranteed to produce.
The remainder of this section is only applicable to thegremlincompilation target. If usingMATCH, all of the queries listed in the remainder of this section will produce the same, complete
result list.Since thegremlincompilation target does not guarantee a complete result list,
querying the database using a query string generated by thegremlincompilation target
will produce only a partial result list resembling the following:[
    {"s_name": "a", "t_name": "x"},
    {"s_name": "b", "t_name": "x"},
]Due to limitations in the underlying query language,gremlinwill by default produce at most one
result for each of the starting locations in the query. The above GraphQL query started at
the typeS, so eachs_namein the returned result list is therefore distinct. Furthermore,
there is no guarantee (and no way to know ahead of time) whetherxorywill be returned as
thet_namevalue in each result, as they are both valid results.Users may apply the@output_sourcedirective on the last scope of the query
to alter this behavior:{S{name@output(out_name:"s_name")out_E@output_source{name@output(out_name:"t_name")}}}Rather than producing at most one result for eachS, the query will now produce
at most one result for each distinct value that can be found atout_E, where the directive
is applied:[{"s_name":"a","t_name":"x"},{"s_name":"a","t_name":"y"},]Conceptually, applying the@output_sourcedirective makes it as if the query were written in
the opposite order:{T{name@output(out_name:"t_name")in_E{name@output(out_name:"s_name")}}}SQLThe following table outlines GraphQL compiler features, and their support (if any) by various
relational database flavors:Feature/DialectRequired Edges@filter@output@recurse@fold@optional@output_sourcePostgreSQLNoLimited,intersects,has_edge_degree, andname_or_aliasfilter unsupportedLimited,__typenameoutput metafield unsupportedNoNoNoNoSQLiteNoLimited,intersects,has_edge_degree, andname_or_aliasfilter unsupportedLimited,__typenameoutput metafield unsupportedNoNoNoNoMicrosoft SQL ServerNoLimited,intersects,has_edge_degree, andname_or_aliasfilter unsupportedLimited,__typenameoutput metafield unsupportedNoNoNoNoMySQLNoLimited,intersects,has_edge_degree, andname_or_aliasfilter unsupportedLimited,__typenameoutput metafield unsupportedNoNoNoNoMariaDBNoLimited,intersects,has_edge_degree, andname_or_aliasfilter unsupportedLimited,__typenameoutput metafield unsupportedNoNoNoNoConfiguring SQLAlchemyRelational databases are supported by compiling to SQLAlchemy core as an intermediate
language, and then relying on SQLAlchemy's compilation of the dialect specific SQL string to query
the target database.For the SQL backend, GraphQL types are assumed to have a SQL table of the same name, and with the
same properties. For example, a schema typetype Animal {
    name: String
}is expected to correspond to a SQLAlchemy table object of the same name, case insensitive. For this
schema type this could look like:fromsqlalchemyimportMetaData,Table,Column,String# table for GraphQL type Animalmetadata=MetaData()animal_table=Table('animal',# name of table matches type name from schemametadata,Column('name',String(length=12)),# Animal.name GraphQL field has corresponding 'name' column)If a table of the schema type name does not exist, an exception will be raised at compile time. SeeConfiguring the SQL Database to Match the GraphQL Schemafor a possible option to resolve such naming discrepancies.End-To-End SQL ExampleAn end-to-end example including relevant GraphQL schema and SQLAlchemy engine preparation follows.This is intended to show the setup steps for the SQL backend of the GraphQL compiler, and
does not represent best practices for configuring and running SQLAlchemy in a production system.fromgraphqlimportparsefromgraphql.utils.build_ast_schemaimportbuild_ast_schemafromsqlalchemyimportMetaData,Table,Column,String,create_enginefromgraphql_compiler.compiler.ir_lowering_sql.metadataimportSqlMetadatafromgraphql_compilerimportgraphql_to_sql# Step 1: Configure a GraphQL schema (note that this can also be done programmatically)schema_text='''schema {query: RootSchemaQuery}# IMPORTANT NOTE: all compiler directives are expected here, but not shown to keep the example briefdirective @filter(op_name: String!, value: [String!]!) on FIELD | INLINE_FRAGMENT# < more directives here, see the GraphQL schema section of this README for more details. >directive @output(out_name: String!) on FIELDtype Animal {name: String}'''schema=build_ast_schema(parse(schema_text))# Step 2: For all GraphQL types, bind all corresponding SQLAlchemy Tables to a single SQLAlchemy# metadata instance, using the expected naming detailed above.# See https://docs.sqlalchemy.org/en/latest/core/metadata.html for more details on this step.metadata=MetaData()animal_table=Table('animal',# name of table matches type name from schemametadata,# Animal.name schema field has corresponding 'name' column in animal tableColumn('name',String(length=12)),)# Step 3: Prepare a SQLAlchemy engine to query the target relational database.# See https://docs.sqlalchemy.org/en/latest/core/engines.html for more detail on this step.engine=create_engine('<connection string>')# Step 4: Wrap the SQLAlchemy metadata and dialect as a SqlMetadata GraphQL compiler objectsql_metadata=SqlMetadata(engine.dialect,metadata)# Step 5: Prepare and compile a GraphQL query against the schemagraphql_query='''{Animal {name @output(out_name: "animal_name")@filter(op_name: "in_collection", value: ["$names"])}}'''parameters={'names':['animal name 1','animal name 2'],}compilation_result=graphql_to_sql(schema,graphql_query,parameters,sql_metadata)# Step 6: Execute compiled query against a SQLAlchemy engine/connection.# See https://docs.sqlalchemy.org/en/latest/core/connections.html for more details.query=compilation_result.queryquery_results=[dict(result_proxy)forresult_proxyinengine.execute(query)]Configuring the SQL Database to Match the GraphQL SchemaFor simplicity, the SQL backend expects an exact match between SQLAlchemy Tables and GraphQL types,
and between SQLAlchemy Columns and GraphQL fields. What if the table name or column name in the
database doesn't conform to these rules? Eventually the plan is to make this aspect of the
SQL backend more configurable. In the near-term, a possible way to address this is by using
SQL views.For example, suppose there is a table in the database calledanimal_tableand it has a column
calledanimal_name. If the desired schema has typetype Animal {
    name: String
}Then this could be exposed via a view like:CREATEVIEWanimalASSELECTanimal_nameASnameFROManimal_tableAt this point, theanimalview can be used in the SQLAlchemy Table for the purposes of compiling.MiscellaneousPretty-Printing GraphQL QueriesTo pretty-print GraphQL queries, use the included pretty-printer:python -m graphql_compiler.tool <input_file.graphql >output_file.graphqlIt's modeled after Python'sjson.tool, reading from stdin and writing to stdout.Expanding@optionalvertex fieldsIncluding an optional statement in GraphQL has no performance issues on its own,
but if you continue expanding vertex fields within an optional scope,
there may be significant performance implications.Going forward, we will refer to two different kinds of@optionaldirectives.A"simple"optional is a vertex with an@optionaldirective that does not expand
any vertex fields within it.
For example:{Animal{name@output(out_name:"name")in_Animal_ParentOf@optional{name@output(out_name:"parent_name")}}}OrientDBMATCHcurrently allows the last step in any traversal to be optional.
Therefore, the equivalentMATCHtraversal for the aboveGraphQLis as follows:SELECT
    Animal___1.name as `name`,
    Animal__in_Animal_ParentOf___1.name as `parent_name`
FROM (
    MATCH {
        class: Animal,
        as: Animal___1
    }.in('Animal_ParentOf') {
        as: Animal__in_Animal_ParentOf___1
    }
    RETURN $matches
)A"compound"optional is a vertex with an@optionaldirective which does expand
vertex fields within it.
For example:{Animal{name@output(out_name:"name")in_Animal_ParentOf@optional{name@output(out_name:"parent_name")in_Animal_ParentOf{name@output(out_name:"grandparent_name")}}}}Currently, this cannot represented by a simpleMATCHquery.
Specifically, the following isNOTa validMATCHstatement,
because the optional traversal follows another edge:-- NOT A VALID QUERY
SELECT
    Animal___1.name as `name`,
    Animal__in_Animal_ParentOf___1.name as `parent_name`
FROM (
    MATCH {
        class: Animal,
        as: Animal___1
    }.in('Animal_ParentOf') {
        optional: true,
        as: Animal__in_Animal_ParentOf___1
    }.in('Animal_ParentOf') {
        as: Animal__in_Animal_ParentOf__in_Animal_ParentOf___1
    }
    RETURN $matches
)Instead, we represent acompoundoptional by taking an union (UNIONALL) of two distinctMATCHqueries. For instance, theGraphQLquery above can be represented as follows:SELECT EXPAND($final_match)
LET
    $match1 = (
        SELECT
            Animal___1.name AS `name`
        FROM (
            MATCH {
                class: Animal,
                as: Animal___1,
                where: (
                    (in_Animal_ParentOf IS null)
                    OR
                    (in_Animal_ParentOf.size() = 0)
                ),
            }
        )
    ),
    $match2 = (
        SELECT
            Animal___1.name AS `name`,
            Animal__in_Animal_ParentOf___1.name AS `parent_name`
        FROM (
            MATCH {
                class: Animal,
                as: Animal___1
            }.in('Animal_ParentOf') {
                as: Animal__in_Animal_ParentOf___1
            }.in('Animal_ParentOf') {
                as: Animal__in_Animal_ParentOf__in_Animal_ParentOf___1
            }
        )
    ),
    $final_match = UNIONALL($match1, $match2)In the first case where the optional edge is not followed,
we have to explicitly filter out all vertices where the edgecould have been followed.
This is to eliminate duplicates between the twoMATCHselections.The previous example is notexactlyhow we implementcompoundoptionals
(we also haveSELECTstatements within$match1and$match2),
but it illustrates the the general idea.Performance PenaltyIf we have manycompoundoptionals in the givenGraphQL,
the above procedure results in the union of a large number ofMATCHqueries.
Specifically, forncompound optionals, we generate 2ndifferentMATCHqueries.
For each of the 2nsubsetsSof thenoptional edges:We remove the@optionalrestriction for each traversal inS.For each traversetin the complement ofS, we entirely discardtalong with all the vertices and directives within it, and we add a filter
on the previous traverse to ensure that the edge corresponding totdoes not exist.Therefore, we get a performance penalty that grows exponentially
with the number ofcompoundoptional edges.
This is important to keep in mind when writing queries with many optional directives.If some of thosecompoundoptionals contain@optionalvertex fields of their own,
the performance penalty grows since we have to account for all possible subsets of@optionalstatements that can be satisfied simultaneously.Optionaltype_equivalence_hintsparameterThis compilation parameter is a workaround for the limitations of the GraphQL and Gremlin
type systems:GraphQL does not allowtypeto inherit from anothertype, only to implement aninterface.Gremlin does not have first-class support for inheritance at all.Assume the following GraphQL schema:typeAnimal{name:String}typeCat{name:String}typeDog{name:String}unionAnimalCatDog=Animal|Cat|DogtypeFoo{adjacent_animal:AnimalCatDog}An appropriatetype_equivalence_hintsvalue here would be{ Animal: AnimalCatDog }.
This lets the compiler know that theAnimalCatDogunion type is implicitly equivalent to
theAnimaltype, as there are no other types that inherit fromAnimalin the database schema.
This allows the compiler to perform accurate type coercions in Gremlin, as well as optimize away
type coercions across edges of union type if the coercion is coercing to the
union's equivalent type.Settingtype_equivalence_hints = { Animal: AnimalCatDog }during compilation
would enable the use of a@foldon theadjacent_animalvertex field ofFoo:{Foo{adjacent_animal@fold{...onAnimal{name@output(out_name:"name")}}}}SchemaGraphWhen building a GraphQL schema from the database metadata, we first build aSchemaGraphfrom
the metadata and then, from theSchemaGraph, build the GraphQL schema. TheSchemaGraphis also
a representation of the underlying database schema, but it has three main advantages that make it a
more powerful schema introspection tool:It's able to store and expose a schema's index information. The interface for accessing index
information is provisional though and might change in the near future.Its classes are allowed to inherit from non-abstract classes.It exposes many utility functions, such asget_subclass_set, that make it easier to explore
the schema.See below for a mock example of how to build and use theSchemaGraph:fromgraphql_compiler.schema_generation.orientdb.schema_graph_builderimport(get_orientdb_schema_graph)fromgraphql_compiler.schema_generation.orientdb.utilsimport(ORIENTDB_INDEX_RECORDS_QUERY,ORIENTDB_SCHEMA_RECORDS_QUERY)# Get schema metadata from hypothetical Animals database.client=your_function_that_returns_an_orientdb_client()schema_records=client.command(ORIENTDB_SCHEMA_RECORDS_QUERY)schema_data=[record.oRecordDataforrecordinschema_records]# Get index data.index_records=client.command(ORIENTDB_INDEX_RECORDS_QUERY)index_query_data=[record.oRecordDataforrecordinindex_records]# Build SchemaGraph.schema_graph=get_orientdb_schema_graph(schema_data,index_query_data)# Get all the subclasses of a class.print(schema_graph.get_subclass_set('Animal'))# {'Animal', 'Dog'}# Get all the outgoing edge classes of a vertex class.print(schema_graph.get_vertex_schema_element_or_raise('Animal').out_connections)# {'Animal_Eats', 'Animal_FedAt', 'Animal_LivesIn'}# Get the vertex classes allowed as the destination vertex of an edge class.print(schema_graph.get_edge_schema_element_or_raise('Animal_Eats').out_connections)# {'Fruit', 'Food'}# Get the superclass of all classes allowed as the destination vertex of an edge class.print(schema_graph.get_edge_schema_element_or_raise('Animal_Eats').base_out_connection)# Food# Get the unique indexes defined on a class.print(schema_graph.get_unique_indexes_for_class('Animal'))# [IndexDefinition(name='uuid', 'base_classname'='Animal', fields={'uuid'}, unique=True, ordered=False, ignore_nulls=False)]In the future, we plan to addSchemaGraphgeneration from SQLAlchemy metadata. We also plan to
add a mechanism where one can query aSchemaGraphusing GraphQL queries.Cypher query parametersRedisGraphdoesn't support query parameters, so we perform manual parameter interpolation in thegraphql_to_redisgraph_cypherfunction. However, for Neo4j, we can use Neo4j's client to do
parameter interpolation on its own so that we don't reinvent the wheel.The functioninsert_arguments_into_querydoes so based on the query language, which isn't
fine-grained enough here-- for Cypher backends, we only want to insert parameters if the backend
is RedisGraph, but not if it's Neo4j.Instead, the correct approach for Neo4j Cypher is as follows, given a Neo4j Python client calledneo4j_client:compilation_result=compile_graphql_to_cypher(schema,graphql_query,type_equivalence_hints=type_equivalence_hints)withneo4j_client.driver.session()assession:result=session.run(compilation_result.query,parameters)Amending Parsed Custom Scalar TypesInformation about the description, serialization and parsing of custom scalar type
objects is lost when a GraphQL schema is parsed from a string. This causes issues when
working with custom scalar type objects. In order to avoid these issues, one can use the code
snippet below to amend the definitions of the custom scalar types used by the compiler.fromgraphql_compiler.schemaimportCUSTOM_SCALAR_TYPESfromgraphql_compiler.schema_generation.utilsimportamend_custom_scalar_typesamend_custom_scalar_types(your_schema,CUSTOM_SCALAR_TYPES)FAQQ: Do you really use GraphQL, or do you just use GraphQL-like syntax?A: We really use GraphQL. Any query that the compiler will accept is entirely valid GraphQL,
and we actually use the Python port of the GraphQL core library for parsing and type checking.
However, since the database queries produced by compiling GraphQL are subject to the limitations
of the database system they run on, our execution model is somewhat different compared to
the one described in the standard GraphQL specification. See theExecution modelsection for more details.Q: Does this project come with a GraphQL server implementation?A: No -- there are many existing frameworks for running a web server. We simply built a tool
that takes GraphQL query strings (and their parameters) and returns a query string you can
use with your database. The compiler does not execute the query string against the database,
nor does it deserialize the results. Therefore, it is agnostic to the choice of
server framework and database client library used.Q: Do you plan to support other databases / more GraphQL features in the future?A: We'd love to, and we could really use your help! Please consider contributing to this project
by opening issues, opening pull requests, or participating in discussions.Q: I think I found a bug, what do I do?A: Please check if an issue has already been created for the bug, and open a new one if not.
Make sure to describe the bug in as much detail as possible, including any stack traces or
error messages you may have seen, which database you're using, and what query you compiled.Q: I think I found a security vulnerability, what do I do?A: Please reach out to us atgraphql-compiler-maintainer@kensho.comso we can triage the issue and take appropriate action.LicenseLicensed under the Apache 2.0 License. Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.Copyright 2017-present Kensho Technologies, LLC. The present date is determined by the timestamp
of the most recent commit in the repository.