chkimes / graphql-net Goto Github PK
View Code? Open in Web Editor NEWConvert GraphQL to IQueryable
License: MIT License
Convert GraphQL to IQueryable
License: MIT License
Hi guys,
I've run an example that based on EF and it works fine.
But, could you clarify me some question according to GraphQL:
according to EntityFrameworkExecutionTests.cs I found that you're using EFcontext (based on DbContext - which includes populating objects with data from a database, change tracking, and persisting data to the database):
var schema = GraphQL.CreateDefaultSchema(() => new EfContext());
It's mean, that we will have up-to-date data (let's assume that our EFContext is configured and working with MSSQLLocalDB).
It would be great if you clarify me (or just send me an example) how it works without EF (in case when we have to retrieve/update data from database like mysql or mssql)
Looking forward to your reply.
Thx, Dima.
Is there a way to escape single and double quotes inside strings?
The parser is throwing an error for strings with ' and "
I saw there is a treatment in the parser for especial chars but i don't read F# very well for digging into the issue.
Thanks
Chris
For GraphQL to be realistically useful it needs to be possible to integrate security restrictions into the schema.
These commonly take 2.5 forms:
This is a big issue that requires a lot of thought, and it depends on #17 . Consider this a long term. It may be best to implement #16 first so we understand how to implement the security requirements around mutations first.
Currently a query with multiple return is of type IEnumerable<T>
(or some other incarnation like WhereSelectListIterator<T>
) since the query that is built for lists does not include the ToList
method. The returned type for lists should be List<T>
where T
is IDictionary<string, object>
.
I see that README states introspection is incompleted.
With a asp.net graphiql project, i can run the queries on http://graphql.org/learn/introspection/ however Kind values in result are not right.
Also, the default graphiql query fails with an exception.
Will this be hard to do for someone new to the codebase?
Populating nested entities currently only works for a single entity, but not for lists. This is the last big thing that needs to be implemented before I would call the library even barely functional. Plenty of other stuff like introspection still needs to be done to match the spec, but this is necessary to support anything more than the most basic queries.
#39 made me realize that right now, there's no way to pass information from a mutation to the query that is run after a mutation. This should change so that we can support mutations that add a new entity and then immediately return that entity, or maybe return different values based on whether the mutation executed successfully or failed.
Example syntax:
schema.AddMutation("addUser",
new {
name = string.Empty,
active = false
},
(db, args) =>
{
var newUser = new User
{
Name = args.name,
Active = args.active
};
db.Users.Add(newUser);
db.SaveChanges();
return newUser.Id; // return information from the mutation
},
(db, args, id) => db.Users.FirstOrDefault(u => u.Id == id)
// pass information to query as a third parameter
);
https://facebook.github.io/graphql/#sec-Type-System.Directives
Need to do this for #8, since you can't introspect what you don't support. Also need to add default Directives @skip
and @include
.
I'd submit a PR but I'm unclear exactly how it's used, myself :)
GraphQLSchema
defines a method AddScalar
, which has something to do with validating and translating scalar types.
It's used in the tests to add a DateTime
with year/month/day components, but I'm a bit unclear where these components come from, since the translation is only in one direction.
Without it, tests that query a model with a DateTime
field will fail; for eg, comment out the AddScalar
line in EntityFrameworkExecutionTests.CreateDefaultContext()
, or alternatively in EntityFrameworkExecutionTests.AddAllFields()
add a query on account (in this test Account
is defined via AddAllFields()
, but without the DateTime
scalar added to the schema):
gql.ExecuteQuery("{ account(id:1) { id } }");
I really like your idea of converting GraphQL to IQueryable and have been trying to do a sample using NHibernate.
Do you know any reason why this wouldn't work with NHibernate.
I get following exception in Executor.cs. I am on .Net 4.5 using NHibernate 4
NHibernate.Exceptions.GenericADOException was unhandled HResult=-2146232832 Message=Could not execute query[SQL: SQL not available] Source=NHibernate SqlString=SQL not available StackTrace: at NHibernate.Impl.SessionImpl.List(IQueryExpression queryExpression, QueryParameters queryParameters, IList results) at NHibernate.Impl.AbstractSessionImpl.List(IQueryExpression queryExpression, QueryParameters parameters) at NHibernate.Impl.AbstractQueryImpl2.List() at NHibernate.Linq.DefaultQueryProvider.ExecuteQuery(NhLinqExpression nhLinqExpression, IQuery query, NhLinqExpression nhQuery) at NHibernate.Linq.DefaultQueryProvider.Execute(Expression expression) at NHibernate.Linq.DefaultQueryProvider.Execute[TResult](Expression expression) at Remotion.Linq.QueryableBase
1.GetEnumerator()
at System.Collections.Generic.List1..ctor(IEnumerable
1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source) at GraphQL.Net.Executor
1.Execute(TContext context, GraphQLField field, ExecSelection1 query) in C:\scratch\graphql-net\GraphQL.Net\Executor.cs:line 56 at GraphQL.Net.Executor
1.Execute(GraphQLSchema1 schema, GraphQLField field, ExecSelection
1 query) in C:\scratch\graphql-net\GraphQL.Net\Executor.cs:line 17
at GraphQL.Net.GraphQL1.ExecuteQuery(String queryStr) in C:\scratch\graphql-net\GraphQL.Net\GraphQL.cs:line 44 at QueryApi.Program.Program.Main(String[] args) in C:\scratch\QueryApi\QueryApi\Program\Program.cs:line 21 at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() **InnerException: HResult=-2146233075 Message=Operation could destabilize the runtime. Source=Anonymously Hosted DynamicMethods Assembly StackTrace: at lambda_method(Closure , Object[] ) at NHibernate.Linq.ResultTransformer.TransformTuple(Object[] tuple, String[] aliases) at NHibernate.Hql.HolderInstantiator.Instantiate(Object[] row) at NHibernate.Loader.Hql.QueryLoader.GetResultList(IList results, IResultTransformer resultTransformer) at NHibernate.Loader.Loader.ListIgnoreQueryCache(ISessionImplementor session, QueryParameters queryParameters) at NHibernate.Loader.Loader.List(ISessionImplementor session, QueryParameters queryParameters, ISet
1 querySpaces, IType[] resultTypes)
at NHibernate.Loader.Hql.QueryLoader.List(ISessionImplementor session, QueryParameters queryParameters)
at NHibernate.Hql.Ast.ANTLR.QueryTranslatorImpl.List(ISessionImplementor session, QueryParameters queryParameters)
at NHibernate.Engine.Query.HQLQueryPlan.PerformList(QueryParameters queryParameters, ISessionImplementor session, IList results)
at NHibernate.Impl.SessionImpl.List(IQueryExpression queryExpression, QueryParameters queryParameters, IList results)
InnerException:`**
Man this is gonna be a pain.
All the expression building currently works with Entity Framework, but it explodes when working in-memory with a scary sounding System.Security.VerificationException
("Operation could destabilize the runtime"... eesh). This is due to the expression not including a cast from whatever type to object
when populating each field on GQLQueryObject20
.
However, if you try to include the cast to object
, then Entity Framework gets mad and won't populate the query. Database support is our primary goal with this project, so I'm not going to change it to support in-memory without being able to support both.
The best way that I can figure to do this is to create a dynamic type for each type in the schema. The fields for that type then won't require any cast when populating them, so EF will be happy and in-memory contexts won't explode. However, this means we should probably add a Finalize
method on the context so we have a single time to generate all of the dynamic types. This would have been inevitable anyway since we want to add schema validation at some point.
Once the library is "barely functional" we can release it on NuGet.
Just started playing around and hit a roadblock, all of our ID fields are Guids.
Any idea what it might take to add Guid support? Is this a 10 minute fix, or something that would take considerable effort?
I'm playing with graphql-net and I really like it.
One thing that I can't figure out is how args are deserialized.
It seems that camelCase/pascalCase conversion is not working for args.
Also, is it possible to get a plain JSON object (patch)?
patchUser(id: 1, patch: { displayName: "X" }) {
id,
displayName
}
A patch would be very useful as it includes information which fields should be updated. That's not available with strongly typed models.
Edit - removed pull request, updated issue here.
Hack MS SQL Test project again just to quickly show issue.
Attempting to support optimistic concurrency:
Midway down the page - Add an Optimistic Concurrency Property to the Department Entity
The tldr, label one of your fields with [Timestamp] with the type of byte[] and SQL will ensure optimistic concurrency.
In the hacked ms sql test project I added
[Timestamp]
public byte[] ServerTimestamp { get; set; }
To User, and tried to run LookupSingleEntity(), test throws an exception trying to InitializeUserSchema when I add the ServerTimestamp field.
Stack trace:
System.IndexOutOfRangeException : Index was outside the bounds of the array.
at GraphQL.Net.GraphQLField.NewInternal[TArgs](GraphQLSchema schema, String name, Func2 exprFunc, Type fieldCLRType, Delegate mutationFunc) in C:\Jobs\graphql-net\GraphQL.Net\GraphQLField.cs:line 72 at GraphQL.Net.GraphQLField.New[TContext,TArgs](GraphQLSchema schema, String name, Func
2 exprFunc, Type fieldCLRType, Action2 mutationFunc) in C:\Jobs\graphql-net\GraphQL.Net\GraphQLField.cs:line 65 at GraphQL.Net.GraphQLTypeBuilder
2.AddFieldInternal[TArgs,TField](String name, Func2 exprFunc, Action
2 mutation) in C:\Jobs\graphql-net\GraphQL.Net\GraphQLTypeBuilder.cs:line 39
at GraphQL.Net.GraphQLTypeBuilder2.AddField[TArgs,TField](String name, Func
2 exprFunc) in C:\Jobs\graphql-net\GraphQL.Net\GraphQLTypeBuilder.cs:line 31
at GraphQL.Net.GraphQLTypeBuilder2.AddField[TArgs,TField](String name, TArgs shape, Func
2 exprFunc) in C:\Jobs\graphql-net\GraphQL.Net\GraphQLTypeBuilder.cs:line 22
at GraphQL.Net.GraphQLTypeBuilder2.AddField[TField](String name, Expression
1 expr) in C:\Jobs\graphql-net\GraphQL.Net\GraphQLTypeBuilder.cs:line 87
at GraphQL.Net.GraphQLTypeBuilder2.AddField[TField](Expression
1 expr) in C:\Jobs\graphql-net\GraphQL.Net\GraphQLTypeBuilder.cs:line 72
at Tests.EF.EntityFrameworkMsSqlTests.InitializeUserSchema(GraphQLSchema`1 schema) in C:\Jobs\graphql-net\Tests.EF\EntityFrameworkMsSqlTests.cs:line 76
at Tests.EF.EntityFrameworkMsSqlTests.CreateDefaultContext() in C:\Jobs\graphql-net\Tests.EF\EntityFrameworkMsSqlTests.cs:line 61
at Tests.EF.EntityFrameworkMsSqlTests.LookupSingleEntity() in C:\Jobs\graphql-net\Tests.EF\EntityFrameworkMsSqlTests.cs:line 128
Hi guys,
By business request, I need host my WCF service (that uses GraphQL.net) under sharepoint (and it deploys to ISAPI folder).
Unfortunately, I get some strange error that related to graph.dll -
error CS0012: The type 'GraphQL<>' is defined in an assembly that is not referenced. You must add a reference to assembly 'GraphQL.Net, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
It occurs when I try to CreateDefaultContext. In case when it is hosted just under IIS it works fine.
Maybe u can give a hint why it happens.
Thx, D.
The latest nuget version of GraphQL.Net is 0.3.0 which was published on 2016-11-11.
I assumed the API to be quite up-to-date in this version, however, i noticed missing API methods when defining a mutation.
The GraphQL.Net.SchemaExtensions
in the DLL of the nuget version 0.3.0 looks like this:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace GraphQL.Net
{
public static class SchemaExtensions
{
public static GraphQLFieldBuilder<TContext, TEntity> AddField<TContext, TEntity>(this GraphQLSchema<TContext> context, string name, Expression<Func<TContext, TEntity>> queryableGetter);
public static GraphQLFieldBuilder<TContext, TEntity> AddField<TContext, TArgs, TEntity>(this GraphQLSchema<TContext> context, string name, TArgs argObj, Expression<Func<TContext, TArgs, TEntity>> queryableGetter);
public static GraphQLFieldBuilder<TContext, TEntity> AddListField<TContext, TEntity>(this GraphQLSchema<TContext> context, string name, Expression<Func<TContext, IEnumerable<TEntity>>> queryableGetter);
public static GraphQLFieldBuilder<TContext, TEntity> AddListField<TContext, TArgs, TEntity>(this GraphQLSchema<TContext> context, string name, TArgs argObj, Expression<Func<TContext, TArgs, IEnumerable<TEntity>>> queryableGetter);
public static GraphQLFieldBuilder<TContext, TEntity> AddListMutation<TContext, TArgs, TEntity>(this GraphQLSchema<TContext> context, string name, TArgs argObj, Expression<Func<TContext, TArgs, IEnumerable<TEntity>>> queryableGetter, Action<TContext, TArgs> mutation);
public static GraphQLFieldBuilder<TContext, TEntity> AddMutation<TContext, TArgs, TEntity>(this GraphQLSchema<TContext> context, string name, TArgs argObj, Expression<Func<TContext, TArgs, TEntity>> queryableGetter, Action<TContext, TArgs> mutation);
}
}
Looking at the current master branch i would expect the GraphQL.Net.SchemaExtensions
to look like this:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace GraphQL.Net
{
public static class SchemaExtensions
{
public static GraphQLFieldBuilder<TContext, TEntity> AddField<TContext, TEntity>(this GraphQLSchema<TContext> context, string name, Expression<Func<TContext, TEntity>> queryableGetter);
public static GraphQLFieldBuilder<TContext, TEntity> AddField<TContext, TArgs, TEntity>(this GraphQLSchema<TContext> context, string name, TArgs argObj, Expression<Func<TContext, TArgs, TEntity>> queryableGetter);
public static GraphQLFieldBuilder<TContext, TEntity> AddListField<TContext, TEntity>(this GraphQLSchema<TContext> context, string name, Expression<Func<TContext, IEnumerable<TEntity>>> queryableGetter);
public static GraphQLFieldBuilder<TContext, TEntity> AddListField<TContext, TArgs, TEntity>(this GraphQLSchema<TContext> context, string name, TArgs argObj, Expression<Func<TContext, TArgs, IEnumerable<TEntity>>> queryableGetter);
[Obsolete]
public static GraphQLFieldBuilder<TContext, TEntity> AddListMutation<TContext, TArgs, TEntity>(this GraphQLSchema<TContext> context, string name, TArgs argObj, Expression<Func<TContext, TArgs, IEnumerable<TEntity>>> queryableGetter, Action<TContext, TArgs> mutation);
public static GraphQLFieldBuilder<TContext, TEntity> AddListMutation<TContext, TArgs, TEntity>(this GraphQLSchema<TContext> context, string name, TArgs argObj, Action<TContext, TArgs> mutation, Expression<Func<TContext, TArgs, IEnumerable<TEntity>>> queryableGetter);
public static GraphQLFieldBuilder<TContext, TEntity> AddListMutation<TContext, TArgs, TEntity, TMutReturn>(this GraphQLSchema<TContext> context, string name, TArgs argObj, Func<TContext, TArgs, TMutReturn> mutation, Expression<Func<TContext, TArgs, TMutReturn, IEnumerable<TEntity>>> queryableGetter);
[Obsolete]
public static GraphQLFieldBuilder<TContext, TEntity> AddMutation<TContext, TArgs, TEntity>(this GraphQLSchema<TContext> context, string name, TArgs argObj, Expression<Func<TContext, TArgs, TEntity>> queryableGetter, Action<TContext, TArgs> mutation);
public static GraphQLFieldBuilder<TContext, TEntity> AddMutation<TContext, TArgs, TEntity>(this GraphQLSchema<TContext> context, string name, TArgs argObj, Action<TContext, TArgs> mutation, Expression<Func<TContext, TArgs, TEntity>> queryableGetter);
public static GraphQLFieldBuilder<TContext, TEntity> AddMutation<TContext, TArgs, TEntity, TMutReturn>(this GraphQLSchema<TContext> context, string name, TArgs argObj, Func<TContext, TArgs, TMutReturn> mutation, Expression<Func<TContext, TArgs, TMutReturn, TEntity>> queryableGetter);
}
}
Have those missing methods been added after the release of version 0.3.0 or is there another reason why they are not available?
Right now fields are defined like:
typeBuilder.AddField("friends", u => u.Friends)
Or, with arguments, like:
typeBuilder
.AddField("friends", new { friendId = 0 },
args => (db, u) => u.Friends.Where(f => f.Id == args.friendId))
Adding a FieldBuilder would allow throwing more options on the fields without having a crazy method signature. Ideally, either of the current approaches would work to start the builder. Then users could add on other fluent calls to define security constraints, complexity, or additional optional arguments. Here's a hypothetical example of how a pretend astronaut in the distant future might use this:
typeBuilder
.AddField("friends", u => u.Friends) // if you used args here, those args would be *required*
.WithComplexity(10, 100) // we estimate a typical user will have between 10 and 100 friends
.WithArguments(new { id = 0 }, // optional filtering argument
args => (db, friends) => friends.Where(f => f.Id == args.id), 1) // modifies complexity to 1
.WithArguments(new { name = "" }, // optional filtering argument
args => (db, friends) => friends.Where(f => f.Name == args.name))
.WithSecurityFilter((db, friends) => friends.Where(f => idk(db, friends)) // filter query
.WithSecurityAssertion(fs => fs.All(SecurityManager.HasPermissionToView)) // in-memory predicate
Currently GraphQL.cs always uses DefaultExecContext.Instance
.
This context doesn't resolve any variable names. It screams and dies when a query contains variables.
We should support passing variables to queries. The most common way is certainly by a JSON blob like { variableName1: value1, variableName2: value2 }
. Maybe XML is desirable too.
At minimum, we should support letting users pass their own IExecContext
in.
Ideally, we should have some helpful standard implementations people can use.
Some options:
IExecContext
implementations with those dependenciesIExecContext
implementation using our own JSON parser with FParsecDepends heavily on #17 .
It should be possible for me, when defining my schema, to plug my own numbers into the complexity estimate system.
For example, user.Commits
may have a typical complexity of anywhere from 0 to 10000, while issue.Comments
has a complexity more like 0 to 30.
At some point we'll need to support defining whether a field or argument is nullable or non-nullable. As for now, a decent starting point will just be forcing the nullability to match the CLR type's nullability. So nullable for classes, non-nullable for structs except Nullable<T>
.
This isn't a long-term solution, since multiple ORMs support non-nullable relationships (e.g. Entity Framework with int SomethingId
foreign keys). Also it would be nice to allow non-nullable arguments that map to a CLR class type.
The tests are looking super goofy right now because of the lack of type information. Here's an example:
var user = (IDictionary<string, object>)gql.ExecuteQuery(
"{ user(id:1) { id, account { id, name } } }")["user"];
Assert.AreEqual(user["id"], 1);
Assert.AreEqual(user.Keys.Count, 2);
Assert.IsTrue(user.ContainsKey("account"));
var account = (IDictionary<string, object>)user["account"];
Assert.AreEqual(account["id"], 1);
Assert.AreEqual(account["name"], "My Test Account");
Assert.AreEqual(account.Keys.Count, 2);
With a DeepEquals assert method, we can just pass JSON describing the expected output:
var results = gql.ExecuteQuery("{ user(id:1) { id, account { id, name } } }");
DeepEquals(results, @"{
user: {
id: 1,
account: {
id: 1,
name: "My Test Account"
}
}
}");
This should make it a lot easier to understand what each test is doing.
Submitting this as a question or discussion point, as much as an issue.
GraphQLTypeBuilder adds fields using ToCamelCase when the name isn't explicitly supplied. (The implementation just lowercases the first character).
What's the rationale behind this choice?
We're using code-first models reverse-engineered from a database where everything is uppercase; it means that to query a property we either have to explicitly add each one by name (user.AddField("USER_ID", u => u.USER_ID)
), or our queries look a bit odd: "uSER_ID
".
The whole ISchema<Info>
deal supports aliasing GraphQL primitive types (e.g. a type called DateTime that's an alias for string and has its own validation function), but this is currently not used.
When defining a schema, it should be possible to set up your own aliases like so:
schema.AddStringPrimitive("DateTime", DateTime.Parse))
It would then be OK to define fields with args like new { time = default(DateTime) }
, and the schema would go "oh, I support primitives of type System.DateTime - I know what GraphQL type they'll be represented by and how to go from that primitive to the CLR type".
The main change would be that in TypeHelpers.cs, all the argument-related calls would need to reference the schema to look up these aliases. I can do this one without interfering with the rest of the code too much.
AddLookup is required to differentiate from AddQuery if we want to have type constraints require that the expressions to be IQueryable<T>
, but that only gives us compile-time safety during the creation of the model (which should happen on every application startup, and therefore fail quickly if you do something incorrectly). In exchange for that safety, it makes the API kind of janky in that you have to specify a different method for single return vs. multiple return.
To clarify, I want to change this:
schema.AddQuery("users", db => db.Users);
schema.AddLookup("user", new { id = 0 },
(db, args) => db.Users.Where(u => u.Id == args.id));
into this:
schema.AddQuery("users", db => db.Users);
schema.AddQuery("user", new { id = 0 },
(db, args) => db.Users.FirstOrDefault(u => u.Id == args.id));
This will require some more in-depth expression inspection and transformation, but I think the end result of a cleaner API will be worth it.
https://github.com/rspeele/FParsec-Pipes
FParsec-Pipes is a neat new extension to FParsec that makes writing parsers easier to write and nicer to read.
I've moved on to testing using a model from an existing project. This is a code-first model, originally reverse-engineered from the database, if that's relevant. I was able to successfully run queries if I manually added some fields (ie calling GraphQLTypeBuilder.AddField()
), but given that we have a fair few models and they're quite large, this isn't ideal.
However, I've encountered issues running a basic example using AddAllFields()
:
var schema = GraphQL<EQContext>.CreateDefaultSchema(() => new EQContext());
schema.AddScalar(new { year = 0, month = 0, day = 0 }, ymd => new DateTime(ymd.year, ymd.month, ymd.day));
schema.AddType<EQ>().AddAllFields();
schema.AddField("eq", new { id = "" }, (db, args) => db.EQs.FirstOrDefault(e => e.EQ_ID == args.id));
schema.Complete();
Specifically, ExecuteQuery
is throwing GraphQL.Parser.ValidationException: Unsupported CLR type EQ
.
The EQ
model is mostly string
, decimal
, and nullable DateTime
fields, along with a bunch of references to other models. Any suggestions for what could be causing this, or how to debug it?
Things I've tried:
User
I added a Supervisor
/DirectReports
many-one pair of fields), to test if that was an issueDateTime
has been added via schema.AddScalar()
I'll move on now, but I'd like to add that this is a super promising project, and I'm happy to help out if I can.
https://facebook.github.io/graphql/#sec-Scalars
GraphQL defines 5 scalars by default, but we have tons of primitives in .NET that should probably be exposed. This issue is for discussion of how to handle .NET primitives in the schema.
If we're gonna be a big boy open source project then we'll need a license.
Hi chad,
i noticed that list fields in selections with type conditions/inline fragments are not working yet.
Here is an example:
public class Character
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Human : Character
{
public double Height { get; set; }
public ICollection<Vehicle> Vehicles { get; set; }
}
public class Stormtrooper : Human
{
public string Specialization { get; set; }
}
public class Droid : Character
{
public string PrimaryFunction { get; set; }
}
public class Vehicle
{
public int Id { get; set; }
public string Name { get; set; }
public int OwnerId { get; set; }
}
{
heros {
name
__typename
... on Human {
height
vehicles { name }
}
... on Stormtrooper {
specialization
}
... on Droid {
primaryFunction
}
}
}
...where we select the list field vehicles
on heros
of type Human
.
The result may look like this:
{
heros: [
{ name: 'Han Solo', __typename: 'Human', height: 5.6430448, vehicles: [ {name: 'Millennium falcon'}] },
{ name: 'FN-2187', __typename: 'Stormtrooper', height: 4.9, vehicles: [ {name: 'Speeder bike'}], specialization: 'Imperial Snowtrooper'},
{ name: 'R2-D2', __typename: 'Droid', primaryFunction: 'Astromech' }
]
}
When executing the query the following exception is thrown:
in \graphql-net\Tests\InMemoryExecutionTests.cs : line 36
....
in graphql-net\Tests\InMemoryExecutionTests.cs:line 36
Result Message: System.ArgumentException : Argument types do not match
Looking at the GraphQl.Net/Executor.cs line 296ff:
if (needsTypeCheck)
{
var typeCheck = Expression.TypeIs(baseBindingExpr, field.DefiningType.CLRType);
var nullResult = Expression.Constant(null, toMember.PropertyType);
// The expression type has to be nullable
if (replacedBase.Type.IsValueType)
{
replacedBase = Expression.Convert(replacedBase, typeof(Nullable<>).MakeGenericType(replacedBase.Type));
}
replacedBase = Expression.Condition(typeCheck, replacedBase, nullResult);
}
The toMember
is a list type, it has the type of the elements of the list (e.g. String
if the list is of type ICollection<String>
).
This can be fixed by using replacedBase.Type
instead:
if (needsTypeCheck)
{
// The expression type has to be nullable
if (replacedBase.Type.IsValueType)
{
replacedBase = Expression.Convert(replacedBase, typeof(Nullable<>).MakeGenericType(replacedBase.Type));
}
var typeCheck = Expression.TypeIs(baseBindingExpr, field.DefiningType.CLRType);
var nullResult = Expression.Constant(null, replacedBase.Type);
replacedBase = Expression.Condition(typeCheck, replacedBase, nullResult);
}
This works fine for in-memory tests, however, for EF this results in the following error:
graphql-net\Tests.EF\EntityFrameworkExecutionTests.cs : line 235
...
at System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition..ctor(DbProviderFactory storeProviderFactory, DbCommandTree commandTree, DbInterceptionContext interceptionContext, IDbDependencyResolver resolver, BridgeDataReaderFactory bridgeDataReaderFactory, ColumnMapFactory columnMapFactory)
Result Message:
System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
----> System.Data.Entity.Core.EntityCommandCompilationException : An error occurred while preparing the command definition. See the inner exception for details.
----> System.NotSupportedException : The nested query is not supported. Operation1='Case' Operation2='Collect'
This seems to be a more complex issue.
I think the sql query has to be split up into more sql queries.. but i do not have an idea to do this in a nice way.
Here is a branch with related unit tests and a first approach to fix this issue:
https://github.com/MarianPalkus/graphql-net/tree/b-inline-fragments-with-list-field
Do you have any idea how to solve this?
GraphQL enums are weird.
They're just identifiers that don't fit any of the other patterns - not variable names, not true, false, or null. We need to be able to distinguish between Color.Orange
and Fruit.Orange
. To avoid having to do some crazy inference thing where we leave the enum type unresolved until the point at which it's used, let's let people define a prefix with enums.
This could be something like: schema.AddEnum<Fruit>(prefix: "Fruit_")
Ideally it should work similarly to the general primitive type alias support, where if you've run that AddEnum
call, you can use anonymous types with that CLR enum type in your field args without issue and with all the schema validation you'd expect.
This one shouldn't be too hard. Iterate through all the fields of a type, build a lambda expression for a selector, then pass each one to AddField
on GraphQLTypeBuilder
.
Tried your example in the readme and it seems very out of date, AddQuery doesn't even exist. Would also love to see a few more basic examples for not only queries but for mutations.
It's annoying to have to write:
schema.AddQuery("user", new { id = 0 }, (db, args) => db.Users.AsQueryable().Where(u => u.Id == args.id).FirstOrDefault());
when you could collapse the FirstOrDefault down to:
schema.AddQuery("user", new { id = 0 }, (db, args) => db.Users.AsQueryable().FirstOrDefault(u => u.Id == args.id));
especially when ReSharper nags you about it. This should just be a simple expression transformation to add a .Where.
Hi Chad,
i think about setting up a more detailled documentation similiar to redux docs which uses gitbook.
Gitbook uses Markdown files which are stored in the git-repostiory (directory docs
).
I tried it out on this fork: https://github.com/MarianPalkus/graphql-net/
The gitbook looks like this: https://www.gitbook.com/read/book/marianpalkus/graphql-net.
There are also some examples in the directory examples
which are referenced in the docs.
What do you think of using gitbook? Do you think it is a good approach to store examples in the git-repo or should they live in their own git repo?
Travis CI doesn't support MSTest, presumably because it runs on a linux box and there's no command-line MSTest test runner that works with Mono. Either we can switch test frameworks to NUnit or XUnit, or we can find a different CI option that works with MSTest.
Great project, thanks for your work ๐
I am trying to implement inline fragments with type conditions analogous to the sample of the official graphql docs graphql docs.
Basically, there is an interface or base type (Character
) and two concrete types (Human
and Droid
), the types are slightly modified for simplicity:
interface Character {
id: ID!
name: String!
}
type Human implements Character {
id: ID!
name: String!
height: Float
}
type Droid implements Character {
id: ID!
name: String!
primaryFunction: String
}
The query looks like this:
heros {
__typename
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
A result could look like this:
"heros": [
{
"__typename": "Human",
"name": "Han Solo",
"height": 5.6430448
},
{
"__typename": "Droid",
"name": "R2-D2",
"primaryFunction": "Astromech"
}
]
I extended the EntityFrameworkExecutionTests
as follows:
class Character
{
public int Id { get; set; }
public string Name { get; set; }
}
class Human : Character
{
public double Height { get; set; }
}
class Droid : Character
{
public string PrimaryFunction { get; set; }
}
private static void InitializeCharacterSchema(GraphQLSchema<EfContext> schema)
{
var character = schema.AddType<Character>();
character.AddField(c => c.Id);
character.AddField(c => c.Name);
var human = schema.AddType<Human>();
human.AddField(c => c.Id);
human.AddField(c => c.Name);
human.AddField(h => h.Height);
var droid = schema.AddType<Droid>();
droid.AddField(c => c.Id);
droid.AddField(c => c.Name);
droid.AddField(h => h.PrimaryFunction);
schema.AddField("hero", new {id = 0}, (db, args) => db.Heros.SingleOrDefault(h => h.Id == args.id));
schema.AddListField("heros", db => db.Heros);
}
public IDbSet<Character> Heros { get; set; }
var human = new Human
{
Id = 1,
Name = "Han Solo",
Height = 5.6430448
};
db.Heros.Add(human);
var droid = new Droid
{
Id = 2,
Name = "R2-D2",
PrimaryFunction = "Astromech"
};
db.Heros.Add(droid);
[Test]
public void QueryInlineFragements()
{
var schema = GraphQL<EfContext>.CreateDefaultSchema(() => new EfContext());
InitializeCharacterSchema(schema);
schema.Complete();
var gql = new GraphQL<EfContext>(schema);
var results = gql.ExecuteQuery("{ heros { name, __typename, ... on Human { height }, ... on Droid { primaryFunction } } }");
Test.DeepEquals(results, "{ heros: [ { name: 'Han Solo', __typename: 'Human', height: 5.6430448}, { name: 'R2-D2', __typename: 'Droid', primaryFunction: 'Astromech' } ] }");
}
This raises the following Exception:
GraphQL.Parser.SourceException : unknown fragment ``on''
at Microsoft.FSharp.Core.Operators.Raise[T](Exception exn)
at GraphQL.Parser.Utilities.failAt[a](SourceInfo pos, String msg) in C:\Projekte\graphql-net\GraphQL.Parser\Utilities.fs:line 49
at GraphQL.Parser.SchemaResolver.Resolver1.ResolveFragmentSpreadSelection(FragmentSpread pspread, SourceInfo pos) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 195 at GraphQL.Parser.SchemaResolver.Resolver
1.ResolveSelection(Selection pselection, SourceInfo pos) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 221
at [email protected](IEnumerable`1& next) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 229
at Microsoft.FSharp.Core.CompilerServices.GeneratedSequenceBase`1.MoveNextImpl()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at Microsoft.FSharp.Collections.SeqModule.ToArray[T](IEnumerable`1 source)
at GraphQL.Parser.SchemaResolver.Resolver`1.ResolveSelections(IEnumerable`1 pselections) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 227
at GraphQL.Parser.SchemaResolver.Resolver`1.ResolveFieldSelection(Field pfield, SourceInfo pos) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 163
at GraphQL.Parser.SchemaResolver.Resolver`1.ResolveSelection(Selection pselection, SourceInfo pos) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 218
at [email protected](IEnumerable`1& next) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 229
at Microsoft.FSharp.Core.CompilerServices.GeneratedSequenceBase`1.MoveNextImpl()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at Microsoft.FSharp.Collections.SeqModule.ToArray[T](IEnumerable`1 source)
at GraphQL.Parser.SchemaResolver.Resolver`1.ResolveSelections(IEnumerable`1 pselections) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 227
at GraphQL.Parser.SchemaResolver.Resolver`1.ResolveOperation(Operation poperation, SourceInfo pos) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 234
at [email protected](IEnumerable`1& next) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 282
at Microsoft.FSharp.Core.CompilerServices.GeneratedSequenceBase`1.MoveNextImpl()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at Microsoft.FSharp.Collections.SeqModule.ToArray[T](IEnumerable`1 source)
at GraphQL.Parser.SchemaResolver.DocumentContext`1.ResolveOperations() in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 276
at GraphQL.Parser.GraphQLDocument`1.Parse(ISchema`1 schema, String source) in C:\Projekte\graphql-net\GraphQL.Parser\Integration\GraphQLDocument.fs:line 42
at GraphQL.Net.GraphQL`1.ExecuteQuery(String queryStr) in C:\Projekte\graphql-net\GraphQL.Net\GraphQL.cs:line 36
at Tests.EF.EntityFrameworkExecutionTests.QueryInlineFragements() in C:\Projekte\graphql-net\Tests.EF\EntityFrameworkExecutionTests.cs:line 222
[Test]
public void QueryInlineFragements2()
{
var schema = GraphQL<EfContext>.CreateDefaultSchema(() => new EfContext());
InitializeCharacterSchema(schema);
schema.Complete();
var gql = new GraphQL<EfContext>(schema);
var results = gql.ExecuteQuery("{ heros { name, __typename, ...human, ...droid } }, fragment human on Human { height }, fragment droid on Droid { primaryFunction }");
Test.DeepEquals(results, "{ heros: [ { name: 'Han Solo', __typename: 'Human', height: 5.6430448}, { name: 'R2-D2', __typename: 'Droid', primaryFunction: 'Astromech' } ] }");
}
This raises the following Exception:
GraphQL.Parser.SourceException :
height'' is not a field of type
Character''
at Microsoft.FSharp.Core.Operators.Raise[T](Exception exn)
at GraphQL.Parser.Utilities.failAt[a](SourceInfo pos, String msg) in C:\Projekte\graphql-net\GraphQL.Parser\Utilities.fs:line 49
at GraphQL.Parser.SchemaResolver.Resolver1.ResolveFieldSelection(Field pfield, SourceInfo pos) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 148 at GraphQL.Parser.SchemaResolver.Resolver
1.ResolveSelection(Selection pselection, SourceInfo pos) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 218
at [email protected](IEnumerable1& next) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 229 at Microsoft.FSharp.Core.CompilerServices.GeneratedSequenceBase
1.MoveNextImpl()
at System.Collections.Generic.List1..ctor(IEnumerable
1 collection)
at Microsoft.FSharp.Collections.SeqModule.ToArray[T](IEnumerable1 source) at GraphQL.Parser.SchemaResolver.Resolver
1.ResolveSelections(IEnumerable1 pselections) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 227 at GraphQL.Parser.SchemaResolver.Resolver
1.ResolveFragment(Fragment pfrag, SourceInfo pos) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 184
at GraphQL.Parser.SchemaResolver.Resolver1.ResolveFragmentSpreadSelection(FragmentSpread pspread, SourceInfo pos) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 197 at GraphQL.Parser.SchemaResolver.Resolver
1.ResolveSelection(Selection pselection, SourceInfo pos) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 221
at [email protected](IEnumerable1& next) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 229 at Microsoft.FSharp.Core.CompilerServices.GeneratedSequenceBase
1.MoveNextImpl()
at System.Collections.Generic.List1..ctor(IEnumerable
1 collection)
at Microsoft.FSharp.Collections.SeqModule.ToArray[T](IEnumerable1 source) at GraphQL.Parser.SchemaResolver.Resolver
1.ResolveSelections(IEnumerable1 pselections) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 227 at GraphQL.Parser.SchemaResolver.Resolver
1.ResolveFieldSelection(Field pfield, SourceInfo pos) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 163
at GraphQL.Parser.SchemaResolver.Resolver1.ResolveSelection(Selection pselection, SourceInfo pos) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 218 at [email protected](IEnumerable
1& next) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 229
at Microsoft.FSharp.Core.CompilerServices.GeneratedSequenceBase1.MoveNextImpl() at System.Collections.Generic.List
1..ctor(IEnumerable1 collection) at Microsoft.FSharp.Collections.SeqModule.ToArray[T](IEnumerable
1 source)
at GraphQL.Parser.SchemaResolver.Resolver1.ResolveSelections(IEnumerable
1 pselections) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 227
at GraphQL.Parser.SchemaResolver.Resolver1.ResolveOperation(Operation poperation, SourceInfo pos) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 234 at [email protected](IEnumerable
1& next) in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 282
at Microsoft.FSharp.Core.CompilerServices.GeneratedSequenceBase1.MoveNextImpl() at System.Collections.Generic.List
1..ctor(IEnumerable1 collection) at Microsoft.FSharp.Collections.SeqModule.ToArray[T](IEnumerable
1 source)
at GraphQL.Parser.SchemaResolver.DocumentContext1.ResolveOperations() in C:\Projekte\graphql-net\GraphQL.Parser\Schema\SchemaResolver.fs:line 276 at GraphQL.Parser.GraphQLDocument
1.Parse(ISchema1 schema, String source) in C:\Projekte\graphql-net\GraphQL.Parser\Integration\GraphQLDocument.fs:line 42 at GraphQL.Net.GraphQL
1.ExecuteQuery(String queryStr) in C:\Projekte\graphql-net\GraphQL.Net\GraphQL.cs:line 36
at Tests.EF.EntityFrameworkExecutionTests.QueryInlineFragements2() in C:\Projekte\graphql-net\Tests.EF\EntityFrameworkExecutionTests.cs:line 234
I'm not really sure, whether my schema definition is wrong or i misunderstood the inline fragments/type condtions of graph-ql.
Do you have a suggestion or hint how to implement it correctly?
Are type conditions already supported yet?
Thanks in advance :-)
Is it possible to define a type field with parameters, like with schema? I'm not getting the syntax right.
Someting like the example below, but for GraphQLTypeBuilder:
GraphQLSchema<EfContext> schema;
schema.AddListField("accountsByGuid", new { guid = Guid.Empty }, (db, args) => db.Accounts.AsQueryable().Where(a => a.SomeGuid == args.guid));
Thanks in advance
Client's query
query AnotherQuery($someId: Int!) { GetUser(userid: $someId) { id, created_dt, username } }
Backend
long userId = context.Argument<long>("userid");
Argument parsing for long type throws exception when the given parameter can be fit with integer type eg. 10001.
The parsing is ok when $someId is passed with 1000000000000000001
Hey guys!
Just started looking into GraphQL and your project looks great! I was wondering if there were short term plans for mutation support. I'm looking into creating a project that would do CQRS via GraphQL and this framework looks good but lacks mutation for now!
See spec here: https://facebook.github.io/graphql/#sec-Introspection
#7 has to be finished before this can be started.
ConsoleParser.csproj isn't cutting it anymore, let's use a real test project that can be hooked into automated testing. In addition to testing an Entity Framework context, we can also test vs. an in-memory context. This will be useful example for when we enable introspection later.
Better name pending, but the idea here is that some fields don't need to be executed against the database (and in many cases can't be). Since we don't need to build an IQueryable
here, the resolver function definition can just be a Func<>
instead of Expression<Func>
. These resolver functions would be executed after the database query completes.
I'm not sure yet if we want to allow access to the data returned from the database in the resolver function, since I think it would necessarily have to be in Dictionary<string, object>
form. Maaaybe we could offer some overload that lets you define parameters for the function in terms of an "args" anonymous object that is built from a member initializer expression and then tack that onto the query, but that's getting pretty complex. However, it would be better than Dictionary access, since you have no idea what will be populated in the dictionary.
The immediate use case for this is introspection, since the schema is in memory and not queryable from the database.
If one wanted to add a new User
from the test project, to the system would you do it via a mutation? Feels like I'm missing something fundamental and I'm not kicking anything up on Google.
Can one do:
schema.AddMutation("addUser",
new
{
name = string.Empty,
active = false
},
(db, args) => db.Users.AsQueryable(),
(db, args) =>
{
var newUser = new User()
{
Name = args.name,
Active = args.active
};
db.Users.Add(newUser);
db.SaveChanges();
}
);
var gql = CreateDefaultContext();
var results = gql.ExecuteQuery("mutation { addUser(name:\"Bob\", active: true) { id } }");
Stack trace
System.ArgumentException : Expression of type 'System.Linq.IQueryable`1[Tests.EF.EntityFrameworkMsSqlTests+User]' cannot be used for parameter of type 'Tests.EF.EntityFrameworkMsSqlTests+User'
at System.Linq.Expressions.Expression.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arg, ParameterInfo pi)
at System.Linq.Expressions.Expression.ValidateArgumentTypes(MethodBase method, ExpressionType nodeKind, ReadOnlyCollection`1& arguments)
at System.Linq.Expressions.Expression.Invoke(Expression expression, IEnumerable`1 arguments)
at GraphQL.Net.Executor`1.Execute(TContext context, GraphQLField field, ExecSelection`1 query) in C:\Jobs\graphql-net\GraphQL.Net\Executor.cs:line 70
at GraphQL.Net.Executor`1.Execute(GraphQLSchema`1 schema, GraphQLField field, ExecSelection`1 query) in C:\Jobs\graphql-net\GraphQL.Net\Executor.cs:line 17
at GraphQL.Net.GraphQL`1.ExecuteQuery(String queryStr) in C:\Jobs\graphql-net\GraphQL.Net\GraphQL.cs:line 44
at Tests.EF.EntityFrameworkMsSqlTests.SimpleMutationAddUser() in C:\Jobs\graphql-net\Tests.EF\EntityFrameworkMsSqlTests.cs:line 211
Currently GraphQL.cs just runs the first operation in the given document.
It should take the operation name to run as an optional parameter. If the parameter is given, the operation must be defined in the document, or an exception is thrown. If the parameter is given, the document must define exactly one anonymous operation, or an exception is thrown.
This doesn't depend on anything.
I appreciate this is probably tricky to address and not a priority, but floating it out there anyway.
We'd love to experiment with this, but our main projects still need to run on a net40 server.
I had a brief attempt; the fparsec dependencies were relatively easy to back-port, but there's more complications here (the read-only collections are an obvious issue, but even replacing those with the mutable interfaces still left a lot of errors).
Any thoughts on how feasible it should be?
If, as currently, we translate:
{
user(id:1) { id, name, bestFriend { id, name } }
}
To something like:
db.Users.Select(u => new
{ id = u.Id
, name = u.Name
, bestFriend = new
{ id = u.BestFriend.Id
, name = u.BestFriend.Name
}
})
This works in Entity Framework and probably most other ORMs but not in memory. If u.BestFriend is null, it will blow up with an NRE while executing the query. Inserting null checks can get really ugly and make the query expression incompatible with ORMs. What should happen?
Severity Code Description Project File Line Suppression State Error CS0121 The call is ambiguous between the following methods or properties: 'GraphQL.Net.InteropHelpers.OrDefault<T>(Microsoft.FSharp.Core.FSharpOption<T>)' and 'GraphQL.Net.InteropHelpers.OrDefault<T>(Microsoft.FSharp.Core.FSharpOption<T>)' GraphQL.Net C:\Users\9zllklkjflkjfljkfdsk\Desktop\graphql-net-master\GraphQL.Net\GraphQLSchema.cs 219 Active
Severity Code Description Project File Line Suppression State Error CS0121 The call is ambiguous between the following methods or properties: 'GraphQL.Net.StringExtensions.ToCamelCase(string)' and 'GraphQL.Net.StringExtensions.ToCamelCase(string)' GraphQL.Net C:\Users\9zllklkjflkjfljkfdsk\Desktop\graphql-net-master\GraphQL.Net\GraphQLTypeBuilder.cs 89 Active
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.