Skip to content

Commit df36cda

Browse files
plcplchasura-bot
authored andcommitted
Refactor insert mutations IR use of "default values"
PR-URL: hasura/graphql-engine-mono#4316 GitOrigin-RevId: 91f80902a2dc2a782821033f455c70c4e96f0950
1 parent 7a6e38b commit df36cda

File tree

14 files changed

+90
-130
lines changed

14 files changed

+90
-130
lines changed

server/src-lib/Data/HashMap/Strict/Extended.hs

+12
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module Data.HashMap.Strict.Extended
1111
unionWithM,
1212
unionsAll,
1313
unionsWith,
14+
homogenise,
1415
)
1516
where
1617

@@ -19,7 +20,10 @@ import Data.Align qualified as A
1920
import Data.Foldable qualified as F
2021
import Data.Function (on)
2122
import Data.HashMap.Strict as M
23+
import Data.HashSet (HashSet)
24+
import Data.HashSet qualified as S
2225
import Data.Hashable (Hashable)
26+
import Data.List qualified as L
2327
import Data.List.NonEmpty (NonEmpty (..))
2428
import Data.These (These (That, These, This))
2529
import Prelude
@@ -133,3 +137,11 @@ unionsWith ::
133137
f (HashMap k a) ->
134138
HashMap k a
135139
unionsWith f ts = F.foldl' (unionWith f) empty ts
140+
141+
-- | Homogenise maps, such that all maps range over the full set of
142+
-- keys, inserting a default value as needed.
143+
homogenise :: (Hashable a, Eq a) => b -> [HashMap a b] -> (HashSet a, [HashMap a b])
144+
homogenise defaultValue maps =
145+
let ks = S.unions $ L.map keysSet maps
146+
defaults = fromList [(k, defaultValue) | k <- S.toList ks]
147+
in (ks, L.map (<> defaults) maps)

server/src-lib/Hasura/Backends/BigQuery/Instances/Schema.hs

-3
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,6 @@ instance BackendSchema 'BigQuery where
5757
computedField = bqComputedField
5858
node = bqNode
5959

60-
-- SQL literals
61-
columnDefaultValue = error "TODO: Make impossible by the type system. BigQuery doesn't support insertions."
62-
6360
----------------------------------------------------------------
6461
-- Top level parsers
6562

server/src-lib/Hasura/Backends/DataWrapper/Adapter/Schema.hs

-2
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ instance BackendSchema 'DataWrapper where
6060
error "computedField: not implemented for GraphQL Data Wrappers."
6161
node =
6262
error "node: not implemented for GraphQL Data Wrappers."
63-
columnDefaultValue =
64-
error "columnDefaultValue: not implemented for GraphQL Data Wrappers."
6563

6664
--------------------------------------------------------------------------------
6765

server/src-lib/Hasura/Backends/MSSQL/Execute/Insert.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ buildInsertTx tableName withAlias stringifyNum insert = do
187187
-- Should be used as part of a bigger transaction in 'buildInsertTx'.
188188
buildUpsertTx :: TSQL.TableName -> AnnotatedInsert 'MSSQL Void Expression -> IfMatched Expression -> Tx.TxET QErr IO ()
189189
buildUpsertTx tableName insert ifMatched = do
190-
let presets = _aiDefaultValues $ _aiData insert
190+
let presets = _aiPresetValues $ _aiData insert
191191
insertColumnNames =
192192
concatMap (map fst . getInsertColumns) (_aiInsertObject $ _aiData insert) <> HM.keys presets
193193
allTableColumns = _aiTableColumns $ _aiData insert

server/src-lib/Hasura/Backends/MSSQL/FromIr/Insert.hs

+17-20
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ module Hasura.Backends.MSSQL.FromIr.Insert
77
)
88
where
99

