mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-24 19:44:21 +01:00
This PR started out as one to improve the API of extensions but I ended up building on top of this quite a bit and it just kept going. Sorry this one is so large but there wasn't really a good stopping point, as it kept leaving stuff in broken states. **VCreate**: Support for `CREATE VIRTUAL TABLE t USING vtab_module` **VUpdate**: Support for `INSERT` and `DELETE` methods on virtual tables. Sqlite uses `xUpdate` function with the `VUpdate` opcode to handle all insert/update/delete functionality in virtual tables.. have to just document that: ``` if args[0] == NULL: INSERT args[1] the values in args[2..] if args[1] == NULL: DELETE args[0] if args[0] != NULL && len(args) > 2: Update values=args[2..] rowid=args[0] ``` I know I asked @jussisaurio on discord about this already, but it just sucked so bad that I added some internal translation so we could expose a [nice API](https://github.com/tursodatabase/limbo/pull/996/files#diff- 3e8f8a660b11786745b48b528222d11671e9f19fa00a032a4eefb5412e8200d1R54) and handle the logic ourselves while keeping with sqlite's opcodes. I'll change it back if I have to, I just thought it was genuinely awful to have to rely on comments to explain all that to extension authors. The included extension is not meant to be a legitimately useful one, it is there for testing purposes. I did something similar in #960 using a test extension, so I figure when they are both merged, I will go back and combine them into one since you can do many kinds at once, and that way it will reduce the amount of crates and therefore compile time. 1. Remaining opcodes. 2. `UPDATE` (when we support the syntax) 3. `xConnect` - expose API for a DB connection to a vtab so it can perform arbitrary queries. Closes #996
47 KiB
47 KiB
Limbo compatibility with SQLite
This document describes the compatibility of Limbo with SQLite.
Table of contents
Overview
Limbo aims to be fully compatible with SQLite, with opt-in features not supported by SQLite.
Features
- ✅ SQLite file format is fully supported
- 🚧 SQLite query language [status] is partially supported
- 🚧 SQLite C API [status] is partially supported
Limitations
- ⛔️ Concurrent access from multiple processes is not supported.
- ⛔️ Savepoints are not supported.
- ⛔️ Triggers are not supported.
- ⛔️ Indexes are not supported.
- ⛔️ Views are not supported.
- ⛔️ Vacuum is not supported.
SQLite query language
Statements
| Statement | Status | Comment |
|---|---|---|
| ALTER TABLE | No | |
| ANALYZE | No | |
| ATTACH DATABASE | No | |
| BEGIN TRANSACTION | Partial | BEGIN DEFERRED is not supported, transaction names are not supported. |
| COMMIT TRANSACTION | Partial | Transaction names are not supported. |
| CREATE INDEX | No | |
| CREATE TABLE | Partial | |
| CREATE TRIGGER | No | |
| CREATE VIEW | No | |
| CREATE VIRTUAL TABLE | No | |
| DELETE | Yes | |
| DETACH DATABASE | No | |
| DROP INDEX | No | |
| DROP TABLE | No | |
| DROP TRIGGER | No | |
| DROP VIEW | No | |
| END TRANSACTION | Partial | Alias for COMMIT TRANSACTION |
| EXPLAIN | Yes | |
| INDEXED BY | No | |
| INSERT | Partial | |
| ON CONFLICT clause | No | |
| REINDEX | No | |
| RELEASE SAVEPOINT | No | |
| REPLACE | No | |
| RETURNING clause | No | |
| ROLLBACK TRANSACTION | No | |
| SAVEPOINT | No | |
| SELECT | Yes | |
| SELECT ... WHERE | Yes | |
| SELECT ... WHERE ... LIKE | Yes | |
| SELECT ... LIMIT | Yes | |
| SELECT ... ORDER BY | Yes | |
| SELECT ... GROUP BY | Yes | |
| SELECT ... HAVING | Yes | |
| SELECT ... JOIN | Yes | |
| SELECT ... CROSS JOIN | Yes | SQLite CROSS JOIN means "do not reorder joins". We don't support that yet anyway. |
| SELECT ... INNER JOIN | Yes | |
| SELECT ... OUTER JOIN | Partial | no RIGHT JOIN |
| SELECT ... JOIN USING | Yes | |
| SELECT ... NATURAL JOIN | Yes | |
| UPDATE | No | |
| UPSERT | No | |
| VACUUM | No | |
| WITH clause | Partial | No RECURSIVE, no MATERIALIZED, only SELECT supported in CTEs |
PRAGMA
| Statement | Status | Comment |
|---|---|---|
| PRAGMA analysis_limit | No | |
| PRAGMA application_id | No | |
| PRAGMA auto_vacuum | No | |
| PRAGMA automatic_index | No | |
| PRAGMA busy_timeout | No | |
| PRAGMA busy_timeout | No | |
| PRAGMA cache_size | Yes | |
| PRAGMA cache_spill | No | |
| PRAGMA case_sensitive_like | Not Needed | deprecated in SQLite |
| PRAGMA cell_size_check | No | |
| PRAGMA checkpoint_fullsync | No | |
| PRAGMA collation_list | No | |
| PRAGMA compile_options | No | |
| PRAGMA count_changes | Not Needed | deprecated in SQLite |
| PRAGMA data_store_directory | Not Needed | deprecated in SQLite |
| PRAGMA data_version | No | |
| PRAGMA database_list | No | |
| PRAGMA default_cache_size | Not Needed | deprecated in SQLite |
| PRAGMA defer_foreign_keys | No | |
| PRAGMA empty_result_callbacks | Not Needed | deprecated in SQLite |
| PRAGMA encoding | No | |
| PRAGMA foreign_key_check | No | |
| PRAGMA foreign_key_list | No | |
| PRAGMA foreign_keys | No | |
| PRAGMA freelist_count | No | |
| PRAGMA full_column_names | Not Needed | deprecated in SQLite |
| PRAGMA fullsync | No | |
| PRAGMA function_list | No | |
| PRAGMA hard_heap_limit | No | |
| PRAGMA ignore_check_constraints | No | |
| PRAGMA incremental_vacuum | No | |
| PRAGMA index_info | No | |
| PRAGMA index_list | No | |
| PRAGMA index_xinfo | No | |
| PRAGMA integrity_check | No | |
| PRAGMA journal_mode | Yes | |
| PRAGMA journal_size_limit | No | |
| PRAGMA legacy_alter_table | No | |
| PRAGMA legacy_file_format | Yes | |
| PRAGMA locking_mode | No | |
| PRAGMA max_page_count | No | |
| PRAGMA mmap_size | No | |
| PRAGMA module_list | No | |
| PRAGMA optimize | No | |
| PRAGMA page_count | Yes | |
| PRAGMA page_size | No | |
| PRAGMA parser_trace | No | |
| PRAGMA pragma_list | Yes | |
| PRAGMA query_only | No | |
| PRAGMA quick_check | No | |
| PRAGMA read_uncommitted | No | |
| PRAGMA recursive_triggers | No | |
| PRAGMA reverse_unordered_selects | No | |
| PRAGMA schema_version | No | |
| PRAGMA secure_delete | No | |
| PRAGMA short_column_names | Not Needed | deprecated in SQLite |
| PRAGMA shrink_memory | No | |
| PRAGMA soft_heap_limit | No | |
| PRAGMA stats | No | Used for testing in SQLite |
| PRAGMA synchronous | No | |
| PRAGMA table_info | Yes | |
| PRAGMA table_list | No | |
| PRAGMA table_xinfo | No | |
| PRAGMA temp_store | No | |
| PRAGMA temp_store_directory | Not Needed | deprecated in SQLite |
| PRAGMA threads | No | |
| PRAGMA trusted_schema | No | |
| PRAGMA user_version | Partial | Only read implemented |
| PRAGMA vdbe_addoptrace | No | |
| PRAGMA vdbe_debug | No | |
| PRAGMA vdbe_listing | No | |
| PRAGMA vdbe_trace | No | |
| PRAGMA wal_autocheckpoint | No | |
| PRAGMA wal_checkpoint | Partial | Not Needed calling with param (pragma-value) |
| PRAGMA writable_schema | No |
Expressions
Feature support of sqlite expr syntax.
| Syntax | Status | Comment |
|---|---|---|
| literals | Yes | |
| schema.table.column | Partial | Schemas aren't supported |
| unary operator | Yes | |
| binary operator | Partial | Only %, !<, and !> are unsupported |
| agg() FILTER (WHERE ...) | No | Is incorrectly ignored |
| ... OVER (...) | No | Is incorrectly ignored |
| (expr) | Yes | |
| CAST (expr AS type) | Yes | |
| COLLATE | No | |
| (NOT) LIKE | Yes | |
| (NOT) GLOB | Yes | |
| (NOT) REGEXP | No | |
| (NOT) MATCH | No | |
| IS (NOT) | Yes | |
| IS (NOT) DISTINCT FROM | Yes | |
| (NOT) BETWEEN ... AND ... | No | |
| (NOT) IN (subquery) | No | |
| (NOT) EXISTS (subquery) | No | |
| CASE WHEN THEN ELSE END | Yes | |
| RAISE | No |
SQL functions
Scalar functions
| Function | Status | Comment |
|---|---|---|
| abs(X) | Yes | |
| changes() | Partial | Still need to support update statements and triggers |
| char(X1,X2,...,XN) | Yes | |
| coalesce(X,Y,...) | Yes | |
| concat(X,...) | Yes | |
| concat_ws(SEP,X,...) | Yes | |
| format(FORMAT,...) | No | |
| glob(X,Y) | Yes | |
| hex(X) | Yes | |
| ifnull(X,Y) | Yes | |
| iif(X,Y,Z) | Yes | |
| instr(X,Y) | Yes | |
| last_insert_rowid() | Yes | |
| length(X) | Yes | |
| like(X,Y) | Yes | |
| like(X,Y,Z) | Yes | |
| likelihood(X,Y) | No | |
| likely(X) | No | |
| load_extension(X) | Yes | sqlite3 extensions not yet supported |
| load_extension(X,Y) | No | |
| lower(X) | Yes | |
| ltrim(X) | Yes | |
| ltrim(X,Y) | Yes | |
| max(X,Y,...) | Yes | |
| min(X,Y,...) | Yes | |
| nullif(X,Y) | Yes | |
| octet_length(X) | Yes | |
| printf(FORMAT,...) | Yes | Still need support additional modifiers |
| quote(X) | Yes | |
| random() | Yes | |
| randomblob(N) | Yes | |
| replace(X,Y,Z) | Yes | |
| round(X) | Yes | |
| round(X,Y) | Yes | |
| rtrim(X) | Yes | |
| rtrim(X,Y) | Yes | |
| sign(X) | Yes | |
| soundex(X) | Yes | |
| sqlite_compileoption_get(N) | No | |
| sqlite_compileoption_used(X) | No | |
| sqlite_offset(X) | No | |
| sqlite_source_id() | Yes | |
| sqlite_version() | Yes | |
| substr(X,Y,Z) | Yes | |
| substr(X,Y) | Yes | |
| substring(X,Y,Z) | Yes | |
| substring(X,Y) | Yes | |
| total_changes() | Partial | Still need to support update statements and triggers |
| trim(X) | Yes | |
| trim(X,Y) | Yes | |
| typeof(X) | Yes | |
| unhex(X) | Yes | |
| unhex(X,Y) | Yes | |
| unicode(X) | Yes | |
| unlikely(X) | No | |
| upper(X) | Yes | |
| zeroblob(N) | Yes |
Mathematical functions
| Function | Status | Comment |
|---|---|---|
| acos(X) | Yes | |
| acosh(X) | Yes | |
| asin(X) | Yes | |
| asinh(X) | Yes | |
| atan(X) | Yes | |
| atan2(Y,X) | Yes | |
| atanh(X) | Yes | |
| ceil(X) | Yes | |
| ceiling(X) | Yes | |
| cos(X) | Yes | |
| cosh(X) | Yes | |
| degrees(X) | Yes | |
| exp(X) | Yes | |
| floor(X) | Yes | |
| ln(X) | Yes | |
| log(B,X) | Yes | |
| log(X) | Yes | |
| log10(X) | Yes | |
| log2(X) | Yes | |
| mod(X,Y) | Yes | |
| pi() | Yes | |
| pow(X,Y) | Yes | |
| power(X,Y) | Yes | |
| radians(X) | Yes | |
| sin(X) | Yes | |
| sinh(X) | Yes | |
| sqrt(X) | Yes | |
| tan(X) | Yes | |
| tanh(X) | Yes | |
| trunc(X) | Yes |
Aggregate functions
| Function | Status | Comment |
|---|---|---|
| avg(X) | Yes | |
| count() | Yes | |
| count(*) | Yes | |
| group_concat(X) | Yes | |
| group_concat(X,Y) | Yes | |
| string_agg(X,Y) | Yes | |
| max(X) | Yes | |
| min(X) | Yes | |
| sum(X) | Yes | |
| total(X) | Yes |
Date and time functions
| Function | Status | Comment |
|---|---|---|
| date() | Yes | partially supports modifiers |
| time() | Yes | partially supports modifiers |
| datetime() | Yes | partially supports modifiers |
| julianday() | Partial | does not support modifiers |
| unixepoch() | Partial | does not support modifiers |
| strftime() | Yes | partially supports modifiers |
| timediff() | No |
Modifiers:
| Modifier | Status | Comment |
|---|---|---|
| Days | Yes | |
| Hours | Yes | |
| Minutes | Yes | |
| Seconds | Yes | |
| Months | Yes | |
| Years | Yes | |
| TimeOffset | Yes | |
| DateOffset | Yes | |
| DateTimeOffset | Yes | |
| Ceiling | No | |
| Floor | No | |
| StartOfMonth | Yes | |
| StartOfYear | Yes | |
| StartOfDay | Yes | |
| Weekday(N) | Yes | |
| Auto | No | |
| UnixEpoch | No | |
| JulianDay | No | |
| Localtime | Partial | requires fixes to avoid double conversions. |
| Utc | Partial | requires fixes to avoid double conversions. |
| Subsec | Yes |
JSON functions
| Function | Status | Comment |
|---|---|---|
| json(json) | Partial | |
| jsonb(json) | ||
| json_array(value1,value2,...) | Yes | |
| jsonb_array(value1,value2,...) | ||
| json_array_length(json) | Yes | |
| json_array_length(json,path) | Yes | |
| json_error_position(json) | Yes | |
| json_extract(json,path,...) | Partial | Does not fully support unicode literal syntax and does not allow numbers > 2^127 - 1 (which SQLite truncates to i32), does not support BLOBs |
| jsonb_extract(json,path,...) | ||
| json -> path | Yes | |
| json ->> path | Yes | |
| json_insert(json,path,value,...) | ||
| jsonb_insert(json,path,value,...) | ||
| json_object(label1,value1,...) | Yes | When keys are duplicated, only the last one processed is returned. This differs from sqlite, where the keys in the output can be duplicated |
| jsonb_object(label1,value1,...) | ||
| json_patch(json1,json2) | Yes | |
| jsonb_patch(json1,json2) | ||
| json_pretty(json) | Partial | Shares same json(val) limitations. Also, when passing blobs for indentation, conversion is not exactly the same as in SQLite |
| json_remove(json,path,...) | Partial | Uses same json path parser as json_extract so shares same limitations. |
| jsonb_remove(json,path,...) | ||
| json_replace(json,path,value,...) | ||
| jsonb_replace(json,path,value,...) | ||
| json_set(json,path,value,...) | Yes | |
| jsonb_set(json,path,value,...) | ||
| json_type(json) | Yes | |
| json_type(json,path) | Yes | |
| json_valid(json) | Yes | |
| json_valid(json,flags) | ||
| json_quote(value) | Yes | |
| json_group_array(value) | ||
| jsonb_group_array(value) | ||
| json_group_object(label,value) | ||
| jsonb_group_object(name,value) | ||
| json_each(json) | ||
| json_each(json,path) | ||
| json_tree(json) | ||
| json_tree(json,path) |
SQLite C API
| Interface | Status | Comment |
|---|---|---|
| sqlite3_open | Partial | |
| sqlite3_close | Yes | |
| sqlite3_prepare | Partial | |
| sqlite3_finalize | Yes | |
| sqlite3_step | Yes | |
| sqlite3_column_text | Yes |
SQLite VDBE opcodes
| Opcode | Status | Comment |
|---|---|---|
| Add | Yes | |
| AddImm | No | |
| Affinity | No | |
| AggFinal | Yes | |
| AggStep | Yes | |
| AggStep | Yes | |
| And | Yes | |
| AutoCommit | Yes | |
| BitAnd | Yes | |
| BitNot | Yes | |
| BitOr | Yes | |
| Blob | Yes | |
| Checkpoint | No | |
| Clear | No | |
| Close | No | |
| CollSeq | No | |
| Column | Yes | |
| Compare | Yes | |
| Concat | Yes | |
| Copy | Yes | |
| Count | No | |
| CreateBTree | Partial | no temp databases |
| CreateTable | No | |
| CreateTable | No | |
| DecrJumpZero | Yes | |
| Delete | No | |
| Destroy | No | |
| Divide | Yes | |
| DropIndex | No | |
| DropTable | No | |
| DropTrigger | No | |
| EndCoroutine | Yes | |
| Eq | Yes | |
| Expire | No | |
| Explain | No | |
| FkCounter | No | |
| FkIfZero | No | |
| Found | No | |
| Function | Yes | |
| Ge | Yes | |
| Gosub | Yes | |
| Goto | Yes | |
| Gt | Yes | |
| Halt | Yes | |
| HaltIfNull | No | |
| IdxDelete | No | |
| IdxGE | Yes | |
| IdxInsert | No | |
| IdxLE | Yes | |
| IdxLT | Yes | |
| IdxRowid | No | |
| If | Yes | |
| IfNeg | No | |
| IfNot | Yes | |
| IfPos | Yes | |
| IfZero | No | |
| IncrVacuum | No | |
| Init | Yes | |
| InitCoroutine | Yes | |
| Insert | No | |
| InsertAsync | Yes | |
| InsertAwait | Yes | |
| InsertInt | No | |
| Int64 | No | |
| Integer | Yes | |
| IntegrityCk | No | |
| IsNull | Yes | |
| IsUnique | No | |
| JournalMode | No | |
| Jump | Yes | |
| Last | No | |
| Le | Yes | |
| LoadAnalysis | No | |
| Lt | Yes | |
| MakeRecord | Yes | |
| MaxPgcnt | No | |
| MemMax | No | |
| Move | No | |
| Multiply | Yes | |
| MustBeInt | Yes | |
| Ne | Yes | |
| NewRowid | Yes | |
| Next | No | |
| NextAsync | Yes | |
| NextAwait | Yes | |
| Noop | Yes | |
| Not | Yes | |
| NotExists | Yes | |
| NotFound | No | |
| NotNull | Yes | |
| Null | Yes | |
| NullRow | Yes | |
| Once | No | |
| OpenAutoindex | No | |
| OpenEphemeral | No | |
| OpenPseudo | Yes | |
| OpenRead | Yes | |
| OpenReadAsync | Yes | |
| OpenWrite | No | |
| OpenWriteAsync | Yes | |
| OpenWriteAwait | Yes | |
| Or | Yes | |
| Pagecount | Partial | no temp databases |
| Param | No | |
| ParseSchema | No | |
| Permutation | No | |
| Prev | No | |
| PrevAsync | Yes | |
| PrevAwait | Yes | |
| Program | No | |
| ReadCookie | Partial | no temp databases, only user_version supported |
| Real | Yes | |
| RealAffinity | Yes | |
| Remainder | Yes | |
| ResetCount | No | |
| ResultRow | Yes | |
| Return | Yes | |
| Rewind | Yes | |
| RewindAsync | Yes | |
| RewindAwait | Yes | |
| RowData | No | |
| RowId | Yes | |
| RowKey | No | |
| RowSetAdd | No | |
| RowSetRead | No | |
| RowSetTest | No | |
| Rowid | Yes | |
| SCopy | No | |
| Savepoint | No | |
| Seek | No | |
| SeekGe | Yes | |
| SeekGt | Yes | |
| SeekLe | No | |
| SeekLt | No | |
| SeekRowid | Yes | |
| Sequence | No | |
| SetCookie | No | |
| ShiftLeft | Yes | |
| ShiftRight | Yes | |
| SoftNull | Yes | |
| Sort | No | |
| SorterCompare | No | |
| SorterData | Yes | |
| SorterInsert | Yes | |
| SorterNext | Yes | |
| SorterOpen | Yes | |
| SorterSort | Yes | |
| String | No | |
| String8 | Yes | |
| Subtract | Yes | |
| TableLock | No | |
| ToBlob | No | |
| ToInt | No | |
| ToNumeric | No | |
| ToReal | No | |
| ToText | No | |
| Trace | No | |
| Transaction | Yes | |
| VBegin | No | |
| VColumn | Yes | |
| VCreate | Yes | |
| VDestroy | No | |
| VFilter | Yes | |
| VNext | Yes | |
| VOpen | Yes | VOpenAsync |
| VRename | No | |
| VUpdate | Yes | |
| Vacuum | No | |
| Variable | No | |
| VerifyCookie | No | |
| Yield | Yes | |
| ZeroOrNull | Yes |
SQLite journaling modes
We currently don't have plan to support the rollback journal mode as it locks the database file during writes.
Therefore, all rollback-type modes (delete, truncate, persist, memory) are marked are Not Needed below.
| Journal mode | Status | Comment |
|---|---|---|
| wal | Yes | |
| wal2 | No | experimental feature in sqlite |
| delete | Not Needed | |
| truncate | Not Needed | |
| persist | Not Needed | |
| memory | Not Needed |
Extensions
Limbo has in-tree extensions.
UUID
UUID's in Limbo are blobs by default.
| Function | Status | Comment |
|---|---|---|
| uuid4() | Yes | UUID version 4 |
| uuid4_str() | Yes | UUID v4 string alias gen_random_uuid() for PG compatibility |
| uuid7(X?) | Yes | UUID version 7 (optional parameter for seconds since epoch) |
| uuid7_timestamp_ms(X) | Yes | Convert a UUID v7 to milliseconds since epoch |
| uuid_str(X) | Yes | Convert a valid UUID to string |
| uuid_blob(X) | Yes | Convert a valid UUID to blob |
regexp
The regexp extension is compatible with sqlean-regexp.
| Function | Status | Comment |
|---|---|---|
| regexp(pattern, source) | Yes | |
| regexp_like(source, pattern) | Yes | |
| regexp_substr(source, pattern) | Yes | |
| regexp_capture(source, pattern[, n]) | No | |
| regexp_replace(source, pattern, replacement) | No |
Vector
The vector extension is compatible with libSQL native vector search.
| Function | Status | Comment |
|---|---|---|
| vector(x) | Yes | |
| vector32(x) | Yes | |
| vector64(x) | Yes | |
| vector_extract(x) | Yes | |
| vector_distance_cos(x, y) | Yes |
Time
The time extension is compatible with sqlean-time.
| Function | Status | Comment |
|---|---|---|
| time_now() | Yes | |
| time_date(year, month, day[, hour, min, sec[, nsec[, offset_sec]]]) | Yes | |
| time_get_year(t) | Yes | |
| time_get_month(t) | Yes | |
| time_get_day(t) | Yes | |
| time_get_hour(t) | Yes | |
| time_get_minute(t) | Yes | |
| time_get_second(t) | Yes | |
| time_get_nano(t) | Yes | |
| time_get_weekday(t) | Yes | |
| time_get_yearday(t) | Yes | |
| time_get_isoyear(t) | Yes | |
| time_get_isoweek(t) | Yes | |
| time_get(t, field) | Yes | |
| time_unix(sec[, nsec]) | Yes | |
| time_milli(msec) | Yes | |
| time_micro(usec) | Yes | |
| time_nano(nsec) | Yes | |
| time_to_unix(t) | Yes | |
| time_to_milli(t) | Yes | |
| time_to_micro(t) | Yes | |
| time_to_nano(t) | Yes | |
| time_after(t, u) | Yes | |
| time_before(t, u) | Yes | |
| time_compare(t, u) | Yes | |
| time_equal(t, u) | Yes | |
| time_add(t, d) | Yes | |
| time_add_date(t, years[, months[, days]]) | Yes | |
| time_sub(t, u) | Yes | |
| time_since(t) | Yes | |
| time_until(t) | Yes | |
| time_trunc(t, field) | Yes | |
| time_trunc(t, d) | Yes | |
| time_round(t, d) | Yes | |
| time_fmt_iso(t[, offset_sec]) | Yes | |
| time_fmt_datetime(t[, offset_sec]) | Yes | |
| time_fmt_date(t[, offset_sec]) | Yes | |
| time_fmt_time(t[, offset_sec]) | Yes | |
| time_parse(s) | Yes | |
| dur_ns() | Yes | |
| dur_us() | Yes | |
| dur_ms() | Yes | |
| dur_s() | Yes | |
| dur_m() | Yes | |
| dur_h() | Yes |