KDB knowledge

Users and roles

Database users with passwords, and the roles read, write, ddl and admin for every database or for one.

Database users are the people and programs that connect to a running kdb serve: you in the web console, your app's backend, a report script. Each user has a password and a role (what it may do).

A small shop with three users: you (admin), the shop's backend (write on one database) and a report script (read everywhere).

Example: Three users for a small shop

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER shop_app WITH PASSWORD 'Another-long-secret-42';
GRANT write ON DATABASE demo TO shop_app;
CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read;
SHOW USERS;

Result:

CREATE USER admin WITH PASSWORD '***' ROLE admin

user 'admin' created with role admin

CREATE USER shop_app WITH PASSWORD '***'

user 'shop_app' created with role none

GRANT write ON DATABASE demo TO shop_app

user 'shop_app' has role write on database 'demo'

CREATE USER report WITH PASSWORD '***' ROLE read

user 'report' created with role read

SHOW USERS

name (STRING)role (STRING)grants (STRING)created (TIMESTAMP)changed (TIMESTAMP)table_grants (STRING)two_factor (BOOL)statement_timeout (STRING)priority (STRING)
adminadminNULL2026-10-22T05:44:39.234Z2026-10-22T05:44:39.234ZNULLfalseNULLNULL
reportreadNULL2026-10-22T05:44:39.366Z2026-10-22T05:44:39.366ZNULLfalseNULLNULL
shop_appnonedemo=write2026-10-22T05:44:39.299Z2026-10-22T05:44:39.301ZNULLfalseNULLNULL

(3 rows)

CREATE USER

Syntax:

CREATE USER [IF NOT EXISTS] name [WITH] PASSWORD 'secret' [ROLE admin | ddl | write | read | none]

Adds a database user with a password. PASSWORD and ROLE may come in any order.

PartMeaning
IF NOT EXISTSno error when the user is already there (nothing is changed then)
nameletters, digits, _ and -, 1 to 63 characters. Bob and bob are the same user.
PASSWORD 'secret'at least 12 characters, not the user name, at most 1024 bytes. Write '' for a ' inside. Required.
ROLEthe role on every database. Default none (nothing until a GRANT).

Example

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER IF NOT EXISTS admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER report ROLE read PASSWORD 'Reports-only-secret-7';

Result:

CREATE USER admin WITH PASSWORD '***' ROLE admin

user 'admin' created with role admin

CREATE USER IF NOT EXISTS admin WITH PASSWORD '***' ROLE admin

user 'admin' already exists

CREATE USER report WITH PASSWORD '***' ROLE read

user 'report' created with role read

Example: The first user must be an admin (shows an error)

CREATE USER shop_app WITH PASSWORD 'Another-long-secret-42';

Result:

Error: the first user must have ROLE admin (CREATE USER name WITH PASSWORD '...' ROLE admin): once a user exists the server asks for a login, and only an admin can manage users

Example: Too short a password (shows an error)

CREATE USER admin WITH PASSWORD 'nepal' ROLE admin;

Result:

Error: the password is too short: use at least 12 characters (a password manager can make a long random one)

Example: The same name in other letters is the same user (shows an error)

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER Admin WITH PASSWORD 'Another-long-secret-42' ROLE read;

Result:

Error: user 'admin' already exists

Example: A password is required (shows an error)

CREATE USER report ROLE read;

Result:

Error: line 1, column 29: CREATE USER needs a password, like CREATE USER report WITH PASSWORD '...' ROLE read

ALTER USER

Syntax:

ALTER USER name [WITH] PASSWORD 'new secret'
ALTER USER name ROLE admin | ddl | write | read | none
ALTER USER name [WITH] PASSWORD 'new secret' ROLE read

Changes the password, the role, or both.

PartMeaning
namean existing user
PASSWORD 'new secret'the new password (at least 12 characters, at most 1024 bytes)
ROLEthe new role on every database. Grants on single databases stay.

