explain a graphql query, similar to explain of an sql statement (close #562) (#805)

This commit is contained in:
Vamshi Surabhi
2018-10-19 07:45:28 +05:30
committed by Shahidh K Muhammed
parent 82e8d45bf9
commit ac537330d0
29 changed files with 3641 additions and 3294 deletions

6037
console/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -59,7 +59,7 @@
"deep-equal": "^1.0.1",
"graphiql": "^0.11.11",
"graphql": "^0.13.2",
"hasura-console-graphiql": "0.0.1",
"hasura-console-graphiql": "0.0.5",
"history": "^3.0.0",
"hoist-non-react-statics": "^1.0.3",
"invariant": "^2.2.0",

View File

@@ -226,6 +226,29 @@ const graphQLFetcherFinal = (graphQLParams, url, headers) => {
}).then(response => response.json());
};
/* Analyse Fetcher */
const analyzeFetcher = (url, headers) => {
return query => {
const editedQuery = {
query,
};
const user = {};
user.role = 'admin';
user.headers = getHeadersAsJSON(headers);
editedQuery.user = user;
return fetch(`${url}/explain`, {
method: 'post',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(editedQuery),
credentials: 'include',
});
};
};
/* End of it */
const changeRequestHeader = (index, key, newValue, isDisabled) => ({
type: REQUEST_HEADER_CHANGED,
data: {
@@ -632,4 +655,5 @@ export {
focusHeaderTextbox,
unfocusTypingHeader,
getRemoteQueries,
analyzeFetcher,
};

View File

@@ -68,6 +68,7 @@ class ApiExplorer extends Component {
numberOfTables={this.props.tables.length}
headerFocus={this.props.headerFocus}
queryParams={this.props.location.query}
serverVersion={this.props.serverVersion}
/>
);

View File

@@ -4,6 +4,7 @@ const generatedApiExplorer = connect => {
const mapStateToProps = state => {
return {
...state.apiexplorer,
serverVersion: state.main.serverVersion ? state.main.serverVersion : '',
credentials: {},
dataApiExplorerData: { ...state.dataApiExplorer },
dataHeaders: state.tables.dataHeaders,

View File

@@ -41,6 +41,7 @@ class ApiRequestWrapper extends Component {
numberOfTables={this.props.numberOfTables}
headerFocus={this.props.headerFocus}
queryParams={this.props.queryParams}
serverVersion={this.props.serverVersion}
/>
{this.props.request.bodyType !== 'graphql' ? (
<ApiResponse

View File

@@ -150,7 +150,6 @@
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
overflow-y: hidden;
}
.graphiql-container .queryWrap {
@@ -227,7 +226,7 @@
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
height: 29px;
height: 30px;
position: relative;
}
@@ -732,6 +731,180 @@ div.CodeMirror-lint-tooltip > * + * {
.cm-atom {
color: #ca9800;
}
/* Hasura Analyse modal css */
.wd25 {
width: 25%;
display: inline-block;
}
.wd75 {
width: 75%;
display: inline-block;
}
.wd40 {
width: 40%;
display: inline-block;
}
.wd60 {
width: 60%;
display: inline-block;
}
.modalWrapper {
position: absolute;
top: 40px;
left: 40px;
right: 40px;
bottom: 40px;
border: 1px solid rgb(204, 204, 204);
background: rgb(255, 255, 255);
border-radius: 4px;
outline: none;
overflow: -webkit-paged-y;
}
.myOverlayClass {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: rgba(255, 255, 255, 0.75);
z-index: 100;
}
.modalHeader {
background-color: #43495a;
border-bottom: 1px solid #ccc;
padding: 10px 20px;
position: relative;
color: #ffc627;
}
.modalTitle {
font-size: 18px;
font-weight: 500;
}
.modalClose {
position: absolute;
right: 10px;
top: 10px;
}
.modalClose button {
cursor: pointer;
border-radius: 5px;
border: 0;
background-color: transparent;
font-size: 16px;
font-weight: 700;
color: #ccc;
padding: 0px;
height: auto;
}
.modalClose button:hover {
border: 0;
background-color: transparent;
color: #fff;
}
.modalClose button:focus {
outline: none;
}
.modalBody {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
height: 100%;
}
.topLevelNodesWrapper {
border-right: 1px solid #ccc;
height: calc(100% - 42px);
}
.textCenter {
text-align: center;
}
.topLevelNodesWrapper .title {
padding: 15px 20px;
background-color: #fff3d5;
margin-bottom: 20px;
color: #767e93;
font-weight: 600;
font-size: 16px;
}
.analysisWrapper {
height: calc(100% - 30px);
}
.topLevelNodesWrapper ul {
-webkit-padding-start: 0px;
padding-inline-start: 0px;
-webkit-padding-inline-start: 0px;
-moz-padding-inline-start: 0px;
-ms-padding-inline-start: 0px;
-o-padding-inline-start: 0px;
margin-top: 0;
margin-bottom: 0;
}
.borderRight {
border-right: 1px solid #ccc;
}
.topLevelNodesWrapper ul li {
list-style-type: none;
padding: 10px 0;
padding-left: 20px;
cursor: pointer;
}
.topLevelNodesWrapper ul li i {
margin-right: 5px;
font-size: 12px;
}
.topLevelNodesWrapper ul li.active {
color: #fd9540;
}
.topLevelNodesWrapper ul li:hover {
color: #e2701a;
}
.plansWrapper {
}
.plansTitle {
padding: 10px 20px;
padding-bottom: 0;
color: #767e93;
font-weight: 600;
}
.overflowAuto {
overflow: auto;
}
.codeBlock {
padding: 10px 20px;
width: 90%;
background-color: rgb(253, 249, 237);
margin: 20px;
border-radius: 5px;
max-height: 150px;
overflow: auto;
margin-top: 10px;
}
.codeBlock pre {
display: block;
padding: 0px;
margin: 0px;
font-size: 13px;
line-height: unset;
word-break: unset;
word-wrap: unset;
color: #000;
background: none;
border: none;
border-radius: 0;
overflow: unset;
}
.codeBlock code {
color: #000;
background: none;
}
.graphiql-container .analyse-button-wrap {
position: relative;
}
/* BASICS */
.CodeMirror {
@@ -1256,7 +1429,7 @@ span.CodeMirror-selectedtext {
-ms-flex: 1;
flex: 1;
font-weight: bold;
overflow-x: initial;
overflow-x: hidden;
padding: 10px 0 10px 10px;
text-align: center;
text-overflow: ellipsis;
@@ -1549,12 +1722,19 @@ span.CodeMirror-selectedtext {
.CodeMirror-foldgutter-folded:after {
content: '\25B8';
}
.graphiql-container .history-contents {
.graphiql-container .history-contents,
.graphiql-container .history-contents input {
font-family: 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace;
padding: 0;
}
.graphiql-container .history-contents p {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
@@ -1564,9 +1744,29 @@ span.CodeMirror-selectedtext {
border-bottom: 1px solid #e0e0e0;
}
.graphiql-container .history-contents p.editable {
padding-bottom: 6px;
padding-top: 7px;
}
.graphiql-container .history-contents input {
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
font-size: 12px;
}
.graphiql-container .history-contents p:hover {
cursor: pointer;
}
.graphiql-container .history-contents p span.history-label {
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.CodeMirror-info {
background: white;
border-radius: 2px;

View File

@@ -2,10 +2,16 @@ import React, { Component } from 'react';
import GraphiQL from 'hasura-console-graphiql';
import PropTypes from 'prop-types';
import ErrorBoundary from './ErrorBoundary';
import { graphQLFetcherFinal, getRemoteQueries } from './Actions';
import {
analyzeFetcher,
graphQLFetcherFinal,
getRemoteQueries,
} from './Actions';
import './GraphiQL.css';
import semverCheck from '../../helpers/semver';
class GraphiQLWrapper extends Component {
constructor(props) {
super(props);
@@ -15,6 +21,7 @@ class GraphiQLWrapper extends Component {
noSchema: false,
onBoardingEnabled: false,
queries: null,
supportAnalyze: false,
};
const queryFile = this.props.queryParams
? this.props.queryParams.query_file
@@ -26,12 +33,44 @@ class GraphiQLWrapper extends Component {
}
}
componentDidMount() {
if (this.props.data.serverVersion) {
this.checkSemVer(this.props.data.serverVersion);
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.data.serverVersion !== this.props.data.serverVersion) {
this.checkSemVer(nextProps.data.serverVersion);
}
}
shouldComponentUpdate(nextProps) {
return !nextProps.headerFocus;
}
checkSemVer(version) {
try {
const showAnalyze = semverCheck('sqlAnalyze', version);
if (showAnalyze) {
this.updateAnalyzeState(true);
} else {
this.updateAnalyzeState(false);
}
} catch (e) {
this.updateAnalyzeState(false);
console.error(e);
}
}
updateAnalyzeState(supportAnalyze) {
this.setState({
...this.state,
supportAnalyze: supportAnalyze,
});
}
render() {
const styles = require('../Common/Common.scss');
const { supportAnalyze } = this.state;
const graphQLFetcher = graphQLParams => {
if (this.state.headerFocus) {
return null;
@@ -43,6 +82,11 @@ class GraphiQLWrapper extends Component {
);
};
const analyzeFetcherInstance = analyzeFetcher(
this.props.data.url,
this.props.data.headers
);
// let content = "fetching schema";
let content = (
<i className={'fa fa-spinner fa-spin ' + styles.graphSpinner} />
@@ -51,15 +95,28 @@ class GraphiQLWrapper extends Component {
if (!this.state.error && this.props.numberOfTables !== 0) {
if (this.state.queries) {
content = (
<GraphiQL fetcher={graphQLFetcher} query={this.state.queries} />
<GraphiQL
fetcher={graphQLFetcher}
analyzeFetcher={analyzeFetcherInstance}
supportAnalyze={supportAnalyze}
query={this.state.queries}
/>
);
} else {
content = <GraphiQL fetcher={graphQLFetcher} />;
content = (
<GraphiQL
fetcher={graphQLFetcher}
analyzeFetcher={analyzeFetcherInstance}
supportAnalyze={supportAnalyze}
/>
);
}
} else if (this.props.numberOfTables === 0) {
content = (
<GraphiQL
fetcher={graphQLFetcher}
supportAnalyze={supportAnalyze}
analyzeFetcher={analyzeFetcherInstance}
query={
'# Looks like you do not have any tables.\n# Click on the "Data" tab on top to create tables\n# You can come back here and try out the GraphQL queries after you create tables\n'
}

View File

@@ -25,6 +25,10 @@ export default class Html extends Component {
<html lang="en-us">
<head>
<link rel="icon" type="image/png" href="/rstatic/favicon.png" />
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.1/build/styles/default.min.css"
/>
{Object.keys(assets.styles).map((style, key) => (
<link
@@ -94,6 +98,8 @@ export default class Html extends Component {
<div id="content" className="content" />
<script src={assets.javascript.main} charSet="UTF-8" />
<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.1/build/highlight.min.js" />
<script type="text/javascript" src="https://unpkg.com/sql-formatter@latest/dist/sql-formatter.min.js" />
</body>
</html>
);

View File

@@ -4,6 +4,7 @@ const componentsSemver = {
eventsTab: '1.0.0-alpha16',
metadataReload: '1.0.0-alpha17',
eventRedeliver: '1.0.0-alpha17',
sqlAnalyze: '1.0.0-alpha25',
};
const getPreRelease = version => {

View File

@@ -30,6 +30,7 @@ library
build-depends: base
, pg-client
, text
, text-builder >= 0.6
, bytestring
, postgresql-libpq
, mtl
@@ -161,7 +162,6 @@ library
, Hasura.RQL.DDL.Utils
, Hasura.RQL.DDL.Subscribe
, Hasura.RQL.DML.Delete
, Hasura.RQL.DML.Explain
, Hasura.RQL.DML.Internal
, Hasura.RQL.DML.Insert
, Hasura.RQL.DML.Returning
@@ -184,6 +184,7 @@ library
, Hasura.GraphQL.Validate.Field
, Hasura.GraphQL.Validate.InputValue
, Hasura.GraphQL.Resolve
, Hasura.GraphQL.Explain
, Hasura.GraphQL.Resolve.LiveQuery
, Hasura.GraphQL.Resolve.BoolExp
, Hasura.GraphQL.Resolve.Context

View File

@@ -0,0 +1,122 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Hasura.GraphQL.Explain
( explainGQLQuery
, GQLExplain
) where
import qualified Data.Aeson as J
import qualified Data.Aeson.Casing as J
import qualified Data.Aeson.TH as J
import qualified Data.HashMap.Strict as Map
import qualified Database.PG.Query as Q
import qualified Language.GraphQL.Draft.Syntax as G
import qualified Text.Builder as TB
import qualified Data.ByteString.Lazy as BL
import Hasura.GraphQL.Resolve.Context
import Hasura.GraphQL.Schema
import Hasura.GraphQL.Validate.Field
import Hasura.Prelude
import Hasura.RQL.DML.Internal
import Hasura.RQL.Types
import Hasura.SQL.Types
import Hasura.SQL.Value
import qualified Hasura.GraphQL.Resolve.Select as RS
import qualified Hasura.GraphQL.Transport.HTTP.Protocol as GH
import qualified Hasura.GraphQL.Validate as GV
import qualified Hasura.RQL.DML.Select as RS
import qualified Hasura.Server.Query as RQ
data GQLExplain
= GQLExplain
{ _gqeQuery :: !GH.GraphQLRequest
, _gqeUser :: !UserInfo
} deriving (Show, Eq)
$(J.deriveJSON (J.aesonDrop 4 J.camelCase){J.omitNothingFields=True}
''GQLExplain
)
data FieldPlan
= FieldPlan
{ _fpField :: !G.Name
, _fpSql :: !(Maybe Text)
, _fpPlan :: !(Maybe [Text])
} deriving (Show, Eq)
$(J.deriveJSON (J.aesonDrop 3 J.camelCase) ''FieldPlan)
type Explain =
(ReaderT (FieldMap, OrdByResolveCtx) (Except QErr))
runExplain
:: (MonadError QErr m)
=> (FieldMap, OrdByResolveCtx) -> Explain a -> m a
runExplain ctx m =
either throwError return $ runExcept $ runReaderT m ctx
explainField
:: UserInfo -> GCtx -> Field -> Q.TxE QErr FieldPlan
explainField userInfo gCtx fld =
case fName of
"__type" -> return $ FieldPlan fName Nothing Nothing
"__schema" -> return $ FieldPlan fName Nothing Nothing
"__typename" -> return $ FieldPlan fName Nothing Nothing
_ -> do
opCxt <- getOpCtx fName
sel <- runExplain (fldMap, orderByCtx) $ case opCxt of
OCSelect tn permFilter permLimit hdrs -> do
validateHdrs hdrs
RS.mkSQLSelect False <$>
RS.fromField txtConverter tn permFilter permLimit fld
OCSelectPkey tn permFilter hdrs -> do
validateHdrs hdrs
RS.mkSQLSelect True <$>
RS.fromFieldByPKey txtConverter tn permFilter fld
_ -> throw500 "unexpected mut field info for explain"
let selectSQL = TB.run $ toSQL sel
withExplain = "EXPLAIN (FORMAT TEXT) " <> selectSQL
planLines <- liftTx $ map runIdentity <$>
Q.listQE dmlTxErrorHandler (Q.fromText withExplain) () True
return $ FieldPlan fName (Just selectSQL) $ Just planLines
where
fName = _fName fld
txtConverter = return . txtEncoder . snd
opCtxMap = _gOpCtxMap gCtx
fldMap = _gFields gCtx
orderByCtx = _gOrdByEnums gCtx
getOpCtx f =
onNothing (Map.lookup f opCtxMap) $ throw500 $
"lookup failed: opctx: " <> showName f
validateHdrs hdrs = do
let receivedHdrs = userHeaders userInfo
forM_ hdrs $ \hdr ->
unless (Map.member hdr receivedHdrs) $
throw400 NotFound $ hdr <<> " header is expected but not found"
explainGQLQuery
:: (MonadError QErr m, MonadIO m)
=> Q.PGPool
-> Q.TxIsolation
-> GCtxMap
-> GQLExplain
-> m BL.ByteString
explainGQLQuery pool iso gCtxMap (GQLExplain query userInfo)= do
(opTy, selSet) <- runReaderT (GV.validateGQ query) gCtx
unless (opTy == G.OperationTypeQuery) $
throw400 InvalidParams "only queries can be explained"
let tx = mapM (explainField userInfo gCtx) (toList selSet)
plans <- liftIO (runExceptT $ runTx tx) >>= liftEither
return $ J.encode plans
where
gCtx = getGCtx (userRole userInfo) gCtxMap
runTx tx =
Q.runTx pool (iso, Nothing) $ RQ.setHeadersTx userInfo >> tx

View File

@@ -6,7 +6,9 @@
module Hasura.GraphQL.Resolve.BoolExp
( parseBoolExp
, pgColValToBoolExpG
, pgColValToBoolExp
, convertBoolExpG
, convertBoolExp
, prepare
) where
@@ -116,26 +118,43 @@ parseBoolExp annGVal = do
| otherwise -> BoolCol <$> parseColExp nt k v parseOpExps
return $ BoolAnd $ fromMaybe [] boolExpsM
convertBoolExpG
:: (MonadError QErr m, MonadReader r m, Has FieldMap r)
=> ((PGColType, PGColValue) -> m S.SQLExp)
-> QualifiedTable
-> AnnGValue
-> m (GBoolExp RG.AnnSQLBoolExp)
convertBoolExpG f tn whereArg = do
whereExp <- parseBoolExp whereArg
RG.convBoolRhs (RG.mkBoolExpBuilder f) (S.mkQual tn) whereExp
convertBoolExp
:: QualifiedTable
-> AnnGValue
-> Convert (GBoolExp RG.AnnSQLBoolExp)
convertBoolExp tn whereArg = do
whereExp <- parseBoolExp whereArg
RG.convBoolRhs (RG.mkBoolExpBuilder prepare) (S.mkQual tn) whereExp
convertBoolExp = convertBoolExpG prepare
type PGColValMap = Map.HashMap G.Name AnnGValue
pgColValToBoolExp
:: QualifiedTable
pgColValToBoolExpG
:: (MonadError QErr m, MonadReader r m, Has FieldMap r)
=> ((PGColType, PGColValue) -> m S.SQLExp)
-> QualifiedTable
-> PGColValMap
-> Convert (GBoolExp RG.AnnSQLBoolExp)
pgColValToBoolExp tn colValMap = do
-> m (GBoolExp RG.AnnSQLBoolExp)
pgColValToBoolExpG f tn colValMap = do
colExps <- forM colVals $ \(name, val) -> do
(ty, _) <- asPGColVal val
let namedTy = mkScalarTy ty
BoolCol <$> parseColExp namedTy name val parseAsEqOp
let whereExp = BoolAnd colExps
RG.convBoolRhs (RG.mkBoolExpBuilder prepare) (S.mkQual tn) whereExp
RG.convBoolRhs (RG.mkBoolExpBuilder f) (S.mkQual tn) whereExp
where
colVals = Map.toList colValMap
pgColValToBoolExp
:: QualifiedTable
-> PGColValMap
-> Convert (GBoolExp RG.AnnSQLBoolExp)
pgColValToBoolExp =
pgColValToBoolExpG prepare

View File

@@ -41,7 +41,7 @@ withSelSet selSet f =
convertReturning
:: QualifiedTable -> G.NamedType -> SelSet -> Convert RS.AnnSel
convertReturning qt ty selSet = do
annFlds <- fromSelSet ty selSet
annFlds <- fromSelSet prepare ty selSet
return $ RS.AnnSel annFlds qt (Just frmItem)
(S.BELit True) Nothing RS.noTableArgs
where

View File

@@ -10,6 +10,8 @@ module Hasura.GraphQL.Resolve.Select
, convertSelectByPKey
, fromSelSet
, fieldAsPath
, fromField
, fromFieldByPKey
) where
import Data.Has
@@ -33,10 +35,12 @@ import Hasura.SQL.Types
import Hasura.SQL.Value
fromSelSet
:: G.NamedType
:: (MonadError QErr m, MonadReader r m, Has FieldMap r, Has OrdByResolveCtx r)
=> ((PGColType, PGColValue) -> m S.SQLExp)
-> G.NamedType
-> SelSet
-> Convert [(FieldName, RS.AnnFld)]
fromSelSet fldTy flds =
-> m [(FieldName, RS.AnnFld)]
fromSelSet f fldTy flds =
forM (toList flds) $ \fld -> do
let fldName = _fName fld
let rqlFldName = FieldName $ G.unName $ G.unAlias $ _fAlias fld
@@ -48,7 +52,7 @@ fromSelSet fldTy flds =
Left colInfo -> return $ RS.FCol colInfo
Right (relInfo, tableFilter, tableLimit, _) -> do
let relTN = riRTable relInfo
relSelData <- fromField relTN tableFilter tableLimit fld
relSelData <- fromField f relTN tableFilter tableLimit fld
let annRel = RS.AnnRel (riName relInfo) (riType relInfo)
(riMapping relInfo) relSelData
return $ RS.FRel annRel
@@ -57,19 +61,24 @@ fieldAsPath :: (MonadError QErr m) => Field -> m a -> m a
fieldAsPath fld = nameAsPath $ _fName fld
parseTableArgs
:: QualifiedTable -> ArgsMap -> Convert RS.TableArgs
parseTableArgs tn args = do
whereExpM <- withArgM args "where" $ convertBoolExp tn
:: (MonadError QErr m, MonadReader r m, Has FieldMap r, Has OrdByResolveCtx r)
=> ((PGColType, PGColValue) -> m S.SQLExp)
-> QualifiedTable -> ArgsMap -> m RS.TableArgs
parseTableArgs f tn args = do
whereExpM <- withArgM args "where" $ convertBoolExpG f tn
ordByExpM <- withArgM args "order_by" parseOrderBy
limitExpM <- withArgM args "limit" parseLimit
offsetExpM <- withArgM args "offset" $ asPGColVal >=> prepare
offsetExpM <- withArgM args "offset" $ asPGColVal >=> f
return $ RS.TableArgs whereExpM ordByExpM limitExpM offsetExpM
fromField
:: QualifiedTable -> S.BoolExp -> Maybe Int -> Field -> Convert RS.AnnSel
fromField tn permFilter permLimitM fld = fieldAsPath fld $ do
tableArgs <- parseTableArgs tn args
annFlds <- fromSelSet (_fType fld) $ _fSelSet fld
:: (MonadError QErr m, MonadReader r m, Has FieldMap r, Has OrdByResolveCtx r)
=> ((PGColType, PGColValue) -> m S.SQLExp)
-> QualifiedTable -> S.BoolExp -> Maybe Int -> Field -> m RS.AnnSel
fromField f tn permFilter permLimitM fld =
fieldAsPath fld $ do
tableArgs <- parseTableArgs f tn args
annFlds <- fromSelSet f (_fType fld) $ _fSelSet fld
return $ RS.AnnSel annFlds tn Nothing permFilter permLimitM tableArgs
where
args = _fArguments fld
@@ -108,10 +117,12 @@ parseLimit v = do
noIntErr = throw400 Unexpected "expecting Integer value for \"limit\""
fromFieldByPKey
:: QualifiedTable -> S.BoolExp -> Field -> Convert RS.AnnSel
fromFieldByPKey tn permFilter fld = fieldAsPath fld $ do
boolExp <- pgColValToBoolExp tn $ _fArguments fld
annFlds <- fromSelSet (_fType fld) $ _fSelSet fld
:: (MonadError QErr m, MonadReader r m, Has FieldMap r, Has OrdByResolveCtx r)
=> ((PGColType, PGColValue) -> m S.SQLExp)
-> QualifiedTable -> S.BoolExp -> Field -> m RS.AnnSel
fromFieldByPKey f tn permFilter fld = fieldAsPath fld $ do
boolExp <- pgColValToBoolExpG f tn $ _fArguments fld
annFlds <- fromSelSet f (_fType fld) $ _fSelSet fld
return $ RS.AnnSel annFlds tn Nothing permFilter Nothing $
RS.noTableArgs { RS._taWhere = Just boolExp}
@@ -119,7 +130,7 @@ convertSelect
:: QualifiedTable -> S.BoolExp -> Maybe Int -> Field -> Convert RespTx
convertSelect qt permFilter permLimit fld = do
selData <- withPathK "selectionSet" $
fromField qt permFilter permLimit fld
fromField prepare qt permFilter permLimit fld
prepArgs <- get
return $ RS.selectP2 False (selData, prepArgs)
@@ -127,6 +138,6 @@ convertSelectByPKey
:: QualifiedTable -> S.BoolExp -> Field -> Convert RespTx
convertSelectByPKey qt permFilter fld = do
selData <- withPathK "selectionSet" $
fromFieldByPKey qt permFilter fld
fromFieldByPKey prepare qt permFilter fld
prepArgs <- get
return $ RS.selectP2 True (selData, prepArgs)

View File

@@ -62,7 +62,6 @@ import Data.Aeson.Casing
import Data.Aeson.TH
import Language.Haskell.TH.Syntax (Lift)
import qualified Data.ByteString.Builder as BB
import qualified Data.HashSet as HS
import qualified Data.Text as T
@@ -90,8 +89,8 @@ buildViewName (QualifiedTable sn tn) (RoleName rTxt) pt =
buildView :: QualifiedTable -> QualifiedTable -> Q.Query
buildView tn vn =
Q.fromBuilder $ mconcat
[ BB.string7 "CREATE VIEW " <> toSQL vn
, BB.string7 " AS SELECT * FROM " <> toSQL tn
[ "CREATE VIEW " <> toSQL vn
, " AS SELECT * FROM " <> toSQL tn
]
dropView :: QualifiedTable -> Q.Tx ()
@@ -99,7 +98,7 @@ dropView vn =
Q.unitQ dropViewS () False
where
dropViewS = Q.fromBuilder $
BB.string7 "DROP VIEW " <> toSQL vn
"DROP VIEW " <> toSQL vn
buildInsPermInfo
:: (QErrM m, CacheRM m)

View File

@@ -20,9 +20,7 @@ import Data.Aeson.Types
import Instances.TH.Lift ()
import Language.Haskell.TH.Syntax (Lift)
import qualified Data.ByteString.Builder as BB
import qualified Data.HashMap.Strict as M
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Extended as T
import Hasura.Prelude
@@ -233,17 +231,18 @@ convFilterExp tq be =
injectDefaults :: QualifiedTable -> QualifiedTable -> Q.Query
injectDefaults qv qt =
Q.fromBuilder $ mconcat
[ BB.string7 "SELECT hdb_catalog.inject_table_defaults("
, TE.encodeUtf8Builder $ pgFmtLit vsn
, BB.string7 ", "
, TE.encodeUtf8Builder $ pgFmtLit vn
, BB.string7 ", "
, TE.encodeUtf8Builder $ pgFmtLit tsn
, BB.string7 ", "
, TE.encodeUtf8Builder $ pgFmtLit tn
, BB.string7 ");"
Q.fromText $ mconcat
[ "SELECT hdb_catalog.inject_table_defaults("
, pgFmtLit vsn
, ", "
, pgFmtLit vn
, ", "
, pgFmtLit tsn
, ", "
, pgFmtLit tn
, ");"
]
where
QualifiedTable (SchemaName vsn) (TableName vn) = qv
QualifiedTable (SchemaName tsn) (TableName tn) = qt

View File

@@ -14,22 +14,21 @@ import qualified Database.PG.Query as Q
import qualified Hasura.SQL.DML as S
import qualified Data.Aeson as J
import qualified Data.ByteString.Builder as BB
import qualified Data.FileEmbed as FE
import qualified Data.Text as T
buildInsTrig :: QualifiedTable -> Q.Query
buildInsTrig qt@(QualifiedTable _ tn) =
Q.fromBuilder $ mconcat
[ BB.string7 "CREATE TRIGGER " <> toSQL tn
, BB.string7 " INSTEAD OF INSERT ON " <> toSQL qt
, BB.string7 " FOR EACH ROW EXECUTE PROCEDURE "
, toSQL qt <> BB.string7 "();"
[ "CREATE TRIGGER " <> toSQL tn
, " INSTEAD OF INSERT ON " <> toSQL qt
, " FOR EACH ROW EXECUTE PROCEDURE "
, toSQL qt <> "();"
]
dropInsTrigFn :: QualifiedTable -> Q.Query
dropInsTrigFn fn =
Q.fromBuilder $ BB.string7 "DROP FUNCTION " <> toSQL fn <> "()"
Q.fromBuilder $ "DROP FUNCTION " <> toSQL fn <> "()"
getInsTrigTmplt :: (MonadError QErr m) => m GingerTmplt
getInsTrigTmplt =
@@ -45,8 +44,7 @@ buildInsTrigFn
=> QualifiedTable -> QualifiedTable -> S.BoolExp -> m Q.Query
buildInsTrigFn fn tn be = do
insTmplt <- getInsTrigTmplt
return $ Q.fromBuilder $ BB.string7 $ T.unpack $
renderGingerTmplt tmpltVals insTmplt
return $ Q.fromText $ renderGingerTmplt tmpltVals insTmplt
where
tmpltVals = J.object [ "function_name" J..= toSQLTxt fn
, "table_name" J..= toSQLTxt tn

View File

@@ -453,7 +453,7 @@ runSqlP2 (RunSQL t cascade) = do
oldMetaU <- liftTx $ Q.catchE defaultTxErrorHandler fetchTableMeta
-- Run the SQL
res <- liftTx $ Q.multiQE rawSqlErrHandler $ Q.fromBuilder $ TE.encodeUtf8Builder t
res <- liftTx $ Q.multiQE rawSqlErrHandler $ Q.fromText t
-- Get the metadata after the sql query
newMeta <- liftTx $ Q.catchE defaultTxErrorHandler fetchTableMeta

View File

@@ -107,7 +107,7 @@ mkTriggerQ trid trn qt allCols (TriggerOpsDef insert update delete) = do
<> getTriggerSql UPDATE trid trn qt allCols update
<> getTriggerSql DELETE trid trn qt allCols delete
case msql of
Just sql -> Q.multiQE defaultTxErrorHandler (Q.fromBuilder $ TE.encodeUtf8Builder sql)
Just sql -> Q.multiQE defaultTxErrorHandler (Q.fromText sql)
Nothing -> throw500 "no trigger sql generated"
addEventTriggerToCatalog
@@ -139,7 +139,7 @@ delEventTriggerFromCatalog trn = do
mapM_ tx [INSERT, UPDATE, DELETE]
where
tx :: Ops -> Q.TxE QErr ()
tx op = Q.multiQE defaultTxErrorHandler (Q.fromBuilder $ TE.encodeUtf8Builder $ getDropFuncSql op trn)
tx op = Q.multiQE defaultTxErrorHandler (Q.fromText $ getDropFuncSql op trn)
updateEventTriggerToCatalog
:: QualifiedTable

View File

@@ -4,14 +4,14 @@ module Hasura.RQL.DDL.Utils
( clearHdbViews
) where
import qualified Data.ByteString.Builder as BB
import qualified Database.PG.Query as Q
import Hasura.Prelude ((<>))
import qualified Data.Text as T
import qualified Database.PG.Query as Q
import Hasura.Prelude ((<>))
clearHdbViews :: Q.Tx ()
clearHdbViews = Q.multiQ (Q.fromBuilder (clearHdbOnlyViews <> clearHdbViewsFunc))
clearHdbViews = Q.multiQ (Q.fromText (clearHdbOnlyViews <> clearHdbViewsFunc))
clearHdbOnlyViews :: BB.Builder
clearHdbOnlyViews :: T.Text
clearHdbOnlyViews =
"DO $$ DECLARE \
\ r RECORD; \
@@ -22,7 +22,7 @@ clearHdbOnlyViews =
\ END $$; "
clearHdbViewsFunc :: BB.Builder
clearHdbViewsFunc :: T.Text
clearHdbViewsFunc =
"DO $$ DECLARE \
\ _sql text; \

View File

@@ -1,57 +0,0 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Hasura.RQL.DML.Explain where
import Data.Aeson
import Data.Aeson.Casing
import Data.Aeson.TH
import qualified Data.ByteString.Builder as BB
import Hasura.Prelude
import Hasura.RQL.DML.Internal
import Hasura.RQL.DML.Select
import Hasura.RQL.GBoolExp
import Hasura.RQL.Types
import Hasura.SQL.Types
import qualified Data.String.Conversions as CS
import qualified Data.Text as T
import qualified Database.PG.Query as Q
data RQLExplain =
RQLExplain
{ rqleQuery :: !SelectQuery
, rqleRole :: !RoleName
, rqleHeaders :: !HeaderObj
} deriving (Show, Eq)
$(deriveJSON (aesonDrop 4 camelCase) ''RQLExplain)
data ExplainResp =
ExplainResp
{ erSql :: !T.Text
, erPlans :: !Value
} deriving (Show, Eq)
$(deriveJSON (aesonDrop 2 camelCase) ''ExplainResp)
phaseOneExplain :: SelectQuery -> P1 AnnSel
phaseOneExplain = convSelectQuery txtRHSBuilder
phaseTwoExplain :: (P2C m) => AnnSel -> m RespBody
phaseTwoExplain sel = do
planResp <- liftTx $ runIdentity . Q.getRow <$> Q.rawQE dmlTxErrorHandler (Q.fromBuilder withExplain) [] True
plans <- decodeBS planResp
return $ encode $ ExplainResp selectSQLT plans
where
selectSQL = toSQL $ mkSQLSelect False sel
explainSQL = BB.string7 "EXPLAIN (FORMAT JSON) "
withExplain = explainSQL <> selectSQL
decodeBS bs = case eitherDecode bs of
Left e -> throw500 $
"Plan query response is invalid json; " <> T.pack e
Right a -> return a
selectSQLT = CS.cs $ BB.toLazyByteString selectSQL

View File

@@ -10,7 +10,6 @@ import Data.Aeson.Types
import Instances.TH.Lift ()
import qualified Data.Aeson.Text as AT
import qualified Data.ByteString.Builder as BB
import qualified Data.HashMap.Strict as HM
import qualified Data.HashSet as HS
import qualified Data.Sequence as DS
@@ -250,7 +249,7 @@ setConflictCtx :: Maybe ConflictCtx -> Q.TxE QErr ()
setConflictCtx conflictCtxM = do
let t = maybe "null" conflictCtxToJSON conflictCtxM
setVal = toSQL $ S.SELit t
setVar = BB.string7 "SET LOCAL hasura.conflict_clause = "
setVar = "SET LOCAL hasura.conflict_clause = "
q = Q.fromBuilder $ setVar <> setVal
Q.unitQE defaultTxErrorHandler q () False
where

View File

@@ -3,6 +3,7 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Hasura.RQL.Types.Permission
( RoleName(..)
@@ -22,6 +23,9 @@ import Hasura.SQL.Types
import qualified Database.PG.Query as Q
import Data.Aeson
import qualified Data.Aeson as J
import qualified Data.Aeson.Casing as J
import qualified Data.Aeson.TH as J
import Data.Hashable
import Data.Word
import Instances.TH.Lift ()
@@ -56,6 +60,10 @@ data UserInfo
instance Hashable UserInfo
$(J.deriveJSON (J.aesonDrop 4 J.camelCase){J.omitNothingFields=True}
''UserInfo
)
adminUserInfo :: UserInfo
adminUserInfo = UserInfo adminRole Map.empty

View File

@@ -9,19 +9,19 @@ module Hasura.SQL.DML where
import Hasura.Prelude
import Hasura.SQL.Types
import Data.String (fromString)
import Language.Haskell.TH.Syntax (Lift)
import qualified Data.ByteString.Builder as BB
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Extended as T
import qualified Text.Builder as TB
infixr 6 <->
(<->) :: BB.Builder -> BB.Builder -> BB.Builder
(<->) l r = l <> BB.char7 ' ' <> r
(<->) :: TB.Builder -> TB.Builder -> TB.Builder
(<->) l r = l <> TB.char ' ' <> r
{-# INLINE (<->) #-}
paren :: BB.Builder -> BB.Builder
paren t = BB.char7 '(' <> t <> BB.char7 ')'
paren :: TB.Builder -> TB.Builder
paren t = TB.char '(' <> t <> TB.char ')'
{-# INLINE paren #-}
data Select
@@ -48,7 +48,7 @@ newtype LimitExp
instance ToSQL LimitExp where
toSQL (LimitExp se) =
BB.string7 "LIMIT" <-> toSQL se
"LIMIT" <-> toSQL se
newtype OffsetExp
= OffsetExp SQLExp
@@ -56,7 +56,7 @@ newtype OffsetExp
instance ToSQL OffsetExp where
toSQL (OffsetExp se) =
BB.string7 "OFFSET" <-> toSQL se
"OFFSET" <-> toSQL se
newtype OrderByExp
= OrderByExp [OrderByItem]
@@ -78,8 +78,8 @@ data OrderType = OTAsc
deriving (Show, Eq, Lift)
instance ToSQL OrderType where
toSQL OTAsc = BB.string7 "ASC"
toSQL OTDesc = BB.string7 "DESC"
toSQL OTAsc = "ASC"
toSQL OTDesc = "DESC"
data NullsOrder
= NFirst
@@ -87,12 +87,12 @@ data NullsOrder
deriving (Show, Eq, Lift)
instance ToSQL NullsOrder where
toSQL NFirst = BB.string7 "NULLS FIRST"
toSQL NLast = BB.string7 "NULLS LAST"
toSQL NFirst = "NULLS FIRST"
toSQL NLast = "NULLS LAST"
instance ToSQL OrderByExp where
toSQL (OrderByExp l) =
BB.string7 "ORDER BY" <-> (", " <+> l)
"ORDER BY" <-> (", " <+> l)
newtype GroupByExp
= GroupByExp [SQLExp]
@@ -100,7 +100,7 @@ newtype GroupByExp
instance ToSQL GroupByExp where
toSQL (GroupByExp idens) =
BB.string7 "GROUP BY" <-> (", " <+> idens)
"GROUP BY" <-> (", " <+> idens)
newtype FromExp
= FromExp [FromItem]
@@ -108,7 +108,7 @@ newtype FromExp
instance ToSQL FromExp where
toSQL (FromExp items) =
BB.string7 "FROM" <-> (", " <+> items)
"FROM" <-> (", " <+> items)
mkIdenFromExp :: (IsIden a) => a -> FromExp
mkIdenFromExp a =
@@ -145,7 +145,7 @@ newtype HavingExp
instance ToSQL HavingExp where
toSQL (HavingExp be) =
BB.string7 "HAVING" <-> toSQL be
"HAVING" <-> toSQL be
newtype WhereFrag
= WhereFrag { getWFBoolExp :: BoolExp }
@@ -153,11 +153,11 @@ newtype WhereFrag
instance ToSQL WhereFrag where
toSQL (WhereFrag be) =
BB.string7 "WHERE" <-> paren (toSQL be)
"WHERE" <-> paren (toSQL be)
instance ToSQL Select where
toSQL sel =
BB.string7 "SELECT"
"SELECT"
<-> toSQL (selDistinct sel)
<-> (", " <+> selExtr sel)
<-> toSQL (selFrom sel)
@@ -184,10 +184,9 @@ mkQual :: QualifiedTable -> Qual
mkQual = QualTable
instance ToSQL Qual where
toSQL (QualIden i) = toSQL i
toSQL (QualIden i) = toSQL i
toSQL (QualTable qt) = toSQL qt
toSQL (QualVar v) =
TE.encodeUtf8Builder v
toSQL (QualVar v) = TB.text v
mkQIden :: (IsIden a, IsIden b) => a -> b -> QIden
mkQIden q t = QIden (QualIden (toIden q)) (toIden t)
@@ -198,7 +197,7 @@ data QIden
instance ToSQL QIden where
toSQL (QIden qual iden) =
mconcat [toSQL qual, BB.char7 '.', toSQL iden]
mconcat [toSQL qual, TB.char '.', toSQL iden]
newtype SQLOp
= SQLOp {sqlOpTxt :: T.Text}
@@ -272,15 +271,15 @@ toAlias = Alias . toIden
instance ToSQL SQLExp where
toSQL (SEPrep argNumber) =
BB.char7 '$' <> BB.intDec argNumber
TB.char '$' <> fromString (show argNumber)
toSQL (SELit tv) =
TE.encodeUtf8Builder $ pgFmtLit tv
TB.text $ pgFmtLit tv
toSQL (SEUnsafe t) =
TE.encodeUtf8Builder t
TB.text t
toSQL (SESelect se) =
paren $ toSQL se
toSQL SEStar =
BB.char7 '*'
TB.char '*'
toSQL (SEIden iden) =
toSQL iden
toSQL (SERowIden iden) =
@@ -289,22 +288,21 @@ instance ToSQL SQLExp where
toSQL qIden
-- https://www.postgresql.org/docs/10/static/sql-expressions.html#SYNTAX-AGGREGATES
toSQL (SEFnApp name args mObe) =
TE.encodeUtf8Builder name <> paren ((", " <+> args) <-> toSQL mObe)
TB.text name <> paren ((", " <+> args) <-> toSQL mObe)
toSQL (SEOpApp op args) =
paren (sqlOpTxt op <+> args)
toSQL (SETyAnn e ty) =
paren (toSQL e) <> BB.string7 "::"
<> TE.encodeUtf8Builder (unAnnType ty)
paren (toSQL e) <> "::" <> TB.text (unAnnType ty)
toSQL (SECond cond te fe) =
BB.string7 "CASE WHEN" <-> toSQL cond <->
BB.string7 "THEN" <-> toSQL te <->
BB.string7 "ELSE" <-> toSQL fe <->
BB.string7 "END"
"CASE WHEN" <-> toSQL cond <->
"THEN" <-> toSQL te <->
"ELSE" <-> toSQL fe <->
"END"
toSQL (SEBool be) = toSQL be
toSQL (SEExcluded t) = BB.string7 "EXCLUDED."
toSQL (SEExcluded t) = "EXCLUDED."
<> toSQL (PGCol t)
toSQL (SEArray exps) = BB.string7 "ARRAY" <> BB.char7 '['
<> (", " <+> exps) <> BB.char7 ']'
toSQL (SEArray exps) = "ARRAY" <> TB.char '['
<> (", " <+> exps) <> TB.char ']'
intToSQLExp :: Int -> SQLExp
intToSQLExp =
@@ -347,9 +345,9 @@ data DistinctExpr
deriving (Show, Eq)
instance ToSQL DistinctExpr where
toSQL DistinctSimple = BB.string7 "DISTINCT"
toSQL DistinctSimple = "DISTINCT"
toSQL (DistinctOn exps) =
BB.string7 "DISTINCT ON" <-> paren ("," <+> exps)
"DISTINCT ON" <-> paren ("," <+> exps)
data FromItem
= FISimple !QualifiedTable !(Maybe Alias)
@@ -378,7 +376,7 @@ newtype Lateral = Lateral Bool
deriving (Show, Eq)
instance ToSQL Lateral where
toSQL (Lateral True) = BB.string7 "LATERAL"
toSQL (Lateral True) = "LATERAL"
toSQL (Lateral False) = mempty
data JoinExpr
@@ -404,10 +402,10 @@ data JoinType
deriving (Eq, Show)
instance ToSQL JoinType where
toSQL Inner = BB.string7 "INNER JOIN"
toSQL LeftOuter = BB.string7 "LEFT OUTER JOIN"
toSQL RightOuter = BB.string7 "RIGHT OUTER JOIN"
toSQL FullOuter = BB.string7 "FULL OUTER JOIN"
toSQL Inner = "INNER JOIN"
toSQL LeftOuter = "LEFT OUTER JOIN"
toSQL RightOuter = "RIGHT OUTER JOIN"
toSQL FullOuter = "FULL OUTER JOIN"
data JoinCond
= JoinOn !BoolExp
@@ -416,9 +414,9 @@ data JoinCond
instance ToSQL JoinCond where
toSQL (JoinOn be) =
BB.string7 "ON" <-> paren (toSQL be)
"ON" <-> paren (toSQL be)
toSQL (JoinUsing cols) =
BB.string7 "USING" <-> paren ("," <+> cols)
"USING" <-> paren ("," <+> cols)
data BoolExp
= BELit !Bool
@@ -458,28 +456,28 @@ mkExists qt whereFrag =
}
instance ToSQL BoolExp where
toSQL (BELit True) = TE.encodeUtf8Builder $ T.squote "true"
toSQL (BELit False) = TE.encodeUtf8Builder $ T.squote "false"
toSQL (BELit True) = TB.text $ T.squote "true"
toSQL (BELit False) = TB.text $ T.squote "false"
toSQL (BEBin bo bel ber) =
paren (toSQL bel) <-> toSQL bo <-> paren (toSQL ber)
toSQL (BENot be) =
BB.string7 "NOT" <-> paren (toSQL be)
"NOT" <-> paren (toSQL be)
toSQL (BECompare co vl vr) =
paren (toSQL vl) <-> toSQL co <-> paren (toSQL vr)
toSQL (BENull v) =
paren (toSQL v) <-> BB.string7 "IS NULL"
paren (toSQL v) <-> "IS NULL"
toSQL (BENotNull v) =
paren (toSQL v) <-> BB.string7 "IS NOT NULL"
paren (toSQL v) <-> "IS NOT NULL"
toSQL (BEExists sel) =
BB.string7 "EXISTS " <-> paren (toSQL sel)
"EXISTS " <-> paren (toSQL sel)
data BinOp = AndOp
| OrOp
deriving (Show, Eq)
instance ToSQL BinOp where
toSQL AndOp = BB.string7 "AND"
toSQL OrOp = BB.string7 "OR"
toSQL AndOp = "AND"
toSQL OrOp = "OR"
data CompareOp
= SEQ
@@ -526,7 +524,7 @@ instance Show CompareOp where
SHasKeysAll -> "?&"
instance ToSQL CompareOp where
toSQL = BB.string7 . show
toSQL = fromString . show
buildInsVal :: PGCol -> Int -> (PGCol, SQLExp)
buildInsVal colName argNumber =
@@ -568,7 +566,7 @@ newtype UsingExp = UsingExp [TableName]
instance ToSQL UsingExp where
toSQL (UsingExp tables)
= BB.string7 "USING" <-> "," <+> tables
= "USING" <-> "," <+> tables
newtype RetExp = RetExp [Extractor]
deriving (Show, Eq)
@@ -583,17 +581,17 @@ instance ToSQL RetExp where
toSQL (RetExp [])
= mempty
toSQL (RetExp exps)
= BB.string7 "RETURNING" <-> (", " <+> exps)
= "RETURNING" <-> (", " <+> exps)
instance ToSQL SQLDelete where
toSQL sd = BB.string7 "DELETE FROM"
toSQL sd = "DELETE FROM"
<-> toSQL (delTable sd)
<-> toSQL (delUsing sd)
<-> toSQL (delWhere sd)
<-> toSQL (delRet sd)
instance ToSQL SQLUpdate where
toSQL a = BB.string7 "UPDATE"
toSQL a = "UPDATE"
<-> toSQL (upTable a)
<-> toSQL (upSet a)
<-> toSQL (upFrom a)
@@ -602,7 +600,7 @@ instance ToSQL SQLUpdate where
instance ToSQL SetExp where
toSQL (SetExp cvs) =
BB.string7 "SET" <-> ("," <+> cvs)
"SET" <-> ("," <+> cvs)
instance ToSQL SetExpItem where
toSQL (SetExpItem (col, val)) =
@@ -615,11 +613,11 @@ data SQLConflictTarget
deriving (Show, Eq)
instance ToSQL SQLConflictTarget where
toSQL (SQLColumn cols) = BB.string7 "("
toSQL (SQLColumn cols) = "("
<-> ("," <+> cols)
<-> BB.string7 ")"
<-> ")"
toSQL (SQLConstraint cons) = BB.string7 "ON CONSTRAINT" <-> toSQL cons
toSQL (SQLConstraint cons) = "ON CONSTRAINT" <-> toSQL cons
data SQLConflict
= DoNothing !(Maybe SQLConflictTarget)
@@ -627,11 +625,11 @@ data SQLConflict
deriving (Show, Eq)
instance ToSQL SQLConflict where
toSQL (DoNothing Nothing) = BB.string7 "ON CONFLICT DO NOTHING"
toSQL (DoNothing (Just ct)) = BB.string7 "ON CONFLICT"
toSQL (DoNothing Nothing) = "ON CONFLICT DO NOTHING"
toSQL (DoNothing (Just ct)) = "ON CONFLICT"
<-> toSQL ct
<-> BB.string7 "DO NOTHING"
toSQL (Update ct ex) = BB.string7 "ON CONFLICT"
<-> "DO NOTHING"
toSQL (Update ct ex) = "ON CONFLICT"
<-> toSQL ct <-> "DO UPDATE"
<-> toSQL ex
@@ -646,13 +644,13 @@ data SQLInsert = SQLInsert
instance ToSQL SQLInsert where
toSQL si =
let insTuples = flip map (siTuples si) $ \tupVals ->
BB.string7 "(" <-> (", " <+> tupVals) <-> BB.string7 ")"
insConflict = maybe (BB.string7 "") toSQL
"(" <-> (", " <+> tupVals) <-> ")"
insConflict = maybe "" toSQL
in "INSERT INTO"
<-> toSQL (siTable si)
<-> BB.string7 "("
<-> "("
<-> (", " <+> siCols si)
<-> BB.string7 ") VALUES"
<-> ") VALUES"
<-> (", " <+> insTuples)
<-> insConflict (siConflict si)
<-> toSQL (siRet si)

View File

@@ -10,37 +10,32 @@ import qualified Database.PG.Query as Q
import qualified Database.PG.Query.PTI as PTI
import Hasura.Prelude
import Hasura.Server.Utils (bsToTxt)
import Data.Aeson
import Data.Aeson.Encoding (text)
import Data.String (fromString)
import Instances.TH.Lift ()
import Language.Haskell.TH.Syntax (Lift)
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Extended as T
import qualified Database.PostgreSQL.LibPQ as PQ
import qualified PostgreSQL.Binary.Decoding as PD
import qualified Text.Builder as TB
class ToSQL a where
toSQL :: a -> BB.Builder
toSQL :: a -> TB.Builder
instance ToSQL BB.Builder where
instance ToSQL TB.Builder where
toSQL x = x
-- instance ToSQL T.Text where
-- toSQL x = TE.encodeUtf8Builder x
toSQLTxt :: (ToSQL a) => a -> T.Text
toSQLTxt = bsToTxt . BL.toStrict . BB.toLazyByteString . toSQL
toSQLTxt = TB.run . toSQL
infixr 6 <+>
(<+>) :: (ToSQL a) => T.Text -> [a] -> BB.Builder
(<+>) :: (ToSQL a) => T.Text -> [a] -> TB.Builder
(<+>) _ [] = mempty
(<+>) kat (x:xs) =
toSQL x <> mconcat [ TE.encodeUtf8Builder kat <> toSQL x' | x' <- xs ]
toSQL x <> mconcat [ TB.text kat <> toSQL x' | x' <- xs ]
{-# INLINE (<+>) #-}
newtype Iden
@@ -49,7 +44,7 @@ newtype Iden
instance ToSQL Iden where
toSQL (Iden t) =
TE.encodeUtf8Builder $ pgFmtIden t
TB.text $ pgFmtIden t
class IsIden a where
toIden :: a -> Iden
@@ -192,7 +187,7 @@ instance Hashable QualifiedTable
instance ToSQL QualifiedTable where
toSQL (QualifiedTable sn tn) =
toSQL sn <> BB.string7 "." <> toSQL tn
toSQL sn <> "." <> toSQL tn
qualTableToTxt :: QualifiedTable -> T.Text
qualTableToTxt (QualifiedTable (SchemaName "public") tn) =
@@ -268,7 +263,7 @@ instance ToJSON PGColType where
toJSON pct = String $ T.pack $ show pct
instance ToSQL PGColType where
toSQL pct = BB.string7 $ show pct
toSQL pct = fromString $ show pct
instance FromJSON PGColType where
parseJSON (String "serial") = return PGSerial

View File

@@ -30,6 +30,7 @@ import qualified Network.HTTP.Client as HTTP
import qualified Network.Wai.Middleware.Static as MS
import qualified Database.PG.Query as Q
import qualified Hasura.GraphQL.Explain as GE
import qualified Hasura.GraphQL.Schema as GS
import qualified Hasura.GraphQL.Transport.HTTP as GH
import qualified Hasura.GraphQL.Transport.HTTP.Protocol as GH
@@ -189,22 +190,6 @@ withLock lk action = do
acquireLock = liftIO $ takeMVar lk
releaseLock = liftIO $ putMVar lk ()
-- v1ExplainHandler :: RQLExplain -> Handler BL.ByteString
-- v1ExplainHandler expQuery = dbAction
-- where
-- dbAction = do
-- onlyAdmin
-- scRef <- scCacheRef . hcServerCtx <$> ask
-- schemaCache <- liftIO $ readIORef scRef
-- pool <- scPGPool . hcServerCtx <$> ask
-- isoL <- scIsolation . hcServerCtx <$> ask
-- runExplainQuery pool isoL userInfo (fst schemaCache) selectQ
-- selectQ = rqleQuery expQuery
-- role = rqleRole expQuery
-- headers = M.toList $ rqleHeaders expQuery
-- userInfo = UserInfo role headers
v1QueryHandler :: RQLQuery -> Handler BL.ByteString
v1QueryHandler query = do
lk <- scCacheLock . hcServerCtx <$> ask
@@ -237,12 +222,14 @@ v1Alpha1GQHandler query = do
isoL <- scIsolation . hcServerCtx <$> ask
GH.runGQ pool isoL userInfo (snd cache) query
-- v1Alpha1GQSchemaHandler :: Handler BL.ByteString
-- v1Alpha1GQSchemaHandler = do
-- scRef <- scCacheRef . hcServerCtx <$> ask
-- schemaCache <- liftIO $ readIORef scRef
-- onlyAdmin
-- GS.generateGSchemaH schemaCache
gqlExplainHandler :: GE.GQLExplain -> Handler BL.ByteString
gqlExplainHandler query = do
onlyAdmin
scRef <- scCacheRef . hcServerCtx <$> ask
cache <- liftIO $ readIORef scRef
pool <- scPGPool . hcServerCtx <$> ask
isoL <- scIsolation . hcServerCtx <$> ask
GE.explainGQLQuery pool isoL (snd cache) query
newtype QueryParser
= QueryParser { getQueryParser :: QualifiedTable -> Handler RQLQuery }
@@ -332,9 +319,9 @@ httpApp mRootDir corsCfg serverCtx enableConsole = do
query <- parseBody
v1QueryHandler query
-- post "v1/query/explain" $ mkSpockAction encodeQErr serverCtx $ do
-- expQuery <- parseBody
-- v1ExplainHandler expQuery
post "v1alpha1/graphql/explain" $ mkSpockAction encodeQErr serverCtx $ do
expQuery <- parseBody
gqlExplainHandler expQuery
post "v1alpha1/graphql" $ mkSpockAction GH.encodeGQErr serverCtx $ do
query <- parseBody

View File

@@ -14,7 +14,6 @@ import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as Map
import qualified Data.Sequence as Seq
import qualified Data.Text as T
import qualified Data.Vector as V
import Hasura.Prelude
@@ -23,7 +22,6 @@ import Hasura.RQL.DDL.Permission
import Hasura.RQL.DDL.QueryTemplate
import Hasura.RQL.DDL.Relationship
import Hasura.RQL.DDL.Schema.Table
import Hasura.RQL.DML.Explain
import Hasura.RQL.DML.QueryTemplate
import Hasura.RQL.DML.Returning (encodeJSONVector)
import Hasura.RQL.Types
@@ -32,20 +30,6 @@ import Hasura.SQL.Types
import qualified Database.PG.Query as Q
-- data QueryWithTxId
-- = QueryWithTxId
-- { qtTxId :: !(Maybe TxId)
-- , qtQuery :: !RQLQuery
-- } deriving (Show, Eq)
-- instance FromJSON QueryWithTxId where
-- parseJSON v@(Object o) =
-- QueryWithTxId
-- <$> o .:! "transaction_id"
-- <*> parseJSON v
-- parseJSON _ =
-- fail "expecting on object for query"
data RQLQuery
= RQAddExistingTableOrView !TrackTable
| RQTrackTable !TrackTable
@@ -124,27 +108,6 @@ runQuery pool isoL userInfo sc query = do
setHeadersTx userInfo >> tx
liftEither res
buildExplainTx
:: UserInfo
-> SchemaCache
-> SelectQuery
-> Either QErr (Q.TxE QErr BL.ByteString)
buildExplainTx userInfo sc q = do
p1Res <- withPathK "query" $ runP1 qEnv $ phaseOneExplain q
res <- return $ flip runReaderT (qcUserInfo qEnv) $
flip runStateT sc $ withPathK "query" $ phaseTwoExplain p1Res
return $ fst <$> res
where
qEnv = QCtx userInfo sc
runExplainQuery
:: Q.PGPool -> Q.TxIsolation
-> UserInfo -> SchemaCache
-> SelectQuery -> ExceptT QErr IO BL.ByteString
runExplainQuery pool isoL userInfo sc query = do
tx <- liftEither $ buildExplainTx userInfo sc query
Q.runTx pool (isoL, Nothing) $ setHeadersTx userInfo >> tx
queryNeedsReload :: RQLQuery -> Bool
queryNeedsReload qi = case qi of
RQAddExistingTableOrView q -> queryModifiesSchema q
@@ -261,6 +224,5 @@ setHeadersTx userInfo =
where
hdrs = Map.toList $ Map.delete accessKeyHeader
$ userHeaders userInfo
mkQ (h, v) = Q.fromBuilder $ BB.string7 $
T.unpack $
mkQ (h, v) = Q.fromText $
"SET LOCAL hasura." <> dropAndSnakeCase h <> " = " <> pgFmtLit v

View File

@@ -2,12 +2,14 @@
# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2)
# resolver: lts-10.8
resolver: lts-12.12
resolver: lts-12.13
compiler: ghc-8.4.4
# Local packages, usually specified by relative directory name
packages:
- '.'
# - '../../graphql-parser-hs'
# - '../../pg-client-hs'
# - extra-libs/aeson
# - extra-libs/logger/wai-logger
@@ -15,11 +17,17 @@ packages:
extra-deps:
# use https URLs so that build systems can clone these repos
- git: https://github.com/hasura/pg-client-hs.git
commit: 7978e04f24790f18f06a67fe6065f470abc1c764
commit: 47b168d252d4adc800137a8b2cd3fc977cb3468d
- git: https://github.com/hasura/graphql-parser-hs.git
commit: eae59812ec537b3756c3ddb5f59a7cc59508869b
- ginger-0.8.0.1
# for text-builder
- text-builder-0.6.4
- deferred-folds-0.9.9
- primitive-0.6.4.0
# Override default flag values for local packages and extra-deps
flags: {}