Skip to content

Commit

Permalink
Merge branch 'master' of github.com:digitallyinduced/ihp
Browse files Browse the repository at this point in the history
  • Loading branch information
mpscholten committed Nov 16, 2022
2 parents c204e47 + 7a84c25 commit 5e6851c
Show file tree
Hide file tree
Showing 6 changed files with 28 additions and 19 deletions.
1 change: 0 additions & 1 deletion IHP/DataSync/DynamicQuery.hs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ data OrderByClause
-- | Represents a WHERE conditions of a 'DynamicSQLQuery'
data ConditionExpression
= ColumnExpression { field :: !Text }
| NullExpression
| InfixOperatorExpression
{ left :: !ConditionExpression
, op :: !ConditionOperator
Expand Down
7 changes: 3 additions & 4 deletions IHP/DataSync/DynamicQueryCompiler.hs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,8 @@ compileSelectedColumns (SelectSpecific fields) = PG.Many args

compileCondition :: ConditionExpression -> (PG.Query, [PG.Action])
compileCondition (ColumnExpression column) = ("?", [PG.toField $ PG.Identifier (fieldNameToColumnName column)])
compileCondition NullExpression = ("NULL", [])
compileCondition (InfixOperatorExpression a OpEqual NullExpression) = compileCondition (InfixOperatorExpression a OpIs NullExpression) -- Turn 'a = NULL' into 'a IS NULL'
compileCondition (InfixOperatorExpression a OpNotEqual NullExpression) = compileCondition (InfixOperatorExpression a OpIsNot NullExpression) -- Turn 'a <> NULL' into 'a IS NOT NULL'
compileCondition (InfixOperatorExpression a OpEqual (LiteralExpression Null)) = compileCondition (InfixOperatorExpression a OpIs (LiteralExpression Null)) -- Turn 'a = NULL' into 'a IS NULL'
compileCondition (InfixOperatorExpression a OpNotEqual (LiteralExpression Null)) = compileCondition (InfixOperatorExpression a OpIsNot (LiteralExpression Null)) -- Turn 'a <> NULL' into 'a IS NOT NULL'
compileCondition (InfixOperatorExpression a operator b) = ("(" <> queryA <> ") " <> compileOperator operator <> " " <> rightOperand, paramsA <> paramsB)
where
(queryA, paramsA) = compileCondition a
Expand All @@ -98,7 +97,7 @@ compileCondition (InfixOperatorExpression a operator b) = ("(" <> queryA <> ") "
rightParentheses :: Bool
rightParentheses =
case b of
NullExpression -> False
LiteralExpression Null -> False
ListExpression {} -> False -- The () are inserted already via @PG.In@
_ -> True
compileCondition (LiteralExpression literal) = ("?", [PG.toField literal])
Expand Down
1 change: 1 addition & 0 deletions IHP/DataSync/REST/Controller.hs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ aesonValueToPostgresValue (Number value) = case Scientific.floatingOrInteger val
Left (floating :: Double) -> PG.toField floating
Right (integer :: Integer) -> PG.toField integer
aesonValueToPostgresValue Data.Aeson.Null = PG.toField PG.Null
aesonValueToPostgresValue (Data.Aeson.Array values) = PG.toField (PG.PGArray (Vector.toList values))
aesonValueToPostgresValue object@(Object values) =
let
tryDecodeAsPoint :: Maybe Point
Expand Down
6 changes: 3 additions & 3 deletions IHP/RouterSupport.hs
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ webSocketAppWithHTTPFallback :: forall webSocketApp application.
, Typeable application
, Typeable webSocketApp
, Controller webSocketApp
) => Parser (IO ResponseReceived)
) => RouteParser
webSocketAppWithHTTPFallback = webSocketAppWithCustomPathAndHTTPFallback @webSocketApp @application typeName
where
typeName :: ByteString
Expand Down Expand Up @@ -801,8 +801,8 @@ webSocketAppWithCustomPathAndHTTPFallback :: forall webSocketApp application.
, Typeable application
, Typeable webSocketApp
, Controller webSocketApp
) => ByteString -> Parser (IO ResponseReceived)
webSocketAppWithCustomPathAndHTTPFallback path = do
) => ByteString -> RouteParser
webSocketAppWithCustomPathAndHTTPFallback path = toRouteParser do
Attoparsec.char '/'
string path
pure (startWebSocketApp @webSocketApp (runActionWithNewContext (WS.initialState @webSocketApp)))
Expand Down
12 changes: 6 additions & 6 deletions Test/DataSync/DynamicQueryCompiler.hs
Original file line number Diff line number Diff line change
Expand Up @@ -148,32 +148,32 @@ tests = do
let query = DynamicSQLQuery
{ table = "posts"
, selectedColumns = SelectAll
, whereCondition = Just $ InfixOperatorExpression (ColumnExpression "userId") OpEqual NullExpression
, whereCondition = Just $ InfixOperatorExpression (ColumnExpression "userId") OpEqual (LiteralExpression Null)
, orderByClause = []
, distinctOnColumn = Nothing
, limit = Nothing
, offset = Nothing
}

