mssql: support query multiplexing in subscriptions

GitOrigin-RevId: 757ceba2c1cdb1107ce0b0e41d2e70ac795d0d73
This commit is contained in:
Abby Sassel 2021-04-20 17:57:14 +01:00 committed by hasura-bot
parent 78abc5423a
commit 703928de9f
24 changed files with 378 additions and 199 deletions

View File

@ -14,6 +14,7 @@ only when there are enough present in the items inventory.
### Bug fixes and improvements
- server: support query multiplexing in MSSQL subscriptions
- console: add bigquery support (#1000)
- cli: add support for bigquery in metadata operations

View File

@ -356,9 +356,9 @@ library
, Hasura.Backends.MSSQL.Connection
, Hasura.Backends.MSSQL.DDL
, Hasura.Backends.MSSQL.DDL.BoolExp
, Hasura.Backends.MSSQL.DDL.RunSQL
, Hasura.Backends.MSSQL.DDL.Source
, Hasura.Backends.MSSQL.DDL.BoolExp
, Hasura.Backends.MSSQL.FromIr
, Hasura.Backends.MSSQL.Instances.Execute
, Hasura.Backends.MSSQL.Instances.Metadata
@ -367,6 +367,7 @@ library
, Hasura.Backends.MSSQL.Instances.Types
, Hasura.Backends.MSSQL.Meta
, Hasura.Backends.MSSQL.Plan
, Hasura.Backends.MSSQL.SQL.Value
, Hasura.Backends.MSSQL.ToQuery
, Hasura.Backends.MSSQL.Types
, Hasura.Backends.MSSQL.Types.Instances
@ -571,6 +572,7 @@ library
, Hasura.SQL.GeoJSON
, Hasura.SQL.Time
, Hasura.SQL.Types
, Hasura.SQL.Value
, Hasura.SQL.WKT
, Hasura.Tracing
, Network.HTTP.Client.Extended

View File

@ -1,23 +1,27 @@
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Hasura.Backends.MSSQL.Instances.Execute (NoMultiplex(..)) where
module Hasura.Backends.MSSQL.Instances.Execute (MultiplexedQuery'(..), multiplexRootReselect) where
import Hasura.Prelude
import qualified Data.Aeson.Extended as J
import qualified Data.Environment as Env
import qualified Data.HashMap.Strict.InsOrd as OMap
import qualified Data.List.NonEmpty as NE
import qualified Data.Text.Extended as T
import qualified Database.ODBC.SQLServer as ODBC
import qualified Language.GraphQL.Draft.Syntax as G
import qualified Network.HTTP.Client as HTTP
import qualified Network.HTTP.Types as HTTP
import Data.Text.Extended
import qualified Hasura.SQL.AnyBackend as AB
import Hasura.Backends.MSSQL.Connection
import Hasura.Backends.MSSQL.FromIr as TSQL
import Hasura.Backends.MSSQL.Plan
import Hasura.Backends.MSSQL.SQL.Value (toTxtEncodedVal)
import Hasura.Backends.MSSQL.ToQuery
import Hasura.Backends.MSSQL.Types as TSQL
import Hasura.EncJSON
import Hasura.GraphQL.Context
import Hasura.GraphQL.Execute.Backend
@ -29,7 +33,7 @@ import Hasura.Session
instance BackendExecute 'MSSQL where
type PreparedQuery 'MSSQL = Text
type MultiplexedQuery 'MSSQL = NoMultiplex
type MultiplexedQuery 'MSSQL = MultiplexedQuery'
type ExecutionMonad 'MSSQL = ExceptT QErr IO
getRemoteJoins = const []
@ -39,12 +43,12 @@ instance BackendExecute 'MSSQL where
mkDBQueryExplain = msDBQueryExplain
mkLiveQueryExplain = msDBLiveQueryExplain
-- multiplexed query
newtype MultiplexedQuery' = MultiplexedQuery' Reselect
newtype NoMultiplex = NoMultiplex (G.Name, ODBC.Query)
instance ToTxt NoMultiplex where
toTxt (NoMultiplex (_name, query)) = toTxt query
instance T.ToTxt MultiplexedQuery' where
toTxt (MultiplexedQuery' reselect) = T.toTxt $ toQueryPretty $ fromReselect reselect
-- query
@ -73,9 +77,7 @@ msDBQueryPlan _env _manager _reqHeaders userInfo _directives sourceName sourceCo
$ DBStepInfo sourceName sourceConfig (Just queryString) odbcQuery
msDBQueryExplain
:: forall m
. ( MonadError QErr m
)
:: MonadError QErr m
=> G.Name
-> UserInfo
-> SourceName
@ -95,18 +97,86 @@ msDBQueryExplain fieldName userInfo sourceName sourceConfig qrf = do
$ DBStepInfo sourceName sourceConfig Nothing odbcQuery
msDBLiveQueryExplain
:: ( MonadError QErr m
, MonadIO m
)
:: MonadError QErr m
=> LiveQueryPlan 'MSSQL (MultiplexedQuery 'MSSQL) -> m LiveQueryPlanExplanation
msDBLiveQueryExplain (LiveQueryPlan plan sourceConfig variables) = do
let NoMultiplex (_name, query) = _plqpQuery plan
select = withExplain $ QueryPrinter query
pool = _mscConnectionPool sourceConfig
-- TODO: execute `select` in separate batch
-- https://github.com/hasura/graphql-engine-mono/issues/1024
_explainInfo <- runJSONPathQuery pool (toQueryFlat select)
pure $ LiveQueryPlanExplanation (toTxt query) [] variables
msDBLiveQueryExplain (LiveQueryPlan plan _sourceConfig variables) = do
let query = _plqpQuery plan
-- TODO: execute `select` in separate batch
-- https://github.com/hasura/graphql-engine-mono/issues/1024
-- select = withExplain $ QueryPrinter query
-- pool = _mscConnectionPool sourceConfig
-- explainInfo <- runJSONPathQuery pool (toQueryFlat select)
pure $ LiveQueryPlanExplanation (T.toTxt query) [] variables
--------------------------------------------------------------------------------
-- Producing the correct SQL-level list comprehension to multiplex a query
-- Problem description:
--
-- Generate a query that repeats the same query N times but with
-- certain slots replaced:
--
-- [ Select x y | (x,y) <- [..] ]
--
multiplexRootReselect
:: [(CohortId, CohortVariables)]
-> TSQL.Reselect
-> TSQL.Select
multiplexRootReselect variables rootReselect =
Select
{ selectTop = NoTop
, selectProjections =
[ FieldNameProjection
Aliased
{ aliasedThing =
TSQL.FieldName
{fieldNameEntity = rowAlias, fieldName = resultIdAlias}
, aliasedAlias = resultIdAlias
}
, ExpressionProjection
Aliased
{ aliasedThing =
ColumnExpression
(TSQL.FieldName
{ fieldNameEntity = resultAlias
, fieldName = TSQL.jsonFieldName
})
, aliasedAlias = resultAlias
}
]
, selectFrom =
FromOpenJson
Aliased
{ aliasedThing =
OpenJson
{ openJsonExpression =
ValueExpression (ODBC.TextValue $ lbsToTxt $ J.encode variables)
, openJsonWith =
NE.fromList
[ UuidField resultIdAlias (Just $ IndexPath RootPath 0)
, JsonField resultVarsAlias (Just $ IndexPath RootPath 1)
]
}
, aliasedAlias = rowAlias
}
, selectJoins =
[ Join
{ joinSource = JoinReselect rootReselect
, joinJoinAlias =
JoinAlias
{ joinAliasEntity = resultAlias
, joinAliasField = Just TSQL.jsonFieldName
}
}
]
, selectWhere = Where mempty
, selectFor =
JsonFor ForJson {jsonCardinality = JsonArray, jsonRoot = NoRoot}
, selectOrderBy = Nothing
, selectOffset = Nothing
}
-- mutation
@ -138,13 +208,18 @@ msDBSubscriptionPlan
-> SourceConfig 'MSSQL
-> InsOrdHashMap G.Name (QueryDB 'MSSQL (UnpreparedValue 'MSSQL))
-> m (LiveQueryPlan 'MSSQL (MultiplexedQuery 'MSSQL))
msDBSubscriptionPlan userInfo _sourceName sourceConfig rootFields = do
-- WARNING: only keeping the first root field for now!
query <- traverse mkQuery $ head $ OMap.toList rootFields
let roleName = _uiRole userInfo
parameterizedPlan = ParameterizedLiveQueryPlan roleName $ NoMultiplex query
msDBSubscriptionPlan UserInfo {_uiSession, _uiRole} _sourceName sourceConfig rootFields = do
(reselect, prepareState) <- planMultiplex rootFields _uiSession
let PrepareState{sessionVariables, namedArguments, positionalArguments} = prepareState
-- TODO: call MSSQL validateVariables
-- We need to ensure that the values provided for variables are correct according to MSSQL.
-- Without this check an invalid value for a variable for one instance of the subscription will
-- take down the entire multiplexed query.
let cohortVariables = mkCohortVariables
sessionVariables
_uiSession
(toTxtEncodedVal namedArguments)
(toTxtEncodedVal positionalArguments)
let parameterizedPlan = ParameterizedLiveQueryPlan _uiRole $ MultiplexedQuery' reselect
pure
$ LiveQueryPlan parameterizedPlan sourceConfig
$ mkCohortVariables mempty mempty mempty mempty
where
mkQuery = fmap (toQueryFlat . fromSelect) . planNoPlan userInfo
$ LiveQueryPlan parameterizedPlan sourceConfig cohortVariables

