imp: the all: query now requires at least one posting

This commit is contained in:
Simon Michael 2025-08-03 06:33:17 +01:00
parent f604b7a416
commit cc3923d6d4
4 changed files with 55 additions and 13 deletions

View File

@ -127,7 +127,7 @@ data Query =
-- compound queries for transactions (any:, all:) -- compound queries for transactions (any:, all:)
-- If used in a non transaction-matching context, these are equivalent to And. -- If used in a non transaction-matching context, these are equivalent to And.
| AnyPosting [Query] -- ^ match if any one posting is matched by all of these | AnyPosting [Query] -- ^ match if any one posting is matched by all of these
| AllPostings [Query] -- ^ match if all postings are matched by all of these | AllPostings [Query] -- ^ match if all of one or more postings are matched by all of these
deriving (Eq,Show) deriving (Eq,Show)
instance Default Query where def = Any instance Default Query where def = Any
@ -820,7 +820,7 @@ matchesCommodity (None) _ = False
matchesCommodity (Or qs) s = any (`matchesCommodity` s) qs matchesCommodity (Or qs) s = any (`matchesCommodity` s) qs
matchesCommodity (And qs) s = all (`matchesCommodity` s) qs matchesCommodity (And qs) s = all (`matchesCommodity` s) qs
matchesCommodity (AnyPosting qs) s = all (`matchesCommodity` s) qs matchesCommodity (AnyPosting qs) s = all (`matchesCommodity` s) qs
matchesCommodity (AllPostings qs) s = all (`matchesCommodity` s) qs matchesCommodity (AllPostings qs) s = all1 (`matchesCommodity` s) qs
matchesCommodity _ _ = False matchesCommodity _ _ = False
-- | Does the match expression match this (simple) amount ? -- | Does the match expression match this (simple) amount ?
@ -831,7 +831,7 @@ matchesAmount (None) _ = False
matchesAmount (Or qs) a = any (`matchesAmount` a) qs matchesAmount (Or qs) a = any (`matchesAmount` a) qs
matchesAmount (And qs) a = all (`matchesAmount` a) qs matchesAmount (And qs) a = all (`matchesAmount` a) qs
matchesAmount (AnyPosting qs) a = all (`matchesAmount` a) qs matchesAmount (AnyPosting qs) a = all (`matchesAmount` a) qs
matchesAmount (AllPostings qs) a = all (`matchesAmount` a) qs matchesAmount (AllPostings qs) a = all1 (`matchesAmount` a) qs
matchesAmount (Amt ord n) a = compareAmount ord n a matchesAmount (Amt ord n) a = compareAmount ord n a
matchesAmount (Sym r) a = matchesCommodity (Sym r) (acommodity a) matchesAmount (Sym r) a = matchesCommodity (Sym r) (acommodity a)
matchesAmount _ _ = True matchesAmount _ _ = True
@ -862,7 +862,7 @@ matchesAccount (Not m) a = not $ matchesAccount m a
matchesAccount (Or ms) a = any (`matchesAccount` a) ms matchesAccount (Or ms) a = any (`matchesAccount` a) ms
matchesAccount (And ms) a = all (`matchesAccount` a) ms matchesAccount (And ms) a = all (`matchesAccount` a) ms
matchesAccount (AnyPosting qs) a = all (`matchesAccount` a) qs matchesAccount (AnyPosting qs) a = all (`matchesAccount` a) qs
matchesAccount (AllPostings qs) a = all (`matchesAccount` a) qs matchesAccount (AllPostings qs) a = all1 (`matchesAccount` a) qs
matchesAccount (Acct r) a = regexMatchText r a matchesAccount (Acct r) a = regexMatchText r a
matchesAccount (Depth d) a = accountNameLevel a <= d matchesAccount (Depth d) a = accountNameLevel a <= d
matchesAccount (DepthAcct r d) a = accountNameLevel a <= d || not (regexMatchText r a) matchesAccount (DepthAcct r d) a = accountNameLevel a <= d || not (regexMatchText r a)
@ -882,7 +882,7 @@ matchesAccountExtra atypes atags (Not q ) a = not $ matchesAccountExtra atypes
matchesAccountExtra atypes atags (Or qs ) a = any (\q -> matchesAccountExtra atypes atags q a) qs matchesAccountExtra atypes atags (Or qs ) a = any (\q -> matchesAccountExtra atypes atags q a) qs
matchesAccountExtra atypes atags (And qs ) a = all (\q -> matchesAccountExtra atypes atags q a) qs matchesAccountExtra atypes atags (And qs ) a = all (\q -> matchesAccountExtra atypes atags q a) qs
matchesAccountExtra atypes atags (AnyPosting qs ) a = all (\q -> matchesAccountExtra atypes atags q a) qs matchesAccountExtra atypes atags (AnyPosting qs ) a = all (\q -> matchesAccountExtra atypes atags q a) qs
matchesAccountExtra atypes atags (AllPostings qs ) a = all (\q -> matchesAccountExtra atypes atags q a) qs matchesAccountExtra atypes atags (AllPostings qs ) a = all1 (\q -> matchesAccountExtra atypes atags q a) qs
matchesAccountExtra atypes _ (Type ts) a = maybe False (\t -> any (t `isAccountSubtypeOf`) ts) $ atypes a matchesAccountExtra atypes _ (Type ts) a = maybe False (\t -> any (t `isAccountSubtypeOf`) ts) $ atypes a
matchesAccountExtra _ atags (Tag npat vpat) a = patternsMatchTags npat vpat $ atags a matchesAccountExtra _ atags (Tag npat vpat) a = patternsMatchTags npat vpat $ atags a
matchesAccountExtra _ _ q a = matchesAccount q a matchesAccountExtra _ _ q a = matchesAccount q a
@ -897,7 +897,7 @@ matchesPosting (None) _ = False
matchesPosting (Or qs) p = any (`matchesPosting` p) qs matchesPosting (Or qs) p = any (`matchesPosting` p) qs
matchesPosting (And qs) p = all (`matchesPosting` p) qs matchesPosting (And qs) p = all (`matchesPosting` p) qs
matchesPosting (AnyPosting qs) p = all (`matchesPosting` p) qs matchesPosting (AnyPosting qs) p = all (`matchesPosting` p) qs
matchesPosting (AllPostings qs) p = all (`matchesPosting` p) qs matchesPosting (AllPostings qs) p = all1 (`matchesPosting` p) qs
matchesPosting (Code r) p = maybe False (regexMatchText r . tcode) $ ptransaction p matchesPosting (Code r) p = maybe False (regexMatchText r . tcode) $ ptransaction p
matchesPosting (Desc r) p = maybe False (regexMatchText r . tdescription) $ ptransaction p matchesPosting (Desc r) p = maybe False (regexMatchText r . tdescription) $ ptransaction p
matchesPosting (Acct r) p = matches p || maybe False matches (poriginal p) where matches = regexMatchText r . paccount matchesPosting (Acct r) p = matches p || maybe False matches (poriginal p) where matches = regexMatchText r . paccount
@ -923,7 +923,7 @@ matchesPostingExtra atype (Not q ) p = not $ matchesPostingExtra atype q p
matchesPostingExtra atype (Or qs) p = any (\q -> matchesPostingExtra atype q p) qs matchesPostingExtra atype (Or qs) p = any (\q -> matchesPostingExtra atype q p) qs
matchesPostingExtra atype (And qs) p = all (\q -> matchesPostingExtra atype q p) qs matchesPostingExtra atype (And qs) p = all (\q -> matchesPostingExtra atype q p) qs
matchesPostingExtra atype (AnyPosting qs) p = all (\q -> matchesPostingExtra atype q p) qs matchesPostingExtra atype (AnyPosting qs) p = all (\q -> matchesPostingExtra atype q p) qs
matchesPostingExtra atype (AllPostings qs) p = all (\q -> matchesPostingExtra atype q p) qs matchesPostingExtra atype (AllPostings qs) p = all1 (\q -> matchesPostingExtra atype q p) qs
matchesPostingExtra atype (Type ts) p = matchesPostingExtra atype (Type ts) p =
-- does posting's account's type, if we can detect it, match any of the given types ? -- does posting's account's type, if we can detect it, match any of the given types ?
(maybe False (\t -> any (t `isAccountSubtypeOf`) ts) . atype $ paccount p) (maybe False (\t -> any (t `isAccountSubtypeOf`) ts) . atype $ paccount p)
@ -944,7 +944,7 @@ matchesTransaction (None) _ = False
matchesTransaction (Or qs) t = any (`matchesTransaction` t) qs matchesTransaction (Or qs) t = any (`matchesTransaction` t) qs
matchesTransaction (And qs) t = all (`matchesTransaction` t) qs matchesTransaction (And qs) t = all (`matchesTransaction` t) qs
matchesTransaction (AnyPosting qs) t = any (\p -> all (`matchesPosting` p) qs) $ tpostings t matchesTransaction (AnyPosting qs) t = any (\p -> all (`matchesPosting` p) qs) $ tpostings t
matchesTransaction (AllPostings qs) t = all (\p -> all (`matchesPosting` p) qs) $ tpostings t matchesTransaction (AllPostings qs) t = all1 (\p -> all (`matchesPosting` p) qs) $ tpostings t
matchesTransaction (Code r) t = regexMatchText r $ tcode t matchesTransaction (Code r) t = regexMatchText r $ tcode t
matchesTransaction (Desc r) t = regexMatchText r $ tdescription t matchesTransaction (Desc r) t = regexMatchText r $ tdescription t
matchesTransaction q@(Acct _) t = any (q `matchesPosting`) $ tpostings t matchesTransaction q@(Acct _) t = any (q `matchesPosting`) $ tpostings t
@ -970,7 +970,7 @@ matchesTransactionExtra atype (Not q) t = not $ matchesTransactionExtra atype q
matchesTransactionExtra atype (Or qs) t = any (\q -> matchesTransactionExtra atype q t) qs matchesTransactionExtra atype (Or qs) t = any (\q -> matchesTransactionExtra atype q t) qs
matchesTransactionExtra atype (And qs) t = all (\q -> matchesTransactionExtra atype q t) qs matchesTransactionExtra atype (And qs) t = all (\q -> matchesTransactionExtra atype q t) qs
matchesTransactionExtra atype (AnyPosting qs) t = any (\p -> all (\q -> matchesPostingExtra atype q p) qs) $ tpostings t matchesTransactionExtra atype (AnyPosting qs) t = any (\p -> all (\q -> matchesPostingExtra atype q p) qs) $ tpostings t
matchesTransactionExtra atype (AllPostings qs) t = all (\p -> all (\q -> matchesPostingExtra atype q p) qs) $ tpostings t matchesTransactionExtra atype (AllPostings qs) t = all1 (\p -> all (\q -> matchesPostingExtra atype q p) qs) $ tpostings t
matchesTransactionExtra atype q@(Type _) t = any (matchesPostingExtra atype q) $ tpostings t matchesTransactionExtra atype q@(Type _) t = any (matchesPostingExtra atype q) $ tpostings t
matchesTransactionExtra _ q t = matchesTransaction q t matchesTransactionExtra _ q t = matchesTransaction q t
@ -983,7 +983,7 @@ matchesDescription (None) _ = False
matchesDescription (Or qs) d = any (`matchesDescription` d) $ filter queryIsDesc qs matchesDescription (Or qs) d = any (`matchesDescription` d) $ filter queryIsDesc qs
matchesDescription (And qs) d = all (`matchesDescription` d) $ filter queryIsDesc qs matchesDescription (And qs) d = all (`matchesDescription` d) $ filter queryIsDesc qs
matchesDescription (AnyPosting qs) d = all (`matchesDescription` d) $ filter queryIsDesc qs matchesDescription (AnyPosting qs) d = all (`matchesDescription` d) $ filter queryIsDesc qs
matchesDescription (AllPostings qs) d = all (`matchesDescription` d) $ filter queryIsDesc qs matchesDescription (AllPostings qs) d = all1 (`matchesDescription` d) $ filter queryIsDesc qs
matchesDescription (Code _) _ = False matchesDescription (Code _) _ = False
matchesDescription (Desc r) d = regexMatchText r d matchesDescription (Desc r) d = regexMatchText r d
matchesDescription _ _ = False matchesDescription _ _ = False
@ -1010,7 +1010,7 @@ matchesTag (None) _ = False
matchesTag (Or qs) t = any (`matchesTag` t) $ filter queryIsTag qs matchesTag (Or qs) t = any (`matchesTag` t) $ filter queryIsTag qs
matchesTag (And qs) t = all (`matchesTag` t) $ filter queryIsTag qs matchesTag (And qs) t = all (`matchesTag` t) $ filter queryIsTag qs
matchesTag (AnyPosting qs) t = all (`matchesTag` t) $ filter queryIsTag qs matchesTag (AnyPosting qs) t = all (`matchesTag` t) $ filter queryIsTag qs
matchesTag (AllPostings qs) t = all (`matchesTag` t) $ filter queryIsTag qs matchesTag (AllPostings qs) t = all1 (`matchesTag` t) $ filter queryIsTag qs
matchesTag (Tag npat mvpat) t = patternsMatchTags npat mvpat [t] matchesTag (Tag npat mvpat) t = patternsMatchTags npat mvpat [t]
matchesTag _ _ = False matchesTag _ _ = False
@ -1021,7 +1021,7 @@ matchesPriceDirective (Not q) p = not $ matchesPriceDirective q p
matchesPriceDirective (Or qs) p = any (`matchesPriceDirective` p) qs matchesPriceDirective (Or qs) p = any (`matchesPriceDirective` p) qs
matchesPriceDirective (And qs) p = all (`matchesPriceDirective` p) qs matchesPriceDirective (And qs) p = all (`matchesPriceDirective` p) qs
matchesPriceDirective (AnyPosting qs) p = all (`matchesPriceDirective` p) qs matchesPriceDirective (AnyPosting qs) p = all (`matchesPriceDirective` p) qs
matchesPriceDirective (AllPostings qs) p = all (`matchesPriceDirective` p) qs matchesPriceDirective (AllPostings qs) p = all1 (`matchesPriceDirective` p) qs
matchesPriceDirective q@(Amt _ _) p = matchesAmount q (pdamount p) matchesPriceDirective q@(Amt _ _) p = matchesAmount q (pdamount p)
matchesPriceDirective q@(Sym _) p = matchesCommodity q (pdcommodity p) matchesPriceDirective q@(Sym _) p = matchesCommodity q (pdcommodity p)
matchesPriceDirective (Date spn) p = spanContainsDate spn (pddate p) matchesPriceDirective (Date spn) p = spanContainsDate spn (pddate p)