Example: A new password and a new role

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read;
ALTER USER report WITH PASSWORD 'New-report-secret-8';
ALTER USER report ROLE write;
SHOW USERS;

Result:

ALTER USER report WITH PASSWORD '***'

user 'report' changed: new password

ALTER USER report ROLE write

user 'report' changed: role write

SHOW USERS

name (STRING)role (STRING)grants (STRING)created (TIMESTAMP)changed (TIMESTAMP)table_grants (STRING)two_factor (BOOL)statement_timeout (STRING)priority (STRING)
adminadminNULL2026-10-22T05:44:39.719Z2026-10-22T05:44:39.719ZNULLfalseNULLNULL
reportwriteNULL2026-10-22T05:44:39.792Z2026-10-22T05:44:40.030ZNULLfalseNULLNULL

(2 rows)

Example: The last admin must stay an admin (shows an error)

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read;
ALTER USER admin ROLE read;

Result:

Error: 'admin' is the last user with ROLE admin; make another user admin first (ALTER USER <name> ROLE admin), or nobody could manage users any more

Example: Nothing to change (shows an error)

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
ALTER USER admin;

Result:

Error: line 1, column 17: ALTER USER needs PASSWORD '...' or ROLE read|write|ddl|admin

ALTER USER ... SET statement_timeout

Syntax:

ALTER USER name SET statement_timeout = '5s' | 5000 | 0 | DEFAULT
ALTER USER name RESET statement_timeout

Gives one user their own time limit for a statement. It takes the place of the server's --statement-timeout (60 s unless set) for that user only.

ValueMeaning
'2s', '500ms', '10min', 5000the time; a plain number is milliseconds
0no limit for this user
DEFAULT, or RESETthe server's limit again

Example: Short queries for the chart app, long ones for reports

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER charts WITH PASSWORD 'Chart-app-secret-11' ROLE read;
CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read;
ALTER USER charts SET statement_timeout = '2s';
ALTER USER report SET statement_timeout = '10min';
SHOW USERS;

Result:

ALTER USER charts SET statement_timeout = '2000ms'

statement_timeout of user 'charts' is 2 s (from their next statement)

ALTER USER report SET statement_timeout = '600000ms'

statement_timeout of user 'report' is 10 min (from their next statement)

SHOW USERS

name (STRING)role (STRING)grants (STRING)created (TIMESTAMP)changed (TIMESTAMP)table_grants (STRING)two_factor (BOOL)statement_timeout (STRING)priority (STRING)
adminadminNULL2026-10-22T05:44:40.103Z2026-10-22T05:44:40.103ZNULLfalseNULLNULL
chartsreadNULL2026-10-22T05:44:40.170Z2026-10-22T05:44:40.381ZNULLfalse2 sNULL
reportreadNULL2026-10-22T05:44:40.237Z2026-10-22T05:44:40.384ZNULLfalse10 minNULL

(3 rows)

Example: Back to the server's limit

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read;
ALTER USER report SET statement_timeout = '10min';
ALTER USER report RESET statement_timeout;

Result:

user 'report' has the server's statement_timeout again

Example: Only a time is accepted (shows an error)

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
ALTER USER admin SET statement_timeout = 'soon';

Result:

Error: line 1, column 42: statement_timeout needs a time like '30s', '500ms', '2min', 0 (no limit) or DEFAULT, not 'soon'

ALTER USER ... SET timezone

Syntax:

ALTER USER name SET timezone = 'Asia/Kathmandu' | 'America/New_York' | '+05:45' | DEFAULT
ALTER USER name SET TIME ZONE 'Asia/Kathmandu'
ALTER USER name RESET timezone

The clock one user's sessions start on: CURRENT_DATE, ::date, date_trunc, EXTRACT, to_char and a DATE met by a TIMESTAMP use it (time zones). The user's own SET TIME ZONE still changes it for a session. Users without one have the server's zone, which is the computer's own unless kdb serve --timezone names another.

