feat: add psql shell and username-to-shell routing
Add a PostgreSQL psql interactive terminal shell with backslash meta-commands, SQL statement handling with multi-line buffering, and canned responses for common queries. Add username-based shell routing via [shell.username_routes] config (second priority after credential- specific shell, before random selection). Bump version to 0.13.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
155
internal/shell/psql/output.go
Normal file
155
internal/shell/psql/output.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package psql
|
||||
|
||||
import "fmt"
|
||||
|
||||
func startupBanner(version string) string {
|
||||
return fmt.Sprintf("psql (%s)\nType \"help\" for help.\n", version)
|
||||
}
|
||||
|
||||
func listTables() string {
|
||||
return ` List of relations
|
||||
Schema | Name | Type | Owner
|
||||
--------+---------------+-------+----------
|
||||
public | audit_log | table | postgres
|
||||
public | credentials | table | postgres
|
||||
public | sessions | table | postgres
|
||||
public | users | table | postgres
|
||||
(4 rows)`
|
||||
}
|
||||
|
||||
func listDatabases() string {
|
||||
return ` List of databases
|
||||
Name | Owner | Encoding | Collate | Ctype | Access privileges
|
||||
-----------+----------+----------+-------------+-------------+-----------------------
|
||||
app_db | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
|
||||
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
|
||||
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
|
||||
| | | | | postgres=CTc/postgres
|
||||
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
|
||||
| | | | | postgres=CTc/postgres
|
||||
(4 rows)`
|
||||
}
|
||||
|
||||
func listRoles() string {
|
||||
return ` List of roles
|
||||
Role name | Attributes | Member of
|
||||
-----------+------------------------------------------------------------+-----------
|
||||
app_user | | {}
|
||||
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
|
||||
readonly | Cannot login | {}`
|
||||
}
|
||||
|
||||
func describeTable(name string) string {
|
||||
switch name {
|
||||
case "users":
|
||||
return ` Table "public.users"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
------------+-----------------------------+-----------+----------+-----------------------------------
|
||||
id | integer | | not null | nextval('users_id_seq'::regclass)
|
||||
username | character varying(255) | | not null |
|
||||
email | character varying(255) | | not null |
|
||||
password | character varying(255) | | not null |
|
||||
created_at | timestamp without time zone | | | now()
|
||||
updated_at | timestamp without time zone | | | now()
|
||||
Indexes:
|
||||
"users_pkey" PRIMARY KEY, btree (id)
|
||||
"users_email_key" UNIQUE, btree (email)
|
||||
"users_username_key" UNIQUE, btree (username)`
|
||||
case "sessions":
|
||||
return ` Table "public.sessions"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
------------+-----------------------------+-----------+----------+--------------------------------------
|
||||
id | integer | | not null | nextval('sessions_id_seq'::regclass)
|
||||
user_id | integer | | |
|
||||
token | character varying(255) | | not null |
|
||||
ip_address | inet | | |
|
||||
created_at | timestamp without time zone | | | now()
|
||||
expires_at | timestamp without time zone | | not null |
|
||||
Indexes:
|
||||
"sessions_pkey" PRIMARY KEY, btree (id)
|
||||
"sessions_token_key" UNIQUE, btree (token)
|
||||
Foreign-key constraints:
|
||||
"sessions_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)`
|
||||
case "credentials":
|
||||
return ` Table "public.credentials"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
-----------+-----------------------------+-----------+----------+-----------------------------------------
|
||||
id | integer | | not null | nextval('credentials_id_seq'::regclass)
|
||||
user_id | integer | | |
|
||||
type | character varying(50) | | not null |
|
||||
value | text | | not null |
|
||||
created_at| timestamp without time zone | | | now()
|
||||
Indexes:
|
||||
"credentials_pkey" PRIMARY KEY, btree (id)
|
||||
Foreign-key constraints:
|
||||
"credentials_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)`
|
||||
case "audit_log":
|
||||
return ` Table "public.audit_log"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
------------+-----------------------------+-----------+----------+---------------------------------------
|
||||
id | integer | | not null | nextval('audit_log_id_seq'::regclass)
|
||||
user_id | integer | | |
|
||||
action | character varying(100) | | not null |
|
||||
details | text | | |
|
||||
ip_address | inet | | |
|
||||
created_at | timestamp without time zone | | | now()
|
||||
Indexes:
|
||||
"audit_log_pkey" PRIMARY KEY, btree (id)
|
||||
Foreign-key constraints:
|
||||
"audit_log_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id)`
|
||||
default:
|
||||
return fmt.Sprintf("Did not find any relation named \"%s\".", name)
|
||||
}
|
||||
}
|
||||
|
||||
func connInfo(dbName string) string {
|
||||
return fmt.Sprintf("You are connected to database \"%s\" as user \"postgres\" via socket in \"/var/run/postgresql\" at port \"5432\".", dbName)
|
||||
}
|
||||
|
||||
func backslashHelp() string {
|
||||
return `General
|
||||
\copyright show PostgreSQL usage and distribution terms
|
||||
\crosstabview [COLUMNS] execute query and display result in crosstab
|
||||
\errverbose show most recent error message at maximum verbosity
|
||||
\g [(OPTIONS)] [FILE] execute query (and send result to file or |pipe)
|
||||
\gdesc describe result of query, without executing it
|
||||
\gexec execute query, then execute each value in its result
|
||||
\gset [PREFIX] execute query and store result in psql variables
|
||||
\gx [(OPTIONS)] [FILE] as \g, but forces expanded output mode
|
||||
\q quit psql
|
||||
\watch [SEC] execute query every SEC seconds
|
||||
|
||||
Informational
|
||||
(options: S = show system objects, + = additional detail)
|
||||
\d[S+] list tables, views, and sequences
|
||||
\d[S+] NAME describe table, view, sequence, or index
|
||||
\da[S] [PATTERN] list aggregates
|
||||
\dA[+] [PATTERN] list access methods
|
||||
\dt[S+] [PATTERN] list tables
|
||||
\du[S+] [PATTERN] list roles
|
||||
\l[+] [PATTERN] list databases`
|
||||
}
|
||||
|
||||
func sqlHelp() string {
|
||||
return `Available help:
|
||||
ABORT CREATE LANGUAGE
|
||||
ALTER AGGREGATE CREATE MATERIALIZED VIEW
|
||||
ALTER COLLATION CREATE OPERATOR
|
||||
ALTER CONVERSION CREATE POLICY
|
||||
ALTER DATABASE CREATE PROCEDURE
|
||||
ALTER DEFAULT PRIVILEGES CREATE PUBLICATION
|
||||
ALTER DOMAIN CREATE ROLE
|
||||
ALTER EVENT TRIGGER CREATE RULE
|
||||
ALTER EXTENSION CREATE SCHEMA
|
||||
ALTER FOREIGN DATA WRAPPER CREATE SEQUENCE
|
||||
ALTER FOREIGN TABLE CREATE SERVER
|
||||
ALTER FUNCTION CREATE STATISTICS
|
||||
ALTER GROUP CREATE SUBSCRIPTION
|
||||
ALTER INDEX CREATE TABLE
|
||||
ALTER LANGUAGE CREATE TABLESPACE
|
||||
BEGIN DELETE
|
||||
COMMIT DROP TABLE
|
||||
CREATE DATABASE INSERT
|
||||
CREATE INDEX ROLLBACK
|
||||
SELECT UPDATE`
|
||||
}
|
||||
Reference in New Issue
Block a user