Skip to content

Commit b9c17fb

Browse files
committed
fix: use index on jsonb/jsonb arrow filter/order
1 parent e332f03 commit b9c17fb

File tree

9 files changed

+112
-43
lines changed

9 files changed

+112
-43
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2929
- #2834, Fix compilation on Ubuntu by being compatible with GHC 9.0.2 - @steve-chavez
3030
- #2840, Fix `Prefer: missing=default` with DOMAIN default values - @steve-chavez
3131
- #2849, Fix HEAD unnecessarily executing aggregates - @steve-chavez
32+
- #2594, Fix unused index on jsonb/jsonb arrow filter (``/bets?data->>contractId=eq.1``) - @steve-chavez
3233

3334
## [11.1.0] - 2023-06-07
3435

src/PostgREST/Plan.hs

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ data ResolverContext = ResolverContext
219219
}
220220

221221
resolveColumnField :: Column -> CoercibleField
222-
resolveColumnField col = CoercibleField (colName col) mempty (colNominalType col) Nothing (colDefault col)
222+
resolveColumnField col = CoercibleField (colName col) mempty False (colNominalType col) Nothing (colDefault col)
223223

224224
resolveTableFieldName :: Table -> FieldName -> CoercibleField
225225
resolveTableFieldName table fieldName =
@@ -228,11 +228,14 @@ resolveTableFieldName table fieldName =
228228

229229
resolveTableField :: Table -> Field -> CoercibleField
230230
resolveTableField table (fieldName, []) = resolveTableFieldName table fieldName
231-
-- If the field is known and a JSON path is given, always assume the JSON type. But don't assume a type for entirely unknown fields.
232231
resolveTableField table (fieldName, jp) =
233232
case resolveTableFieldName table fieldName of
234-
cf@CoercibleField{cfIRType=""} -> cf{cfJsonPath=jp}
235-
cf -> cf{cfJsonPath=jp, cfIRType="json"}
233+
-- types that are already json/jsonb don't need to be converted with `to_jsonb` for using arrow operators `data->attr`
234+
-- this prevents indexes not applying https://github.com/PostgREST/postgrest/issues/2594
235+
cf@CoercibleField{cfIRType="json"} -> cf{cfJsonPath=jp}
236+
cf@CoercibleField{cfIRType="jsonb"} -> cf{cfJsonPath=jp}
237+
-- other types will get converted `to_jsonb(col)->attr`
238+
cf -> cf{cfJsonPath=jp, cfToJson=True}
236239

