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 KDB 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), JSON functions, Text functions, Date and time functions, Constraints, Transactions
A PostgreSQL app sends more than plain SQL. It also calls arrays, sequences and advisory locks. KDB 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']. KDB keeps an array as a JSON list, so aJSONcolumn 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.
nextvalgives the next one. SeeCREATE SEQUENCEin constraints 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, the time ones in date and time, initcap, md5, to_str and string_to_array in text functions.
Example: A watchlist as one array value (sample data: sample)
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 KDB
Syntax:
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. KDB 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/FALSEandNULLmixed together. KDB 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
JSONcolumn keeps JSON only. To save a PostgreSQL array text in one, read it first:string_to_array(t, ','), orarray_cat(t, ARRAY[]). - There are no arrays of arrays with their own functions.
array_length(a, 2)(a second dimension) always givesNULL. - To store a list, use a
JSONcolumn. To take a list apart into rows, use unnest.
Example: A watchlist column an app reads and writes (sample data: sample)
Setup:
CREATE TABLE watchlists (user_id INT PRIMARY KEY, symbols JSON);
INSERT INTO watchlists VALUES (1, '["CITY","COAST"]'), (2, array_cat('{VOLT,ACME}', ARRAY[]));
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:
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). ANULLvalue is added as JSONnull. - Notes: PostgreSQL writes this as
array || value. In KDB||joins text, so use the function name.
Example: Add a company to a user's watchlist (sample data: sample)
Setup:
CREATE TABLE watchlists (user_id INT PRIMARY KEY, symbols JSON);
INSERT INTO watchlists VALUES (1, '["CITY","COAST"]');
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
1 row(s) updated
SELECT user_id, symbols FROM watchlists
| user_id (INT) | symbols (JSON) |
|---|---|
| 1 | ["CITY","COAST","VOLT"] |
(1 row)
array_prepend
Syntax:
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
NULLarray counts as empty. ANULLvalue is put in front as JSONnull.
Example (sample data: sample)
SELECT array_prepend('ACME', ARRAY['CITY', 'COAST']) AS watchlist;
Result:
| watchlist (JSON) |
|---|
| ["ACME","CITY","COAST"] |
(1 row)
array_cat
Syntax:
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. OneNULLcounts as an empty list.
Example: One watchlist for two users (sample data: sample)
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:
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)isNULL.array_remove(a, NULL)takes thenullelements out.
Example: A user removes a company from the watchlist (sample data: sample)
Setup:
CREATE TABLE watchlists (user_id INT PRIMARY KEY, symbols JSON);
INSERT INTO watchlists VALUES (1, '["CITY","COAST","CITY"]');
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
1 row(s) updated
SELECT user_id, symbols FROM watchlists
| user_id (INT) | symbols (JSON) |
|---|---|
| 1 | ["COAST"] |
(1 row)
array_position
Syntax:
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, orNULLwhen the value is not there. - NULL: a
NULLarray givesNULL.array_position(a, NULL)finds anullelement.
Example: Where is VOLT in the watchlist? (sample data: sample)
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:
array_length(array, dimension)
array_upper(array, dimension)
How many elements the array has. dimension must be 1; KDB 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; usecardinalityfor0). ANULLarray, or a dimension that is not1, also givesNULL.
Example (sample data: sample)
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:
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)isNULL. An empty array gives0, notNULL. - Notes: this is the safe one to use in
WHERE:cardinality(symbols) > 0.
Example: How many companies each user follows (sample data: sample)
Setup:
CREATE TABLE watchlists (user_id INT PRIMARY KEY, symbols JSON);
INSERT INTO watchlists VALUES (1, '["CITY","COAST"]'), (2, '[]'), (3, NULL);
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:
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
NULLarray or aNULLseparator givesNULL.
Example: A watchlist in one line for an email (sample data: sample)
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:
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
NULLarray 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)
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)
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:
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
NULLname givesNULL. - 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 AUTOis easier (see constraints).
Example: Order numbers from a sequence
Setup:
CREATE SEQUENCE order_no_seq START WITH 1001;
CREATE TABLE orders (order_no LONG PRIMARY KEY, symbol SYMBOL, quantity LONG);
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',
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:
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
NULLname givesNULL. - Notes: it is an error before this connection has called
nextvalon that sequence. Another connection's numbers are never shown, socurrvalis safe to read after an insert.
Example: The order number just given
Setup:
CREATE SEQUENCE order_no_seq START WITH 1001;
CREATE TABLE orders (order_no LONG PRIMARY KEY, symbol SYMBOL);
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')
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:
CREATE SEQUENCE order_no_seq;
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:
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
nextvalat all. With more than one sequence,currval('name')says clearly which one you mean.
Example: The last number of any sequence
Setup:
CREATE SEQUENCE order_no_seq START WITH 1001;
CREATE SEQUENCE invoice_no_seq START WITH 500;
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:
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, thevalueyou gave. - NULL: a
NULLname or aNULLvalue givesNULLand changes nothing. - Notes: a value outside the sequence's
MINVALUE/MAXVALUEis an error.setvalis written to disk at once; aROLLBACKdoes not take it back.
Example: Move the counter above the order numbers already loaded
Setup:
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');
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')
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:
CREATE SEQUENCE order_no_seq;
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:
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 frompg_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
NULLargument, or an oid nothing has, givesNULL. A name that does not exist is an error (42P01), as in PostgreSQL. - Notes: KDB keeps indexes in memory only (built from the rows when the database opens), so they take
0bytes on disk.pg_database_sizemeasures 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 writingt(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)overpg_class- the size waits for every writer, asSHOW TABLESdoes.
Example: How big are the tables?
Setup:
CREATE TABLE orders (id LONG PRIMARY KEY, symbol SYMBOL, quantity LONG);
INSERT INTO orders VALUES (1, 'CITY', 100), (2, 'VOLT', 50);
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:
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
/execand the Postgres port. With an API token you are its user. - No user: with the server's
--tokenor a local tool (kdb sql) there is no user; the name iskdb. 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_logreads the column.
Example: Who am I, and on what?
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:
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, changed by GRANT / REVOKE ... ON TABLE.
| 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: KDB 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.
publicis PostgreSQL's everyone and has no rights. A user whose ownROLEisadminhas every right, as a PostgreSQL superuser. - privilege: one word or a list,
'SELECT, INSERT':TRUEwhen any of them is allowed.... WITH GRANT OPTIONisTRUEonly for an admin: KDB has no grant options. - Objects: a name (
'orders','public.orders') or an oid frompg_classorpg_database. An oid nothing has givesNULL; a name that does not exist is an error. - Errors, as PostgreSQL:
22023unrecognized privilege type,42704a user that does not exist,42P01a table,42703a column,3D000a database,3F000a schema.
Example: What a read-only user may do
Setup:
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);
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:
CREATE TABLE orders (id LONG PRIMARY KEY);
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:
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 ONother objects (an index, a function, a schema) is accepted and says it is not kept. A view is not a table:COMMENT ON TABLE vis error42809.pg_get_partkeydefandpg_get_functiondefgiveNULL: KDB tables are not PostgreSQL partitioned tables andpg_proclists no functions.pg_get_serial_sequence('t', 'id')givespublic.t_id_seqfor aSERIALcolumn: that is the name of the column's counter, whichnextval,currval,setval,lastvalandALTER SEQUENCE ... RESTART WITH ntake, so apg_dumprestore that ends withSELECT setval('t_id_seq', max)works as it is. The counter only goes up: asetvalbelow the ids already in the table means their largest + 1.- Over HTTP,
'orders'::regclassstays the name'orders'; these functions take the name as well as an oid.
Example: Describe a table and a column
Setup:
CREATE TABLE orders (id LONG PRIMARY KEY, quantity LONG);
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'
COMMENT
COMMENT ON COLUMN orders.quantity IS 'shares, never negative'
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:
CREATE TABLE orders (id LONG PRIMARY KEY, symbol VARCHAR(12));
CREATE INDEX orders_symbol ON orders (symbol);
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:
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 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 insideBEGIN ... 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_*andpg_advisory_unlock*are typedboolbefore they run, so a driver can read them into a boolean (Go:bool). SET statement_timeoutalso ends the wait (see protection).
Example: Only one copy of the app closes the day (sample data: sample)
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:
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; KDB shows an empty value). - NULL: a
NULLkey givesNULLand takes no lock. - Notes:
_sharedlets several connections hold the same key at once; a lock without_sharedwaits 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)
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:
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
NULLkey givesNULLand takes no lock. - Notes:
COMMITandROLLBACKfree it. A statement outsideBEGIN ... COMMITis 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:
BEGIN;
SELECT pg_advisory_xact_lock(918273) AS taken_for_this_transaction;
COMMIT;
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:
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.TRUEthe lock is yours,FALSEsomeone else holds it. - NULL: a
NULLkey givesNULL. - Notes: this is the one to use for "do this job only if nobody else is doing it". Remember to call
pg_advisory_unlockwith the same key when the job is done.
Example: Skip the job when another copy already has the lock (sample data: sample)
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:
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.TRUEthe lock is yours until the transaction ends,FALSEsomeone else holds it. - NULL: a
NULLkey givesNULL. - Notes: nothing to unlock:
COMMITorROLLBACKfrees it.
Example: Take it only if free, inside a transaction (sample data: sample)
BEGIN;
SELECT pg_try_advisory_xact_lock(918273) AS got_it;
COMMIT;
Result:
BEGIN
transaction started
SELECT pg_try_advisory_xact_lock(918273) AS got_it
| got_it (BOOL) |
|---|
| true |
(1 row)
COMMIT
transaction committed: 0 change(s) saved
pg_advisory_unlock, pg_advisory_unlock_shared
Syntax:
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.TRUEa lock was given back,FALSEthis connection did not hold that key. - NULL: a
NULLkey givesNULL. - Notes: use
_sharedfor a lock taken withpg_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)
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:
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)
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:
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 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). Another connection never sees it.
- Triggers and procedures that your statement sets off see your values. Triggers made
DEFERREDrun later, outside your connection, so they do not. - A
LOCALvalue outsideBEGIN ... COMMITlasts for the call only, as each call is its own transaction. On the Postgres port,SET LOCALoutside a transaction does nothing and sends warning25P01, as PostgreSQL does. - After a
LOCALvalue ends, the name reads''(empty text), as in PostgreSQL. - Built-in names answer too:
current_setting('TimeZone')is the session's zone (UTCunlessSET TIME ZONEorkdb serve --timezonenames one).
Example: Set a value, read it back, and ask for one never set (sample data: sample)
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:
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