airgql 0.7.1.2 → 0.7.1.3
raw patch · 9 files changed
+176/−105 lines, 9 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- AirGQL.Utils: mainServerDb :: FilePath
- AirGQL.GraphQL: getMutationResponse :: Text -> [ColumnEntry] -> Type IO
+ AirGQL.GraphQL: getMutationResponse :: AccessMode -> Text -> [ColumnEntry] -> Type IO
Files
- airgql.cabal +1/−1
- readme.md +9/−0
- source/AirGQL/Config.hs +1/−2
- source/AirGQL/ExternalAppContext.hs +2/−2
- source/AirGQL/GraphQL.hs +49/−42
- source/AirGQL/Introspection.hs +45/−38
- source/AirGQL/Lib.hs +19/−9
- source/AirGQL/Utils.hs +0/−5
- tests/Spec.hs +50/−6
airgql.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: airgql-version: 0.7.1.2+version: 0.7.1.3 synopsis: Automatically generate a GraphQL API for an SQLite database description: AirGQL automatically generates a GraphQL API for SQLite databases. It analyses the database schema
readme.md view
@@ -51,6 +51,15 @@ - … ``` +Also set the `lib-only` flag in your `stack.yaml` file+to avoid irrelevant errors:++```yaml+flags:+ airgql:+ lib-only: true+```+ ## Usage
source/AirGQL/Config.hs view
@@ -9,7 +9,7 @@ import Data.Int (Int) --- | The maximum number of results allowed for the graphql playground+-- | The maximum number of results allowed for the GraphiQL playground maxGraphqlResultCount :: Int maxGraphqlResultCount = 10000 @@ -27,7 +27,6 @@ } --- | Configuration for Airsequel's Pro Plan defaultConfig :: Config defaultConfig = Config
source/AirGQL/ExternalAppContext.hs view
@@ -60,8 +60,8 @@ getExternalAppContext :: Text -> IO ExternalAppContext getExternalAppContext baseUrl = do sqlite <- lookupBinaryPath "sqlite3"- sqliteEnv <- lookupEnv "AIRSEQUEL_SQLITE_BIN"- sqliteLib <- lookupEnv "AIRSEQUEL_SQLITE_LIB"+ sqliteEnv <- lookupEnv "AIRGQL_SQLITE_BIN"+ sqliteLib <- lookupEnv "AIRGQL_SQLITE_LIB" pure $ ExternalAppContext
source/AirGQL/GraphQL.hs view
@@ -840,10 +840,11 @@ getMutationResponse- :: Text+ :: AccessMode+ -> Text -> [ColumnEntry] -> Out.Type IO-getMutationResponse tableName columnEntries =+getMutationResponse accessMode tableName columnEntries = Out.NamedObjectType $ outObjectTypeToObjectType $ OutObjectType@@ -853,7 +854,7 @@ tableName <> " mutation response description" , interfaceTypes = [] , fields =- HashMap.fromList+ HashMap.fromList $ [ ( "affected_rows" , let@@ -878,43 +879,47 @@ in ValueResolver field value )- ,- ( "returning"- , let- field :: Out.Field IO- field =- outFieldToField $- OutField- { descriptionMb =- Just- "Non null returning description"- , fieldType =- Out.NonNullListType $- Out.NamedObjectType $- Out.ObjectType- "returning"- (Just "short desc")- []- ( HashMap.fromList $- colNamesWithValResolver columnEntries- )- , arguments = HashMap.empty- }-- value :: ReaderT Out.Context IO Value- value = do- context <- ask- case context & Out.values of- Object obj ->- pure $- fromMaybe (Object P.mempty) $- HashMap.lookup "returning" obj- _ -> pure $ Object P.mempty- in- ValueResolver field value- ) , mutationTypeNameField tableName ]+ <> case accessMode of+ WriteOnly -> []+ _ ->+ [+ ( "returning"+ , let+ field :: Out.Field IO+ field =+ outFieldToField $+ OutField+ { descriptionMb =+ Just+ "Non null seturning description"+ , fieldType =+ Out.NonNullListType $+ Out.NamedObjectType $+ Out.ObjectType+ "returning"+ (Just "short desc")+ []+ ( HashMap.fromList $+ colNamesWithValResolver columnEntries+ )+ , arguments = HashMap.empty+ }++ value :: ReaderT Out.Context IO Value+ value = do+ context <- ask+ case context & Out.values of+ Object obj ->+ pure $+ fromMaybe (Object P.mempty) $+ HashMap.lookup "returning" obj+ _ -> pure $ Object P.mempty+ in+ ValueResolver field value+ )+ ] } @@ -1085,10 +1090,11 @@ mutationType :: Connection -> Integer+ -> AccessMode -> Text -> [TableEntryRaw] -> IO (Maybe (Out.ObjectType IO))-mutationType connection maxRowsPerTable dbId tables = do+mutationType connection maxRowsPerTable accessMode dbId tables = do let documentation = "Available queries for database \"" <> dbId <> "\""@@ -1206,7 +1212,7 @@ outFieldToField $ OutField { descriptionMb = Just "description"- , fieldType = getMutationResponse tableName columnEntries+ , fieldType = getMutationResponse accessMode tableName columnEntries , arguments = HashMap.fromList [ ("objects", objectsType)@@ -1520,7 +1526,7 @@ outFieldToField $ OutField { descriptionMb = Just $ "Provides entries from " <> tableName- , fieldType = getMutationResponse tableName columnEntries+ , fieldType = getMutationResponse accessMode tableName columnEntries , arguments = HashMap.fromList [@@ -1559,7 +1565,7 @@ outFieldToField $ OutField { descriptionMb = Just $ "Provides entries from " <> tableName- , fieldType = getMutationResponse tableName columnEntries+ , fieldType = getMutationResponse accessMode tableName columnEntries , arguments = HashMap.fromList [@@ -1659,6 +1665,7 @@ mutationType connection schemaConf.maxRowsPerTable+ schemaConf.accessMode dbId tables
source/AirGQL/Introspection.hs view
@@ -27,6 +27,7 @@ not, ($), (&),+ (&&), (<&>), (>>=), )@@ -73,6 +74,7 @@ column_name_gql, datatype_gql, getColumns,+ isOmittable, notnull, select_options, )@@ -1217,7 +1219,7 @@ colName "" -- TODO: Reactivate description when user can specify it [] -- No arguments- ( if columnEntry.notnull+ ( if columnEntry.notnull && not columnEntry.isOmittable then ["NON_NULL", "SCALAR"] else ["SCALAR"] )@@ -1326,6 +1328,10 @@ ReadOnly -> Null WriteOnly -> obj ReadAndWrite -> obj+ requiresRead obj = case accessMode of+ ReadOnly -> Null+ WriteOnly -> obj+ ReadAndWrite -> obj pure $ customRowTypes@@ -1379,42 +1385,43 @@ ] ) ]- , Object $- HashMap.fromList- [ ("name", "returning")- ,- ( "type"- , Object $- HashMap.fromList- [ ("kind", "NON_NULL")- ,- ( "ofType"- , Object $- HashMap.fromList- [ ("kind", "LIST")- ,- ( "ofType"- , Object $- HashMap.fromList- [ ("kind", "NON_NULL")- ,- ( "ofType"- , Object $- HashMap.fromList- [ ("kind", "OBJECT")- ,- ( "name"- , String $ doubleXEncodeGql table.name <> "_row"- )- ]- )- ]- )- ]- )- ]- )- ]+ , requiresRead $+ Object $+ HashMap.fromList+ [ ("name", "returning")+ ,+ ( "type"+ , Object $+ HashMap.fromList+ [ ("kind", "NON_NULL")+ ,+ ( "ofType"+ , Object $+ HashMap.fromList+ [ ("kind", "LIST")+ ,+ ( "ofType"+ , Object $+ HashMap.fromList+ [ ("kind", "NON_NULL")+ ,+ ( "ofType"+ , Object $+ HashMap.fromList+ [ ("kind", "OBJECT")+ ,+ ( "name"+ , String $ doubleXEncodeGql table.name <> "_row"+ )+ ]+ )+ ]+ )+ ]+ )+ ]+ )+ ] ] ) ]@@ -1578,7 +1585,7 @@ ) , ( "subscriptionType"- , -- Airsequel does not yet support Subscriptions+ , -- AirGQL doesn't support Subscriptions yet ValueResolver subscriptionTypeType $ pure Null ) ,
source/AirGQL/Lib.hs view
@@ -135,7 +135,7 @@ { errSQLType = "Object Type" , errHaskellType = "String" , errMessage =- "\"" <> show sqlData <> "\" is not a vaild object type"+ "\"" <> show sqlData <> "\" is not a valid object type" } ] @@ -337,6 +337,13 @@ { name = getFirstName names , columns = [col_name] }+ -- Primary keys are unique by default, even though they do not have an unique index+ SQL.ColConstraintDef names (SQL.ColPrimaryKeyConstraint _) ->+ Just $+ UniqueConstraint+ { name = getFirstName names+ , columns = [col_name]+ } _ -> Nothing @@ -348,6 +355,13 @@ , columns = P.fmap nameAsText columns } ]+ -- Primary keys are unique by default, even though they do not have an unique index+ SQL.TableConstraintDef names (SQL.TablePrimaryKeyConstraint columns) ->+ [ UniqueConstraint+ { name = getFirstName names+ , columns = P.fmap nameAsText columns+ }+ ] SQL.TableColumnDef (SQL.ColumnDef col_name _ _ constraints) -> P.mapMaybe (getColumnUniqueConstraint (nameAsText col_name)) constraints _ -> []@@ -779,12 +793,8 @@ , datatype = "INTEGER" , datatype_gql = Just $ stringToGqlTypeName "Int" , select_options = P.Nothing- , -- While the rowid is actually NOT NULL,- -- it must be set to false here- -- to show in the GraphQL docs that it can be omitted- -- since it will be set automatically.- notnull = False- , isUnique = False+ , notnull = True+ , isUnique = True , isOmittable = True , isGenerated = False , dflt_value = P.Nothing@@ -989,7 +999,7 @@ -- TODO: Remove after -- https://github.com/JakeWheat/simple-sql-parser/issues/27 & replaceWithSpace "if not exists" ""- -- TOOD: Remove after+ -- TODO: Remove after -- https://github.com/JakeWheat/simple-sql-parser/issues/37 & replaceCaseInsensitive "insert or abort " "insert " & replaceCaseInsensitive "insert or fail " "insert "@@ -1188,7 +1198,7 @@ , "nothing" , "notnull" , -- although "null" is on the official list of keywords, adding it here- -- seems to break "select NULL as ..." statemenets+ -- seems to break "select NULL as ..." statements -- , "null" "nulls" , "of"
source/AirGQL/Utils.hs view
@@ -22,7 +22,6 @@ getSqliteBinaryVersion, getSqliteEmbeddedVersion, headerJsonContent,- mainServerDb, quoteKeyword, quoteText, removeIfExists,@@ -93,10 +92,6 @@ ) import AirGQL.ExternalAppContext (ExternalAppContext (sqlite))---mainServerDb :: FilePath-mainServerDb = "data" </> "airsequel.sqlite" getDbDir :: Text -> FilePath
tests/Spec.hs view
@@ -115,7 +115,6 @@ import AirGQL.Types.Utils (encodeToText) import AirGQL.Utils ( getOrderOfLinkedList,- mainServerDb, removeIfExists, withRetryConn, )@@ -272,8 +271,8 @@ , datatype = "INTEGER" , datatype_gql = Just $ stringToGqlTypeName "Int" , select_options = Nothing- , notnull = False- , isUnique = False+ , notnull = True+ , isUnique = True , isOmittable = True , isGenerated = False , dflt_value = Nothing@@ -478,7 +477,7 @@ , datatype_gql = Just $ stringToGqlTypeName "Int" , select_options = Nothing , notnull = False- , isUnique = False+ , isUnique = True , isOmittable = True , isGenerated = False , dflt_value = Nothing@@ -3373,7 +3372,54 @@ Ae.encode result `shouldBe` rmSpaces expected + it "doesn't allow writeonly tokens to return data" $ do+ let dbName = "no-writeonly-return.db"+ withTestDbConn shouldSaveDbs dbName $ \conn -> do+ execute_+ conn+ [sql|+ CREATE TABLE items (+ id INTEGER PRIMARY KEY+ )+ |] + let+ query :: Text+ query =+ [gql|+ mutation items {+ update_items(filter: { id: { eq: 0 }}, set: { id: 0 }) {+ returning { id }+ }+ }+ |]++ expected =+ rmSpaces+ [raw|+ {+ "data": null,+ "errors": [{+ "locations": [{ "column":3, "line":2 }],+ "message": "Cannot query field \"update_items\" on type \"Mutation\"."+ }]+ }+ |]++ schema <- withRetryConn (unpack dbPath) $ \conn -> do+ tables <- getTables conn+ getDerivedSchema+ defaultSchemaConf{accessMode = WriteOnly}+ conn+ dbPath+ tables++ Right response <-+ graphql schema Nothing mempty query++ Ae.encode response `shouldBe` expected++ deleteDbEntries :: Text -> IO () deleteDbEntries databasePath = do conn <- open $ unpack databasePath@@ -3386,8 +3432,6 @@ cwd <- getWorkingDirectory createDirectoryIfMissing True (".." </> "data" </> "TEST" </> "data") changeWorkingDirectory (cwd </> ".." </> "data" </> "TEST")-- removeIfExists mainServerDb removeIfExists $ unpack dbPath