# PostgreSQL-compatible functions > Functions a PostgreSQL app expects: arrays, sequence numbers (nextval), who you are and what you may do (current_user, has_table_privilege), comments (COMMENT ON), advisory locks and session settings, so app code runs on KarelaDB without changes. - Category: Postgres protocol - Keywords: PostgreSQL functions, to\_regclass, pg\_relation\_size, pg\_total\_relation\_size, pg\_table\_size, pg\_indexes\_size, pg\_database\_size, pg\_size\_pretty, table size, database size, current\_database, postgres compatibility, array, ARRAY\[\], arrays, list of values, watchlist, tags, sequence, auto number, order number, invoice number, advisory lock, named lock, job lock, one worker at a time, pg\_advisory, nextval, currval, lastval, setval, unnest, cardinality, current\_setting, set\_config, SET LOCAL, session setting, custom setting, current\_user, session\_user, current\_role, current\_schema, current\_schemas, current\_catalog, version, has\_table\_privilege, has\_column\_privilege, has\_database\_privilege, has\_schema\_privilege, pg\_has\_role, privileges, rights, COMMENT ON, comment, description, obj\_description, col\_description, pg\_get\_indexdef, pg\_get\_constraintdef, pg\_get\_viewdef, pg\_get\_expr, format\_type, pg\_table\_is\_visible - See also: [PostgreSQL protocol (port 8832)](postgres-protocol.md), [JSON functions](functions-json.md), [Text functions](functions-text.md), [Date and time functions](functions-date-time.md), [Constraints](constraints.md), [Transactions](transactions.md) A PostgreSQL app sends more than plain SQL. It also calls **arrays**, **sequences** and **advisory locks**. KarelaDB answers all three with the PostgreSQL names, so the same app code runs here. - An **array** is a list of values in one value: `ARRAY['CITY', 'COAST']`. KarelaDB keeps an array as a [JSON](functions-json.md) list, so a `JSON` column can hold one and every JSON function still works on it. - A **sequence** is a counter the database keeps for you: order numbers, invoice numbers. `nextval` gives the next one. See `CREATE SEQUENCE` in [constraints](constraints.md#auto) for how to make one. - An **advisory lock** is a name (a number) your app takes so two copies of it do not do the same job twice. The other PostgreSQL function names live with their own kind: the JSON ones in [JSON functions](functions-json.md), the time ones in [date and time](functions-date-time.md), `initcap`, `md5`, `to_str` and `string_to_array` in [text functions](functions-text.md). **Example: A watchlist as one array value** (sample data: sample) ```sql SELECT ARRAY['CITY', 'COAST', 'VOLT'] AS watchlist, cardinality(ARRAY['CITY', 'COAST', 'VOLT']) AS how_many, array_to_string(ARRAY['CITY', 'COAST', 'VOLT'], ', ') AS shown; ``` Result: | watchlist (TEXT[]) | how_many (LONG) | shown (STRING) | |---|---|---| | ["CITY","COAST","VOLT"] | 3 | CITY, COAST, VOLT | (1 row) ## Arrays in KarelaDB Syntax: ```sql ARRAY[value, value, ...] an array value '["CITY","COAST"]' JSON text an array function reads as an array '{CITY,COAST}' PostgreSQL array text an array function reads too ``` An array is a list of values kept as one value. KarelaDB stores it as a JSON list, so `ARRAY['CITY','COAST']` shows as `["CITY","COAST"]` and fits in a `JSON` column. - **Counting starts at 1**, as in PostgreSQL: the first element is number 1. - An array may hold text, numbers, `TRUE`/`FALSE` and `NULL` mixed together. KarelaDB does not check that all elements are of one type. - The functions on this page also read **text**: text that starts with `[` as JSON, text that starts with `{` as PostgreSQL array text (`'{CITY,COAST}'`). So a value a PostgreSQL driver sends still works. - A `JSON` **column** keeps JSON only. To save a PostgreSQL array text in one, read it first: `string_to_array(t, ',')`, or `array_cat(t, ARRAY[])`. - There are no arrays of arrays with their own functions. `array_length(a, 2)` (a second dimension) always gives `NULL`. - To store a list, use a `JSON` column. To take a list apart into rows, use [unnest](functions-postgres.md#unnest). **Example: A watchlist column an app reads and writes** (sample data: sample) Setup: ```sql CREATE TABLE watchlists (user_id INT PRIMARY KEY, symbols JSON); INSERT INTO watchlists VALUES (1, '["CITY","COAST"]'), (2, array_cat('{VOLT,ACME}', ARRAY[])); ``` ```sql SELECT user_id, symbols, cardinality(symbols) AS how_many, symbols ->> 0 AS first_symbol FROM watchlists ORDER BY user_id; ``` Result: | user_id (INT) | symbols (JSON) | how_many (LONG) | first_symbol (STRING) | |---|---|---|---| | 1 | ["CITY","COAST"] | 2 | CITY | | 2 | ["VOLT","ACME"] | 2 | VOLT | (2 rows) ## array_append Syntax: ```sql array_append(array, value) ``` The array with one more value at the **end**. | Argument | Type | Meaning | |---|---|---| | `array` | array (`JSON` list, or array text) | the list to add to | | `value` | any | the value to put at the end | - **Returns:** the new array (`JSON`). The old one is not changed. - **NULL:** `array_append(NULL, 'CITY')` is `["CITY"]` (a NULL array counts as empty). A `NULL` value is added as JSON `null`. - **Notes:** PostgreSQL writes this as `array || value`. In KarelaDB `||` joins text, so use the function name. **Example: Add a company to a user's watchlist** (sample data: sample) Setup: ```sql CREATE TABLE watchlists (user_id INT PRIMARY KEY, symbols JSON); INSERT INTO watchlists VALUES (1, '["CITY","COAST"]'); ``` ```sql UPDATE watchlists SET symbols = array_append(symbols, 'VOLT') WHERE user_id = 1; SELECT user_id, symbols FROM watchlists; ``` Result: `UPDATE watchlists SET symbols = array_append(symbols, 'VOLT') WHERE user_id = 1` ```text 1 row(s) updated ``` `SELECT user_id, symbols FROM watchlists` | user_id (INT) | symbols (JSON) | |---|---| | 1 | ["CITY","COAST","VOLT"] | (1 row) ## array_prepend Syntax: ```sql array_prepend(value, array) ``` The array with one more value at the **front**. The value comes first, the array second (as in PostgreSQL). | Argument | Type | Meaning | |---|---|---| | `value` | any | the value to put in front | | `array` | array | the list to add to | - **Returns:** the new array (`JSON`). - **NULL:** a `NULL` array counts as empty. A `NULL` value is put in front as JSON `null`. **Example** (sample data: sample) ```sql SELECT array_prepend('ACME', ARRAY['CITY', 'COAST']) AS watchlist; ``` Result: | watchlist (JSON) | |---| | ["ACME","CITY","COAST"] | (1 row) ## array_cat Syntax: ```sql array_cat(array1, array2) ``` The two arrays joined into one, in order. | Argument | Type | Meaning | |---|---|---| | `array1` | array | the first list | | `array2` | array | the list added after it | - **Returns:** the joined array (`JSON`). - **NULL:** only when **both** are `NULL`. One `NULL` counts as an empty list. **Example: One watchlist for two users** (sample data: sample) ```sql SELECT array_cat(ARRAY['CITY', 'COAST'], ARRAY['VOLT']) AS together, array_cat(NULL, ARRAY['TELCO']) AS one_side_null, array_cat(NULL, NULL) AS both_null; ``` Result: | together (TEXT[]) | one_side_null (TEXT[]) | both_null (TEXT[]) | |---|---|---| | ["CITY","COAST","VOLT"] | ["TELCO"] | NULL | (1 row) ## array_remove Syntax: ```sql array_remove(array, value) ``` The array without **every** element equal to `value`. | Argument | Type | Meaning | |---|---|---| | `array` | array | the list | | `value` | any | the value to take out (all of them) | - **Returns:** the shorter array (`JSON`). Nothing equal: the same array. - **NULL:** `array_remove(NULL, x)` is `NULL`. `array_remove(a, NULL)` takes the `null` elements out. **Example: A user removes a company from the watchlist** (sample data: sample) Setup: ```sql CREATE TABLE watchlists (user_id INT PRIMARY KEY, symbols JSON); INSERT INTO watchlists VALUES (1, '["CITY","COAST","CITY"]'); ``` ```sql UPDATE watchlists SET symbols = array_remove(symbols, 'CITY') WHERE user_id = 1; SELECT user_id, symbols FROM watchlists; ``` Result: `UPDATE watchlists SET symbols = array_remove(symbols, 'CITY') WHERE user_id = 1` ```text 1 row(s) updated ``` `SELECT user_id, symbols FROM watchlists` | user_id (INT) | symbols (JSON) | |---|---| | 1 | ["COAST"] | (1 row) ## array_position Syntax: ```sql array_position(array, value [, start]) ``` Which place `value` has in the array. The first place is `1`. | Argument | Type | Meaning | |---|---|---| | `array` | array | the list to look in | | `value` | any | the value to find | | `start` | `INT` | look from this place on (default `1`) | - **Returns:** `LONG`, the place of the **first** match, or `NULL` when the value is not there. - **NULL:** a `NULL` array gives `NULL`. `array_position(a, NULL)` finds a `null` element. **Example: Where is VOLT in the watchlist?** (sample data: sample) ```sql SELECT array_position(ARRAY['CITY', 'COAST', 'VOLT'], 'VOLT') AS at_place, array_position(ARRAY['CITY', 'COAST', 'VOLT'], 'TELCO') AS not_there, array_position(ARRAY['CITY', 'COAST', 'CITY'], 'CITY', 2) AS from_place_2; ``` Result: | at_place (LONG) | not_there (STRING) | from_place_2 (LONG) | |---|---|---| | 3 | NULL | 3 | (1 row) ## array_length, array_upper Syntax: ```sql array_length(array, dimension) array_upper(array, dimension) ``` How many elements the array has. `dimension` must be `1`; KarelaDB has one dimension only. `array_upper` gives the number of the last place, which is the same number. | Argument | Type | Meaning | |---|---|---| | `array` | array | the list | | `dimension` | `INT` | always `1` | - **Returns:** `LONG`. - **NULL:** an **empty** array gives `NULL` (that is what PostgreSQL does; use `cardinality` for `0`). A `NULL` array, or a dimension that is not `1`, also gives `NULL`. **Example** (sample data: sample) ```sql SELECT array_length(ARRAY['CITY', 'COAST'], 1) AS how_many, array_upper(ARRAY['CITY', 'COAST'], 1) AS last_place, array_length(ARRAY[], 1) AS empty_list, array_length(ARRAY['CITY'], 2) AS no_second_dimension; ``` Result: | how_many (LONG) | last_place (LONG) | empty_list (STRING) | no_second_dimension (STRING) | |---|---|---|---| | 2 | 2 | NULL | NULL | (1 row) ## cardinality Syntax: ```sql cardinality(array) ``` How many elements the array has, counting an empty array as `0`. | Argument | Type | Meaning | |---|---|---| | `array` | array | the list | - **Returns:** `LONG`. - **NULL:** `cardinality(NULL)` is `NULL`. An empty array gives `0`, not `NULL`. - **Notes:** this is the safe one to use in `WHERE`: `cardinality(symbols) > 0`. **Example: How many companies each user follows** (sample data: sample) Setup: ```sql CREATE TABLE watchlists (user_id INT PRIMARY KEY, symbols JSON); INSERT INTO watchlists VALUES (1, '["CITY","COAST"]'), (2, '[]'), (3, NULL); ``` ```sql SELECT user_id, cardinality(symbols) AS follows, array_length(symbols, 1) AS same_but_null_when_empty FROM watchlists ORDER BY user_id; ``` Result: | user_id (INT) | follows (LONG) | same_but_null_when_empty (LONG) | |---|---|---| | 1 | 2 | 2 | | 2 | 0 | NULL | | 3 | NULL | NULL | (3 rows) ## array_to_string Syntax: ```sql array_to_string(array, separator [, null_text]) ``` The elements written one after the other with `separator` between them. | Argument | Type | Meaning | |---|---|---| | `array` | array | the list | | `separator` | `STRING` | text put between two elements | | `null_text` | `STRING` | what a `NULL` element is written as; without it `NULL` elements are left out | - **Returns:** `STRING`. - **NULL:** a `NULL` array or a `NULL` separator gives `NULL`. **Example: A watchlist in one line for an email** (sample data: sample) ```sql SELECT array_to_string(ARRAY['CITY', 'COAST', 'VOLT'], ', ') AS watchlist, array_to_string(ARRAY['CITY', NULL, 'VOLT'], ', ') AS nulls_left_out, array_to_string(ARRAY['CITY', NULL, 'VOLT'], ', ', '?') AS nulls_shown; ``` Result: | watchlist (STRING) | nulls_left_out (STRING) | nulls_shown (STRING) | |---|---|---| | CITY, COAST, VOLT | CITY, VOLT | CITY, ?, VOLT | (1 row) ## unnest Syntax: ```sql unnest(array) ``` Turns one array into **many rows**, one row per element, in order. | Argument | Type | Meaning | |---|---|---| | `array` | array | the list to take apart | - **Returns:** one row per element. - **NULL:** a `NULL` array gives **no rows**. - **Notes:** this is how a PostgreSQL app sends a list of symbols in one parameter: `WHERE symbol IN (SELECT unnest($1))`. **Example: One row per company in the list** (sample data: sample) ```sql SELECT unnest(ARRAY['CITY', 'COAST', 'VOLT']) AS symbol; ``` Result: | symbol (STRING) | |---| | CITY | | COAST | | VOLT | (3 rows) **Example: An app sends a watchlist and asks for those companies** (sample data: sample) ```sql SELECT symbol, name, sector FROM securities WHERE symbol IN (SELECT unnest(ARRAY['CITY', 'VOLT'])) ORDER BY symbol; ``` Result: | symbol (SYMBOL) | name (STRING) | sector (STRING) | |---|---|---| | CITY | City Bank Limited | Banking | | VOLT | Volt River Hydropower Limited | Energy | (2 rows) ## nextval Syntax: ```sql nextval('sequence_name') ``` The next number of a sequence. Each call gives a number that was never given before, even when many connections ask at the same time. | Argument | Type | Meaning | |---|---|---| | `sequence_name` | `STRING` | the sequence name in **single quotes**; `'public.orders_id_seq'` and `'"Name"'` are accepted too | - **Returns:** `LONG`. - **NULL:** a `NULL` name gives `NULL`. - **Notes:** make the sequence first with `CREATE SEQUENCE order_no_seq START WITH 1001`. A number that was given is never given again, also when the transaction is rolled back - so there can be gaps. A sequence that does not exist is an error. For a simple id column, `PRIMARY KEY AUTO` is easier (see [constraints](constraints.md#auto)). **Example: Order numbers from a sequence** Setup: ```sql CREATE SEQUENCE order_no_seq START WITH 1001; CREATE TABLE orders (order_no LONG PRIMARY KEY, symbol SYMBOL, quantity LONG); ``` ```sql INSERT INTO orders VALUES (nextval('order_no_seq'), 'CITY', 100), (nextval('order_no_seq'), 'COAST', 50); SELECT * FROM orders ORDER BY order_no; ``` Result: `INSERT INTO orders VALUES (nextval('order_no_seq'), 'CITY', 100), (nextval('order_no_seq'), 'COAST',` ```text 2 row(s) inserted ``` `SELECT * FROM orders ORDER BY order_no` | order_no (LONG) | symbol (SYMBOL) | quantity (LONG) | |---|---|---| | 1001 | CITY | 100 | | 1002 | COAST | 50 | (2 rows) ## currval Syntax: ```sql currval('sequence_name') ``` The number this connection got **last** from that sequence. It does not move the sequence on. | Argument | Type | Meaning | |---|---|---| | `sequence_name` | `STRING` | the sequence name in single quotes | - **Returns:** `LONG`. - **NULL:** a `NULL` name gives `NULL`. - **Notes:** it is an error before this connection has called `nextval` on that sequence. Another connection's numbers are never shown, so `currval` is safe to read after an insert. **Example: The order number just given** Setup: ```sql CREATE SEQUENCE order_no_seq START WITH 1001; CREATE TABLE orders (order_no LONG PRIMARY KEY, symbol SYMBOL); ``` ```sql INSERT INTO orders VALUES (nextval('order_no_seq'), 'CITY'); SELECT currval('order_no_seq') AS my_order_no; ``` Result: `INSERT INTO orders VALUES (nextval('order_no_seq'), 'CITY')` ```text 1 row(s) inserted ``` `SELECT currval('order_no_seq') AS my_order_no` | my_order_no (LONG) | |---| | 1001 | (1 row) **Example: currval before nextval is an error** (shows an error) Setup: ```sql CREATE SEQUENCE order_no_seq; ``` ```sql SELECT currval('order_no_seq'); ``` Result: Error: `currval of sequence 'order_no_seq' is not yet defined in this session (call nextval first)` ## lastval Syntax: ```sql lastval() ``` The number this connection got last from **any** sequence. - **Returns:** `LONG`. - **NULL:** never `NULL`. - **Notes:** it is an error before this connection has called `nextval` at all. With more than one sequence, `currval('name')` says clearly which one you mean. **Example: The last number of any sequence** Setup: ```sql CREATE SEQUENCE order_no_seq START WITH 1001; CREATE SEQUENCE invoice_no_seq START WITH 500; ``` ```sql SELECT nextval('order_no_seq') AS order_no, nextval('invoice_no_seq') AS invoice_no; SELECT lastval() AS last_of_any, currval('order_no_seq') AS last_order_no; ``` Result: `SELECT nextval('order_no_seq') AS order_no, nextval('invoice_no_seq') AS invoice_no` | order_no (LONG) | invoice_no (LONG) | |---|---| | 1001 | 500 | (1 row) `SELECT lastval() AS last_of_any, currval('order_no_seq') AS last_order_no` | last_of_any (LONG) | last_order_no (LONG) | |---|---| | 500 | 1001 | (1 row) ## setval Syntax: ```sql setval('sequence_name', value) setval('sequence_name', value, is_called) ``` Sets the sequence to `value`. Use it after loading old rows, so new numbers start above them. | Argument | Type | Meaning | |---|---|---| | `sequence_name` | `STRING` | the sequence name in single quotes | | `value` | `LONG` | the number to set | | `is_called` | `BOOL` | `TRUE` (default): `value` counts as already given, so the next `nextval` is `value + 1`. `FALSE`: the next `nextval` gives `value` itself. | - **Returns:** `LONG`, the `value` you gave. - **NULL:** a `NULL` name or a `NULL` value gives `NULL` and changes nothing. - **Notes:** a value outside the sequence's `MINVALUE`/`MAXVALUE` is an error. `setval` is written to disk at once; a `ROLLBACK` does not take it back. **Example: Move the counter above the order numbers already loaded** Setup: ```sql CREATE SEQUENCE order_no_seq START WITH 1; CREATE TABLE orders (order_no LONG PRIMARY KEY, symbol SYMBOL); INSERT INTO orders VALUES (5000, 'CITY'), (5001, 'COAST'); ``` ```sql SELECT setval('order_no_seq', (SELECT max(order_no) FROM orders)) AS set_to; INSERT INTO orders VALUES (nextval('order_no_seq'), 'VOLT'); SELECT * FROM orders ORDER BY order_no; ``` Result: `SELECT setval('order_no_seq', (SELECT max(order_no) FROM orders)) AS set_to` | set_to (LONG) | |---| | 5001 | (1 row) `INSERT INTO orders VALUES (nextval('order_no_seq'), 'VOLT')` ```text 1 row(s) inserted ``` `SELECT * FROM orders ORDER BY order_no` | order_no (LONG) | symbol (SYMBOL) | |---|---| | 5000 | CITY | | 5001 | COAST | | 5002 | VOLT | (3 rows) **Example: is_called = FALSE: the next number is the one you gave** Setup: ```sql CREATE SEQUENCE order_no_seq; ``` ```sql SELECT setval('order_no_seq', 7000, FALSE) AS set_to; SELECT nextval('order_no_seq') AS next_number; ``` Result: `SELECT setval('order_no_seq', 7000, FALSE) AS set_to` | set_to (LONG) | |---| | 7000 | (1 row) `SELECT nextval('order_no_seq') AS next_number` | next_number (LONG) | |---| | 7000 | (1 row) ## Names and sizes: to_regclass, pg_relation_size, ... Syntax: ```sql to_regclass('name') pg_relation_size(relation [, 'main']) pg_table_size(relation) pg_indexes_size(relation) pg_total_relation_size(relation) pg_database_size(name_or_oid) pg_size_pretty(bytes) current_database() ``` | Function | Returns | |---|---| | `to_regclass('name')` | the name of the table, index, view or sequence, or `NULL` when there is none | | `pg_relation_size`, `pg_table_size`, `pg_total_relation_size` | `LONG` bytes of the table on disk | | `pg_indexes_size` | `LONG`, always `0` | | `pg_database_size` | `LONG` bytes of every file of the database: tables, the log, kept versions | | `pg_size_pretty(bytes)` | text like `8192 bytes`, `24 kB`, `1536 MB`, exactly as PostgreSQL writes it | | `current_database()` | the database's name | - **Arguments:** a relation is a name in single quotes (`'public.orders'` and `'"Orders"'` work) or an oid from `pg_class`. - **Returns:** a table's size is its partition files plus its new rows that are still in the log (they move into the files at the next checkpoint), so a table that was just filled is never `0`. - **NULL:** a `NULL` argument, or an oid nothing has, gives `NULL`. A name that does not exist is an error (`42P01`), as in PostgreSQL. - **Notes:** KarelaDB keeps indexes in memory only (built from the rows when the database opens), so they take `0` bytes on disk. `pg_database_size` measures only the database the statement runs in. - **Waits:** a size reads the table the way a query of it does. `pg_total_relation_size('t')` waits for a statement that is writing `t` (never for writers of other tables), even with snapshot reads on. With an oid or a column instead of a name in quotes - `pg_total_relation_size(c.oid)` over `pg_class` - the size waits for every writer, as `SHOW TABLES` does. **Example: How big are the tables?** Setup: ```sql CREATE TABLE orders (id LONG PRIMARY KEY, symbol SYMBOL, quantity LONG); INSERT INTO orders VALUES (1, 'CITY', 100), (2, 'VOLT', 50); ``` ```sql SELECT to_regclass('orders') AS found, to_regclass('orderz') AS missing, pg_total_relation_size('orders') > 0 AS has_bytes, pg_size_pretty(10240) AS ten_kb, pg_size_pretty(1610612736) AS big; ``` Result: | found (STRING) | missing (STRING) | has_bytes (BOOL) | ten_kb (STRING) | big (STRING) | |---|---|---|---|---| | orders | NULL | true | 10 kB | 1536 MB | (1 row) ## Who and where: current_user, version() Syntax: ```sql current_user current_role user session_user current_schema current_schemas(include_implicit) current_catalog version() ``` | Function | Returns | |---|---| | `current_user`, `current_role`, `user` | the user whose rights your statements use: after `SET ROLE` that user, else who logged in | | `session_user` | who logged in, whatever `SET ROLE` did | | `current_schema`, `current_schema()` | `public`: every table is in it | | `current_schemas(false)` | `{public}`; with `true` also `pg_catalog`, which is searched first | | `current_catalog` | the database's name, like `current_database()` | | `version()` | `PostgreSQL 14.0 (KDB ...), compatible server` - drivers read the `14` | - The same answers over HTTP [`/exec`](http-exec.md) and the [Postgres port](postgres-protocol.md). With an [API token](api-tokens.md) you are its user. - **No user:** with the server's `--token` or a local tool (`kareladb sql`) there is no user; the name is `kareladb`. On the Postgres port of a server without users it is the name the client connected as. - Write the first four without brackets, as SQL does. A column of that name wins: `SELECT user FROM _audit_log` reads the column. **Example: Who am I, and on what?** ```sql SELECT current_user, session_user, current_schema, current_catalog, version() LIKE 'PostgreSQL 14%' AS pg14; ``` Result: | current_user (STRING) | session_user (STRING) | current_schema (STRING) | current_catalog (STRING) | pg14 (BOOL) | |---|---|---|---|---| | kdb | kdb | public | demo | true | (1 row) ## Rights: has_table_privilege and friends Syntax: ```sql has_table_privilege([user,] table, privilege) has_column_privilege([user,] table, column, privilege) has_database_privilege([user,] database, privilege) has_schema_privilege([user,] schema, privilege) pg_has_role([user,] role, privilege) ``` `TRUE` when the user may do it now: the same rules the statements are checked with - the user's [role](users-and-roles.md), changed by [GRANT / REVOKE ... ON TABLE](table-rights.md). | Function | Privileges | `TRUE` when | |---|---|---| | `has_table_privilege` | `SELECT`, `INSERT`, `UPDATE`, `DELETE`, `TRUNCATE`, `REFERENCES`, `TRIGGER` | `SELECT` needs role `read`, the changes `write` (`TRUNCATE` counts as `DELETE`), each changed by the table's grants; `REFERENCES` and `TRIGGER` need `ddl` | | `has_column_privilege` | `SELECT`, `INSERT`, `UPDATE`, `REFERENCES` | as for the table: KarelaDB has no rights per column | | `has_database_privilege` | `CONNECT`, `TEMPORARY` (`TEMP`), `CREATE` | any role on that database; `CREATE` needs `ddl` | | `has_schema_privilege` | `USAGE`, `CREATE` | `USAGE` of `public` needs a role; `pg_catalog` and `information_schema` are open to all. `CREATE` in `public` needs `ddl` | | `pg_has_role` | `MEMBER`, `USAGE` | the role is the user, or one of `read`, `write`, `ddl`, `admin` the user has here | - **user:** a user name; without it, the current user. `public` is PostgreSQL's everyone and has no rights. A user whose own `ROLE` is `admin` has every right, as a PostgreSQL superuser. - **privilege:** one word or a list, `'SELECT, INSERT'`: `TRUE` when any of them is allowed. `... WITH GRANT OPTION` is `TRUE` only for an admin: KarelaDB has no grant options. - **Objects:** a name (`'orders'`, `'public.orders'`) or an oid from `pg_class` or `pg_database`. An oid nothing has gives `NULL`; a name that does not exist is an error. - **Errors**, as PostgreSQL: `22023` unrecognized privilege type, `42704` a user that does not exist, `42P01` a table, `42703` a column, `3D000` a database, `3F000` a schema. **Example: What a read-only user may do** Setup: ```sql CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin; CREATE USER charts WITH PASSWORD 'Chart-app-secret-11' ROLE read; CREATE TABLE orders (id LONG PRIMARY KEY, quantity LONG); ``` ```sql SELECT has_table_privilege('charts', 'orders', 'SELECT') AS reads, has_table_privilege('charts', 'orders', 'INSERT') AS writes, has_schema_privilege('charts', 'public', 'CREATE') AS creates, pg_has_role('charts', 'read', 'MEMBER') AS is_reader; ``` Result: | reads (BOOL) | writes (BOOL) | creates (BOOL) | is_reader (BOOL) | |---|---|---|---| | true | false | false | true | (1 row) **Example: A privilege PostgreSQL does not have** (shows an error) Setup: ```sql CREATE TABLE orders (id LONG PRIMARY KEY); ``` ```sql SELECT has_table_privilege('orders', 'DROP'); ``` Result: Error: `line 1, column 8: has_table_privilege: unrecognized privilege type: "DROP"` ## Comments and definitions: COMMENT ON, obj_description, pg_get_indexdef Syntax: ```sql COMMENT ON TABLE table IS 'text' | NULL COMMENT ON COLUMN table.column IS 'text' | NULL obj_description(table [, 'pg_class']) col_description(table, column_number) pg_get_indexdef(index [, column, pretty]) pg_get_constraintdef(constraint_oid [, pretty]) pg_get_viewdef(view [, pretty]) format_type(type_oid, typmod) pg_table_is_visible(relation) pg_get_expr(text, relation) ``` `COMMENT ON` keeps a description with a table or a column. It stays after a restart, and `\d+` in psql shows it. `IS NULL` (or `IS ''`) removes it. It needs role `ddl`, like `ALTER TABLE`. | Function | Returns | |---|---| | `obj_description(t, 'pg_class')` | the table's comment, `NULL` when none | | `col_description(t, n)` | the comment of column `n` (1 = the first), `NULL` when none | | `pg_get_indexdef(i)` | the `CREATE INDEX` line of an index (also of `t_pkey`); with a column number, that key only | | `pg_get_constraintdef(oid)` | `PRIMARY KEY (id)`, `CHECK (...)`, `FOREIGN KEY ... REFERENCES ...`, by the oid in `pg_constraint` | | `pg_get_viewdef(v)` | the view's `SELECT`; `NULL` for a table | | `format_type(oid, typmod)` | the type as PostgreSQL writes it: `integer`, `character varying(45)`, `numeric(10,2)` | | `pg_table_is_visible(oid)` | `TRUE`: everything is in `public` | - `COMMENT ON` other objects (an index, a function, a schema) is accepted and says it is not kept. A view is not a table: `COMMENT ON TABLE v` is error `42809`. - `pg_get_partkeydef` and `pg_get_functiondef` give `NULL`: KarelaDB tables are not PostgreSQL partitioned tables and `pg_proc` lists no functions. `pg_get_serial_sequence('t', 'id')` gives `public.t_id_seq` for a `SERIAL` column: that is the name of the column's counter, which `nextval`, `currval`, `setval`, `lastval` and `ALTER SEQUENCE ... RESTART WITH n` take, so a `pg_dump` restore that ends with `SELECT setval('t_id_seq', max)` works as it is. The counter only goes up: a `setval` below the ids already in the table means their largest + 1. - Over HTTP, `'orders'::regclass` stays the name `'orders'`; these functions take the name as well as an oid. **Example: Describe a table and a column** Setup: ```sql CREATE TABLE orders (id LONG PRIMARY KEY, quantity LONG); ``` ```sql COMMENT ON TABLE orders IS 'one row per order'; COMMENT ON COLUMN orders.quantity IS 'shares, never negative'; SELECT obj_description('orders'::regclass, 'pg_class') AS about_table, col_description('orders'::regclass, 2) AS about_quantity; ``` Result: `COMMENT ON TABLE orders IS 'one row per order'` ```text COMMENT ``` `COMMENT ON COLUMN orders.quantity IS 'shares, never negative'` ```text COMMENT ``` `SELECT obj_description(CAST('orders' AS STRING), 'pg_class') AS about_table, col_description(CAST('o` | about_table (STRING) | about_quantity (STRING) | |---|---| | one row per order | shares, never negative | (1 row) **Example: The SQL behind an index, a key and a type** Setup: ```sql CREATE TABLE orders (id LONG PRIMARY KEY, symbol VARCHAR(12)); CREATE INDEX orders_symbol ON orders (symbol); ``` ```sql SELECT pg_get_indexdef('orders_symbol'::regclass) AS index_sql, pg_get_indexdef('orders_pkey'::regclass) AS key_sql, format_type(1043, 16) AS symbol_type; ``` Result: | index_sql (STRING) | key_sql (STRING) | symbol_type (STRING) | |---|---|---| | CREATE INDEX orders_symbol ON public.orders USING btree (symbol) | CREATE UNIQUE INDEX orders_pkey ON public.orders USING btree (id) | character varying(12) | (1 row) ## Advisory locks Syntax: ```sql pg_advisory_lock(key) take a lock, wait if someone else has it pg_try_advisory_lock(key) take it only if it is free (TRUE / FALSE) pg_advisory_unlock(key) give it back ``` An **advisory lock** is a name your app agrees on. It locks **no table and no row** - it only stops two copies of your program from doing the same job at the same time. Say your app closes the trading day at 15:05: it reads the trade log and writes the daily candles. If two copies of the app run, the job must run once. Each copy asks for the lock `918273`; the one that gets it does the job, the other one skips it. | Part | Meaning | |---|---| | `key` | the lock name as a whole number (`BIGINT`), for example `918273` | | `key1, key2` | two `INT` numbers instead of one; they are a **different** set of names (so `(1, 2)` and the single number 1 never meet) | | `_shared` | many holders at the same time (readers); a lock without `_shared` waits until no one holds the key | | `_xact` | the lock ends by itself with the transaction | | `pg_try_*` | never waits: `TRUE` when the lock was taken, `FALSE` when someone else has it | - Locks of **different databases** never meet, even with the same number. - A session lock is held until it is given back **as many times as it was taken**, or until the connection closes. Over HTTP, a statement without a [session](http-sessions-and-cursors.md#post-session) is its own session, so its locks end with the statement. - Waiting has a limit of **30 seconds** (PostgreSQL waits forever). After that the statement fails with a "locked" error; try again later. - A `SELECT pg_advisory_xact_lock(...)` sent on its own waits **without holding up the database**, also inside `BEGIN ... COMMIT`: other readers and writers go on, and the waiter gets the lock as soon as the holder commits. A lock call inside a procedure or a longer statement list waits in place. - On the Postgres port, `pg_try_*` and `pg_advisory_unlock*` are typed `bool` before they run, so a driver can read them into a boolean (Go: `bool`). - `SET statement_timeout` also ends the wait (see [protection](protection.md#statement-timeout)). **Example: Only one copy of the app closes the day** (sample data: sample) ```sql SELECT pg_try_advisory_lock(918273) AS i_do_the_job; SELECT count(*) AS trades_to_sum FROM trade_log; SELECT pg_advisory_unlock(918273) AS given_back; ``` Result: `SELECT pg_try_advisory_lock(918273) AS i_do_the_job` | i_do_the_job (BOOL) | |---| | true | (1 row) `SELECT count(*) AS trades_to_sum FROM trade_log` | trades_to_sum (LONG) | |---| | 32 | (1 row) `SELECT pg_advisory_unlock(918273) AS given_back` | given_back (BOOL) | |---| | true | (1 row) ## pg_advisory_lock, pg_advisory_lock_shared Syntax: ```sql pg_advisory_lock(key) pg_advisory_lock(key1, key2) pg_advisory_lock_shared(key) pg_advisory_lock_shared(key1, key2) ``` Takes a **session** lock and waits until it is free. The lock is held until `pg_advisory_unlock` gives it back, or the connection closes. | Argument | Type | Meaning | |---|---|---| | `key` | `LONG` | the lock name as one whole number | | `key1`, `key2` | `INT` | the lock name as two numbers (a different set of names) | - **Returns:** nothing (PostgreSQL calls this `void`; KarelaDB shows an empty value). - **NULL:** a `NULL` key gives `NULL` and takes no lock. - **Notes:** `_shared` lets several connections hold the same key at once; a lock without `_shared` waits for all of them. Taking the same key twice in one connection works and needs two unlocks. Waiting stops after 30 seconds with an error. **Example: Take the day-close lock, do the work, give it back** (sample data: sample) ```sql SELECT pg_advisory_lock(918273) AS taken; SELECT symbol, sum(quantity) AS shares FROM trade_log GROUP BY symbol ORDER BY symbol; SELECT pg_advisory_unlock(918273) AS given_back; ``` Result: `SELECT pg_advisory_lock(918273) AS taken` | taken (STRING) | |---| | | (1 row) `SELECT symbol, sum(quantity) AS shares FROM trade_log GROUP BY symbol ORDER BY symbol` | symbol (SYMBOL) | shares (LONG) | |---|---| | CITY | 2420 | | COAST | 3070 | | TELCO | 2770 | | VOLT | 400 | (4 rows) `SELECT pg_advisory_unlock(918273) AS given_back` | given_back (BOOL) | |---| | true | (1 row) ## pg_advisory_xact_lock, pg_advisory_xact_lock_shared Syntax: ```sql pg_advisory_xact_lock(key) pg_advisory_xact_lock(key1, key2) pg_advisory_xact_lock_shared(key) pg_advisory_xact_lock_shared(key1, key2) ``` The same, but the lock ends **with the transaction**. There is no unlock function for it. | Argument | Type | Meaning | |---|---|---| | `key` | `LONG` | the lock name as one whole number | | `key1`, `key2` | `INT` | the lock name as two numbers | - **Returns:** nothing (`void`). - **NULL:** a `NULL` key gives `NULL` and takes no lock. - **Notes:** `COMMIT` and `ROLLBACK` free it. A statement outside `BEGIN ... COMMIT` is its own transaction, so the lock ends with that statement. This is the safest kind: a crash in the middle cannot leave the lock behind. **Example: The lock is gone after COMMIT** (sample data: sample) Setup: ```sql BEGIN; SELECT pg_advisory_xact_lock(918273) AS taken_for_this_transaction; COMMIT; ``` ```sql SELECT pg_try_advisory_lock(918273) AS free_again; ``` Result: | free_again (BOOL) | |---| | true | (1 row) ## pg_try_advisory_lock, pg_try_advisory_lock_shared Syntax: ```sql pg_try_advisory_lock(key) pg_try_advisory_lock(key1, key2) pg_try_advisory_lock_shared(key) pg_try_advisory_lock_shared(key1, key2) ``` Takes a **session** lock only if it is free right now. It never waits. | Argument | Type | Meaning | |---|---|---| | `key` | `LONG` | the lock name as one whole number | | `key1`, `key2` | `INT` | the lock name as two numbers | - **Returns:** `BOOL`. `TRUE` the lock is yours, `FALSE` someone else holds it. - **NULL:** a `NULL` key gives `NULL`. - **Notes:** this is the one to use for "do this job only if nobody else is doing it". Remember to call `pg_advisory_unlock` with the same key when the job is done. **Example: Skip the job when another copy already has the lock** (sample data: sample) ```sql SELECT pg_try_advisory_lock(918273) AS first_call, pg_try_advisory_lock(918273) AS same_connection_again; SELECT pg_advisory_unlock(918273) AS unlock_1, pg_advisory_unlock(918273) AS unlock_2; ``` Result: `SELECT pg_try_advisory_lock(918273) AS first_call, pg_try_advisory_lock(918273) AS same_connection_a` | first_call (BOOL) | same_connection_again (BOOL) | |---|---| | true | true | (1 row) `SELECT pg_advisory_unlock(918273) AS unlock_1, pg_advisory_unlock(918273) AS unlock_2` | unlock_1 (BOOL) | unlock_2 (BOOL) | |---|---| | true | true | (1 row) ## pg_try_advisory_xact_lock, pg_try_advisory_xact_lock_shared Syntax: ```sql pg_try_advisory_xact_lock(key) pg_try_advisory_xact_lock(key1, key2) pg_try_advisory_xact_lock_shared(key) pg_try_advisory_xact_lock_shared(key1, key2) ``` Takes a **transaction** lock only if it is free right now. It never waits, and it ends with the transaction. | Argument | Type | Meaning | |---|---|---| | `key` | `LONG` | the lock name as one whole number | | `key1`, `key2` | `INT` | the lock name as two numbers | - **Returns:** `BOOL`. `TRUE` the lock is yours until the transaction ends, `FALSE` someone else holds it. - **NULL:** a `NULL` key gives `NULL`. - **Notes:** nothing to unlock: `COMMIT` or `ROLLBACK` frees it. **Example: Take it only if free, inside a transaction** (sample data: sample) ```sql BEGIN; SELECT pg_try_advisory_xact_lock(918273) AS got_it; COMMIT; ``` Result: `BEGIN` ```text transaction started ``` `SELECT pg_try_advisory_xact_lock(918273) AS got_it` | got_it (BOOL) | |---| | true | (1 row) `COMMIT` ```text transaction committed: 0 change(s) saved ``` ## pg_advisory_unlock, pg_advisory_unlock_shared Syntax: ```sql pg_advisory_unlock(key) pg_advisory_unlock(key1, key2) pg_advisory_unlock_shared(key) pg_advisory_unlock_shared(key1, key2) ``` Gives back one **session** lock this connection holds. | Argument | Type | Meaning | |---|---|---| | `key` | `LONG` | the lock name as one whole number | | `key1`, `key2` | `INT` | the lock name as two numbers | - **Returns:** `BOOL`. `TRUE` a lock was given back, `FALSE` this connection did not hold that key. - **NULL:** a `NULL` key gives `NULL`. - **Notes:** use `_shared` for a lock taken with `pg_advisory_lock_shared`. A key taken twice needs two unlocks. Transaction locks (`_xact`) cannot be unlocked this way; they end with the transaction. **Example: Unlocking a key you do not hold gives FALSE** (sample data: sample) ```sql SELECT pg_try_advisory_lock(918273) AS taken; SELECT pg_advisory_unlock(918273) AS given_back, pg_advisory_unlock(918273) AS again, pg_advisory_unlock(55) AS never_held; ``` Result: `SELECT pg_try_advisory_lock(918273) AS taken` | taken (BOOL) | |---| | true | (1 row) `SELECT pg_advisory_unlock(918273) AS given_back, pg_advisory_unlock(918273) AS again, pg_advisory_un` | given_back (BOOL) | again (BOOL) | never_held (BOOL) | |---|---|---| | true | false | false | (1 row) ## pg_advisory_unlock_all Syntax: ```sql pg_advisory_unlock_all() ``` Gives back **every** session lock this connection holds. - **Returns:** nothing (`void`). - **NULL:** never `NULL`. - **Notes:** transaction locks are not touched; they end with the transaction. A closing connection does this by itself, so you rarely need to call it. **Example: Clean up after a job** (sample data: sample) ```sql SELECT pg_advisory_lock(918273) AS lock_1; SELECT pg_advisory_lock(918274) AS lock_2; SELECT pg_advisory_unlock_all() AS all_given_back; SELECT pg_try_advisory_lock(918273) AS free_again; ``` Result: `SELECT pg_advisory_lock(918273) AS lock_1` | lock_1 (STRING) | |---| | | (1 row) `SELECT pg_advisory_lock(918274) AS lock_2` | lock_2 (STRING) | |---| | | (1 row) `SELECT pg_advisory_unlock_all() AS all_given_back` | all_given_back (STRING) | |---| | | (1 row) `SELECT pg_try_advisory_lock(918273) AS free_again` | free_again (BOOL) | |---| | true | (1 row) ## Session settings: current_setting, set_config Syntax: ```sql current_setting(name) the value, an error when it was never set current_setting(name, missing_ok) the value, or NULL when missing_ok is TRUE set_config(name, value, is_local) sets it and returns the value SET name = 'value' the same as set_config(name, value, FALSE) (Postgres port) SET LOCAL name = 'value' the same as set_config(name, value, TRUE) (Postgres port) ``` A **setting** is a named text value your connection keeps. Apps use names of their own with a dot in them, like `app.allow_edit`, to tell a [trigger](triggers.md) what the next statement may do. | Part | Meaning | |---|---| | `name` | the setting's name, any case; your own names have a dot: `nchartpro.allow_identity_update` | | `value` | text | | `is_local` | `TRUE`: only until the transaction ends (`COMMIT` or `ROLLBACK`); `FALSE`: until the connection closes | | `missing_ok` | `TRUE`: a name never set gives `NULL` instead of an error | - A value belongs to **one connection** (over HTTP, one [session](http-sessions-and-cursors.md#post-session)). Another connection never sees it. - Triggers and procedures that your statement sets off see your values. Triggers made `DEFERRED` run later, outside your connection, so they do not. - A `LOCAL` value outside `BEGIN ... COMMIT` lasts for the call only, as each call is its own transaction. On the Postgres port, `SET LOCAL` outside a transaction does nothing and sends warning `25P01`, as PostgreSQL does. - After a `LOCAL` value ends, the name reads `''` (empty text), as in PostgreSQL. - Built-in names answer too: `current_setting('TimeZone')` is the session's zone (`UTC` unless `SET TIME ZONE` or `kareladb serve --timezone` names one). **Example: Set a value, read it back, and ask for one never set** (sample data: sample) ```sql SELECT set_config('app.allow_edit', 'on', TRUE) AS set_to, current_setting('app.allow_edit') AS read_back, current_setting('app.never_set', TRUE) AS missing, current_setting('TimeZone') AS time_zone; ``` Result: | set_to (STRING) | read_back (STRING) | missing (STRING) | time_zone (STRING) | |---|---|---|---| | on | on | NULL | UTC | (1 row) A trigger that lets a DBA through only when the statement asked for it: ```sql CREATE FUNCTION guard_email() RETURNS trigger AS $$ BEGIN IF current_setting('app.allow_edit', true) = 'on' THEN RETURN NEW; END IF; IF NEW.email IS DISTINCT FROM OLD.email THEN RAISE EXCEPTION 'email is write-once'; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; -- the DBA, on the Postgres port: BEGIN; SET LOCAL app.allow_edit = 'on'; UPDATE users SET email = 'new@x.com' WHERE id = 7; COMMIT; -- the setting ends here ```