graphql-w-persistent 0.1.0.0 → 0.1.0.1
raw patch · 6 files changed
+194/−51 lines, 6 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- graphql-w-persistent.cabal +2/−2
- src/Components/ObjectHandlers/ObjectsHandler.hs +4/−0
- src/Components/QueryComposers/SQLQueryComposer.hs +2/−3
- src/GraphQL.hs +175/−22
- src/Model/ServerExceptions.hs +10/−17
- src/Model/ServerObjectTypes.hs +1/−7
graphql-w-persistent.cabal view
@@ -10,13 +10,13 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change -version: 0.1.0.0 +version: 0.1.0.1 -- A short (one-line) description of the package. synopsis: Haskell GraphQL query parser-interpreter-data processor. -- A longer description of the package. -description: This is a general purpose Haskell GraphQL query parser and interpreter. It is including data processing to return GraphQL object formats. The query parser and interpreter are universal (on available query types). It is accepting any string query, and it is returning a list of string database-queries. The data processing unit is designed around the return values from the Yesod and Persistent interface (cast as Text data values from PersistValue). With another server that is same data representation on the returned values from the database, this entire package is applicable. +description: This is a general purpose Haskell GraphQL query parser and interpreter. It is including data processing to return GraphQL object formats. The query parser and interpreter are universal (on available query types). It is accepting any string query, and it is returning a list of string database-queries. The data processing unit is designed around the return values from the Yesod and Persistent interface (cast as Text data values from PersistValue). With another server that is same data representation on the returned values from the database, this entire package is applicable. To read more detailed information, you should go to the below module page. -- URL for the project homepage or repository. homepage: https://github.com/jasonsychau/graphql-w-persistent
src/Components/ObjectHandlers/ObjectsHandler.hs view
@@ -6,6 +6,10 @@ import Model.ServerObjectTypes +{- +parsing frontend query to server query +- A valid argument is any string that is provided to reference database objects +-} readServerObject :: String -> [(String,[String])] -> ServerObject readServerObject str [] = E.throw InvalidObjectException readServerObject str ((a,b):t) = if (elem str b)==True then (a :: ServerObject) else readServerObject str t
src/Components/QueryComposers/SQLQueryComposer.hs view
@@ -21,8 +21,7 @@ addSqlQueryFields [] counts select from order [] (h:t) ltable _ _ = E.throw CreatingSqlQueryObjectFieldsException addSqlQueryFields [] counts select from order (h:t) [] ltable _ _ = [(removeLastChar select)++from++order++";"] addSqlQueryFields [] counts select from order (a:b) (h:t) ltable sodn sor = addSqlQueryFields h counts select from order b t ltable sodn sor -addSqlQueryFields (h:t) counts select from order [] [] ltable _ _ = E.throw CreatingSqlQueryObjectsException -addSqlQueryFields (h:t) counts select from order [] (a:b) ltable _ _ = E.throw CreatingSqlQueryObjectsException +addSqlQueryFields (h:t) counts select from order [] _ ltable _ _ = E.throw CreatingSqlQueryObjectFieldsException addSqlQueryFields (h:t) counts select from order names fields ltable sodn sor | (isLeft h)==True = addSqlQueryFields t counts (select++(head names)++"."++(getScalarName $ fromLeft (E.throw InvalidScalarException) h)++",") from order names fields ltable sodn sor -- since only difference is table name, I should remove repeated computations by changing only name... @@ -54,4 +53,4 @@ completeTransition :: M.Map String Int -> [String] -> NestedObject -> String completeTransition counts (h1:h2:h3:h4:h5:[]) nobj = " inner join "++(if (withSubSelection nobj)==True then ("(select * from "++h1++" where "++(getSubSelectionField nobj)++"="++(getSubSelectionArgument nobj)++")") else h1)++" as "++h1++(show $ (M.!) counts h1)++" on "++h3++(show $ (M.!) counts h3)++"."++h5++"="++h1++(show $ (M.!) counts h1)++"."++h2 completeTransition counts (h1:h2:h3:h4:h5:h6:h7:h8:t) nobj = " inner join "++h6++" as "++h6++(show $ (M.!) counts h6)++" on "++h3++(show $ (M.!) counts h3)++"."++h5++"="++h6++(show $ (M.!) counts h6)++"."++h7++(completeTransition counts (h1:h2:h6:h7:h8:t) nobj) -completeTransition counts _ _ = E.throw RelationshipConfigurationException+completeTransition counts _ _ = E.throw CreatingSqlQueryObjectFieldsException
src/GraphQL.hs view
@@ -5,16 +5,32 @@ Maintainer : jasonsychau@live.ca Stability : provisional -<https://graphql.github.io/ Here> is a link to the official documentations. You can learn and get a feel for how can you implement this package. +<https://graphql.github.io/ Here> is a link to the official documentations. You can learn and get a feel on how can you implement this package which is a GraphQL-to-SQL translator with Persist return value processing to make a GraphQL format return object string. -This is the main module to interpret your queries. The expected query type is a single string to comprise all your GraphQL queries and fragments. +This module is made to enable interpreting your GraphQL queries. The expected query type is a single string to comprise all your GraphQL queries and fragments. -When errors are encountered, this module is simply going to throw an uncaught Exception. The Exception name is some hint to where was the error encountered. +When errors are encountered, this module is simply going to throw an uncaught Exception. The Exception name is some hint to where was the error encountered. More information is provided in the below function description. + +Not all GraphQL features are currently supported. For a list of updates and current state, you should check the GitHub updates and example <https://github.com/jasonsychau/graphql-w-persistent page>. + +__Instructions to make server schema:__ + +(1) make a list of all server objects in the heirarchy in separate tuples with a list to all pseudonyms +(2) make a list of all above mentioned server objects in separate tuples with a list to all valid scalar subfields as you read them from your database +(3) make a list of all above mentioned server objects in separate tuples with a list to all valid nested object subfields as you want them to be named and as they are repeated in the later list +(4) make a list of all above mentioned server objects in separate tuples with a list to all reference database table name(s) as you read them +(5) make a list of all database table names in separate tuples with a String (of refence to third list relationship names) and a list to the SQL tables that are between the two desired tables; the list order is: +identity table name (the first String in this tuple), identity leaving field name, referring table name (the table that's corresponding to the second String in this tuple), referring table entering field name, and (if present) +three String sequences that is itermediate table name, intermediate table entering column name, and intermediate table leaving column name. The intermediary table are order from closest to indentity table to closest to referencing table. + +These lists are later used for arguments in a query parsing function. -} module GraphQL ( -- * Functions + -- This is the available methods which are giving access to the package interpretation, validation, and formatting features. module GraphQL, -- * Server data types + -- These are server data types that are return from processQueryString. You do not need to know these to know how is this package used, but you'll get some insight to what is used to store and map your GraphQL data. module Model.ServerObjectTypes ) where @@ -25,40 +41,177 @@ import GraphQLHelper -{- | Function processQueryString: - This the function to call to get queries from a GraphQL query. +{- | + This is the function to call to get queries from a GraphQL query. + The ordered arguments are: - - the GraphQL query as a string - - a list of tuple of String and list of String that is unique server object names and all names that are referencing then with any query - - a list of tuple of String and list of String that is exact same unique server object names while the list is the valid server object + + * the GraphQL query as a string + * a list of tuple of String and list of String that is unique server object names and all names that are referencing then with any query + * a list of tuple of String and list of String that is exact same unique server object names while the list is the valid server object scalar subfields as they are spelt in the database - - a list of tuple of String and list of String that is exact same unique server object names while the list is the valid server object + * a list of tuple of String and list of String that is exact same unique server object names while the list is the valid server object nested object subfields as they are referenced in the next list of relationship tables - - a list of tuple of String, String, and list of String that is above server object names to make an identity name, a reference name, + * a list of tuple of String, String, and list of String that is above server object names to make an identity name, a reference name, and a list of String that is identity table, escape identity column name, reference table, arrival reference column name, and an from-to order of intermediate table triplet strings of intermediate table name, intermediate table arrival column name, and intermediate table departure column name + The return value is a tuple of server representation objects and a list of list of sql queries. The first list is holding lists that are grouping queries from the same GraphQL query. The inner list is holding the String value sql queries. The server representation objects is later used to format database results to the GraphQL return value syntax. + + + + Here is an example of the GraphQL schema arguments that are expected from the last five arguments. We'll use the GitHub <https://github.com/jasonsychau/graphql-w-persistent example> database schema + to illustrate the expected arguments. + + + * The first schema argument (second argument) is a mapping to names that a server object is possibly referred to in any GraphQL query: + + @ + [("Person",["Person","person","owner"]),("Family",["Family","family"]),("Genus",["Genus","genus"]),("Species",["Species","species"]),("Breed",["Breed","breed"]),("Pet",["Pet","pet"]),("Taxonomy",["Taxonomy","taxonomy"])] + @ + + * The second schema argument is a mapping to valid scalar subfields from every server object that is listed in the first schema argument: + + @ + [("Person",["name","gender"]),("Family",["name"]),("Genus",["name"]),("Species",["name"]),("Breed",["name"]),("Pet",["name","gender"]),("Taxonomy",["name"])] + @ + + NOTE: You should give only the intersection subfields set to parent server objects with all the children server objects. + + * The third schema argument is a mapping to valid nested object (or entity) subfields from every server object that is listed in the first schema argument: + + @ + [("Person",["pet"]),("Family",["genus","species","breed","pet"]),("Genus",["family","species","breed","pet"]),("Species",["family","genus","breed","pet"]),("Breed",["family","genus","species","pet"]),("Pet",["owner","breed","species","genus","family"]),("Taxonomy",["pet"])] + @ + + NOTE: You should give only the intersection subfields set to parent server objects with all the children server objects. + + NOTE: These nested object names are exact reference to the destination argument in the below relationships schema argument (the last argument). + + * The fourth schema argument is a mapping to exact database names (it is maybe helpful to first have the database schema and copy names to this list mapping) for every listed server object in the first schema argument: + + @ + [("Person",["person"]),("Family",["family"]),("Genus",["genus"]),("Species",["species"]),("Breed",["breed"]),("Pet",["pet"]),("Taxonomy",["family","genus","species","breed"])] + @ + + NOTE: This is where can you introduce type heirarchies and generalizations. The above Taxonomy server object is an example. You can see that it is an encompassing term for all the biological classifications in our database schema. + + * The fifth schema argument is a mapping to exact database table names and fields names to link the from-object to the to-object. + + Within every tuple, + + the first String is the database table name (with type heirarchies, you make a separate tuple on every referenced database table if the to-from pair is not already mentioned as explicit pairing). + + The second String is the nested object subfield name that is given in the third schema argument (there is a tuple for every pairing). + + The third tuple value is a list that is showing the link between our identity table and referring table. The order is first identity database table name, second identity database leaving field name, + third referencing database table name, fourth referencing database table arrival name, and the remainder (if identity table and referencing table are not directly linked) is a triplet of Strings that + are ordered from nearest to identity table to closer to referencing table. The triplets are intermediate database table name, intermediate database arrival field, and intermediate database exiting + field. Here is an illustration: + + @ + -- Person + [("person","pet",["person","id","pet","id","pet_ownership","owner_id","animal_id"]), + ("person","breed",["person","id","breed","id","pet_ownership","owner_id","animal_id","pet","id","id","pet_type","pet_id","breed_id"]), + ("person","species",["person","id","species","id","pet_ownership","owner_id","animal_id","pet","id","id","pet_type","pet_id","breed_id","breed","id","species_id"]), + ("person","genus",["person","id","genus","id","pet_ownership","owner_id","animal_id","pet","id","id","pet_type","pet_id","breed_id","breed","id","species_id","species","id","genus_id"]), + ("person","family",["person","id","family","id","pet_ownership","owner_id","animal_id","pet","id","id","pet_type","pet_id","breed_id","breed","id","species_id","species","id","genus_id","genus","id","family_id"]), + -- Family + ("family","pet",["family","id","pet","id","genus","family_id","id","species","genus_id","id","breed","species_id","id","pet_type","breed_id","pet_id"]), + ("family","genus",["family","id","genus","family_id"]), + ("family","species",["family","id","species","genus_id","genus","family_id","id"]), + ("family","breed",["family","id","breed","species_id","genus","family_id","id","species","genus_id","id"]), + -- Genus + ("genus","pet",["genus","id","pet","id","species","genus_id","id","breed","species_id","id","pet_type","breed_id","pet_id"]), + ("genus","family",["genus","family_id","family","id"]), + ("genus","species",["genus","id","species","genus_id"]), + ("genus","breed",["genus","id","breed","species_id","species","genus_id","id"]), + -- Species + ("species","pet",["species","id","pet","id","breed","species_id","id","pet_type","breed_id","pet_id"]), + ("species","breed",["species","id","breed","species_id"]), + ("species","genus",["species","genus_id","genus","id"]), + ("species","family",["species","id","family","genus_id","genus","species_id","id"]), + -- Breed + ("breed","pet",["breed","id","pet","id","pet_type","breed_id","pet_id"]), + ("breed","species",["breed","species_id","species","id"]), + ("breed","genus",["breed","species_id","genus","id","species","id","genus_id"]), + ("breed","family",["breed","species_id","family","id","species","id","genus_id","genus","id","family_id"]), + -- Pet + ("pet","owner",["pet","id","person","id","pet_ownership","animal_id","owner_id"]), + ("pet","breed",["pet","id","breed","id","pet_type","pet_id","breed_id"]), + ("pet","species",["pet","id","species","id","pet_type","pet_id","breed_id","breed","id","species_id"]), + ("pet","genus",["pet","id","genus","id","pet_type","pet_id","breed_id","breed","id","genus_id"]), + ("pet","family",["pet","id","family","id","pet_type","pet_id","breed_id","breed","id","genus_id","genus","id","family_id"])] + @ + + + + To implement server type heirarchy: + + * add new tuple to first schema argument, + * add new tuple to second schema argument (where list is the intersection fields on all children), + * add new tuple to third schema argument (where list is the intersection fields on all children), + * and add new tuple to fourth schema argument (where list is all children database tables). + + You should not need more cases within fifth schema argument, but you may find a child database table ane and third schema argument list elements pair that is not included. + + Then, you will need to include this, or error is thrown. + + As a final note, we turn our attention to what is posted on the official GraphQL docs: + + @"In contrast, GraphQL only returns the data that's explicitly requested, so new capabilities can be added via new types and new fields on those types without creating a breaking change. This has lead to a common practice of always avoiding breaking changes and serving a versionless API."@ - <https://graphql.github.io/learn/best-practices/> + + With respect to that, you should be sure to be explicit with your here representation on your data schema. You should give as much detail as possisble (sorry for the bad pun), and you should make adjustments when they are needed to evolve your server with your desired schema. + + + + When an error is encountered, an uncaught exception is thrown. Exceptions thrown by the module are: + + * SyntaxException (when there is a problem with the given GraphQL query syntax) + * ParseFragmentException (when there is a problem with a Fragment syntax) + * EmptyQueryException (when the query is left blank) + * InvalidObjectException (when there is problem in nested object syntax, an object is not recognized, or server data is misinterpreted) + * InvalidObjectSubFieldException (when a subfield is requested from nested object that is not having ownership of the subfield) + * InvalidScalarException (when there is syntax error in listing scalar fields, or server data is misinterpreted) + * NullArgumentException (when a transformation is set but no argument is given though we do not yet support transformations) + * CreatingSqlQueryObjectFieldsException (when there are too many nested object subfields than nested objects to hold them or when there is a problem with the aligning of server object relationship argument - that's the fifth schema argument) + * RelationshipConfigurationException (when the relationship schema fifth arugment is incorrect number of String values or when an unrecognized pairing is found) -} -processQueryString :: String -- ^ This is the given GraphQL query. - -> [(String,[String])] -- ^ This is the ServerObject to list of query reference names. - -> [(String,[String])] -- ^ This is the ServerObject to list of valid scalar subfields (which are exactly named after database column names). - -> [(String,[String])] -- ^ This is the ServerObject to list of valid nested object subfields (which are exactly named after the from and to strings within the last function argument). - -> [(String,[String])] -- ^ This is the ServerObject to list of database table names (which are exact references to table names). - -> [(String,String,[String])] -- ^ This is the ServerObject to list of from-to-and intermediate triplet strings as described above to identify all GraphQL relationships with database sequences. +processQueryString :: String -- ^ GraphQL query argument as String. + -> [(String,[String])] -- ^ Server object name to list of query reference names. + -> [(String,[String])] -- ^ Server object name to list of valid scalar subfields (which are exactly named after database column names). + -> [(String,[String])] -- ^ Server object name to list of valid nested object subfields (which are exactly named after the from and to strings within the last function argument). + -> [(String,[String])] -- ^ Server object name to list of database table names (which are exact references to table names). + -> [(String,String,[String])] -- ^ Server object name to list of from-to-and intermediate triplet strings as described above to identify all GraphQL relationships with database sequences. -> ([RootObject],[[String]]) -- ^ The return value is one tuple with server objects and list of grouped sql query strings. processQueryString str svrobjs sss sos sodn sor = checkObjects sss sos sodn sor $ flip checkString svrobjs $ QP.processString str -{- | Function processPersistentData: +{- | This is the function to call after casting PersistValues to Text from processQueryString. + The ordered arguments are: - - all the data that is cast to Text type - - an unmodified copy of the RootObject list that is returned from processQueryString. + + * all the data that is cast to Text type + * an unmodified copy of the RootObject list that is returned from processQueryString. + The return result is a string to resemble the GraphQL return value. + + Making the first argument is demonstrated in the GitHub <https://github.com/jasonsychau/graphql-w-persistent example>. + + Current implementation is to cast all values to Text since arbitrary columns data types are not infered. + + + When an error is encountered, an uncaught exception is thrown. The exceptions thrown are of: + + * InvalidObjectException (when server data is misinterpreted) + * InvalidScalarException (when server data is misinterpreted) + * EOFDataProcessingException (when the given data is shorter than expected in reference to the given serve objects) + * InvalidArgumentException (when there is an internal argument error - you should not observe this) -} -processPersistentData :: [[[[Text]]]] -- ^ This is the unmodified Persistent database query return value (a list of GraphQL-grouped results list of SQL query results list of data row lists). - -> [RootObject] -- ^ This is the unmodified server objects that was composed from previous processQueryString function. - -> String -- ^ The return value is a string type to describe the GraphQL-organized return values. +processPersistentData :: [[[[Text]]]] -- ^ database query return value as casted to only Text data types and with no other alterations (a list of GraphQL-grouped results list of SQL query results list of data row lists) + -> [RootObject] -- ^ unmodified server objects that was given by the previous processQueryString function. + -> String -- ^ The return value is a string type to describe the GraphQL-organized return values. processPersistentData dt ro = DP.processReturnedValues ro dt
src/Model/ServerExceptions.hs view
@@ -3,31 +3,24 @@ import Control.Exception -data QueryException = SyntaxException | - VariableException | +data QueryException = SyntaxException | ParseFragmentException | EmptyQueryException | InvalidObjectException | - InvalidScalarException | - InvalidObjectNestedObjectFieldException | - InvalidObjectScalarFieldException | InvalidObjectSubFieldException | - InvalidAttributeTransformation | + InvalidScalarException | NullArgumentException | CreatingSqlQueryObjectFieldsException | - CreatingSqlQueryObjectsException | - TooManyTablesException | - EmptyRowException | EOFDataProcessingException | InvalidArgumentException | - Foundlinebreakexception | RelationshipConfigurationException deriving Show - -instance Exception QueryException + -- InvalidObjectNestedObjectFieldException | + -- InvalidObjectScalarFieldException | + -- InvalidAttributeTransformation | + -- CreatingSqlQueryObjectsException | + -- TooManyTablesException | + -- EmptyRowException | + -- Foundlinebreakexception | --- use Control.Exception.assert to make your unit tests. --- simply use throw SyntaxException --- can I catch from main block? --- you can catch an exception and display message at next page. --- TODO: abstract read ServerObject: if read is wrong we catch and throw read server object error+instance Exception QueryException
src/Model/ServerObjectTypes.hs view
@@ -1,10 +1,3 @@-{- | -Module : Model.ServerObjectTypes -Description : Here is the data types for the server objects. -License : IPS -Maintainer : jasonsychau@live.ca -Stability : provisional --} module Model.ServerObjectTypes where @@ -15,6 +8,7 @@ -- | NestedObjects are the general object type. They are found as RootObjects or as object Subfields. data NestedObject = NestedObject Alias Name ServerObject SubSelection SubFields deriving Show + type Alias = Maybe String type ServerObject = String type SubSelection = Maybe ScalarType