Example: Asha sees days on the Nepal clock, the others on the server's

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER asha WITH PASSWORD 'Asha-Long-Passw0rd' ROLE read;
ALTER USER asha SET timezone = 'Asia/Kathmandu';

Result:

time zone of user 'asha' is Asia/Kathmandu (from their next session)

DROP USER

Syntax:

DROP USER [IF EXISTS] name

Removes a user and its grants. Its logins stop working at once.

PartMeaning
IF EXISTSno error when there is no such user
namethe user to remove

Example

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read;
DROP USER report;
DROP USER IF EXISTS report;
SHOW USERS;

Result:

DROP USER report

user 'report' dropped

DROP USER IF EXISTS report

user 'report' does not exist

SHOW USERS

name (STRING)role (STRING)grants (STRING)created (TIMESTAMP)changed (TIMESTAMP)table_grants (STRING)two_factor (BOOL)statement_timeout (STRING)priority (STRING)
adminadminNULL2026-10-22T05:44:40.692Z2026-10-22T05:44:40.692ZNULLfalseNULLNULL

(1 row)

Example: The last admin can not be dropped while others exist (shows an error)

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read;
DROP USER admin;

Result:

Error: 'admin' is the last user with ROLE admin; make another user admin first (ALTER USER <name> ROLE admin), or nobody could manage users any more

SHOW USERS

Syntax:

SHOW USERS

Lists the users with their roles and grants. It never shows passwords or hashes.

ColumnMeaning
namethe user name as it was created
rolethe role on every database
grantsthe roles on single databases, like shop=write, demo=read; NULL when there are none
createdwhen the user was made (UTC)
changedthe last change of password, role or grants (UTC)
table_grantstable rights, like shop.ledger: -DELETE; NULL when there are none
two_factortrue when the user logs in with a two-factor code
statement_timeoutthe user's own time limit, like 2 s or off; NULL = the server's

Example

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER shop_app WITH PASSWORD 'Another-long-secret-42' ROLE none;
CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read;
CREATE DATABASE shop;
GRANT write ON DATABASE shop TO shop_app;
GRANT read ON DATABASE demo TO shop_app;
SHOW USERS;

Result:

name (STRING)role (STRING)grants (STRING)created (TIMESTAMP)changed (TIMESTAMP)table_grants (STRING)two_factor (BOOL)statement_timeout (STRING)priority (STRING)
adminadminNULL2026-10-22T05:44:40.810Z2026-10-22T05:44:40.810ZNULLfalseNULLNULL
reportreadNULL2026-10-22T05:44:40.953Z2026-10-22T05:44:40.953ZNULLfalseNULLNULL
shop_appnonedemo=read, shop=write2026-10-22T05:44:40.877Z2026-10-22T05:44:40.985ZNULLfalseNULLNULL

(3 rows)

GRANT

Syntax:

GRANT read | write | ddl | admin | ALL ON DATABASE db TO name

Gives a user a role on one database. ALL is the same as admin.

PartMeaning
roleread, write, ddl, admin or ALL
dban existing database
namean existing user

Example: The app may write in its own database only

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER shop_app WITH PASSWORD 'Another-long-secret-42';
CREATE DATABASE shop;
GRANT write ON DATABASE shop TO shop_app;
GRANT ALL ON DATABASE demo TO shop_app;
SHOW USERS;

Result:

GRANT write ON DATABASE shop TO shop_app

user 'shop_app' has role write on database 'shop'

GRANT admin ON DATABASE demo TO shop_app

user 'shop_app' has role admin on database 'demo'

SHOW USERS

name (STRING)role (STRING)grants (STRING)created (TIMESTAMP)changed (TIMESTAMP)table_grants (STRING)two_factor (BOOL)statement_timeout (STRING)priority (STRING)
adminadminNULL2026-10-22T05:44:40.924Z2026-10-22T05:44:40.924ZNULLfalseNULLNULL
shop_appnonedemo=admin, shop=write2026-10-22T05:44:40.999Z2026-10-22T05:44:41.200ZNULLfalseNULLNULL