237240
-- | Resolve a type within the context based on the given field name and JSON path. Although there are situations where failure to resolve a field is considered an error (see `resolveOrError`), there are also situations where we allow it (RPC calls). If it should be an error and `resolveOrError` doesn't fit, ensure to check the `cfIRType` isn't empty.
238241
resolveTypeOrUnknown :: ResolverContext -> Field -> CoercibleField
@@ -289,7 +292,7 @@ readPlan qi@QualifiedIdentifier{..} AppConfig{configDbMaxRows} SchemaCache{dbTab
289292
addRels qiSchema (iAction apiRequest) dbRelationships Nothing =<<
290293
addLogicTrees ctx apiRequest =<<
291294
addRanges apiRequest =<<
292-
addOrders apiRequest =<<
295+
addOrders ctx apiRequest =<<
293296
addFilters ctx apiRequest (initReadRequest ctx $ QueryParams.qsSelect $ iQueryParams apiRequest)
294297

295298
-- Build the initial read plan tree
@@ -526,38 +529,41 @@ addFilters ctx ApiRequest{..} rReq =
526529
addFilterToNode =
527530
updateNode (\flt (Node q@ReadPlan{from=fromTable, where_=lf} f) -> Node q{ReadPlan.where_=addFilterToLogicForest (resolveFilter ctx{qi=fromTable} flt) lf} f)
528531

529-
addOrders :: ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree
530-
addOrders ApiRequest{..} rReq =
532+
addOrders :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree
533+
addOrders ctx ApiRequest{..} rReq =
531534
case iAction of
532535
ActionMutate _ -> Right rReq
533536
_ -> foldr addOrderToNode (Right rReq) qsOrder
534537
where
535538
QueryParams.QueryParams{..} = iQueryParams
536539

537540
addOrderToNode :: (EmbedPath, [OrderTerm]) -> Either ApiRequestError ReadPlanTree -> Either ApiRequestError ReadPlanTree
538-
addOrderToNode = updateNode (\o (Node q f) -> Node q{order=o} f)
541+
addOrderToNode = updateNode (\o (Node q f) -> Node q{order=resolveOrder ctx <$> o} f)
542+
543+
resolveOrder :: ResolverContext -> OrderTerm -> CoercibleOrderTerm
544+
resolveOrder _ (OrderRelationTerm a b c d) = CoercibleOrderRelationTerm a b c d
545+
resolveOrder ctx (OrderTerm fld dir nulls) = CoercibleOrderTerm (resolveTypeOrUnknown ctx fld) dir nulls
539546

540547
-- Validates that the related resource on the order is an embedded resource,
541548
-- e.g. if `clients` is inside the `select` in /projects?order=clients(id)&select=*,clients(*),
542549
-- and if it's a to-one relationship, it adds the right alias to the OrderRelationTerm so the generated query can succeed.
543-
-- TODO might be clearer if there's an additional intermediate type
544550
addRelatedOrders :: ReadPlanTree -> Either ApiRequestError ReadPlanTree
545551
addRelatedOrders (Node rp@ReadPlan{order,from} forest) = do
546-
newOrder <- getRelOrder `traverse` order
552+
newOrder <- newRelOrder `traverse` order
547553
Node rp{order=newOrder} <$> addRelatedOrders `traverse` forest
548554
where
549-
getRelOrder ot@OrderTerm{} = Right ot
550-
getRelOrder ot@OrderRelationTerm{otRelation} =
551-
let foundRP = rootLabel <$> find (\(Node ReadPlan{relName, relAlias} _) -> otRelation == fromMaybe relName relAlias) forest in
555+
newRelOrder cot@CoercibleOrderTerm{} = Right cot
556+
newRelOrder cot@CoercibleOrderRelationTerm{coRelation} =
557+
let foundRP = rootLabel <$> find (\(Node ReadPlan{relName, relAlias} _) -> coRelation == fromMaybe relName relAlias) forest in
552558
case foundRP of
553559
Just ReadPlan{relName,relAlias,relAggAlias,relToParent} ->
554560
let isToOne = relIsToOne <$> relToParent
555561
name = fromMaybe relName relAlias in
556562
if isToOne == Just True
557-
then Right $ ot{otRelation=relAggAlias}
563+
then Right $ cot{coRelation=relAggAlias}
558564
else Left $ RelatedOrderNotToOne (qiName from) name
559565
Nothing ->
560-
Left $ NotEmbedded otRelation
566+
Left $ NotEmbedded coRelation
561567

562568
-- | Searches for null filters on embeds, e.g. `projects=not.is.null` on `GET /clients?select=*,projects(*)&projects=not.is.null`
563569
--
@@ -598,7 +604,7 @@ addRelatedOrders (Node rp@ReadPlan{order,from} forest) = do
598604
-- where_ = [
599605
-- CoercibleStmnt (
600606
-- CoercibleFilter {
601-
-- field = CoercibleField {cfName = "projects", cfJsonPath = [], cfIRType = "", cfTransform = Nothing, cfDefault = Nothing},
607+
-- field = CoercibleField {cfName = "projects", cfJsonPath = [], cfToJson=False, cfIRType = "", cfTransform = Nothing, cfDefault = Nothing},
602608
-- opExpr = op
603609
-- }
604610
-- )
@@ -613,7 +619,7 @@ addRelatedOrders (Node rp@ReadPlan{order,from} forest) = do
613619
-- Don't do anything to the filter if there's no embedding (a subtree) on projects. Assume it's a normal filter.
614620
--
615621
-- >>> ReadPlan.where_ . rootLabel <$> addNullEmbedFilters (readPlanTree nullOp [])
616-
-- Right [CoercibleStmnt (CoercibleFilter {field = CoercibleField {cfName = "projects", cfJsonPath = [], cfIRType = "", cfTransform = Nothing, cfDefault = Nothing}, opExpr = OpExpr True (Is TriNull)})]
622+
-- Right [CoercibleStmnt (CoercibleFilter {field = CoercibleField {cfName = "projects", cfJsonPath = [], cfToJson = False, cfIRType = "", cfTransform = Nothing, cfDefault = Nothing}, opExpr = OpExpr True (Is TriNull)})]
617623
--
618624
-- If there's an embedding on projects, then change the filter to use the internal aggregate name (`clients_projects_1`) so the filter can succeed later.
619625
--
@@ -637,7 +643,7 @@ addNullEmbedFilters (Node rp@ReadPlan{where_=curLogic} forest) = do
637643
newNullFilters rPlans = \case
638644
(CoercibleExpr b lOp trees) ->
639645
CoercibleExpr b lOp <$> (newNullFilters rPlans `traverse` trees)
640-
flt@(CoercibleStmnt (CoercibleFilter (CoercibleField fld [] _ _ _) opExpr)) ->
646+
flt@(CoercibleStmnt (CoercibleFilter (CoercibleField fld [] _ _ _ _) opExpr)) ->
641647
let foundRP = find (\ReadPlan{relName, relAlias} -> fld == fromMaybe relName relAlias) rPlans in
642648
case (foundRP, opExpr) of
643649
(Just ReadPlan{relAggAlias}, OpExpr b (Is TriNull)) -> Right $ CoercibleStmnt $ CoercibleFilterNullEmbed b relAggAlias
@@ -726,7 +732,7 @@ mutatePlan mutation qi ApiRequest{iPreferences=Preferences{..}, ..} SchemaCache{
726732
tbl = HM.lookup qi dbTables
727733
pkCols = maybe mempty tablePKCols tbl
728734
logic = map (resolveLogicTree ctx . snd) qsLogic
729-
rootOrder = maybe [] snd $ find (\(x, _) -> null x) qsOrder
735+
rootOrder = resolveOrder ctx <$> maybe [] snd (find (\(x, _) -> null x) qsOrder)
730736
combinedLogic = foldr (addFilterToLogicForest . resolveFilter ctx) logic qsFiltersRoot
731737
body = payRaw <$> iPayload -- the body is assumed to be json at this stage(ApiRequest validates)
732738
applyDefaults = preferMissing == Just ApplyDefaults

src/PostgREST/Plan/MutatePlan.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ where
66
import qualified Data.ByteString.Lazy as LBS
77

88
import PostgREST.ApiRequest.Preferences (PreferResolution)
9-
import PostgREST.ApiRequest.Types (OrderTerm)
109
import PostgREST.Plan.Types (CoercibleField,
11-
CoercibleLogicTree)
10+
CoercibleLogicTree,
11+
CoercibleOrderTerm)
1212
import PostgREST.RangeQuery (NonnegRange)
1313
import PostgREST.SchemaCache.Identifiers (FieldName,
1414
QualifiedIdentifier)
@@ -33,14 +33,14 @@ data MutatePlan
3333
, updBody :: Maybe LBS.ByteString
3434
, where_ :: [CoercibleLogicTree]
3535
, mutRange :: NonnegRange
36-
, mutOrder :: [OrderTerm]
36+
, mutOrder :: [CoercibleOrderTerm]
3737
, returning :: [FieldName]
3838
, applyDefs :: Bool
3939
}
4040
| Delete
4141
{ in_ :: QualifiedIdentifier
4242
, where_ :: [CoercibleLogicTree]
4343
, mutRange :: NonnegRange
44-
, mutOrder :: [OrderTerm]
44+
, mutOrder :: [CoercibleOrderTerm]
4545
, returning :: [FieldName]
4646
}

src/PostgREST/Plan/ReadPlan.hs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ module PostgREST.Plan.ReadPlan
77
import Data.Tree (Tree (..))
88

99
import PostgREST.ApiRequest.Types (Alias, Cast, Depth, Hint,
10-
JoinType, NodeName,
11-
OrderTerm)
10+
JoinType, NodeName)
1211
import PostgREST.Plan.Types (CoercibleField (..),
13-
CoercibleLogicTree)
12+
CoercibleLogicTree,
13+
CoercibleOrderTerm)
1414
import PostgREST.RangeQuery (NonnegRange)
1515
import PostgREST.SchemaCache.Identifiers (FieldName,
1616
QualifiedIdentifier)
@@ -32,7 +32,7 @@ data ReadPlan = ReadPlan
3232
, from :: QualifiedIdentifier
3333
, fromAlias :: Maybe Alias
3434
, where_ :: [CoercibleLogicTree]
35-
, order :: [OrderTerm]
35+
, order :: [CoercibleOrderTerm]
3636
, range_ :: NonnegRange
3737
, relName :: NodeName
3838
, relToParent :: Maybe Relationship

