feat: regular expression and array filters, GET procedures; 0.3.0

This commit is contained in:
2026-11-02 02:09:06 +01:00
parent 8735abfe79
commit 215d1c2f7e
30 changed files with 171 additions and 44 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ A dialect is the package that knows one database: how to read its catalog, how t
| `@crudy/restloom-mssql` | SQL Server 2022+ | `JSON_OBJECT`, `STRING_AGG` | `OUTPUT` | no | security policies | scalar and table functions |
| `@crudy/restloom-sqlite` | SQLite 3.44+ (better-sqlite3) | `json_object`, `json_group_array` | `RETURNING` | no | no | none |
Every dialect passes the same conformance suite, so a request answers the same rows on each. Where they differ, the sections below say how.
Every dialect passes the same conformance suite, so a request answers the same rows on each. Regular expressions (`match`, `imatch`) need Postgres or MySQL, and array filters (`ov`, `cs`, `cd`) Postgres array columns; elsewhere they answer `unsupported`. Where else the dialects differ, the sections below say how.
## Postgres
+3 -1
View File
@@ -16,7 +16,7 @@ A message never contains SQL or database text.
| `method_not_allowed` | 405 | the resource does not allow this method, or a procedure was not called with POST |
| `unknown_column` | 400 | the column does not exist or is hidden |
| `unknown_relation` | 400 | the relation does not exist |
| `unknown_operator` | 400 | the operator is not one of `eq neq gt gte lt lte in like ilike is` |
| `unknown_operator` | 400 | the operator is not one of `eq neq gt gte lt lte in like ilike match imatch is ov cs cd` |
| `bad_operator` | 400 | the operator does not apply to the column's type |
| `invalid_value` | 400 | the value does not fit the column's type |
| `bad_filter` | 400 | a filter is not `operator.value` |
@@ -37,6 +37,7 @@ A message never contains SQL or database text.
| `bad_limit`, `bad_offset` | 400 | not a whole number |
| `limit_exceeded` | 400 | a limit over `limits.maxPage` |
| `bad_count` | 400 | `count` is not `exact` or `estimated` |
| `unsupported` | 400 | the database cannot do what was asked, e.g. a regular expression on SQLite |
## Writes and procedures
@@ -50,6 +51,7 @@ A message never contains SQL or database text.
| `out_of_scope` | 403 | a value contradicts the host's scope |
| `unknown_argument` | 400 | the procedure's function takes no argument of that name |
| `missing_argument` | 400 | an argument without a default is missing |
| `bad_argument` | 400 | a GET procedure's argument is given twice |
## The database
+2
View File
@@ -92,6 +92,8 @@ A filter is `column=operator.value`. Every filter must hold.
| `gt`, `gte`, `lt`, `lte` | greater or less than | text, numbers, dates and times |
| `in.(a,b,c)` | one of the list; quote an item holding a comma: `in.("a,b",c)` | any comparable column |
| `like`, `ilike` | pattern, `*` for any text; `ilike` ignores case | text |
| `match`, `imatch` | regular expression; `imatch` ignores case | text, where the database has them (Postgres, MySQL) |
| `ov.{a,b}`, `cs.{a,b}`, `cd.{a,b}` | the array overlaps, contains, or is contained by the list | array columns (Postgres) |
| `is.null`, `is.true`, `is.false` | null, or a boolean value | `null` any column, `true` and `false` booleans |
`not.` before the operator negates it: `status=not.in.(retired,lost)`.
+3 -1
View File
@@ -160,7 +160,9 @@ POST /incidents
## Procedures
A procedure calls a database function at a path you choose. A `{name}` in the path passes that segment as the argument of the same name; the body's keys pass the others. Arguments are checked by name and type, and one with a default may be left out. A procedure is always a POST.
A procedure calls a database function at a path you choose. A `{name}` in the path passes that segment as the argument of the same name; the body's keys pass the others. Arguments are checked by name and type, and one with a default may be left out.
A procedure is a POST unless declared `method: 'GET'`. A GET procedure takes its arguments from the query string (`/search?q=vpn&limit=10`), each once, and runs in a read-only transaction: use it for a function without side effects.
```http
POST /assets/3/retire
+2
View File
@@ -40,6 +40,8 @@ The core decides everything else from the logical types: which operators a colum
| `nested(expr)` | a nested JSON subquery, unchanged |
| `like(expr, pattern, insensitive)` | `LIKE` or `ILIKE ... ESCAPE '\'` |
| `isBoolean(expr, value)` | `IS TRUE`, `IS FALSE` |
| `regex(expr, pattern, insensitive)` | `~`, `~*`; throw `RestError(400, 'unsupported')` without regular expressions |
| `arrayCompare(expr, op, array)` | `&&`, `@>`, `<@` |
| `orderBy(expr, desc, nulls)` | `ASC`/`DESC NULLS FIRST`/`LAST` |
| `page(limit, offset)` | `LIMIT n OFFSET m` |
| `first(select)` | `... LIMIT 1` |