See gist https://gist.github.com/ancorgs/07d8c772226cfd1794631f284b5ad903, section "Searching with drives by their partitions".
I am trying to evaluate the JSON schema proposed in the gist to search for partitions. There are several details I am not convinced about.
max: 0 is used to indicate that none of the partitions matches the given condition:
{
"search": {
"condition": {
"partitions": {
"condition": {
"size": {
"greater": "10 GiB"
},
"max": 0
}
}
}
}
}This is equivalent to negate the condition:
{
"search": {
"condition": {
"partitions": {
"condition": {
"not": {
"size": {
"greater": "10 GiB"
}
}
}
}
}
}
}The proposal accepts min: 0, but actually it means nothing.
To check there are no partitions we need to use max: 0 without condition:
{
"search": {
"condition": {
"partitions": {
"condition": {
"max": 0
}
}
}
}
}I am not sure if this syntax is understandable.
If the device has no partitions, should a condition over all partitions match? For example:
{
"search": {
"condition": {
"partitions": {
"condition": {
"not": {
"size": {
"greater": "10 GiB"
}
}
}
}
}
}
}Please, analyze the current syntax proposal. Add a new section to this document indicating some possible improvements. Ask me any doubt.
The gist overloads a single partitions object with two unrelated jobs, and the
meaning of condition silently changes depending on whether min/max are
present:
- Without
min/max→conditionis a universal predicate: "all partitions must match". (No matching partitions = vacuously true.) - With
min/max→conditionbecomes a filter: "the count of partitions matching it must fall in[min, max]".
So the same key means "for all" in one case and "how many of the ones matching" in the other. This mode switch is the source of most of the doubts below.
count >= 0 is always true, so min: 0 never constrains anything. It should be
rejected by the schema (or at least flagged by the config checker) rather
than silently accepted, so users don't write conditions that look meaningful but
aren't.
{ max: 0, condition: X } ("zero partitions match X") is logically identical
to the default-quantifier form { condition: { not: X } } ("all partitions match
not-X") — verified across the non-empty, all-pass, and empty-drive cases; they
agree in every case, including the empty drive (both vacuously true).
Because we already have the and/or/not operator tree, max combined with a
condition adds no expressive power. Recommendation: do not allow max
together with condition. Keep max: 0 only for the condition-less "no
partitions at all" case (see below).
The awkward { min: 1 } (has partitions) and { max: 0 } (no partitions) are
exactly the presence/absence check we already solved for filesystem with
"any" / "none" string shortcuts. For consistency:
{ "partitions": "any" } // has at least one partition (was { "min": 1 })
{ "partitions": "none" } // has no partitions (was { "max": 0 })This directly answers the "is max: 0 understandable?" doubt: replace it with
"none".
Rather than patching min/max, make the quantifier explicit. This maps 1:1 to
natural language and removes the mode switch entirely:
{ "partitions": "any" } // has at least one partition
{ "partitions": "none" } // has no partitions
{ "partitions": { "any": <condition> } } // at least one partition matches
{ "partitions": { "none": <condition> } } // no partition matches
{ "partitions": { "all": <condition> } } // has >= 1 partition AND every one matches
{ "partitions": { "count": { "condition": <optional>, "min": N, "max": <optional> } } }The bare strings "any" / "none" are the condition-less presence/absence
shortcuts. count is the only form carrying numbers, and (see "The count form
in detail" below) its condition is optional, min is mandatory and
>= 1, and max is optional and >= min — so count only ever
expresses "one or more" and the zero-cases stay in "none" / { none: X }.
Mapping from the gist:
| Gist | Proposed |
|---|---|
{ min: 1 } |
"any" |
{ max: 0 } |
"none" |
{ condition: X } |
{ all: X } |
{ min: 1, condition: X } |
{ any: X } |
{ max: 0, condition: X } |
{ none: X } |
{ min: 2, condition: X } |
{ count: { condition: X, min: 2 } } |
{ min: 2, max: 4, condition: X } |
{ count: { condition: X, min: 2, max: 4 } } |
{ min: 2 } (count all partitions) |
{ count: { min: 2 } } |
Benefits: no meaningless min: 0; no max: 0 anywhere (the zero-cases have
dedicated forms); min/max live only in count, which does nothing but count;
and each form has one clear meaning.
The guiding rule: a form matches an empty drive only if it is asking about the
absence of partitions. Every form that asserts something about existing
partitions fails, and all is defined to require at least one partition (no
vacuous truth). Concretely, for a drive with zero partitions:
| Form | Empty drive | Why |
|---|---|---|
"any" |
no match | needs >= 1 partition |
{ any: X } |
no match | needs >= 1 partition matching X |
"none" |
match | there are indeed no partitions |
{ none: X } |
match | no partition matches X (there are none to match) |
{ all: X } |
no match | all requires >= 1 partition (see below) |
{ count: { min: N, … } } |
no match | count is 0, and min >= 1 |
So only the none family matches an empty drive — which reads exactly as
intended.
Why all is not vacuously true. In plain logic "every partition matches X"
is true for zero partitions, but that is surprising for users: a search like
"a drive whose partitions are all Linux" matching a completely empty drive is
almost never what was meant. We therefore define { all: X } as "there is at
least one partition and every partition matches X".
If you ever do want the vacuous "empty or all match" meaning, it is still
expressible via the operator tree without overloading all:
{
"or": [
{ "partitions": "none" },
{ "partitions": { "all": { "size": { "greater": "10 GiB" } } } }
]
}This is another argument for the explicit-quantifier design: the leaves each have one unambiguous meaning, and the operator tree handles any combination.
count is the only form that carries min/max, and it should stay focused on
its one job: counting one or more partitions. Shape:
{ "partitions": { "count": { "condition": <optional>, "min": N, "max": <optional> } } }min is mandatory, max is optional. count is only worth using when you
actually need to count, so:
minis required and must be>= 1.min: 0is meaningless (count >= 0is always true) and is rejected — acountwith no lower bound is not counting anything.maxis optional; omit it for "at leastmin". When present it must be>= min(somaxis always>= 1andmax: 0can never occur here).
This deliberately keeps the two zero-cases out of count, where they read
poorly, and expresses them with the forms built for them instead:
- "no partitions at all" →
"none". - "no partition matches X" →
{ none: X }.
Is condition optional? Yes. When present, count counts only the
partitions matching it; when omitted, it counts all partitions. This is what
makes "just count partitions, no other filter" expressible.
Note count with only min: 1 overlaps the sugar keywords ({ count: { min: 1 } }
== "any"; { count: { condition: X, min: 1 } } == { any: X }) — prefer the
keyword forms for those, and reach for count when min >= 2 or a max is
involved.
Question: a disk with at least 2 partitions and no other condition. Omit
condition and set min:
{
"search": {
"condition": {
"partitions": { "count": { "min": 2 } }
}
}
}A range ("between 2 and 5 matching a condition") adds max and a condition:
{
"search": {
"condition": {
"partitions": {
"count": {
"condition": { "size": { "greater": "10 GiB" } },
"min": 2,
"max": 5
}
}
}
}
}