src/PostgREST/Plan/Types.hs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ module PostgREST.Plan.Types
44
, CoercibleLogicTree(..)
55
, CoercibleFilter(..)
66
, TransformerProc
7+
, CoercibleOrderTerm(..)
78
) where
89

9-
import PostgREST.ApiRequest.Types (JsonPath, LogicOperator, OpExpr)
10+
import PostgREST.ApiRequest.Types (Field, JsonPath, LogicOperator,
11+
OpExpr, OrderDirection, OrderNulls)
1012

1113
import PostgREST.SchemaCache.Identifiers (FieldName)
1214

@@ -28,13 +30,14 @@ type TransformerProc = Text
2830
data CoercibleField = CoercibleField
2931
{ cfName :: FieldName
3032
, cfJsonPath :: JsonPath
33+
, cfToJson :: Bool
3134
, cfIRType :: Text -- ^ The native Postgres type of the field, the intermediate (IR) type before mapping.
3235
, cfTransform :: Maybe TransformerProc -- ^ The optional mapping from irType -> targetType.
3336
, cfDefault :: Maybe Text
3437
} deriving (Eq, Show)
3538

3639
unknownField :: FieldName -> JsonPath -> CoercibleField
37-
unknownField name path = CoercibleField name path "" Nothing Nothing
40+
unknownField name path = CoercibleField name path False "" Nothing Nothing
3841