(2 rows)

Example: DROP DATABASE removes the grants on it

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER shop_app WITH PASSWORD 'Another-long-secret-42';
CREATE DATABASE shop;
GRANT write ON DATABASE shop TO shop_app;
DROP DATABASE shop;
SHOW USERS;

Result:

DROP DATABASE shop

database 'shop' dropped; its folder was moved to kdb-data/_trash/1792647881547-shop

SHOW USERS

name (STRING)role (STRING)grants (STRING)created (TIMESTAMP)changed (TIMESTAMP)table_grants (STRING)two_factor (BOOL)statement_timeout (STRING)priority (STRING)
adminadminNULL2026-10-22T05:44:41.285Z2026-10-22T05:44:41.285ZNULLfalseNULLNULL
shop_appnoneNULL2026-10-22T05:44:41.351Z2026-10-22T05:44:41.553ZNULLfalseNULLNULL

(2 rows)

Example: The database must exist (shows an error)

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER shop_app WITH PASSWORD 'Another-long-secret-42';
GRANT write ON DATABASE accounts TO shop_app;

Result:

Error: database 'accounts' does not exist

Example: none is not a role to grant: use REVOKE (shows an error)

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
GRANT none ON DATABASE demo TO admin;

Result:

Error: line 1, column 7: expected a role (read, write, ddl or admin, or ALL) in GRANT, found 'none'

REVOKE

Syntax:

REVOKE [read | write | ddl | admin | ALL] ON DATABASE db FROM name

Removes the user's grant on one database.

PartMeaning
roleoptional, and only for reading comfort: the whole grant on db goes, whatever role you write
dbthe database. It may already be gone.
namean existing user

Example

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER shop_app WITH PASSWORD 'Another-long-secret-42';
GRANT write ON DATABASE demo TO shop_app;
REVOKE ALL ON DATABASE demo FROM shop_app;
SHOW USERS;

Result:

REVOKE ALL ON DATABASE demo FROM shop_app

user 'shop_app' has no grant on database 'demo' now

SHOW USERS

name (STRING)role (STRING)grants (STRING)created (TIMESTAMP)changed (TIMESTAMP)table_grants (STRING)two_factor (BOOL)statement_timeout (STRING)priority (STRING)
adminadminNULL2026-10-22T05:44:41.537Z2026-10-22T05:44:41.537ZNULLfalseNULLNULL
shop_appnoneNULL2026-10-22T05:44:41.605Z2026-10-22T05:44:41.774ZNULLfalseNULLNULL

(2 rows)

Example: REVOKE does not touch the user's own ROLE

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read;
GRANT write ON DATABASE demo TO report;
REVOKE read ON DATABASE demo FROM report;
SHOW USERS;

Result:

REVOKE read ON DATABASE demo FROM report

user 'report' keeps role write on database 'demo': REVOKE read takes away only a read grant; use REVOKE write or REVOKE ALL. Nothing was revoked

SHOW USERS

name (STRING)role (STRING)grants (STRING)created (TIMESTAMP)changed (TIMESTAMP)table_grants (STRING)two_factor (BOOL)statement_timeout (STRING)priority (STRING)
adminadminNULL2026-10-22T05:44:41.895Z2026-10-22T05:44:41.895ZNULLfalseNULLNULL
reportreaddemo=write2026-10-22T05:44:41.963Z2026-10-22T05:44:41.965ZNULLfalseNULLNULL

(2 rows)

SET ROLE

Syntax:

SET ROLE name          -- from now on, statements use the rights of user `name`
SET ROLE NONE          -- back to the login user's rights
RESET ROLE             -- the same

An app that logs in as an admin (for its start-up migrations) can drop to the rights of its own user for everything else. The session keeps the role until RESET ROLE, as in PostgreSQL.

Who logged inMay take
the token, or a server without usersany user
a user whose own ROLE is adminany user
any other useronly itself

