percent
A percent is a bare integer on a 0-100 scale (50% is 50, and 0.5 is rejected as Float), but nothing is clamped, so -1, 1000 and 2**31-1 all store at HTTP 200.
Data type percent, probed on Shot.sg_vendor_percentage_complete (stock, editable). Same on
Shot.sg___complete. The schema declares neither a range nor a scale: properties is only
{"default_value": null, "summary_default": "none"}.
Read A bare JSON integer under attributes on a 0-100 scale: 50% is 50, not a string, not a
fraction, no % sign. An unset field reads null, not 0 and not absent.
{"sg_vendor_percentage_complete":50}
a Shot created without the field ever set reads null
Against number and float
| type | accepted on write | read shape | operators |
|---|---|---|---|
percent |
[Integer, NilClass] |
bare integer | is, is_not, greater_than, less_than, between, in, not_in |
number |
[String, Integer, NilClass], coerces "42" |
bare integer | the same list, word for word |
float |
rejects the Integer percent demands |
quoted string | the same list, word for word |
Write PUT /entity/shots/<id> with a bare {"sg_vendor_percentage_complete": 42};
POST /entity/shots takes the same value shape and 201s. Every rejection names the accepted set:
[Integer, NilClass], the narrowest of the three numeric types.
| sent | result |
|---|---|
42 |
200, reads 42 |
1 |
200, reads 1 |
50 |
200, reads 50 |
100 |
200, reads 100 |
101 |
200, reads 101 |
-1 |
200, reads -1 |
-1000 |
200, reads -1000 |
1000 |
200, reads 1000 |
1000000 |
200, reads 1000000 |
2147483647 |
200, reads 2147483647 |
2147483648, PUT |
400 {"status": 400, "code": 104, "source": null, "title": "Update failed for [Shot.sg_vendor_percentage_complete]: Invalid statement."} |
2147483648, POST |
400 {"status": 400, "code": 104, "source": null, "title": "Create failed for [Shot]: PG::NumericValueOutOfRange: ERROR: integer out of range\n", "meta": {"crud_error_uuid": "<uuid>"}} |
0.5 |
400 API update() Shot.sg_vendor_percentage_complete expected [Integer, NilClass] data type(s) but got Float: 0.5 |
42.5 |
400 ... but got Float: 42.5 |
0.001 |
400 ... but got Float: 0.001; no rounding, no truncation |
'42' |
400 ... but got String: "42" |
' 42 ' |
400 ... but got String: " 42 " |
'50%' |
400 ... but got String: "50%" |
'abc' |
400 ... but got String: "abc" |
'' |
400 ... but got String: "" |
True |
400 ... but got TrueClass: true |
'50%', POST |
400 API create() Shot.sg_vendor_percentage_complete expected [Integer, NilClass] data type(s) but got String: "50%" |
The only ceiling is the signed 32-bit one the number finding records, and the two verbs fail
differently there in the same way.
Clear
| sent | result |
|---|---|
null |
200, reads null |
0 |
200, reads 0: a value, not a clear |
"" |
400, rejected as String |
0 and null stay distinct on write, on read and under a filter: is 0 matched 1 row, is None
matched 2: the row set to null and the row never set.
Filter POST /entity/shots/_search, Content-Type: application/vnd+shotgun.api3_array+json.
A bogus relation 400s with the whole vocabulary (probe 017):
Valid relations: ["is", "is_not", "greater_than", "less_than", "between", "in", "not_in"]
contains, starts_with and not_between are absent, and each 400s with that same list. There is no
>= or <=. Bracket with between, or shift the bound by one.
Match counts over 5 sandbox rows holding 0, null, 50, 50, null:
| operator | value | matches |
|---|---|---|
is |
50 |
2 |
is |
0 |
1 |
is |
null |
2 |
is |
99999 |
0 |
is_not |
50 |
3 |
is_not |
null |
3 |
in |
[50, 0] |
3 |
in |
[99999] |
0 |
not_in |
[50] |
3 |
greater_than |
0 |
2 |
greater_than |
-1 |
3 |
less_than |
1 |
1 |
less_than |
100 |
3 |
between |
[0, 100] |
3 |
between |
[200, 300] |
0 |
Rejected filter values:
| sent | result |
|---|---|
is 50.0 |
400 API read() Shot.sg_vendor_percentage_complete expected [Integer, NilClass] data type(s) but got Float: 50.0 |
is 0.5 |
400, the same [Integer, NilClass] rejection |
is '50' |
400, the same [Integer, NilClass] rejection |
between [0, 0.9] |
400, the same [Integer, NilClass] rejection |
between 50 |
400 API read() 'between' 'relation' expects a 2-element array: [50] |
contains '5' |
400 ... 'percent' data type doesn't support 'contains' 'relation' |
Traps
- Integer or nothing, on both halves.
PUT {"field": "50"}and["field", "is", "50"]each 400 withexpected [Integer, NilClass] ... but got String.int()a value out of a CSV or a form field before sending it. - The 0-100 range is a convention, not a constraint.
-1,101and1000000all store at 200 and read back unchanged, and nopropertieskey declares a bound. Validate before writing, and clamp on read; a percent field is not evidence that its value is a fraction of a whole. 0is a value and""is not a clear. Followingprobe 007, a row holding0isis_not None, so a percent field full of zeroes scans as 100% filled. Rank bygreater_than 0, never by fill rate.- Negation includes nulls; comparison excludes them.
is_not 50andnot_in [50]return the null rows too, 3 of 5;greater_than -1returns only the 3 rows holding a value. A "not yet at 100%" filter written asis_not 100sweeps in every unset row.