From ec1108656bbb7a1863d25e4b3631b68ac918c041 Mon Sep 17 00:00:00 2001 From: Marc Scholten Date: Sat, 5 Nov 2022 13:51:26 +0100 Subject: [PATCH 1/4] Fixed type of webSocketAppWithHTTPFallback --- IHP/RouterSupport.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/IHP/RouterSupport.hs b/IHP/RouterSupport.hs index 62601807f..cd3349f27 100644 --- a/IHP/RouterSupport.hs +++ b/IHP/RouterSupport.hs @@ -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 @@ -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))) From 27aa2eadf907f6647b6199ef725bf4aa85eb6fd6 Mon Sep 17 00:00:00 2001 From: Marc Scholten Date: Sat, 5 Nov 2022 14:36:35 +0100 Subject: [PATCH 2/4] Fixed aesonValueToPostgresValue for Arrays in DataSync --- IHP/DataSync/REST/Controller.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/IHP/DataSync/REST/Controller.hs b/IHP/DataSync/REST/Controller.hs index 3bb45c0b9..13b142d53 100644 --- a/IHP/DataSync/REST/Controller.hs +++ b/IHP/DataSync/REST/Controller.hs @@ -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 From 29fe460ddba2f8cf338a102083091131da67ae03 Mon Sep 17 00:00:00 2001 From: Marc Scholten Date: Mon, 7 Nov 2022 10:42:04 +0100 Subject: [PATCH 3/4] Normalized Null handling in DataSync This fixes a bug where queries like 'col = NULL' is generated instead of the correct 'col IS NULL' --- IHP/DataSync/DynamicQuery.hs | 1 - IHP/DataSync/DynamicQueryCompiler.hs | 7 +++---- Test/DataSync/DynamicQueryCompiler.hs | 12 ++++++------ lib/IHP/DataSync/ihp-querybuilder.js | 1 - 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/IHP/DataSync/DynamicQuery.hs b/IHP/DataSync/DynamicQuery.hs index 89df13c38..d137b72a1 100644 --- a/IHP/DataSync/DynamicQuery.hs +++ b/IHP/DataSync/DynamicQuery.hs @@ -65,7 +65,6 @@ data OrderByClause -- | Represents a WHERE conditions of a 'DynamicSQLQuery' data ConditionExpression = ColumnExpression { field :: !Text } - | NullExpression | InfixOperatorExpression { left :: !ConditionExpression , op :: !ConditionOperator diff --git a/IHP/DataSync/DynamicQueryCompiler.hs b/IHP/DataSync/DynamicQueryCompiler.hs index db04d0c7a..a26259c0c 100644 --- a/IHP/DataSync/DynamicQueryCompiler.hs +++ b/IHP/DataSync/DynamicQueryCompiler.hs @@ -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 @@ -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]) diff --git a/Test/DataSync/DynamicQueryCompiler.hs b/Test/DataSync/DynamicQueryCompiler.hs index bb55e5f62..b2a02f5d0 100644 --- a/Test/DataSync/DynamicQueryCompiler.hs +++ b/Test/DataSync/DynamicQueryCompiler.hs @@ -148,7 +148,7 @@ 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 @@ -156,15 +156,15 @@ tests = do } 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 @@ -172,8 +172,8 @@ tests = do } 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 diff --git a/lib/IHP/DataSync/ihp-querybuilder.js b/lib/IHP/DataSync/ihp-querybuilder.js index 0f15b8faf..2eea462c2 100644 --- a/lib/IHP/DataSync/ihp-querybuilder.js +++ b/lib/IHP/DataSync/ihp-querybuilder.js @@ -414,7 +414,6 @@ export function recordMatchesQuery(query, record) { default: throw new Error('Unsupported operator ' + expression.op); } } - case 'NullExpression': return null; case 'LiteralExpression': return (expression.value.tag === 'Null' ? null : expression.value.contents); default: throw new Error('Unsupported expression in evaluate: ' + expression.tag); } From 7a84c25eb72e5401b3248a07ed719ff3c351b693 Mon Sep 17 00:00:00 2001 From: Marc Scholten Date: Thu, 10 Nov 2022 16:00:49 +0100 Subject: [PATCH 4/4] Fixed whereIn --- lib/IHP/DataSync/ihp-querybuilder.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lib/IHP/DataSync/ihp-querybuilder.js b/lib/IHP/DataSync/ihp-querybuilder.js index 2eea462c2..4fb961568 100644 --- a/lib/IHP/DataSync/ihp-querybuilder.js +++ b/lib/IHP/DataSync/ihp-querybuilder.js @@ -214,7 +214,7 @@ class ConditionBuildable { } whereIn(field, values) { - return { + const expression = { tag: 'InfixOperatorExpression', left: { tag: 'ColumnExpression', @@ -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; } } @@ -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; @@ -411,10 +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 '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); } }