From dd46aa6715337411ac0ab9d3b357ed93c90e063d Mon Sep 17 00:00:00 2001 From: Samir Talwar Date: Fri, 2 Jun 2023 11:29:48 +0200 Subject: [PATCH] server: Preserve ordering when possible, and sort when it's not. When upgrading to GHC v9.4, we noticed a number of failures because the sort order of HashMaps has changed. With this changeset, I am endeavoring to mitigate this now and in the future. This makes one of two changes in a few areas where we depend on the sort order of elements in a `HashMap`: 1. the ordering of the request is preserved with `InsOrdHashMap`, or 2. we sort the data after retrieving it. Fortunately, we do not do this anywhere where we _must_ preserve order; it's "just" descriptions, error messages, and OpenAPI metadata. The main problem is that tests are likely to fail each time we upgrade GHC (or whatever is providing the hash seed). [NDAT-705]: https://hasurahq.atlassian.net/browse/NDAT-705?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9390 GitOrigin-RevId: 84503e029b44094edbbc298651744bc2843c15f3 --- .../Hasura/GraphQL/Parser/Internal/Parser.hs | 19 +++--- .../Backends/Postgres/Instances/Schema.hs | 2 +- .../Select/Internal/GenerateSelect.hs | 3 +- .../Translate/Select/Internal/JoinTree.hs | 8 +-- .../Translate/Select/Internal/OrderBy.hs | 8 +-- .../Translate/Select/Internal/Process.hs | 15 ++--- .../Backends/Postgres/Translate/Types.hs | 2 +- server/src-lib/Hasura/GraphQL/Schema/Table.hs | 2 +- server/src-lib/Hasura/RQL/Types/Column.hs | 3 +- server/src-lib/Hasura/Server/OpenAPI.hs | 2 +- .../BoolExp/AggregationPredicatesSpec.hs | 14 ++--- .../Hasura/GraphQL/Schema/Build/UpdateSpec.hs | 7 ++- server/src-test/Test/Backend/Postgres/Misc.hs | 3 + server/src-test/Test/Parser/Internal.hs | 4 +- .../explain/limit_orderby_column_query.yaml | 8 +-- .../orderby_array_relationship_query.yaml | 12 ++-- .../enums/insert_enum_field_bad_value.yaml | 11 ++-- .../enums/select_where_enum_eq_bad_value.yaml | 11 ++-- ...lect_where_enum_eq_variable_bad_value.yaml | 11 ++-- ...penapi_endpoint_with_multiple_methods.yaml | 60 +++++++++---------- .../openapi_multiple_endpoints_same_path.yaml | 14 ++--- .../openapi_multiple_endpoints_test.yaml | 25 ++++---- ...multiple_endpoints_with_path_segments.yaml | 12 ++-- .../openapi_post_endpoint_test_with_args.yaml | 14 ++--- ...napi_post_endpoint_test_with_args_url.yaml | 12 ++-- .../fail_conflicting_custom_table_name.yaml | 21 +++---- 26 files changed, 153 insertions(+), 150 deletions(-) diff --git a/server/lib/schema-parsers/src/Hasura/GraphQL/Parser/Internal/Parser.hs b/server/lib/schema-parsers/src/Hasura/GraphQL/Parser/Internal/Parser.hs index 1c0756be13d..e64d5b01233 100644 --- a/server/lib/schema-parsers/src/Hasura/GraphQL/Parser/Internal/Parser.hs +++ b/server/lib/schema-parsers/src/Hasura/GraphQL/Parser/Internal/Parser.hs @@ -24,6 +24,7 @@ import Data.Function ((&)) import Data.Functor ((<&>)) import Data.HashMap.Strict (HashMap) import Data.HashMap.Strict qualified as HashMap +import Data.HashMap.Strict.InsOrd (InsOrdHashMap) import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap import Data.HashSet qualified as S import Data.Hashable (Hashable) @@ -175,7 +176,7 @@ selectionSet :: Name -> Maybe Description -> [FieldParser origin m a] -> - Parser origin 'Output m (InsOrdHashMap.InsOrdHashMap Name (ParsedSelection a)) + Parser origin 'Output m (InsOrdHashMap Name (ParsedSelection a)) selectionSet name desc fields = selectionSetObject name desc fields [] safeSelectionSet :: @@ -184,7 +185,7 @@ safeSelectionSet :: Name -> Maybe Description -> [FieldParser origin m a] -> - n (Parser origin 'Output m (InsOrdHashMap.InsOrdHashMap Name (ParsedSelection a))) + n (Parser origin 'Output m (InsOrdHashMap Name (ParsedSelection a))) {-# INLINE safeSelectionSet #-} safeSelectionSet name description fields = case duplicatesList of @@ -194,10 +195,14 @@ safeSelectionSet name description fields = -- plural printedDuplicates -> throwError $ "Encountered conflicting definitions in the selection set for " <> toErrorValue name <> " for fields: " <> toErrorValue printedDuplicates <> ". Fields must not be defined more than once across all sources." where - namesOrigins :: HashMap Name [Maybe origin] - namesOrigins = HashMap.fromListWith (<>) $ (dName &&& (pure . dOrigin)) . fDefinition <$> fields - duplicates :: HashMap Name [Maybe origin] - duplicates = HashMap.filter ((> 1) . length) namesOrigins + namesOrigins :: InsOrdHashMap Name [Maybe origin] + namesOrigins = + foldr + (uncurry (InsOrdHashMap.insertWith (<>))) + InsOrdHashMap.empty + ((dName &&& (pure . dOrigin)) . fDefinition <$> fields) + duplicates :: InsOrdHashMap Name [Maybe origin] + duplicates = InsOrdHashMap.filter ((> 1) . length) namesOrigins uniques = S.toList . S.fromList printEntry (fieldName, originsM) = let origins = uniques $ catMaybes originsM @@ -207,7 +212,7 @@ safeSelectionSet name description fields = toErrorValue fieldName <> " defined in " <> toErrorValue origins <> " and of unknown origin" | otherwise -> toErrorValue fieldName <> " defined in " <> toErrorValue origins - duplicatesList = printEntry <$> HashMap.toList duplicates + duplicatesList = printEntry <$> InsOrdHashMap.toList duplicates -- Should this rather take a non-empty `FieldParser` list? -- See also Note [Selectability of tables]. diff --git a/server/src-lib/Hasura/Backends/Postgres/Instances/Schema.hs b/server/src-lib/Hasura/Backends/Postgres/Instances/Schema.hs index 3b1e5cce860..85f909a15ae 100644 --- a/server/src-lib/Hasura/Backends/Postgres/Instances/Schema.hs +++ b/server/src-lib/Hasura/Backends/Postgres/Instances/Schema.hs @@ -450,7 +450,7 @@ columnParser columnType nullability = case columnType of `onLeft` (P.parseErrorWith P.ParseFailed . toErrorMessage . qeError) } ColumnEnumReference (EnumReference tableName enumValues tableCustomName) -> - case nonEmpty (HashMap.toList enumValues) of + case nonEmpty . sortOn fst $ HashMap.toList enumValues of Just enumValuesList -> peelWithOrigin . fmap (ColumnValue columnType) diff --git a/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/GenerateSelect.hs b/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/GenerateSelect.hs index 344b61a0609..049371865a7 100644 --- a/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/GenerateSelect.hs +++ b/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/GenerateSelect.hs @@ -11,6 +11,7 @@ module Hasura.Backends.Postgres.Translate.Select.Internal.GenerateSelect where import Data.HashMap.Strict qualified as HashMap +import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap import Data.List.NonEmpty qualified as NE import Hasura.Backends.Postgres.SQL.DML qualified as S import Hasura.Backends.Postgres.SQL.Types @@ -42,7 +43,7 @@ generateSQLSelect :: generateSQLSelect joinCondition selectSource selectNode = S.mkSelect { S.selExtr = - case [S.Extractor e $ Just a | (a, e) <- HashMap.toList extractors] of + case [S.Extractor e $ Just a | (a, e) <- InsOrdHashMap.toList extractors] of -- If the select list is empty we will generated code which looks like this: -- > SELECT FROM ... -- This works for postgres, but not for cockroach, which expects a non-empty diff --git a/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/JoinTree.hs b/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/JoinTree.hs index 07f955a34af..4405dd119b2 100644 --- a/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/JoinTree.hs +++ b/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/JoinTree.hs @@ -43,7 +43,7 @@ withWriteObjectRelation :: (MonadWriter SelectWriter m) => m ( ObjectRelationSource, - HashMap.HashMap S.ColumnAlias S.SQLExp, + InsOrdHashMap S.ColumnAlias S.SQLExp, a ) -> m a @@ -61,7 +61,7 @@ withWriteArrayRelation :: m ( ArrayRelationSource, S.Extractor, - HashMap.HashMap S.ColumnAlias S.SQLExp, + InsOrdHashMap S.ColumnAlias S.SQLExp, a ) -> m a @@ -81,7 +81,7 @@ withWriteArrayConnection :: m ( ArrayConnectionSource, S.Extractor, - HashMap.HashMap S.ColumnAlias S.SQLExp, + InsOrdHashMap S.ColumnAlias S.SQLExp, a ) -> m a @@ -101,7 +101,7 @@ withWriteComputedFieldTableSet :: m ( ComputedFieldTableSetSource, S.Extractor, - HashMap.HashMap S.ColumnAlias S.SQLExp, + InsOrdHashMap S.ColumnAlias S.SQLExp, a ) -> m a diff --git a/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/OrderBy.hs b/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/OrderBy.hs index 6fe196257d8..4aa8ea12121 100644 --- a/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/OrderBy.hs +++ b/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/OrderBy.hs @@ -5,7 +5,7 @@ module Hasura.Backends.Postgres.Translate.Select.Internal.OrderBy where import Control.Lens ((^?)) -import Data.HashMap.Strict qualified as HashMap +import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap import Data.List.NonEmpty qualified as NE import Hasura.Backends.Postgres.SQL.DML qualified as S import Hasura.Backends.Postgres.SQL.Types @@ -133,7 +133,7 @@ processOrderByItems sourcePrefix' fieldAlias' similarArrayFields distOnCols = \c relSource = ObjectRelationSource relName colMapping selectSource Nullable pure ( relSource, - HashMap.singleton relOrderByAlias relOrdByExp, + InsOrdHashMap.singleton relOrderByAlias relOrdByExp, S.mkQIdenExp relSourcePrefix relOrderByAlias ) AOCArrayAggregation relInfo relFilter aggOrderBy -> withWriteArrayRelation $ do @@ -160,7 +160,7 @@ processOrderByItems sourcePrefix' fieldAlias' similarArrayFields distOnCols = \c pure ( relSource, topExtractor, - HashMap.fromList $ aggregateFieldsToExtractorExps relSourcePrefix fields, + InsOrdHashMap.fromList $ aggregateFieldsToExtractorExps relSourcePrefix fields, S.mkQIdenExp relSourcePrefix (mkAggregateOrderByAlias aggOrderBy) ) AOCComputedField ComputedFieldOrderBy {..} -> @@ -186,7 +186,7 @@ processOrderByItems sourcePrefix' fieldAlias' similarArrayFields distOnCols = \c pure ( source, topExtractor, - HashMap.fromList $ aggregateFieldsToExtractorExps computedFieldSourcePrefix fields, + InsOrdHashMap.fromList $ aggregateFieldsToExtractorExps computedFieldSourcePrefix fields, S.mkQIdenExp computedFieldSourcePrefix (mkAggregateOrderByAlias aggOrderBy) ) diff --git a/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/Process.hs b/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/Process.hs index 6ba2ae86e3d..58d9dd91f01 100644 --- a/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/Process.hs +++ b/server/src-lib/Hasura/Backends/Postgres/Translate/Select/Internal/Process.hs @@ -24,6 +24,7 @@ module Hasura.Backends.Postgres.Translate.Select.Internal.Process where import Data.HashMap.Strict qualified as HashMap +import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap import Data.List.NonEmpty qualified as NE import Data.Text.Extended (ToTxt (toTxt)) import Data.Text.NonEmpty qualified as TNE @@ -205,7 +206,7 @@ processAnnAggregateSelect :: AnnAggregateSelect ('Postgres pgKind) -> m ( SelectSource, - HashMap.HashMap S.ColumnAlias S.SQLExp, + InsOrdHashMap S.ColumnAlias S.SQLExp, S.Extractor ) processAnnAggregateSelect sourcePrefixes fieldAlias annAggSel = do @@ -247,7 +248,7 @@ processAnnAggregateSelect sourcePrefixes fieldAlias annAggSel = do $ flip concatMap (map (second snd) processedFields) $ \(FieldName fieldText, fieldExp) -> [S.SELit fieldText, fieldExp] nodeExtractors = - HashMap.fromList + InsOrdHashMap.fromList $ concatMap (fst . snd) processedFields <> orderByAndDistinctExtrs @@ -347,7 +348,7 @@ processAnnFields sourcePrefix fieldAlias similarArrFields annFields tCase = do annFieldsExtr <- processAnnFields (identifierToTableIdentifier $ _pfThis sourcePrefixes) fieldName HashMap.empty objAnnFields tCase pure ( objRelSource, - HashMap.fromList [annFieldsExtr], + uncurry InsOrdHashMap.singleton annFieldsExtr, S.mkQIdenExp objRelSourcePrefix fieldName ) AFArrayRelation arrSel -> do @@ -602,7 +603,7 @@ processAnnSimpleSelect :: AnnSimpleSelect ('Postgres pgKind) -> m ( SelectSource, - HashMap.HashMap S.ColumnAlias S.SQLExp + InsOrdHashMap S.ColumnAlias S.SQLExp ) processAnnSimpleSelect sourcePrefixes fieldAlias permLimitSubQuery annSimpleSel = do (selectSource, orderByAndDistinctExtrs, _) <- @@ -621,7 +622,7 @@ processAnnSimpleSelect sourcePrefixes fieldAlias permLimitSubQuery annSimpleSel similarArrayFields annSelFields tCase - let allExtractors = HashMap.fromList $ annFieldsExtr : orderByAndDistinctExtrs + let allExtractors = InsOrdHashMap.fromList $ annFieldsExtr : orderByAndDistinctExtrs pure (selectSource, allExtractors) where AnnSelectG annSelFields tableFrom tablePermissions tableArgs _ tCase = annSimpleSel @@ -644,7 +645,7 @@ processConnectionSelect :: m ( ArrayConnectionSource, S.Extractor, - HashMap.HashMap S.ColumnAlias S.SQLExp + InsOrdHashMap S.ColumnAlias S.SQLExp ) processConnectionSelect sourcePrefixes fieldAlias relAlias colMapping connectionSelect = do (selectSource, orderByAndDistinctExtrs, maybeOrderByCursor) <- @@ -666,7 +667,7 @@ processConnectionSelect sourcePrefixes fieldAlias relAlias colMapping connection mkCursorExtractor primaryKeyColumnsObjectExp : primaryKeyColumnExtractors (topExtractorExp, exps) <- flip runStateT [] $ processFields selectSource let topExtractor = S.Extractor topExtractorExp $ Just $ S.toColumnAlias fieldIdentifier - allExtractors = HashMap.fromList $ cursorExtractors <> exps <> orderByAndDistinctExtrs + allExtractors = InsOrdHashMap.fromList $ cursorExtractors <> exps <> orderByAndDistinctExtrs arrayConnectionSource = ArrayConnectionSource relAlias diff --git a/server/src-lib/Hasura/Backends/Postgres/Translate/Types.hs b/server/src-lib/Hasura/Backends/Postgres/Translate/Types.hs index a42f173d50c..15ad6ad3ee8 100644 --- a/server/src-lib/Hasura/Backends/Postgres/Translate/Types.hs +++ b/server/src-lib/Hasura/Backends/Postgres/Translate/Types.hs @@ -136,7 +136,7 @@ applySortingAndSlicing SortingAndSlicing {..} = ApplySortingAndSlicing (Nothing, noSlicing, Nothing) (Just nodeOrderBy, _sasSlicing, nodeDistinctOn) data SelectNode = SelectNode - { _snExtractors :: HashMap.HashMap Postgres.ColumnAlias Postgres.SQLExp, + { _snExtractors :: InsOrdHashMap Postgres.ColumnAlias Postgres.SQLExp, _snJoinTree :: JoinTree } deriving stock (Eq, Show) diff --git a/server/src-lib/Hasura/GraphQL/Schema/Table.hs b/server/src-lib/Hasura/GraphQL/Schema/Table.hs index 40901f1c0b0..61526138f3b 100644 --- a/server/src-lib/Hasura/GraphQL/Schema/Table.hs +++ b/server/src-lib/Hasura/GraphQL/Schema/Table.hs @@ -258,7 +258,7 @@ tableSelectFields tableInfo = do tableColumns :: forall b. TableInfo b -> [ColumnInfo b] tableColumns tableInfo = - mapMaybe columnInfo . HashMap.elems . _tciFieldInfoMap . _tiCoreInfo $ tableInfo + sortOn ciPosition . mapMaybe columnInfo . HashMap.elems . _tciFieldInfoMap . _tiCoreInfo $ tableInfo where columnInfo (FIColumn (SCIScalarColumn ci)) = Just ci columnInfo _ = Nothing diff --git a/server/src-lib/Hasura/RQL/Types/Column.hs b/server/src-lib/Hasura/RQL/Types/Column.hs index 35dfc8c542c..dba60fa8258 100644 --- a/server/src-lib/Hasura/RQL/Types/Column.hs +++ b/server/src-lib/Hasura/RQL/Types/Column.hs @@ -159,7 +159,6 @@ parseScalarValueColumnType :: parseScalarValueColumnType columnType value = case columnType of ColumnScalar scalarType -> liftEither $ parseScalarValue @b scalarType value ColumnEnumReference (EnumReference tableName enumValues _) -> - -- maybe (pure $ PGNull PGText) parseEnumValue =<< decodeValue value parseEnumValue =<< decodeValue value where parseEnumValue :: Maybe G.Name -> m (ScalarValue b) @@ -169,7 +168,7 @@ parseScalarValueColumnType columnType value = case columnType of unless (evn `elem` enums) $ throw400 UnexpectedPayload $ "expected one of the values " - <> dquoteList enums + <> dquoteList (sort enums) <> " for type " <> snakeCaseTableName @b tableName <<> ", given " diff --git a/server/src-lib/Hasura/Server/OpenAPI.hs b/server/src-lib/Hasura/Server/OpenAPI.hs index f2685216094..bb7ec70ad58 100644 --- a/server/src-lib/Hasura/Server/OpenAPI.hs +++ b/server/src-lib/Hasura/Server/OpenAPI.hs @@ -122,7 +122,7 @@ buildEndpoint schemaTypes method EndpointMetadata {..} = do -- We expect one optional parameter per known scalar variable. collectParams :: Structure -> EndpointUrl -> [Referenced Param] collectParams (Structure _ vars) eURL = do - (G.unName -> varName, VariableInfo {..}) <- HashMap.toList vars + (G.unName -> varName, VariableInfo {..}) <- sortOn fst $ HashMap.toList vars case _viTypeInfo of -- we do not allow input objects or enums in parameters InputFieldObjectInfo _ -> empty diff --git a/server/src-test/Hasura/GraphQL/Schema/BoolExp/AggregationPredicatesSpec.hs b/server/src-test/Hasura/GraphQL/Schema/BoolExp/AggregationPredicatesSpec.hs index 8fc2d6d1e1a..2a8cf8f68fd 100644 --- a/server/src-test/Hasura/GraphQL/Schema/BoolExp/AggregationPredicatesSpec.hs +++ b/server/src-test/Hasura/GraphQL/Schema/BoolExp/AggregationPredicatesSpec.hs @@ -44,13 +44,7 @@ import Test.Hspec import Test.Hspec.Extended import Test.Parser.Field qualified as GQL import Test.Parser.Internal - ( ColumnInfoBuilder - ( ColumnInfoBuilder, - cibIsPrimaryKey, - cibName, - cibNullable, - cibType - ), + ( ColumnInfoBuilder (..), TableInfoBuilder (columns, relations), buildTableInfo, mkTable, @@ -255,12 +249,14 @@ spec = do { columns = [ ColumnInfoBuilder { cibName = "id", + cibPosition = 0, cibType = ColumnScalar PGInteger, cibNullable = False, cibIsPrimaryKey = True }, ColumnInfoBuilder { cibName = "title", + cibPosition = 1, cibType = ColumnScalar PGText, cibNullable = False, cibIsPrimaryKey = False @@ -277,24 +273,28 @@ spec = do { columns = [ ColumnInfoBuilder { cibName = "id", + cibPosition = 0, cibType = ColumnScalar PGInteger, cibNullable = False, cibIsPrimaryKey = True }, ColumnInfoBuilder { cibName = "title", + cibPosition = 1, cibType = ColumnScalar PGText, cibNullable = False, cibIsPrimaryKey = False }, ColumnInfoBuilder { cibName = "duration_seconds", + cibPosition = 2, cibType = ColumnScalar PGInteger, cibNullable = False, cibIsPrimaryKey = False }, ColumnInfoBuilder { cibName = "album_id", + cibPosition = 3, cibType = ColumnScalar PGInteger, cibNullable = False, cibIsPrimaryKey = False diff --git a/server/src-test/Hasura/GraphQL/Schema/Build/UpdateSpec.hs b/server/src-test/Hasura/GraphQL/Schema/Build/UpdateSpec.hs index 81592040c84..41d6e6a795c 100644 --- a/server/src-test/Hasura/GraphQL/Schema/Build/UpdateSpec.hs +++ b/server/src-test/Hasura/GraphQL/Schema/Build/UpdateSpec.hs @@ -79,12 +79,13 @@ spec = do } |] } + describe "update many" do it "one update" do runUpdateFieldTest UpdateTestSetup { utsTable = "artist", - utsColumns = [P.nameColumnBuilder, P.descColumnBuilder, P.idColumnBuilder], + utsColumns = [P.idColumnBuilder, P.nameColumnBuilder, P.descColumnBuilder], utsExpect = UpdateExpectationBuilder { utbOutput = MOutMultirowFields [("affected_rows", MCount)], @@ -117,7 +118,7 @@ spec = do runUpdateFieldTest UpdateTestSetup { utsTable = "artist", - utsColumns = [P.nameColumnBuilder, P.descColumnBuilder, P.idColumnBuilder], + utsColumns = [P.idColumnBuilder, P.nameColumnBuilder, P.descColumnBuilder], utsExpect = UpdateExpectationBuilder { utbOutput = MOutMultirowFields [("affected_rows", MCount)], @@ -157,7 +158,7 @@ spec = do runUpdateFieldTest UpdateTestSetup { utsTable = "artist", - utsColumns = [P.nameColumnBuilder, P.descColumnBuilder, P.idColumnBuilder], + utsColumns = [P.idColumnBuilder, P.nameColumnBuilder, P.descColumnBuilder], utsExpect = UpdateExpectationBuilder { utbOutput = MOutMultirowFields [("affected_rows", MCount)], diff --git a/server/src-test/Test/Backend/Postgres/Misc.hs b/server/src-test/Test/Backend/Postgres/Misc.hs index 480653efac6..f38fd183fdd 100644 --- a/server/src-test/Test/Backend/Postgres/Misc.hs +++ b/server/src-test/Test/Backend/Postgres/Misc.hs @@ -41,6 +41,7 @@ idColumnBuilder :: Expect.ColumnInfoBuilder idColumnBuilder = Expect.ColumnInfoBuilder { cibName = "id", + cibPosition = 0, cibType = ColumnScalar PGInteger, cibNullable = False, cibIsPrimaryKey = True @@ -50,6 +51,7 @@ nameColumnBuilder :: Expect.ColumnInfoBuilder nameColumnBuilder = Expect.ColumnInfoBuilder { cibName = "name", + cibPosition = 1, cibType = ColumnScalar PGText, cibNullable = False, cibIsPrimaryKey = False @@ -59,6 +61,7 @@ descColumnBuilder :: Expect.ColumnInfoBuilder descColumnBuilder = Expect.ColumnInfoBuilder { cibName = "description", + cibPosition = 2, cibType = ColumnScalar PGText, cibNullable = False, cibIsPrimaryKey = False diff --git a/server/src-test/Test/Parser/Internal.hs b/server/src-test/Test/Parser/Internal.hs index 5978894092a..8c428b90945 100644 --- a/server/src-test/Test/Parser/Internal.hs +++ b/server/src-test/Test/Parser/Internal.hs @@ -55,6 +55,8 @@ mkTable name = data ColumnInfoBuilder = ColumnInfoBuilder { -- | name of the column cibName :: Text, + -- | column position + cibPosition :: Int, -- | Column type, e.g. -- -- > ColumnScalar PGText @@ -73,7 +75,7 @@ mkColumnInfo ColumnInfoBuilder {..} = ColumnInfo { ciColumn = unsafePGCol cibName, ciName = unsafeMkName cibName, - ciPosition = 0, + ciPosition = cibPosition, ciType = cibType, ciIsNullable = cibNullable, ciDescription = Nothing, diff --git a/server/tests-py/queries/explain/limit_orderby_column_query.yaml b/server/tests-py/queries/explain/limit_orderby_column_query.yaml index 105c10058e2..75a51384aee 100644 --- a/server/tests-py/queries/explain/limit_orderby_column_query.yaml +++ b/server/tests-py/queries/explain/limit_orderby_column_query.yaml @@ -14,10 +14,10 @@ query: response: - field: article sql: "SELECT coalesce(json_agg(\"root\" ORDER BY \"root.pg.author_id\" DESC NULLS\ - \ FIRST), '[]' ) AS \"root\" FROM (SELECT \"_root.base\".\"author_id\" AS \"\ - root.pg.author_id\", row_to_json((SELECT \"_e\" FROM (SELECT \"_root.base\"\ - .\"id\" AS \"id\", \"_root.base\".\"title\" AS \"title\", \"_root.base\".\"content\"\ - \ AS \"content\" ) AS \"_e\" ) ) AS \"root\" FROM (SELECT * FROM\ + \ FIRST), '[]' ) AS \"root\" FROM (SELECT row_to_json((SELECT \"_e\" FROM\ + \ (SELECT \"_root.base\".\"id\" AS \"id\", \"_root.base\".\"title\" AS \"title\"\ + , \"_root.base\".\"content\" AS \"content\" ) AS \"_e\" ) ) AS \"root\"\ + , \"_root.base\".\"author_id\" AS \"root.pg.author_id\" FROM (SELECT * FROM\ \ \"public\".\"article\" WHERE ('true') ORDER BY \"author_id\" DESC NULLS FIRST\ \ LIMIT 5 ) AS \"_root.base\" ORDER BY \"root.pg.author_id\" DESC NULLS FIRST\ \ ) AS \"_root\" " diff --git a/server/tests-py/queries/explain/orderby_array_relationship_query.yaml b/server/tests-py/queries/explain/orderby_array_relationship_query.yaml index 262f84c4db2..05ad4a8fe1d 100644 --- a/server/tests-py/queries/explain/orderby_array_relationship_query.yaml +++ b/server/tests-py/queries/explain/orderby_array_relationship_query.yaml @@ -19,11 +19,11 @@ response: .\"articles\" AS \"articles\" ) AS \"_e\" ) ) AS \"root\" FROM (SELECT\ \ * FROM \"public\".\"author\" WHERE ('true') ) AS \"_root.base\" LEFT\ \ OUTER JOIN LATERAL (SELECT coalesce(json_agg(\"articles\" ORDER BY \"root.ar.root.articles.pg.id\"\ - \ DESC NULLS FIRST), '[]' ) AS \"articles\" FROM (SELECT \"_root.ar.root.articles.base\"\ - .\"id\" AS \"root.ar.root.articles.pg.id\", row_to_json((SELECT \"_e\" FROM\ - \ (SELECT \"_root.ar.root.articles.base\".\"title\" AS \"title\" ) AS\ - \ \"_e\" ) ) AS \"articles\" FROM (SELECT * FROM \"public\".\"article\"\ - \ WHERE ((\"_root.base\".\"id\") = (\"author_id\")) ORDER BY \"id\" DESC NULLS\ - \ FIRST ) AS \"_root.ar.root.articles.base\" ORDER BY \"root.ar.root.articles.pg.id\"\ + \ DESC NULLS FIRST), '[]' ) AS \"articles\" FROM (SELECT row_to_json((SELECT\ + \ \"_e\" FROM (SELECT \"_root.ar.root.articles.base\".\"title\" AS \"title\"\ + \ ) AS \"_e\" ) ) AS \"articles\", \"_root.ar.root.articles.base\"\ + .\"id\" AS \"root.ar.root.articles.pg.id\" FROM (SELECT * FROM \"public\".\"\ + article\" WHERE ((\"_root.base\".\"id\") = (\"author_id\")) ORDER BY \"id\"\ + \ DESC NULLS FIRST ) AS \"_root.ar.root.articles.base\" ORDER BY \"root.ar.root.articles.pg.id\"\ \ DESC NULLS FIRST ) AS \"_root.ar.root.articles\" ) AS \"_root.ar.root.articles\"\ \ ON ('true') ) AS \"_root\" " diff --git a/server/tests-py/queries/graphql_mutation/enums/insert_enum_field_bad_value.yaml b/server/tests-py/queries/graphql_mutation/enums/insert_enum_field_bad_value.yaml index 6a9716a92e5..12a92efa28c 100644 --- a/server/tests-py/queries/graphql_mutation/enums/insert_enum_field_bad_value.yaml +++ b/server/tests-py/queries/graphql_mutation/enums/insert_enum_field_bad_value.yaml @@ -3,12 +3,11 @@ url: /v1/graphql status: 200 response: errors: - - message: >- - expected one of the values ['orange', 'yellow', 'green', 'purple', 'blue', 'red'] - for type 'colors_enum', but found 'not_a_real_color' - extensions: - code: validation-failed - path: $.selectionSet.insert_users.args.objects[0].favorite_color + - extensions: + code: validation-failed + path: $.selectionSet.insert_users.args.objects[0].favorite_color + message: expected one of the values ['blue', 'green', 'orange', 'purple', 'red', + 'yellow'] for type 'colors_enum', but found 'not_a_real_color' query: query: | mutation { diff --git a/server/tests-py/queries/graphql_query/enums/select_where_enum_eq_bad_value.yaml b/server/tests-py/queries/graphql_query/enums/select_where_enum_eq_bad_value.yaml index 57413714983..d3199d13ac1 100644 --- a/server/tests-py/queries/graphql_query/enums/select_where_enum_eq_bad_value.yaml +++ b/server/tests-py/queries/graphql_query/enums/select_where_enum_eq_bad_value.yaml @@ -3,12 +3,11 @@ url: /v1/graphql status: 200 response: errors: - - extensions: - code: validation-failed - path: $.selectionSet.users.args.where.favorite_color._eq - message: >- - expected one of the values ['orange', 'yellow', 'green', 'purple', 'blue', 'red'] - for type 'colors_enum', but found 'not_a_real_color' + - extensions: + code: validation-failed + path: $.selectionSet.users.args.where.favorite_color._eq + message: expected one of the values ['blue', 'green', 'orange', 'purple', 'red', + 'yellow'] for type 'colors_enum', but found 'not_a_real_color' query: query: | { diff --git a/server/tests-py/queries/graphql_query/enums/select_where_enum_eq_variable_bad_value.yaml b/server/tests-py/queries/graphql_query/enums/select_where_enum_eq_variable_bad_value.yaml index 604acba7fc6..411cc6d4390 100644 --- a/server/tests-py/queries/graphql_query/enums/select_where_enum_eq_variable_bad_value.yaml +++ b/server/tests-py/queries/graphql_query/enums/select_where_enum_eq_variable_bad_value.yaml @@ -3,12 +3,11 @@ url: /v1/graphql status: 200 response: errors: - - extensions: - code: validation-failed - path: $.selectionSet.users.args.where.favorite_color._eq - message: >- - expected one of the values ['orange', 'yellow', 'green', 'purple', 'blue', 'red'] - for type 'colors_enum', but found 'not_a_real_color' + - extensions: + code: validation-failed + path: $.selectionSet.users.args.where.favorite_color._eq + message: expected one of the values ['blue', 'green', 'orange', 'purple', 'red', + 'yellow'] for type 'colors_enum', but found 'not_a_real_color' query: query: | query ($color: colors_enum) { diff --git a/server/tests-py/queries/openapi/openapi_endpoint_with_multiple_methods.yaml b/server/tests-py/queries/openapi/openapi_endpoint_with_multiple_methods.yaml index 36aafe526cb..8388fb99f94 100644 --- a/server/tests-py/queries/openapi/openapi_endpoint_with_multiple_methods.yaml +++ b/server/tests-py/queries/openapi/openapi_endpoint_with_multiple_methods.yaml @@ -45,18 +45,18 @@ name: x-hasura-admin-secret schema: type: string - - description: _"last_name" is required (enter it either in parameters or - request body)_ - in: query - name: last_name - schema: - type: string - description: _"first_name" is required (enter it either in parameters or request body)_ in: query name: first_name schema: type: string + - description: _"last_name" is required (enter it either in parameters or + request body)_ + in: query + name: last_name + schema: + type: string requestBody: content: application/json: @@ -115,18 +115,18 @@ name: x-hasura-admin-secret schema: type: string - - description: _"last_name" is required (enter it either in parameters or - request body)_ - in: query - name: last_name - schema: - type: string - description: _"first_name" is required (enter it either in parameters or request body)_ in: query name: first_name schema: type: string + - description: _"last_name" is required (enter it either in parameters or + request body)_ + in: query + name: last_name + schema: + type: string requestBody: content: application/json: @@ -185,18 +185,18 @@ name: x-hasura-admin-secret schema: type: string - - description: _"last_name" is required (enter it either in parameters or - request body)_ - in: query - name: last_name - schema: - type: string - description: _"first_name" is required (enter it either in parameters or request body)_ in: query name: first_name schema: type: string + - description: _"last_name" is required (enter it either in parameters or + request body)_ + in: query + name: last_name + schema: + type: string requestBody: content: application/json: @@ -255,18 +255,18 @@ name: x-hasura-admin-secret schema: type: string - - description: _"last_name" is required (enter it either in parameters or - request body)_ - in: query - name: last_name - schema: - type: string - description: _"first_name" is required (enter it either in parameters or request body)_ in: query name: first_name schema: type: string + - description: _"last_name" is required (enter it either in parameters or + request body)_ + in: query + name: last_name + schema: + type: string requestBody: content: application/json: @@ -325,18 +325,18 @@ name: x-hasura-admin-secret schema: type: string - - description: _"last_name" is required (enter it either in parameters or - request body)_ - in: query - name: last_name - schema: - type: string - description: _"first_name" is required (enter it either in parameters or request body)_ in: query name: first_name schema: type: string + - description: _"last_name" is required (enter it either in parameters or + request body)_ + in: query + name: last_name + schema: + type: string requestBody: content: application/json: diff --git a/server/tests-py/queries/openapi/openapi_multiple_endpoints_same_path.yaml b/server/tests-py/queries/openapi/openapi_multiple_endpoints_same_path.yaml index 279e8d1f274..ef60f4eeff8 100644 --- a/server/tests-py/queries/openapi/openapi_multiple_endpoints_same_path.yaml +++ b/server/tests-py/queries/openapi/openapi_multiple_endpoints_same_path.yaml @@ -113,18 +113,18 @@ name: x-hasura-admin-secret schema: type: string - - description: _"last_name" is required (enter it either in parameters or - request body)_ - in: query - name: last_name - schema: - type: string - description: _"first_name" is required (enter it either in parameters or request body)_ in: query name: first_name schema: type: string + - description: _"last_name" is required (enter it either in parameters or + request body)_ + in: query + name: last_name + schema: + type: string requestBody: content: application/json: @@ -177,8 +177,6 @@ pattern: '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}' title: uuid type: string - - - description: Try to remove the endpoint url: /v1/query status: 200 diff --git a/server/tests-py/queries/openapi/openapi_multiple_endpoints_test.yaml b/server/tests-py/queries/openapi/openapi_multiple_endpoints_test.yaml index 12703dc0f1c..37185586fad 100644 --- a/server/tests-py/queries/openapi/openapi_multiple_endpoints_test.yaml +++ b/server/tests-py/queries/openapi/openapi_multiple_endpoints_test.yaml @@ -75,18 +75,18 @@ name: x-hasura-admin-secret schema: type: string - - description: _"last_name" is required (enter it either in parameters or - request body)_ - in: query - name: last_name - schema: - type: string - description: _"first_name" is required (enter it either in parameters or request body)_ in: query name: first_name schema: type: string + - description: _"last_name" is required (enter it either in parameters or + request body)_ + in: query + name: last_name + schema: + type: string requestBody: content: application/json: @@ -146,18 +146,18 @@ name: x-hasura-admin-secret schema: type: string - - description: _"last_name" is required (enter it either in parameters or - request body)_ - in: path - name: last_name - schema: - type: string - description: _"first_name" is required (enter it either in parameters or request body)_ in: path name: first_name schema: type: string + - description: _"last_name" is required (enter it either in parameters or + request body)_ + in: path + name: last_name + schema: + type: string requestBody: content: application/json: @@ -262,7 +262,6 @@ pattern: '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}' title: uuid type: string - - description: Try to remove the endpoint url: /v1/query status: 200 diff --git a/server/tests-py/queries/openapi/openapi_multiple_endpoints_with_path_segments.yaml b/server/tests-py/queries/openapi/openapi_multiple_endpoints_with_path_segments.yaml index a671bbeaef8..6c506b6e09e 100644 --- a/server/tests-py/queries/openapi/openapi_multiple_endpoints_with_path_segments.yaml +++ b/server/tests-py/queries/openapi/openapi_multiple_endpoints_with_path_segments.yaml @@ -96,18 +96,18 @@ name: x-hasura-admin-secret schema: type: string - - description: _"last_name" is required (enter it either in parameters or - request body)_ - in: path - name: last_name - schema: - type: string - description: _"first_name" is required (enter it either in parameters or request body)_ in: path name: first_name schema: type: string + - description: _"last_name" is required (enter it either in parameters or + request body)_ + in: path + name: last_name + schema: + type: string requestBody: content: application/json: diff --git a/server/tests-py/queries/openapi/openapi_post_endpoint_test_with_args.yaml b/server/tests-py/queries/openapi/openapi_post_endpoint_test_with_args.yaml index 8453e42fe31..079a10afcb2 100644 --- a/server/tests-py/queries/openapi/openapi_post_endpoint_test_with_args.yaml +++ b/server/tests-py/queries/openapi/openapi_post_endpoint_test_with_args.yaml @@ -41,18 +41,18 @@ name: x-hasura-admin-secret schema: type: string - - description: _"last_name" is required (enter it either in parameters or - request body)_ - in: query - name: last_name - schema: - type: string - description: _"first_name" is required (enter it either in parameters or request body)_ in: query name: first_name schema: type: string + - description: _"last_name" is required (enter it either in parameters or + request body)_ + in: query + name: last_name + schema: + type: string requestBody: content: application/json: @@ -78,7 +78,6 @@ properties: test_table: items: - title: test_table description: columns and relationships of "test_table" nullable: false properties: @@ -90,6 +89,7 @@ nullable: true title: String type: string + title: test_table type: object nullable: false type: array diff --git a/server/tests-py/queries/openapi/openapi_post_endpoint_test_with_args_url.yaml b/server/tests-py/queries/openapi/openapi_post_endpoint_test_with_args_url.yaml index d84a64141ad..99e245e54b9 100644 --- a/server/tests-py/queries/openapi/openapi_post_endpoint_test_with_args_url.yaml +++ b/server/tests-py/queries/openapi/openapi_post_endpoint_test_with_args_url.yaml @@ -41,18 +41,18 @@ name: x-hasura-admin-secret schema: type: string - - description: _"last_name" is required (enter it either in parameters or - request body)_ - in: path - name: last_name - schema: - type: string - description: _"first_name" is required (enter it either in parameters or request body)_ in: path name: first_name schema: type: string + - description: _"last_name" is required (enter it either in parameters or + request body)_ + in: path + name: last_name + schema: + type: string requestBody: content: application/json: diff --git a/server/tests-py/queries/v1/set_table_configuration/fail_conflicting_custom_table_name.yaml b/server/tests-py/queries/v1/set_table_configuration/fail_conflicting_custom_table_name.yaml index 4c44cd4aee1..9bb7cd855e5 100644 --- a/server/tests-py/queries/v1/set_table_configuration/fail_conflicting_custom_table_name.yaml +++ b/server/tests-py/queries/v1/set_table_configuration/fail_conflicting_custom_table_name.yaml @@ -4,18 +4,15 @@ url: /v1/query response: code: unexpected error: >- - Encountered conflicting definitions in the selection set for 'mutation_root' - for fields: ['update_article' defined in [table article in source default, - table author in source default], 'delete_article' defined in [table article - in source default, table author in source default], 'update_article_many' - defined in [table article in source default, table author in source default], - 'insert_article_one' defined in [table article in source default, table - author in source default], 'update_article_by_pk' defined in [table article - in source default, table author in source default], 'delete_article_by_pk' - defined in [table article in source default, table author in source - default], 'insert_article' defined in [table article in source default, - table author in source default]]. Fields must not be defined more than once - across all sources. + Encountered conflicting definitions in the selection set for 'mutation_root' for fields: [ + 'delete_article_by_pk' defined in [table article in source default, table author in source default], + 'delete_article' defined in [table article in source default, table author in source default], + 'update_article_many' defined in [table article in source default, table author in source default], + 'update_article_by_pk' defined in [table article in source default, table author in source default], + 'update_article' defined in [table article in source default, table author in source default], + 'insert_article_one' defined in [table article in source default, table author in source default], + 'insert_article' defined in [table article in source default, table author in source default] + ]. Fields must not be defined more than once across all sources. path: $.args query: type: set_table_customization