3942
-- | Like an API request LogicTree, but with coercible field information.
4043
data CoercibleLogicTree
@@ -48,3 +51,17 @@ data CoercibleFilter = CoercibleFilter
4851
}
4952
| CoercibleFilterNullEmbed Bool FieldName
5053
deriving (Eq, Show)
54+
55+
data CoercibleOrderTerm
56+
= CoercibleOrderTerm
57+
{ coField :: CoercibleField
58+
, coDirection :: Maybe OrderDirection
59+
, coNullOrder :: Maybe OrderNulls
60+
}
61+
| CoercibleOrderRelationTerm
62+
{ coRelation :: FieldName
63+
, coRelTerm :: Field
64+
, coDirection :: Maybe OrderDirection
65+
, coNullOrder :: Maybe OrderNulls
66+
}
67+
deriving (Eq, Show)

src/PostgREST/Query/QueryBuilder.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ mutatePlanToQuery (Update mainQi uCols body logicForest range ordts returnings a
138138
emptyBodyReturnedColumns = if null returnings then "NULL" else intercalateSnippet ", " (pgFmtColumn (QualifiedIdentifier mempty $ qiName mainQi) <$> returnings)
139139
nonRangeCols = intercalateSnippet ", " (pgFmtIdent . cfName <> const " = " <> pgFmtColumn (QualifiedIdentifier mempty "pgrst_body") . cfName <$> uCols)
140140
rangeCols = intercalateSnippet ", " ((\col -> pgFmtIdent (cfName col) <> " = (SELECT " <> pgFmtIdent (cfName col) <> " FROM pgrst_update_body) ") <$> uCols)
141-
(whereRangeIdF, rangeIdF) = mutRangeF mainQi (fst . otTerm <$> ordts)
141+
(whereRangeIdF, rangeIdF) = mutRangeF mainQi (cfName . coField <$> ordts)
142142

143143
mutatePlanToQuery (Delete mainQi logicForest range ordts returnings)
144144
| range == allRange =
@@ -161,7 +161,7 @@ mutatePlanToQuery (Delete mainQi logicForest range ordts returnings)
161161

162162
where
163163
whereLogic = if null logicForest then mempty else " WHERE " <> intercalateSnippet " AND " (pgFmtLogicTree mainQi <$> logicForest)
164-
(whereRangeIdF, rangeIdF) = mutRangeF mainQi (fst . otTerm <$> ordts)
164+
(whereRangeIdF, rangeIdF) = mutRangeF mainQi (cfName . coField <$> ordts)
165165

166166
callPlanToQuery :: CallPlan -> PgVersion -> SQL.Snippet
167167
callPlanToQuery (FunctionCall qi params args returnsScalar returnsSetOfScalar returnsCompositeAlias returnings) pgVer =
@@ -171,7 +171,7 @@ callPlanToQuery (FunctionCall qi params args returnsScalar returnsSetOfScalar re
171171
fromCall = case params of
172172
OnePosParam prm -> "FROM " <> callIt (singleParameter args $ encodeUtf8 $ ppType prm)
173173
KeyParams [] -> "FROM " <> callIt mempty
174-
KeyParams prms -> fromJsonBodyF args ((\p -> CoercibleField (ppName p) mempty (ppType p) Nothing Nothing) <$> prms) False True False <> ", " <>
174+
KeyParams prms -> fromJsonBodyF args ((\p -> CoercibleField (ppName p) mempty False (ppType p) Nothing Nothing) <$> prms) False True False <> ", " <>
175175
"LATERAL " <> callIt (fmtParams prms)
176176

177177
callIt :: SQL.Snippet -> SQL.Snippet

src/PostgREST/Query/SqlFragment.hs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ import PostgREST.ApiRequest.Types (Alias, Cast,
6161
Operation (..),
6262
OrderDirection (..),
6363
OrderNulls (..),
64-
OrderTerm (..),
6564
QuantOperator (..),
6665
SimpleOperator (..),
6766
TrileanVal (..))
@@ -71,6 +70,7 @@ import PostgREST.Plan.ReadPlan (JoinCondition (..))
7170
import PostgREST.Plan.Types (CoercibleField (..),
7271
CoercibleFilter (..),
7372
CoercibleLogicTree (..),
73+
CoercibleOrderTerm (..),
7474
unknownField)
7575
import PostgREST.RangeQuery (NonnegRange, allRange,
7676
rangeLimit, rangeOffset)
@@ -241,10 +241,9 @@ pgFmtCallUnary :: Text -> SQL.Snippet -> SQL.Snippet
241241
pgFmtCallUnary f x = SQL.sql (encodeUtf8 f) <> "(" <> x <> ")"
242242

243243
pgFmtField :: QualifiedIdentifier -> CoercibleField -> SQL.Snippet
244-
pgFmtField table CoercibleField{cfName=fn, cfJsonPath=[]} = pgFmtColumn table fn
245-
-- Using to_jsonb instead of to_json to avoid missing operator errors when filtering:
246-
-- "operator does not exist: json = unknown"
247-
pgFmtField table CoercibleField{cfName=fn, cfJsonPath=jp} = "to_jsonb(" <> pgFmtColumn table fn <> ")" <> pgFmtJsonPath jp
244+
pgFmtField table CoercibleField{cfName=fn, cfJsonPath=[]} = pgFmtColumn table fn
245+
pgFmtField table CoercibleField{cfName=fn, cfToJson=doToJson, cfJsonPath=jp} | doToJson = "to_jsonb(" <> pgFmtColumn table fn <> ")" <> pgFmtJsonPath jp
246+
| otherwise = pgFmtColumn table fn <> pgFmtJsonPath jp
248247

249248
-- Select the value of a named element from a table, applying its optional coercion mapping if any.
250249
pgFmtTableCoerce :: QualifiedIdentifier -> CoercibleField -> SQL.Snippet
@@ -299,16 +298,16 @@ fromJsonBodyF body fields includeSelect includeLimitOne includeDefaults =
299298
else ("pgrst_uniform_json.val", "json_typeof", "json_build_array", "json_array_elements", "json_to_recordset")
300299
jsonPlaceHolder = SQL.encoderAndParam (HE.nullable $ if includeDefaults then HE.jsonbLazyBytes else HE.jsonLazyBytes) body
301300

302-
pgFmtOrderTerm :: QualifiedIdentifier -> OrderTerm -> SQL.Snippet
301+
pgFmtOrderTerm :: QualifiedIdentifier -> CoercibleOrderTerm -> SQL.Snippet
303302
pgFmtOrderTerm qi ot =
304303
fmtOTerm ot <> " " <>
305304
SQL.sql (BS.unwords [
306-
maybe mempty direction $ otDirection ot,
307-
maybe mempty nullOrder $ otNullOrder ot])
305+
maybe mempty direction $ coDirection ot,
306+
maybe mempty nullOrder $ coNullOrder ot])
308307
where
309308
fmtOTerm = \case
310-
OrderTerm{otTerm=(fn, jp)} -> pgFmtField qi (unknownField fn jp)
311-
OrderRelationTerm{otRelation, otRelTerm=(fn, jp)} -> pgFmtField (QualifiedIdentifier mempty otRelation) (unknownField fn jp)
309+
CoercibleOrderTerm{coField=cof} -> pgFmtField qi cof
310+
CoercibleOrderRelationTerm{coRelation, coRelTerm=(fn, jp)} -> pgFmtField (QualifiedIdentifier mempty coRelation) (unknownField fn jp)
312311