10-
import Data.Containers.ListUtils (nubOrd)
1110
import Data.HashMap.Strict qualified as HM
11+
import Data.HashMap.Strict.Extended qualified as HM
12+
import Data.HashSet qualified as HS
1213
import Hasura.Backends.MSSQL.FromIr (FromIr)
1314
import Hasura.Backends.MSSQL.FromIr.Constants (tempTableNameInserted, tempTableNameValues)
1415
import Hasura.Backends.MSSQL.FromIr.Expression (fromGBoolExp)
@@ -23,13 +24,12 @@ import Hasura.SQL.Backend
2324
fromInsert :: IR.AnnotatedInsert 'MSSQL Void Expression -> Insert
2425
fromInsert IR.AnnotatedInsert {..} =
2526
let IR.AnnotatedInsertData {..} = _aiData
26-
insertRows = normalizeInsertRows _aiDefaultValues $ map IR.getInsertColumns _aiInsertObject
27-
insertColumnNames = maybe [] (map fst) $ listToMaybe insertRows
28-
insertValues = map (Values . map snd) insertRows
27+
(insertColumnNames, insertRows) = normalizeInsertRows _aiPresetValues $ _aiInsertObject
28+
insertValues = map (Values . HM.elems) insertRows
2929
allColumnNames = map IR.ciColumn _aiTableColumns
3030
insertOutput = Output Inserted $ map OutputColumn allColumnNames
3131
tempTable = TempTable tempTableNameInserted allColumnNames
32-
in Insert _aiTableName insertColumnNames insertOutput tempTable insertValues
32+
in Insert _aiTableName (HS.toList insertColumnNames) insertOutput tempTable insertValues
3333