Example: An admin session takes the rights of a report user (shows an error)

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read;
SET ROLE report;
SELECT 1 AS reading_is_fine;
CREATE TABLE made_by_report (id INT);

Result:

statements now use the rights of user 'report' (RESET ROLE goes back)

Error: CREATE TABLE needs role ddl on database 'demo'; user 'report' has read

Example: RESET ROLE goes back to the login user's rights

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read;
SET ROLE report;
RESET ROLE;
CREATE TABLE made_after_reset (id INT);

Result:

SET role = 'report'

statements now use the rights of user 'report' (RESET ROLE goes back)

SET role = ''

back to the rights of the login user

CREATE TABLE made_after_reset (

table 'made_after_reset' created

Roles: what each one may do

Syntax:

none  <  read  <  write  <  ddl  <  admin

Each role may do everything of the roles before it.

RoleMay do
nonenothing (until a GRANT). It may still log in, change its own password and see its own row in SHOW USERS.
readSELECT, SHOW ..., EXPLAIN, USE, CSV export, cursors (DECLARE, FETCH, CLOSE), BEGIN / COMMIT / ROLLBACK / SAVEPOINT, /api/info, list backups
writealso INSERT, UPDATE, DELETE (with RETURNING), TRUNCATE, CALL of procedures, SELECT ... FOR UPDATE, line protocol (POST /write)
ddlalso the schema: CREATE / ALTER / DROP / RENAME of tables, views, indexes, sequences, functions, procedures and triggers, REFRESH VIEW, a new table with POST /imp. No users, no server settings, no backups, no audit log.
adminalso BACKUP, CHECK TABLE, SHOW AUDIT, ALTER TABLE ... SET AUDIT, ALTER DATABASE, TRUNCATE ALL TABLES, the trigger queue (RETRY / SKIP)

The HTTP routes that are not SQL need these roles:

NeedsRoutes
a login onlyGET /api/me, POST /api/feedback (send a comment)
read on the databaseGET /api/info, GET /api/disk, GET /api/backups, GET /api/stats, GET /api/trigger-queue, GET /api/timers, GET /api/pins, cursors
write on the databasePOST /write, POST /api/v2/write, POST /imp (ddl when it creates the table)
admin on the databasePOST /api/check, POST /api/backup, save, run, turn off or delete a timer, pin or unpin a statement
the user's own ROLE adminGET and POST /api/settings, POST /api/restart, POST and DELETE /api/databases, GET /api/feedback, POST /api/feedback/<id>, GET /api/audit (with kind=data: admin on that database is enough)

Two places give a role:

WhereForWritten
the user's own ROLEevery databaseCREATE USER ... ROLE read, ALTER USER ... ROLE read
a grantone databaseGRANT write ON DATABASE shop TO shop_app

Example: A report user that may read every database, and an app that may write one

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read;
CREATE USER shop_app WITH PASSWORD 'Another-long-secret-42' ROLE none;
GRANT write ON DATABASE demo TO shop_app;
SHOW USERS;

Result:

CREATE USER report WITH PASSWORD '***' ROLE read

user 'report' created with role read

CREATE USER shop_app WITH PASSWORD '***' ROLE none

user 'shop_app' created with role none

GRANT write ON DATABASE demo TO shop_app

user 'shop_app' has role write on database 'demo'

SHOW USERS

name (STRING)role (STRING)grants (STRING)created (TIMESTAMP)changed (TIMESTAMP)table_grants (STRING)two_factor (BOOL)statement_timeout (STRING)priority (STRING)
adminadminNULL2026-10-22T05:44:42.114Z2026-10-22T05:44:42.114ZNULLfalseNULLNULL
reportreadNULL2026-10-22T05:44:42.334Z2026-10-22T05:44:42.334ZNULLfalseNULLNULL
shop_appnonedemo=write2026-10-22T05:44:42.406Z2026-10-22T05:44:42.408ZNULLfalseNULLNULL

(3 rows)

Example: A migration user may change tables, not users (shows an error)

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER migrate WITH PASSWORD 'Migrations-only-secret-9' ROLE ddl;
SET ROLE migrate;
CREATE TABLE orders (id INT PRIMARY KEY, total DOUBLE);
ALTER TABLE orders ADD COLUMN placed TIMESTAMP;
CREATE INDEX orders_placed ON orders (placed);
INSERT INTO orders VALUES (1, 12.5, '2026-09-25T10:00:00Z');
CREATE USER intern WITH PASSWORD 'Intern-long-secret-3';

Result:

SET role = 'migrate'

statements now use the rights of user 'migrate' (RESET ROLE goes back)

CREATE TABLE orders (

table 'orders' created

ALTER TABLE orders ADD COLUMN placed TIMESTAMP

table 'orders' altered

CREATE INDEX orders_placed ON orders (placed)

index 'orders_placed' created on table 'orders' (0 rows)

INSERT INTO orders VALUES (1, 12.5, '2026-09-25T10:00:00Z')

1 row(s) inserted

Error: CREATE USER needs role admin as its own ROLE (it acts on the whole server); user 'migrate' has ddl

Example: A writer may change rows but not the schema (shows an error)

Setup:

CREATE USER admin WITH PASSWORD 'Long-Admin-Passw0rd' ROLE admin;
CREATE USER shop_app WITH PASSWORD 'Another-long-secret-42' ROLE write;
SET ROLE shop_app;
CREATE TABLE app_made (id INT);

Result:

statements now use the rights of user 'shop_app' (RESET ROLE goes back)

Error: CREATE TABLE needs role ddl on database 'demo'; user 'shop_app' has write

Where users are kept

Syntax:

<data folder>/_system/        the hidden database with the table users

Users belong to the data folder, not to one database. KDB keeps them in a hidden database named _system inside the data folder. All databases of that folder share the users.

Column of usersHolds
name, role, grantswhat SHOW USERS shows
password_hash$pbkdf2-sha256$i=600000$... (the same form as password_hash())
scramSCRAM-SHA-256$4096:... for PostgreSQL clients
created_at, changed_attimes

Example: _system is not a normal database (shows an error)

USE _system;

Result:

Error: line 1, column 5: bad database name '_system': '_system' is where KDB keeps its users

Command line: kdb user and SQL see the same users

kdb user add admin --role admin
> Long-Admin-Passw0rd
> Long-Admin-Passw0rd
kdb user list
kdb sql kdb-data/shop "CREATE TABLE orders (id INT PRIMARY KEY)"
kdb sql shop "CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read; SHOW USERS"

Output:

> kdb user add admin --role admin

user 'admin' added with role admin in ./kdb-data

> kdb user list

USER                     ROLE   GRANTS                         CHANGED
admin                    admin  -                              2026-10-22 11:29:42
1 user in ./kdb-data

> kdb sql kdb-data/shop "CREATE TABLE orders (id INT PRIMARY KEY)"

table 'orders' created

> kdb sql shop "CREATE USER report WITH PASSWORD 'Reports-only-secret-7' ROLE read; SHOW USERS"

user 'report' created with role read

name   | role  | grants | created                  | changed                  | table_grants | two_factor | statement_timeout | priority
-------+-------+--------+--------------------------+--------------------------+--------------+------------+-------------------+---------
admin  | admin | NULL   | 2026-10-22T05:44:42.569Z | 2026-10-22T05:44:42.569Z | NULL         | false      | NULL              | NULL    
report | read  | NULL   | 2026-10-22T05:44:43.043Z | 2026-10-22T05:44:43.043Z | NULL         | false      | NULL              | NULL    
(2 rows, 0.0 ms)

Here the data folder is kdb-data. The first kdb sql makes the database shop in it by its path. After that, the plain name shop means the database in the data folder, so the user statements work. A plain name that is not in the data folder is taken as a new folder in the current folder, and then user statements say users need a data folder.