mirror of
https://github.com/zhigang1992/graphql-engine.git
synced 2026-01-12 22:47:35 +08:00
This commit is contained in:
committed by
Shahidh K Muhammed
parent
82e8d45bf9
commit
ac537330d0
6037
console/package-lock.json
generated
6037
console/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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
|
||||
|
||||
122
server/src-lib/Hasura/GraphQL/Explain.hs
Normal file
122
server/src-lib/Hasura/GraphQL/Explain.hs
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; \
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user