compileQuery query `shouldBe`
( "SELECT ? FROM ? WHERE (?) IS NULL"
, [PG.Plain "*", PG.EscapeIdentifier "posts", PG.EscapeIdentifier "user_id"]
( "SELECT ? FROM ? WHERE (?) IS ?"
, [PG.Plain "*", PG.EscapeIdentifier "posts", PG.EscapeIdentifier "user_id", PG.Plain "null"]
)

it "compile 'field <> NULL' conditions to 'field IS NOT NULL'" do
let query = DynamicSQLQuery
{ table = "posts"
, selectedColumns = SelectAll
, whereCondition = Just $ InfixOperatorExpression (ColumnExpression "userId") OpNotEqual NullExpression
, whereCondition = Just $ InfixOperatorExpression (ColumnExpression "userId") OpNotEqual (LiteralExpression Null)
, orderByClause = []
, distinctOnColumn = Nothing
, limit = Nothing
, offset = Nothing
}

compileQuery query `shouldBe`
( "SELECT ? FROM ? WHERE (?) IS NOT NULL"
, [PG.Plain "*", PG.EscapeIdentifier "posts", PG.EscapeIdentifier "user_id"]
( "SELECT ? FROM ? WHERE (?) IS NOT ?"
, [PG.Plain "*", PG.EscapeIdentifier "posts", PG.EscapeIdentifier "user_id", PG.Plain "null"]
)

it "compile queries with TS expressions" do
Expand Down
20 changes: 15 additions & 5 deletions lib/IHP/DataSync/ihp-querybuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ class ConditionBuildable {
}

whereIn(field, values) {
return {
const expression = {
tag: 'InfixOperatorExpression',
left: {
tag: 'ColumnExpression',
Expand All @@ -223,9 +223,11 @@ class ConditionBuildable {
op: 'OpIn',
right: {
tag: 'ListExpression',
value: values.map(jsValueToDynamicValue),
values: values.map(jsValueToDynamicValue),
},
}
};
this._addCondition('OpAnd', expression);
return this;
}
}

Expand Down Expand Up @@ -396,6 +398,9 @@ function jsValueToDynamicValue(value) {
}

export function recordMatchesQuery(query, record) {
function evaluateDynamicValue(value) {
return (value.tag === 'Null' ? null : value.contents);
}
function evaluate(expression) {
switch (expression.tag) {
case 'ColumnExpression': return (expression.field in record) ? record[expression.field] : null;
Expand All @@ -411,11 +416,16 @@ export function recordMatchesQuery(query, record) {
case 'OpOr': return evaluate(expression.left) || evaluate(expression.right);
case 'OpIs': return evaluate(expression.left) == evaluate(expression.right);
case 'OpIsNot': return evaluate(expression.left) != evaluate(expression.right);
case 'OpIn': {
const left = evaluate(expression.left);
const right = evaluate(expression.right);
return Array.isArray(right) && right.includes(left);
}
default: throw new Error('Unsupported operator ' + expression.op);
}
}
case 'NullExpression': return null;
case 'LiteralExpression': return (expression.value.tag === 'Null' ? null : expression.value.contents);
case 'LiteralExpression': return evaluateDynamicValue(expression);
case 'ListExpression': return expression.values.map(value => evaluateDynamicValue(value));
default: throw new Error('Unsupported expression in evaluate: ' + expression.tag);
}
}
Expand Down

0 comments on commit 5e6851c

Please sign in to comment.