313312
direction OrderAsc = "ASC"
314313
direction OrderDesc = "DESC"
@@ -446,7 +445,7 @@ mutRangeF mainQi rangeId =
446445
, intercalateSnippet ", " (pgFmtColumn mainQi <$> rangeId)
447446
)
448447

449-
orderF :: QualifiedIdentifier -> [OrderTerm] -> SQL.Snippet
448+
orderF :: QualifiedIdentifier -> [CoercibleOrderTerm] -> SQL.Snippet
450449
orderF _ [] = mempty
451450
orderF qi ordts = "ORDER BY " <> intercalateSnippet ", " (pgFmtOrderTerm qi <$> ordts)
452451

test/spec/Feature/Query/PlanSpec.hs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,43 @@ spec actualPgVersion = do
348348
liftIO $ do
349349
resBody `shouldSatisfy` (\t -> not $ T.isInfixOf "getitemrange" (decodeUtf8 $ LBS.toStrict t))
350350

351+
context "index usage" $ do
352+
it "should use an index for a json arrow operator filter" $ do
353+
r <- request methodGet "/bets?data_json->>contractId=eq.1"
354+
[(hAccept, "application/vnd.pgrst.plan")] ""
355+
356+
let resBody = simpleBody r
357+
358+
liftIO $ do
359+
resBody `shouldSatisfy` (\t -> T.isInfixOf "Index Cond" (decodeUtf8 $ LBS.toStrict t))
360+
361+
it "should use an index for a jsonb arrow operator filter" $ do
362+
r <- request methodGet "/bets?data_jsonb->>contractId=eq.1"
363+
[(hAccept, "application/vnd.pgrst.plan")] ""
364+
365+
let resBody = simpleBody r
366+
367+
liftIO $ do
368+
resBody `shouldSatisfy` (\t -> T.isInfixOf "Index" (decodeUtf8 $ LBS.toStrict t))
369+
370+
it "should use an index for ordering on a json arrow operator" $ do
371+
r <- request methodGet "/bets?order=data_json->>contractId"
372+
[(hAccept, "application/vnd.pgrst.plan")] ""
373+
374+
let resBody = simpleBody r
375+
376+
liftIO $ do
377+
resBody `shouldSatisfy` (\t -> T.isInfixOf "Index" (decodeUtf8 $ LBS.toStrict t))
378+
379+
it "should use an index for ordering on a jsonb arrow operator" $ do
380+
r <- request methodGet "/bets?order=data_jsonb->>contractId"
381+
[(hAccept, "application/vnd.pgrst.plan")] ""
382+
383+
let resBody = simpleBody r
384+
385+
liftIO $ do
386+
resBody `shouldSatisfy` (\t -> T.isInfixOf "Index" (decodeUtf8 $ LBS.toStrict t))
387+
351388
disabledSpec :: SpecWith ((), Application)
352389
disabledSpec =
353390
it "doesn't work if db-plan-enabled=false(the default)" $ do

0 commit comments

Comments
 (0)