View File

@ -24,6 +24,7 @@ module Hledger.Utils (
minimumStrict, minimumStrict,
splitAtElement, splitAtElement,
sumStrict, sumStrict,
all1,
-- * Trees -- * Trees
treeLeaves, treeLeaves,
@ -174,6 +175,12 @@ splitAtElement x l =
sumStrict :: Num a => [a] -> a sumStrict :: Num a => [a] -> a
sumStrict = foldl' (+) 0 sumStrict = foldl' (+) 0
-- | Version of all that fails on an empty list.
{-# INLINABLE all1 #-}
all1 :: (a -> Bool) -> [a] -> Bool
all1 _ [] = False
all1 p as = all p as
-- Trees -- Trees
-- | Get the leaves of this tree as a list. -- | Get the leaves of this tree as a list.

View File

@ -5508,7 +5508,8 @@ means "show transactions where at least one posting posts a positive amount to a
### all: query ### all: query
**`all:'QUERYEXPR'`**\ **`all:'QUERYEXPR'`**\
Like `expr:`, but when used with transaction-oriented commands like `print`, Like `expr:`, but when used with transaction-oriented commands like `print`,
it matches the transaction only if all postings are matched by all of QUERYEXPR.\ it matches the transaction only if all postings are matched by all of QUERYEXPR
(and there is at least one posting).\
So, `hledger print all:'cash and amt:0'` So, `hledger print all:'cash and amt:0'`
means "show transactions where all postings involve a cash account and have a zero amount".\ means "show transactions where all postings involve a cash account and have a zero amount".\
Or, `hledger print all:'cash or checking'` Or, `hledger print all:'cash or checking'`

View File

@ -198,3 +198,37 @@ $ hledger -f sample2.journal print -x any:'(checking and amt:>0) or credit'
liabilities:credit card 200 USD = -200 USD liabilities:credit card 200 USD = -200 USD
>= >=
# ** 15. any: matches transactions where the whole query matches at least one of the postings.
<
2025-01-01 no postings
2025-01-02 a, b
a 1
b
2025-01-03 a, .5
a 1
a -0.5
a -0.5
2025-01-03 a, 1
a 1
a
$ hledger -f- print any:'a and amt:0.5'
2025-01-03 a, .5
a 1
a -0.5
a -0.5
>=
# ** 16. all: matches transactions where the query matches all of one or more postings.
$ hledger -f- print all:'a and amt:1'
2025-01-03 a, 1
a 1
a
>=