diff --git a/src/PostgREST/QueryBuilder.hs b/src/PostgREST/QueryBuilder.hs index cd3ba5036..b7c84e49f 100644 --- a/src/PostgREST/QueryBuilder.hs +++ b/src/PostgREST/QueryBuilder.hs @@ -120,20 +120,25 @@ createWriteStatement qi selectQuery mutateQuery isSingle Full | otherwise = asJsonF addRelations :: Schema -> [Relation] -> Maybe ReadRequest -> ReadRequest -> Either Text ReadRequest -addRelations schema allRelations parentNode node@(Node n@(query, (table, _)) forest) = +addRelations schema allRelations parentNode node@(Node readNode@(query, (name, _)) forest) = case parentNode of - Nothing -> Node (query, (table, Nothing)) <$> updatedForest - (Just (Node (_, (parentTable, _)) _)) -> Node <$> (addRel n <$> rel) <*> updatedForest + (Just (Node (Select{from=[parentTable]}, (_, _)) _)) -> Node <$> (addRel readNode <$> rel) <*> updatedForest where - rel = note ("no relation between " <> table <> " and " <> parentTable) - $ findRelation schema table parentTable - <|> findRelation schema parentTable table + rel = note ("no relation between " <> parentTable <> " and " <> name) + $ findRelationTable schema name parentTable + <|> findRelationTable schema parentTable name + <|> findRelationColumn schema parentTable name addRel :: (ReadQuery, (NodeName, Maybe Relation)) -> Relation -> (ReadQuery, (NodeName, Maybe Relation)) - addRel (q, (t, _)) r = (q, (t, Just r)) + addRel (q, (n, _)) r = (q {from=fromRelation}, (n, Just r)) + where fromRelation = map (\t -> if t == n then tableName (relTable r) else t) (from q) + + _ -> Node (query, (name, Nothing)) <$> updatedForest where updatedForest = mapM (addRelations schema allRelations (Just node)) forest - findRelation s t1 t2 = - find (\r -> s == (tableSchema . relTable) r && t1 == (tableName . relTable) r && t2 == (tableName . relFTable) r) allRelations + findRelationTable s t1 t2 = + find (\r -> s == tableSchema (relTable r) && t1 == tableName (relTable r) && t2 == tableName (relFTable r)) allRelations + findRelationColumn s t c = + find (\r -> s == tableSchema (relTable r) && s == tableSchema (relFTable r) && t == tableName (relFTable r) && length (relFColumns r) == 1 && c == (colName . head . relFColumns) r) allRelations addJoinConditions :: Schema -> ReadRequest -> Either Text ReadRequest addJoinConditions schema (Node (query, (n, r)) forest) = @@ -218,11 +223,12 @@ requestToCountQuery schema (DbRead (Node (Select _ _ conditions _, (mainTbl, _)) requestToQuery :: Schema -> DbRequest -> SqlQuery requestToQuery _ (DbMutate (Insert _ (PayloadParseError _))) = undefined requestToQuery _ (DbMutate (Update _ (PayloadParseError _) _)) = undefined -requestToQuery schema (DbRead (Node (Select colSelects tbls conditions ord, (mainTbl, _)) forest)) = +requestToQuery schema (DbRead (Node (Select colSelects tbls conditions ord, (nodeName, maybeRelation)) forest)) = query where -- TODO! the folloing helper functions are just to remove the "schema" part when the table is "source" which is the name -- of our WITH query part + mainTbl = fromMaybe nodeName (tableName . relTable <$> maybeRelation) tblSchema tbl = if tbl == sourceCTEName then "" else schema qi = QualifiedIdentifier (tblSchema mainTbl) mainTbl toQi t = QualifiedIdentifier (tblSchema t) t @@ -253,28 +259,27 @@ requestToQuery schema (DbRead (Node (Select colSelects tbls conditions ord, (mai intercalate " AND " ( map (pgFmtCondition qi ) joinConditions ) where joinConditions = filter (filterParentConditions t) conditions - filterParentConditions parentTable (Filter _ _ (VForeignKey (QualifiedIdentifier "" t) _)) = - parentTable == t + filterParentConditions parentTable (Filter _ _ (VForeignKey (QualifiedIdentifier "" t) _)) = parentTable == t filterParentConditions _ _ = False getQueryParts :: Tree ReadNode -> ([(SqlFragment, TableName)], [SqlFragment]) -> ([(SqlFragment,TableName)], [SqlFragment]) - getQueryParts (Node n@(_, (table, Just (Relation {relType=Child}))) forst) (j,s) = (j,sel:s) + getQueryParts (Node n@(_, (name, Just (Relation {relType=Child,relTable=Table{tableName=table}}))) forst) (j,s) = (j,sel:s) where sel = "COALESCE((" <> "SELECT array_to_json(array_agg(row_to_json("<>table<>"))) " <> "FROM (" <> subquery <> ") " <> table - <> "), '[]') AS " <> table + <> "), '[]') AS " <> pgFmtIdent name where subquery = requestToQuery schema (DbRead (Node n forst)) - getQueryParts (Node n@(_, (table, Just (Relation {relType=Parent}))) forst) (j,s) = (joi:j,sel:s) + getQueryParts (Node n@(_, (name, Just (Relation {relType=Parent,relTable=Table{tableName=table}}))) forst) (j,s) = (joi:j,sel:s) where - sel = "row_to_json(" <> table <> ".*) AS "<>table --TODO must be singular + sel = "row_to_json(" <> table <> ".*) AS "<>pgFmtIdent name --TODO must be singular joi = ("( " <> subquery <> " ) AS " <> table, table) where subquery = requestToQuery schema (DbRead (Node n forst)) - getQueryParts (Node n@(_, (table, Just (Relation {relType=Many}))) forst) (j,s) = (j,sel:s) + getQueryParts (Node n@(_, (name, Just (Relation {relType=Many,relTable=Table{tableName=table}}))) forst) (j,s) = (j,sel:s) where sel = "COALESCE ((" <> "SELECT array_to_json(array_agg(row_to_json("<>table<>"))) " <> "FROM (" <> subquery <> ") " <> table - <> "), '[]') AS " <> table + <> "), '[]') AS " <> pgFmtIdent name where subquery = requestToQuery schema (DbRead (Node n forst)) --the following is just to remove the warning --getQueryParts is not total but requestToQuery is called only after addJoinConditions which ensures the only diff --git a/src/PostgREST/Types.hs b/src/PostgREST/Types.hs index 2737ca386..8bd6b91c8 100644 --- a/src/PostgREST/Types.hs +++ b/src/PostgREST/Types.hs @@ -106,10 +106,10 @@ type Cast = Text type NodeName = Text type SelectItem = (Field, Maybe Cast) type Path = [Text] -data ReadQuery = Select { select::[SelectItem], from::[Text], flt_::[Filter], order::Maybe [OrderTerm] } deriving (Show, Eq) -data MutateQuery = Insert { in_::Text, qPayload::Payload } - | Delete { in_::Text, where_::[Filter] } - | Update { in_::Text, qPayload::Payload, where_::[Filter] } deriving (Show, Eq) +data ReadQuery = Select { select::[SelectItem], from::[TableName], flt_::[Filter], order::Maybe [OrderTerm] } deriving (Show, Eq) +data MutateQuery = Insert { in_::TableName, qPayload::Payload } + | Delete { in_::TableName, where_::[Filter] } + | Update { in_::TableName, qPayload::Payload, where_::[Filter] } deriving (Show, Eq) data Filter = Filter {field::Field, operator::Operator, value::FValue} deriving (Show, Eq) type ReadNode = (ReadQuery, (NodeName, Maybe Relation)) type ReadRequest = Tree ReadNode diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index aadc8edaf..1be7b7103 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -224,7 +224,11 @@ spec struct pool = around (withApp cfgDefault struct pool) $ do it "detect relations in views from exposed schema that are based on tables in private schema and have columns renames" $ get "/articles?id=eq.1&select=id,articleStars{users{*}}" `shouldRespondWith` - [str|[{"id":1,"articlestars":[{"users":{"id":1,"name":"Angela Martin"}},{"users":{"id":2,"name":"Michael Scott"}},{"users":{"id":3,"name":"Dwight Schrute"}}]}]|] + [str|[{"id":1,"articleStars":[{"users":{"id":1,"name":"Angela Martin"}},{"users":{"id":2,"name":"Michael Scott"}},{"users":{"id":3,"name":"Dwight Schrute"}}]}]|] + + it "can select by column name" $ + get "/projects?id=in.1,3&select=id,name,client_id,client_id{id,name}" `shouldRespondWith` + "[{\"id\":1,\"name\":\"Windows 7\",\"client_id\":1,\"client_id\":{\"id\":1,\"name\":\"Microsoft\"}},{\"id\":3,\"name\":\"IOS\",\"client_id\":2,\"client_id\":{\"id\":2,\"name\":\"Apple\"}}]" describe "Plurality singular" $ do