3434
-- | Normalize a row by adding missing columns with @DEFAULT@ value and sort by
3535
-- column name to make sure all rows are consistent in column values and order.
@@ -58,16 +58,12 @@ fromInsert IR.AnnotatedInsert {..} =
5858
-- > VALUES (1, 'Foo', 21), (2, 'Bar', DEFAULT)
5959
normalizeInsertRows ::
6060
HM.HashMap (Column 'MSSQL) Expression ->
61-
[[(Column 'MSSQL, Expression)]] ->
62-
[[(Column 'MSSQL, Expression)]]
61+
[IR.AnnotatedInsertRow 'MSSQL Expression] ->
62+
(HashSet (Column 'MSSQL), [HM.HashMap (Column 'MSSQL) Expression])
6363
normalizeInsertRows presets insertRows =
64-
let insertColumns = nubOrd (concatMap (map fst) insertRows <> HM.keys presets)
65-
allColumnsWithDefaultValue =
66-
map (\col -> (col, fromMaybe DefaultExpression $ HM.lookup col presets)) insertColumns
67-
addMissingColumns insertRow =
68-
HM.toList $ HM.fromList insertRow `HM.union` HM.fromList allColumnsWithDefaultValue
69-
sortByColumn = sortBy (\l r -> compare (fst l) (fst r))
70-
in map (sortByColumn . addMissingColumns) insertRows
64+
HM.homogenise
65+
DefaultExpression
66+
(map ((presets <>) . HM.fromList . IR.getInsertColumns) insertRows)
7167

7268
-- | Construct a MERGE statement from AnnotatedInsert information.
7369
-- A MERGE statement is responsible for actually inserting and/or updating
@@ -79,8 +75,10 @@ toMerge ::
7975
IfMatched Expression ->
8076
FromIr Merge
8177
toMerge tableName insertRows allColumns IfMatched {..} = do
82-
let normalizedInsertRows = normalizeInsertRows _imColumnPresets $ map IR.getInsertColumns insertRows
83-
insertColumnNames = maybe [] (map fst) $ listToMaybe normalizedInsertRows
78+
let insertColumnNames =
79+
HS.toList $
80+
HM.keysSet _imColumnPresets
81+
<> HS.unions (map (HM.keysSet . HM.fromList . IR.getInsertColumns) insertRows)
8482
allColumnNames = map IR.ciColumn allColumns
8583

8684
matchConditions <-
@@ -108,11 +106,10 @@ toMerge tableName insertRows allColumns IfMatched {..} = do
108106
toInsertValuesIntoTempTable :: TempTableName -> IR.AnnotatedInsert 'MSSQL Void Expression -> InsertValuesIntoTempTable
109107
toInsertValuesIntoTempTable tempTable IR.AnnotatedInsert {..} =
110108
let IR.AnnotatedInsertData {..} = _aiData
111-
insertRows = normalizeInsertRows _aiDefaultValues $ map IR.getInsertColumns _aiInsertObject
112-
insertColumnNames = maybe [] (map fst) $ listToMaybe insertRows
113-
insertValues = map (Values . map snd) insertRows
109+
(insertColumnNames, insertRows) = normalizeInsertRows _aiPresetValues _aiInsertObject
110+
insertValues = map (Values . HM.elems) insertRows
114111
in InsertValuesIntoTempTable
115112
{ ivittTempTableName = tempTable,
116-
ivittColumns = insertColumnNames,
113+
ivittColumns = HS.toList insertColumnNames,
117114
ivittValues = insertValues
118115
}

server/src-lib/Hasura/Backends/MSSQL/Instances/Schema.hs

-12
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,6 @@ instance BackendSchema 'MSSQL where
7171
computedField = msComputedField
7272
node = msNode
7373

74-
-- SQL literals
75-
columnDefaultValue = msColumnDefaultValue
76-
7774
----------------------------------------------------------------
7875

7976
-- * Top level parsers
@@ -474,12 +471,3 @@ msNode ::
474471
)
475472
)
476473
msNode = throw500 "MSSQL does not support relay; `node` should never be exposed in the schema."
477-
478-
----------------------------------------------------------------
479-
480-
-- * SQL literals
481-
482-
-- FIXME: this is nonsensical for MSSQL, we'll need to adjust the corresponding mutation
483-
-- and its representation.
484-
msColumnDefaultValue :: Column 'MSSQL -> SQLExpression 'MSSQL
485-
msColumnDefaultValue = const $ MSSQL.ValueExpression ODBC.NullValue

server/src-lib/Hasura/Backends/MySQL/Instances/Schema.hs

-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ instance BackendSchema 'MySQL where
4747
aggregateOrderByCountType = error "aggregateOrderByCountType: MySQL backend does not support this operation yet."
4848
computedField = error "computedField: MySQL backend does not support this operation yet."
4949
node = error "node: MySQL backend does not support this operation yet."
50-
columnDefaultValue = error "columnDefaultValue: MySQL backend does not support this operation yet."
5150

5251
mysqlTableArgs ::
5352
forall r m n.

server/src-lib/Hasura/Backends/Postgres/Execute/Insert.hs

+31-33
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ where
1010

1111
import Data.Aeson qualified as J
1212
import Data.HashMap.Strict qualified as Map
13+
import Data.HashMap.Strict.Extended qualified as Map
1314
import Data.List qualified as L
1415
import Data.Sequence qualified as Seq
1516
import Data.Text qualified as T
@@ -55,7 +56,7 @@ convertToSQLTransaction (IR.AnnotatedInsert fieldName isSingle annIns mutationOu
5556
then pure $ IR.buildEmptyMutResp mutationOutput
5657
else
5758
withPaths ["selectionSet", fieldName, "args", suffix] $
58-
insertMultipleObjects annIns [] userInfo mutationOutput planVars stringifyNum
59+
insertMultipleObjects annIns mempty userInfo mutationOutput planVars stringifyNum
5960
where
6061
withPaths p x = foldr ($) x $ withPathK <$> p
6162
suffix = bool "objects" "object" isSingle
@@ -70,7 +71,7 @@ insertMultipleObjects ::
7071
MonadReader QueryTagsComment m
7172
) =>
7273
IR.MultiObjectInsert ('Postgres pgKind) PG.SQLExp ->
73-
[(PGCol, PG.SQLExp)] ->
74+
Map.HashMap PGCol PG.SQLExp ->
7475
UserInfo ->
7576
IR.MutationOutput ('Postgres pgKind) ->
7677
Seq.Seq Q.PrepArg ->
@@ -79,21 +80,21 @@ insertMultipleObjects ::
7980
insertMultipleObjects multiObjIns additionalColumns userInfo mutationOutput planVars stringifyNum =
8081
bool withoutRelsInsert withRelsInsert anyRelsToInsert
8182
where
82-
IR.AnnotatedInsertData insObjs table checkCondition columnInfos defVals (BackendInsert conflictClause) = multiObjIns
83+
IR.AnnotatedInsertData insObjs table checkCondition columnInfos presetRow (BackendInsert conflictClause) = multiObjIns
8384
allInsObjRels = concatMap IR.getInsertObjectRelationships insObjs
8485
allInsArrRels = concatMap IR.getInsertArrayRelationships insObjs
8586
anyRelsToInsert = not $ null allInsArrRels && null allInsObjRels
8687

8788
withoutRelsInsert = do
8889
indexedForM_ (IR.getInsertColumns <$> insObjs) \column ->
89-
validateInsert (map fst column) [] (map fst additionalColumns)
90-
let columnValues = map (mkSQLRow defVals) $ union additionalColumns . IR.getInsertColumns <$> insObjs
91-
columnNames = Map.keys defVals
90+
validateInsert (map fst column) [] (Map.keys additionalColumns)
91+
let insObjRows = Map.fromList . IR.getInsertColumns <$> insObjs
92+
(columnNames, insertRows) = Map.homogenise PG.columnDefaultValue $ map ((presetRow <> additionalColumns) <>) insObjRows
9293
insertQuery =
9394
IR.InsertQueryP1
9495
table
95-
columnNames
96-
columnValues
96+
(toList columnNames)
97+
(map Map.elems insertRows)
9798
conflictClause
9899
checkCondition
99100
mutationOutput
@@ -105,7 +106,7 @@ insertMultipleObjects multiObjIns additionalColumns userInfo mutationOutput plan
105106

106107
withRelsInsert = do
107108
insertRequests <- indexedForM insObjs \obj -> do
108-
let singleObj = IR.AnnotatedInsertData (IR.Single obj) table checkCondition columnInfos defVals (BackendInsert conflictClause)
109+
let singleObj = IR.AnnotatedInsertData (IR.Single obj) table checkCondition columnInfos presetRow (BackendInsert conflictClause)
109110
insertObject singleObj additionalColumns userInfo planVars stringifyNum
110111
let affectedRows = sum $ map fst insertRequests
111112
columnValues = mapMaybe snd insertRequests
@@ -129,23 +130,23 @@ insertObject ::
129130
MonadReader QueryTagsComment m
130131
) =>
131132
IR.SingleObjectInsert ('Postgres pgKind) PG.SQLExp ->
132-
[(PGCol, PG.SQLExp)] ->
133+
HashMap PGCol PG.SQLExp ->
133134
UserInfo ->
134135
Seq.Seq Q.PrepArg ->
135136
StringifyNumbers ->
136137
m (Int, Maybe (ColumnValues ('Postgres pgKind) TxtEncodedVal))
137138
insertObject singleObjIns additionalColumns userInfo planVars stringifyNum = Tracing.trace ("Insert " <> qualifiedObjectToText table) do
138-
validateInsert (map fst columns) (map IR._riRelationInfo objectRels) (map fst additionalColumns)
139+
validateInsert (Map.keys columns) (map IR._riRelationInfo objectRels) (Map.keys additionalColumns)
139140

140141
-- insert all object relations and fetch this insert dependent column values
141142
objInsRes <- forM beforeInsert $ insertObjRel planVars userInfo stringifyNum
142143

143144
-- prepare final insert columns
144145
let objRelAffRows = sum $ map fst objInsRes
145-
objRelDeterminedCols = concatMap snd objInsRes
146-
finalInsCols = columns <> objRelDeterminedCols <> additionalColumns
146+
objRelDeterminedCols = Map.fromList $ concatMap snd objInsRes
147+
finalInsCols = presetValues <> columns <> objRelDeterminedCols <> additionalColumns
147148

148-
let cte = mkInsertQ table onConflict finalInsCols defaultValues checkCond
149+
let cte = mkInsertQ table onConflict finalInsCols checkCond
149150

150151
PGE.MutateResp affRows colVals <-
151152
liftTx $
@@ -157,8 +158,8 @@ insertObject singleObjIns additionalColumns userInfo planVars stringifyNum = Tra
157158

158159
return (totAffRows, colValM)
159160
where
160-
IR.AnnotatedInsertData (IR.Single annObj) table checkCond allColumns defaultValues (BackendInsert onConflict) = singleObjIns
161-
columns = IR.getInsertColumns annObj
161+
IR.AnnotatedInsertData (IR.Single annObj) table checkCond allColumns presetValues (BackendInsert onConflict) = singleObjIns
162+
columns = Map.fromList $ IR.getInsertColumns annObj
162163
objectRels = IR.getInsertObjectRelationships annObj
163164
arrayRels = IR.getInsertArrayRelationships annObj
164165

@@ -220,7 +221,7 @@ insertObjRel ::
220221
m (Int, [(PGCol, PG.SQLExp)])
221222
insertObjRel planVars userInfo stringifyNum objRelIns =
222223
withPathK (relNameToTxt relName) $ do
223-
(affRows, colValM) <- withPathK "data" $ insertObject singleObjIns [] userInfo planVars stringifyNum
224+
(affRows, colValM) <- withPathK "data" $ insertObject singleObjIns mempty userInfo planVars stringifyNum
224225
colVal <- onNothing colValM $ throw400 NotSupported errMsg
225226
retColsWithVals <- fetchFromColVals colVal rColInfos
226227
let columns = flip mapMaybe (Map.toList mapCols) \(column, target) -> do
@@ -256,9 +257,10 @@ insertArrRel ::
256257
m Int
257258
insertArrRel resCols userInfo planVars stringifyNum arrRelIns =
258259
withPathK (relNameToTxt $ riName relInfo) $ do
259-
let additionalColumns = flip mapMaybe resCols \(column, value) -> do
260-
target <- Map.lookup column mapping
261-
Just (target, value)
260+
let additionalColumns = Map.fromList $
261+
flip mapMaybe resCols \(column, value) -> do
262+
target <- Map.lookup column mapping
263+
Just (target, value)
262264
resBS <-
263265
withPathK "data" $
264266
insertMultipleObjects multiObjIns additionalColumns userInfo mutOutput planVars stringifyNum
@@ -270,8 +272,12 @@ insertArrRel resCols userInfo planVars stringifyNum arrRelIns =
270272
mapping = riMapping relInfo
271273
mutOutput = IR.MOutMultirowFields [("affected_rows", IR.MCount)]
272274

273-
-- | validate an insert object based on insert columns,
274-
-- | insert object relations and additional columns from parent
275+
-- | Validate an insert object based on insert columns,
276+
-- insert object relations and additional columns from parent:
277+
--
278+
-- * There should be no overlap between 'insCols' and 'addCols'.
279+
-- * There should be no overlap between any object relationship columns and
280+
-- 'insCols' and 'addCols'.
275281
validateInsert ::
276282
(MonadError QErr m) =>
277283
-- | inserting columns
@@ -307,15 +313,14 @@ mkInsertQ ::
307313
Backend ('Postgres pgKind) =>
308314
QualifiedTable ->
309315
Maybe (IR.OnConflictClause ('Postgres pgKind) PG.SQLExp) ->
310-
[(PGCol, PG.SQLExp)] ->
311316
Map.HashMap PGCol PG.SQLExp ->
312317
(AnnBoolExpSQL ('Postgres pgKind), Maybe (AnnBoolExpSQL ('Postgres pgKind))) ->
313318
PG.CTE
314-
mkInsertQ table onConflictM insCols defVals (insCheck, updCheck) =
319+
mkInsertQ table onConflictM insertRow (insCheck, updCheck) =
315320
let sqlConflict = PGT.toSQLConflict table <$> onConflictM
316-
sqlExps = mkSQLRow defVals insCols
321+
sqlExps = Map.elems insertRow
317322
valueExp = PG.ValuesExp [PG.TupleExp sqlExps]
318-
tableCols = Map.keys defVals
323+
tableCols = Map.keys insertRow
319324
sqlInsert =
320325
PG.SQLInsert table tableCols valueExp sqlConflict
321326
. Just
@@ -347,13 +352,6 @@ fetchFromColVals colVal reqCols =
347352
TELit t -> PG.SELit t
348353
return (ciColumn ci, pgColVal)
349354

350-
mkSQLRow :: Map.HashMap PGCol PG.SQLExp -> [(PGCol, PG.SQLExp)] -> [PG.SQLExp]
351-
mkSQLRow defVals withPGCol = map snd $
352-
flip map (Map.toList defVals) $
353-
\(col, defVal) -> (col,) $ fromMaybe defVal $ Map.lookup col withPGColMap
354-
where
355-
withPGColMap = Map.fromList withPGCol
356-
357355
decodeEncJSON :: (J.FromJSON a, QErrM m) => EncJSON -> m a
358356
decodeEncJSON =
359357
either (throw500 . T.pack) decodeValue

server/src-lib/Hasura/Backends/Postgres/Instances/Schema.hs

-3
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,6 @@ instance
160160
computedField = computedFieldPG
161161
node = pgkNode
162162

163-
-- SQL literals
164-
columnDefaultValue = const PG.columnDefaultValue
165-
166163
backendInsertParser ::
167164
forall pgKind m r n.
168165
MonadBuildSchema ('Postgres pgKind) r m n =>

server/src-lib/Hasura/GraphQL/Schema/Backend.hs

-3
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,6 @@ class
225225
MonadBuildSchema b r m n =>
226226
m (Parser 'Output n (HashMap (TableName b) (SourceName, SourceConfig b, SelPermInfo b, PrimaryKeyColumns b, AnnotatedFields b)))
227227

228-
-- SQL literals
229-
columnDefaultValue :: Column b -> SQLExpression b
230-
231228
type ComparisonExp b = OpExpG b (UnpreparedValue b)
232229

233230
-- $modelling

server/src-lib/Hasura/GraphQL/Schema/Mutation.hs

+2-9
Original file line numberDiff line numberDiff line change
@@ -311,22 +311,15 @@ mkInsertObject objects tableInfo backendInsert insertPerms updatePerms =
311311
_aiTableName = table,
312312
_aiCheckCondition = (insertCheck, updateCheck),
313313
_aiTableColumns = columns,
314-
_aiDefaultValues = defaultValues,
314+
_aiPresetValues = presetValues,
315315
_aiBackendInsert = backendInsert
316316
}
317317
where
318318
table = tableInfoName tableInfo
319319
columns = tableColumns tableInfo
320320
insertCheck = fmap partialSQLExpToUnpreparedValue <$> ipiCheck insertPerms
321321
updateCheck = (fmap . fmap . fmap) partialSQLExpToUnpreparedValue $ upiCheck =<< updatePerms
322-
defaultValues =
323-
Map.union (partialSQLExpToUnpreparedValue <$> ipiSet insertPerms) $
324-
Map.fromList
325-
[ (column, UVLiteral $ columnDefaultValue @b column)
326-
| ci <- columns,
327-
_cmIsInsertable (ciMutability ci),
328-
let column = ciColumn ci
329-
]
322+
presetValues = partialSQLExpToUnpreparedValue <$> ipiSet insertPerms
330323

331324
-- delete
332325

server/src-lib/Hasura/RQL/IR/Insert.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ data AnnotatedInsertData (b :: BackendType) (f :: Type -> Type) (v :: Type) = An
7272
_aiTableName :: TableName b,
7373
_aiCheckCondition :: (AnnBoolExp b v, Maybe (AnnBoolExp b v)),
7474
_aiTableColumns :: [ColumnInfo b],
75-
_aiDefaultValues :: PreSetColsG b v,
75+
_aiPresetValues :: PreSetColsG b v,
7676
_aiBackendInsert :: BackendInsert b v
7777
}
7878
deriving (Functor, Foldable, Traversable)

0 commit comments

Comments
 (0)