View File

@ -6,16 +6,18 @@ import Hasura.Prelude
import qualified Data.Aeson as J
import qualified Data.ByteString as B
import qualified Database.ODBC.SQLServer as ODBC
import qualified Language.GraphQL.Draft.Syntax as G
import Data.String (fromString)
import Data.Text.Encoding (encodeUtf8)
import Data.Text.Extended
import Hasura.RQL.Types.Error as HE
import qualified Hasura.Logging as L
import Hasura.Backends.MSSQL.Connection
import Hasura.Backends.MSSQL.Instances.Execute
import Hasura.Backends.MSSQL.ToQuery
import Hasura.EncJSON
import Hasura.GraphQL.Execute.Backend
import Hasura.GraphQL.Execute.LiveQuery.Plan
@ -34,6 +36,14 @@ instance BackendTransport 'MSSQL where
runDBMutation = runMutation
runDBSubscription = runSubscription
newtype CohortResult = CohortResult (CohortId, Text)
instance J.FromJSON CohortResult where
parseJSON = J.withObject "CohortResult" \o -> do
cohortId <- o J..: "result_id"
cohortData <- o J..: "result"
pure $ CohortResult (cohortId, cohortData)
runQuery
:: ( MonadIO m
, MonadQueryLog m
@ -88,25 +98,29 @@ runMutation reqId query fieldName _userInfo logger _sourceConfig tx _genSql = d
$ run tx
runSubscription
:: ( MonadIO m
)
:: MonadIO m
=> SourceConfig 'MSSQL
-> MultiplexedQuery 'MSSQL
-> [(CohortId, CohortVariables)]
-> m (DiffTime, Either QErr [(CohortId, B.ByteString)])
runSubscription sourceConfig (NoMultiplex (name, query)) variables = do
runSubscription sourceConfig (MultiplexedQuery' reselect) variables = do
let pool = _mscConnectionPool sourceConfig
withElapsedTime $ runExceptT $ for variables $ traverse $ const $
fmap toResult $ run $ runJSONPathQuery pool query
where
toResult :: Text -> B.ByteString
toResult = encodeUtf8 . addFieldName
-- TODO: This should probably be generated from the database or should
-- probably return encjson so that encJFromAssocList can be used
addFieldName result =
"{\"" <> G.unName name <> "\":" <> result <> "}"
multiplexed = multiplexRootReselect variables reselect
query = toQueryFlat $ fromSelect multiplexed
withElapsedTime $ runExceptT $ executeMultiplexedQuery pool query
executeMultiplexedQuery
:: MonadIO m
=> MSSQLPool
-> ODBC.Query
-> ExceptT QErr m [(CohortId, B.ByteString)]
executeMultiplexedQuery pool query = do
let parseResult r = J.eitherDecodeStrict (encodeUtf8 r) `onLeft` \s -> throw400 ParseFailed (fromString s)
convertFromJSON :: [CohortResult] -> [(CohortId, B.ByteString)]
convertFromJSON = map \(CohortResult (cid, cresult)) -> (cid, encodeUtf8 cresult)
textResult <- run $ runJSONPathQuery pool query
parsedResult <- parseResult textResult
pure $ convertFromJSON parsedResult
run :: (MonadIO m, MonadError QErr m) => ExceptT QErr IO a -> m a
run action = do

View File

@ -13,7 +13,6 @@ import qualified Data.Aeson as J
import qualified Data.HashMap.Strict as HM
import qualified Data.HashMap.Strict.InsOrd as OMap
import qualified Data.HashSet as Set
import qualified Data.List.NonEmpty as NE
import qualified Data.Text as T
import qualified Database.ODBC.SQLServer as ODBC
import qualified Language.GraphQL.Draft.Syntax as G
@ -59,21 +58,22 @@ planNoPlan userInfo queryDB = do
JsonFor forJson -> JsonFor forJson {jsonRoot = Root "root"}
}
-- planMultiplex ::
-- OMap.InsOrdHashMap G.Name (SubscriptionRootFieldMSSQL (GraphQL.UnpreparedValue 'MSSQL))
-- -> Either PrepareError Select
-- planMultiplex _unpreparedMap =
-- let rootFieldMap =
-- evalState
-- (traverse
-- (traverseQueryRootField prepareValueMultiplex)
-- unpreparedMap)
-- emptyPrepareState
-- selectMap <-
-- first
-- FromIrError
-- (runValidate (TSQL.runFromIr (traverse TSQL.fromRootField rootFieldMap)))
-- pure (multiplexRootReselect (collapseMap selectMap))
planMultiplex
:: MonadError QErr m
=> OMap.InsOrdHashMap G.Name (QueryDB 'MSSQL (GraphQL.UnpreparedValue 'MSSQL))
-> SessionVariables
-> m (Reselect, PrepareState)
planMultiplex unpreparedMap sessionVariables = do
let (rootFieldMap, prepareState) =
runState
(traverse
(traverseQueryDB (prepareValueMultiplex (getSessionVariablesSet sessionVariables)))
unpreparedMap)
emptyPrepareState
selectMap <-
runValidate (TSQL.runFromIr (traverse TSQL.fromRootField rootFieldMap))
`onLeft` (throw400 NotSupported . tshow)
pure (collapseMap selectMap, prepareState)
-- Plan a query without prepare/exec.
-- planNoPlanMap ::
@ -119,10 +119,6 @@ globalSessionExpression :: TSQL.Expression
globalSessionExpression =
ValueExpression (ODBC.TextValue "current_setting('hasura.user')::json")
-- TODO: real env object.
envObjectExpression :: TSQL.Expression
envObjectExpression =
ValueExpression (ODBC.TextValue "[{\"result_id\":1,\"result_vars\":{\"synthetic\":[10]}}]")
--------------------------------------------------------------------------------
-- Resolving values
@ -131,14 +127,17 @@ data PrepareError
= FromIrError (NonEmpty TSQL.Error)
data PrepareState = PrepareState
{ positionalArguments :: !Integer
{ positionalArguments :: ![RQL.ColumnValue 'MSSQL]
, namedArguments :: !(HashMap G.Name (RQL.ColumnValue 'MSSQL))
, sessionVariables :: !(Set.HashSet SessionVariable)
}
emptyPrepareState :: PrepareState
emptyPrepareState =
PrepareState {positionalArguments = 0, namedArguments = mempty, sessionVariables = mempty}
emptyPrepareState = PrepareState
{ positionalArguments = mempty
, namedArguments = mempty
, sessionVariables = mempty
}
-- | Prepare a value without any query planning; we just execute the
-- query with the values embedded.
@ -158,22 +157,37 @@ prepareValueNoPlan sessionVariables =
pure $ ValueExpression $ ODBC.TextValue value
-- | Prepare a value for multiplexed queries.
prepareValueMultiplex ::
GraphQL.UnpreparedValue 'MSSQL
prepareValueMultiplex
:: Set.HashSet SessionVariable
-> GraphQL.UnpreparedValue 'MSSQL
-> State PrepareState TSQL.Expression
prepareValueMultiplex =
prepareValueMultiplex globalVariables =
\case
GraphQL.UVLiteral x -> pure x
GraphQL.UVSession ->
pure (JsonQueryExpression globalSessionExpression)
GraphQL.UVSession -> do
modify' (\s -> s {sessionVariables = sessionVariables s <> globalVariables})
pure $ JsonValueExpression
(ColumnExpression
FieldName
{ fieldNameEntity = rowAlias
, fieldName = resultVarsAlias
})
(RootPath `FieldPath` "session")
GraphQL.UVSessionVar _typ text -> do
modify' (\s -> s {sessionVariables = text `Set.insert` sessionVariables s})
pure $ JsonValueExpression globalSessionExpression (FieldPath RootPath (toTxt text))
GraphQL.UVParameter mVariableInfo pgcolumnvalue ->
pure $ JsonValueExpression
(ColumnExpression
FieldName
{ fieldNameEntity = rowAlias
, fieldName = resultVarsAlias
})
(RootPath `FieldPath` "session" `FieldPath` toTxt text)
GraphQL.UVParameter mVariableInfo columnValue ->
case fmap GraphQL.getName mVariableInfo of
Nothing -> do
index <- gets positionalArguments
modify' (\s -> s {positionalArguments = index + 1})
modify' (\s -> s {
positionalArguments = positionalArguments s <> [columnValue] })
index <- (toInteger . length) <$> gets positionalArguments
pure
(JsonValueExpression
(ColumnExpression
@ -187,76 +201,17 @@ prepareValueMultiplex =
(\s ->
s
{ namedArguments =
HM.insert name pgcolumnvalue (namedArguments s)
HM.insert name columnValue (namedArguments s)
})
pure
(JsonValueExpression
envObjectExpression
(ColumnExpression
FieldName
{ fieldNameEntity = rowAlias
, fieldName = resultVarsAlias
})
(RootPath `FieldPath` "query" `FieldPath` G.unName name))
--------------------------------------------------------------------------------
-- Producing the correct SQL-level list comprehension to multiplex a query
-- Problem description:
--
-- Generate a query that repeats the same query N times but with
-- certain slots replaced:
--
-- [ Select x y | (x,y) <- [..] ]
--
multiplexRootReselect :: TSQL.Reselect -> TSQL.Select
multiplexRootReselect rootReselect =
Select
{ selectTop = NoTop
, selectProjections =
[ FieldNameProjection
Aliased
{ aliasedThing =
FieldName
{fieldNameEntity = rowAlias, fieldName = resultIdAlias}
, aliasedAlias = resultIdAlias
}
, ExpressionProjection
Aliased
{ aliasedThing =
JsonQueryExpression
(ColumnExpression
(FieldName
{ fieldNameEntity = resultAlias
, fieldName = TSQL.jsonFieldName
}))
, aliasedAlias = resultAlias
}
]
, selectFrom =
FromOpenJson
Aliased
{ aliasedThing =
OpenJson
{ openJsonExpression = envObjectExpression
, openJsonWith =
NE.fromList
[IntField resultIdAlias, JsonField resultVarsAlias]
}
, aliasedAlias = rowAlias
}
, selectJoins =
[ Join
{ joinSource = JoinReselect rootReselect
, joinJoinAlias =
JoinAlias
{ joinAliasEntity = resultAlias
, joinAliasField = Just TSQL.jsonFieldName
}
}
]
, selectWhere = Where mempty
, selectFor =
JsonFor ForJson {jsonCardinality = JsonArray, jsonRoot = NoRoot}
, selectOrderBy = Nothing
, selectOffset = Nothing
}
resultIdAlias :: T.Text
resultIdAlias = "result_id"

View File

@ -0,0 +1,19 @@
module Hasura.Backends.MSSQL.SQL.Value where
import Hasura.Prelude
import Hasura.Backends.MSSQL.Types.Internal (Value)
import Hasura.SQL.Value (TxtEncodedVal (..))
import qualified Database.ODBC.SQLServer as ODBC
import qualified Hasura.GraphQL.Execute.LiveQuery.Plan as LQP
import qualified Hasura.RQL.Types.Column as RQL
import Hasura.SQL.Backend
txtEncodedVal :: Value -> TxtEncodedVal
txtEncodedVal ODBC.NullValue = TENull
txtEncodedVal val = TELit $ tshow val
toTxtEncodedVal :: forall f. Functor f => f (RQL.ColumnValue 'MSSQL) -> LQP.ValidatedVariables f
toTxtEncodedVal = LQP.ValidatedVariables . fmap (txtEncodedVal . RQL.cvValue)

View File

@ -7,6 +7,7 @@ module Hasura.Backends.MSSQL.ToQuery
( fromSelect
, withExplain
, fromReselect
, toSQL
, toQueryFlat
, toQueryPretty
, fromDelete
@ -16,8 +17,6 @@ module Hasura.Backends.MSSQL.ToQuery
import Hasura.Prelude hiding (GT, LT)
import qualified Data.Text as T
import qualified Data.Text.Lazy as L
import qualified Data.Text.Lazy.Builder as L
import Data.List (intersperse)
import Data.String
@ -64,7 +63,7 @@ fromExpression =
\case
JsonQueryExpression e -> "JSON_QUERY(" <+> fromExpression e <+> ")"
JsonValueExpression e path ->
"JSON_VALUE(" <+> fromExpression e <+> fromPath path <+> ")"
"JSON_VALUE(" <+> fromExpression e <+> ", " <+> fromPath path <+> ")"
ValueExpression value -> QueryPrinter (toSql value)
AndExpression xs ->
SepByPrinter
@ -120,16 +119,10 @@ fromOp =
NLIKE -> "NOT LIKE"
fromPath :: JsonPath -> Printer
fromPath path =
", " <+> string path
where
string = fromExpression .
ValueExpression . TextValue . L.toStrict . L.toLazyText . go
go =
\case
RootPath -> "$"
IndexPath r i -> go r <> "[" <> L.fromString (show i) <> "]"
FieldPath r f -> go r <> ".\"" <> L.fromText f <> "\""
fromPath = \case
RootPath -> "$"
IndexPath r i -> fromPath r <+> "[" <+> fromString (show i) <+> "]"
FieldPath r f -> fromPath r <+> ".\"" <+> fromString (T.unpack f) <+> "\""
fromFieldName :: FieldName -> Printer
fromFieldName (FieldName {..}) =
@ -344,8 +337,12 @@ fromOpenJson OpenJson {openJsonExpression, openJsonWith} =
fromJsonFieldSpec :: JsonFieldSpec -> Printer
fromJsonFieldSpec =
\case
IntField name -> fromNameText name <+> " INT"
JsonField name -> fromNameText name <+> " NVARCHAR(MAX) AS JSON"
IntField name mPath -> fromNameText name <+> " INT" <+> quote mPath
StringField name mPath -> fromNameText name <+> " NVARCHAR(MAX)" <+> quote mPath
UuidField name mPath -> fromNameText name <+> " UNIQUEIDENTIFIER" <+> quote mPath
JsonField name mPath -> fromJsonFieldSpec (StringField name mPath) <+> " AS JSON"
where
quote mPath = maybe "" ((\p -> " '" <+> p <+> "'"). fromPath) mPath
fromTableName :: TableName -> Printer
fromTableName TableName {tableName, tableSchema} =

View File

@ -203,8 +203,10 @@ data OpenJson = OpenJson
}
data JsonFieldSpec
= IntField Text
| JsonField Text
= IntField Text (Maybe JsonPath)
| JsonField Text (Maybe JsonPath)
| StringField Text (Maybe JsonPath)
| UuidField Text (Maybe JsonPath)
data Aliased a = Aliased
{ aliasedThing :: !a

View File

@ -68,7 +68,7 @@ validateVariables pgExecCtx variableValues = do
let valSel = mkValidationSel $ toList variableValues
Q.Discard () <- runQueryTx_ $ liftTx $
Q.rawQE dataExnErrHandler (Q.fromBuilder $ toSQL valSel) [] False
pure . ValidatedVariables $ fmap (txtEncodedPGVal . cvValue) variableValues
pure . ValidatedVariables $ fmap (txtEncodedVal . cvValue) variableValues
where
mkExtr = flip S.Extractor Nothing . toTxtValue
mkValidationSel vars =

View File

@ -241,7 +241,7 @@ mutateAndFetchCols
-> [ColumnInfo 'Postgres]
-> (MutationCTE, DS.Seq Q.PrepArg)
-> Bool
-> Q.TxE QErr (MutateResp TxtEncodedPGVal)
-> Q.TxE QErr (MutateResp TxtEncodedVal)
mutateAndFetchCols qt cols (cte, p) strfyNum = do
let mutationTx :: Q.FromRes a => Q.TxE QErr a
mutationTx =

View File

@ -6,8 +6,8 @@ module Hasura.Backends.Postgres.SQL.Value
, scientificToInteger
, scientificToFloat
, TxtEncodedPGVal(..)
, txtEncodedPGVal
, TxtEncodedVal(..)
, txtEncodedVal
, binEncoder
, txtEncoder
@ -16,6 +16,8 @@ module Hasura.Backends.Postgres.SQL.Value
import Hasura.Prelude
import Hasura.SQL.Value (TxtEncodedVal (..))
import qualified Data.Aeson.Text as AE
import qualified Data.Aeson.Types as AT
import qualified Data.ByteString as B
@ -201,25 +203,8 @@ parsePGValue ty val = case (ty, val) of
PGUnknown tyName ->
fail $ "A string is expected for type: " ++ T.unpack tyName
data TxtEncodedPGVal
= TENull
| TELit !Text
deriving (Show, Eq, Generic)
instance Hashable TxtEncodedPGVal
instance ToJSON TxtEncodedPGVal where
toJSON = \case
TENull -> Null
TELit t -> String t
instance FromJSON TxtEncodedPGVal where
parseJSON Null = pure TENull
parseJSON (String t) = pure $ TELit t
parseJSON v = AT.typeMismatch "String" v
txtEncodedPGVal :: PGScalarValue -> TxtEncodedPGVal
txtEncodedPGVal = \case
txtEncodedVal :: PGScalarValue -> TxtEncodedVal
txtEncodedVal = \case
PGValInteger i -> TELit $ tshow i
PGValSmallInt i -> TELit $ tshow i
PGValBigInt i -> TELit $ tshow i
@ -317,7 +302,7 @@ binEncoder = \case
PGValUnknown t -> (PTI.auto, Just (TE.encodeUtf8 t, PQ.Text))
txtEncoder :: PGScalarValue -> S.SQLExp
txtEncoder colVal = case txtEncodedPGVal colVal of
txtEncoder colVal = case txtEncodedVal colVal of
TENull -> S.SENull
TELit t -> S.SELit t

View File

@ -25,7 +25,7 @@ import Hasura.SQL.Types
-- `SELECT ("row"::table).* VALUES (1, 'Robert', 23) AS "row"`.
mkSelectExpFromColumnValues
:: (MonadError QErr m)
=> QualifiedTable -> [ColumnInfo 'Postgres] -> [ColumnValues 'Postgres TxtEncodedPGVal] -> m S.Select
=> QualifiedTable -> [ColumnInfo 'Postgres] -> [ColumnValues 'Postgres TxtEncodedVal] -> m S.Select
mkSelectExpFromColumnValues qt allCols = \case
[] -> return selNoRows
colVals -> do

View File

@ -146,7 +146,7 @@ insertObject
-> PGE.MutationRemoteJoinCtx
-> Seq.Seq Q.PrepArg
-> Bool
-> m (Int, Maybe (ColumnValues 'Postgres TxtEncodedPGVal))
-> m (Int, Maybe (ColumnValues 'Postgres TxtEncodedVal))
insertObject env singleObjIns additionalColumns remoteJoinCtx planVars stringifyNum = Tracing.trace ("Insert " <> qualifiedObjectToText table) do
validateInsert (map fst columns) (map IR._riRelInfo objectRels) (map fst additionalColumns)
@ -197,7 +197,7 @@ insertObject env singleObjIns additionalColumns remoteJoinCtx planVars stringify
_aiDefVals
withArrRels
:: Maybe (ColumnValues 'Postgres TxtEncodedPGVal)
:: Maybe (ColumnValues 'Postgres TxtEncodedVal)
-> m Int
withArrRels colValM = do
colVal <- onNothing colValM $ throw400 NotSupported cannotInsArrRelErr
@ -207,8 +207,8 @@ insertObject env singleObjIns additionalColumns remoteJoinCtx planVars stringify
return $ sum arrInsARows
asSingleObject
:: [ColumnValues 'Postgres TxtEncodedPGVal]
-> m (Maybe (ColumnValues 'Postgres TxtEncodedPGVal))
:: [ColumnValues 'Postgres TxtEncodedVal]
-> m (Maybe (ColumnValues 'Postgres TxtEncodedVal))
asSingleObject = \case
[] -> pure Nothing
[r] -> pure $ Just r
@ -325,7 +325,7 @@ mkInsertQ table onConflictM insCols defVals (insCheck, updCheck) = do
fetchFromColVals
:: MonadError QErr m
=> ColumnValues 'Postgres TxtEncodedPGVal
=> ColumnValues 'Postgres TxtEncodedVal
-> [ColumnInfo 'Postgres]
-> m [(PGCol, PG.SQLExp)]
fetchFromColVals colVal reqCols =

View File

@ -125,7 +125,7 @@ import Hasura.Session
-- Cohort
newtype CohortId = CohortId { unCohortId :: UUID }
deriving (Show, Eq, Hashable, J.ToJSON, Q.FromCol)
deriving (Show, Eq, Hashable, J.ToJSON, J.FromJSON, Q.FromCol)
newCohortId :: (MonadIO m) => m CohortId
newCohortId = CohortId <$> liftIO UUID.nextRandom
@ -233,14 +233,14 @@ instance Q.ToPrepArg CohortVariablesArray where
--
-- so if any variable values are invalid, the error will be caught early.
newtype ValidatedVariables f = ValidatedVariables (f TxtEncodedPGVal)
newtype ValidatedVariables f = ValidatedVariables (f TxtEncodedVal)
deriving instance (Show (f TxtEncodedPGVal)) => Show (ValidatedVariables f)
deriving instance (Eq (f TxtEncodedPGVal)) => Eq (ValidatedVariables f)
deriving instance (Hashable (f TxtEncodedPGVal)) => Hashable (ValidatedVariables f)
deriving instance (J.ToJSON (f TxtEncodedPGVal)) => J.ToJSON (ValidatedVariables f)
deriving instance (Semigroup (f TxtEncodedPGVal)) => Semigroup (ValidatedVariables f)
deriving instance (Monoid (f TxtEncodedPGVal)) => Monoid (ValidatedVariables f)
deriving instance (Show (f TxtEncodedVal)) => Show (ValidatedVariables f)
deriving instance (Eq (f TxtEncodedVal)) => Eq (ValidatedVariables f)
deriving instance (Hashable (f TxtEncodedVal)) => Hashable (ValidatedVariables f)
deriving instance (J.ToJSON (f TxtEncodedVal)) => J.ToJSON (ValidatedVariables f)
deriving instance (Semigroup (f TxtEncodedVal)) => Semigroup (ValidatedVariables f)
deriving instance (Monoid (f TxtEncodedVal)) => Monoid (ValidatedVariables f)
type ValidatedQueryVariables = ValidatedVariables (Map.HashMap G.Name)
type ValidatedSyntheticVariables = ValidatedVariables []

View File

@ -0,0 +1,25 @@
module Hasura.SQL.Value where
import Hasura.Prelude
import qualified Data.Aeson as A
import qualified Data.Aeson.Types as AT
import qualified Data.Text as T
data TxtEncodedVal
= TENull
| TELit !T.Text
deriving (Show, Eq, Generic)
instance Hashable TxtEncodedVal
instance A.ToJSON TxtEncodedVal where
toJSON = \case
TENull -> AT.Null
TELit t -> AT.String t
instance A.FromJSON TxtEncodedVal where
parseJSON A.Null = pure TENull
parseJSON (A.String t) = pure $ TELit t
parseJSON v = AT.typeMismatch "String" v

View File

@ -0,0 +1,12 @@
type: bulk
args:
- type: mssql_run_sql
args:
source: mssql
sql: |
CREATE TABLE [user](
id int identity NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER
);

View File

@ -0,0 +1,8 @@
type: bulk
args:
- type: mssql_run_sql
args:
source: mssql
cascade: true
sql: |
DROP TABLE [user];

View File

@ -0,0 +1,18 @@
type: bulk
args:
- type: mssql_track_table
args:
source: mssql
table:
name: user
- type: mssql_create_select_permission
args:
source: mssql
table:
name: user
role: user
permission:
columns: '*'
filter:
id: X-Hasura-User-Id

View File

@ -0,0 +1,2 @@
type: bulk
args: []

View File

@ -0,0 +1,14 @@
type: bulk
args:
- type: mssql_run_sql
args:
source: mssql
sql: |
CREATE TABLE test(id int identity NOT NULL PRIMARY KEY);
create table articles(
id int identity NOT NULL PRIMARY KEY,
user_id int,
content text,
title text,
is_public bit default 0
);

View File

@ -0,0 +1,8 @@
type: bulk
args:
- type: mssql_run_sql
args:
source: mssql
sql: |
DROP TABLE test;
DROP TABLE articles;

View File

@ -0,0 +1,39 @@
type: bulk
args:
- type: mssql_track_table
args:
source: mssql
table:
name: test
- type: mssql_track_table
args:
source: mssql
table:
name: articles
- type: mssql_create_select_permission
args:
source: mssql
table:
name: articles
role: public
permission:
columns:
- title
- content
filter:
is_public: 1
- type: mssql_create_select_permission
args:
source: mssql
table:
name: articles
role: user
permission:
columns:
- user_id
- title
- content
- is_public
filter:
id:
_eq: X-Hasura-User-Id

View File

@ -0,0 +1,2 @@
type: bulk
args: []

View File

@ -250,7 +250,8 @@ class TestSubscriptionLiveQueries:
with pytest.raises(queue.Empty):
ev = ws_client.get_ws_event(3)
@usefixtures('per_method_tests_db_state')
@pytest.mark.parametrize("backend", ['mssql', 'postgres'])
@usefixtures('per_class_tests_db_state', 'per_backend_tests')
class TestSubscriptionMultiplexing:
@classmethod