A um cliente PostgreSQL para o terminal, escrito em Swift com SwiftTUI.
export PKG_CONFIG_PATH="$(brew --prefix libpq)/lib/pkgconfig" swift build -c release
Requer Swift 6.0 ou posterior e macOS 13 ou posterior.
swsql # variáveis de ambiente PG* e padrões
swsql shop # um banco de dados por nome
swsql postgres://alice@db/shop # uma URI
swsql "host=db user=alice" # uma string de palavra-chave libpq
swsql -h db.internal -U alice -d shop # opções discretasTudo que não for dado na linha de comando é resolvido pela libpq exatamente como o psql resolve. As conexões são marcadas com application_name=swsql, então elas são fáceis de identificar em pg_stat_activity.
Rode swsql sem argumentos e sem nada salvo ainda, e ele abrirá na tela de configuração que pede por uma conexão. Dê um nome opcional (prod, staging), uma URL - uma URI postgres://, uma string de palavra-chave libpq ou um nome de banco de dados simples, e se for produção, ative o interruptor production. Pressione ⏎ no campo da URL para conectar. Apenas a conexão que realmente conectou será salva. Pressionar ⏎ em uma URL vazia cai de volta nos padrões do ambiente libpq sem salvar nada.
O swsql lembra cada conexão que você adiciona, então você pode manter staging e prod lado a lado e alternar entre eles:
- Clique na conexão mostrada na barra de título (marcada com um
▾) ou pressione o botãoConnpara abrir a lista de conexões. Selecione uma para se conectar, ou escolha+ Adicionar uma conexãopara adicionar outra. swsqlsem argumentos reestabelece a última conexão usada;swsql <name>abre uma conexão salva pelo nome.- A lista é armazenada como JSON em
~/.config/swsql/connections.json(respeitandoXDG_CONFIG_HOME). Uma entrada pode conter uma senha, então o arquivo é escrito0600, da mesma forma quepsqlrequer para~/.pgpass. Um arquivo de conexão mais antigo é migrado automaticamente na primeira execução. - Uma conexão nomeada na linha de comando como uma URL (em vez de um nome salvo) é usada conforme está e nunca é salva.
Uma conexão marcada como produção não pode ser confundida com nada mais:
toda vez que você estiver conectado a ela, a barra de título vira vermelha e exibe
⚠ PRODUÇÃO, e é indicada na lista de conexões. (A marcação é passiva -
alerta, não bloqueia; uma etapa de confirmação antes de se conectar ou escrever
é um adicional natural.)
A interface é controlada por mover o foco e ativar o que está em foco, seja do teclado ou com o mouse.
| Tecla | Faz |
|---|---|
↑ ↓ ← → |
mover entre a prompt, o painel lateral, as linhas de resultado e os botões |
⏎ |
executar a declaração na prompt, abrir uma tabela, inspecionar uma linha, pressionar um botão |
⌫ |
apagar o último caractere digitado |
Esc |
voltar à grade de resultados a partir de qualquer outro painel (ou fechar o menu de autocompletar) |
^C / ^D |
sair |
Os botões sob os resultados fazem o resto: ⇟ ⇞ navegar por um resultado grande,
◀ ▶ rolar uma coluna de cada vez, Struct mostra as colunas da tabela
selecionada, Hist lista declarações anteriores, Conn alternar entre conexões salvas,
? abre ajuda e Rows retorna à grade de resultados a partir de qualquer outro painel.
O filtro de objetos é um campo em uma linha: digite, então pressione ⏎.
A entrada SQL é um editor multi-linha.
| Chave | Faz |
|---|---|
↑ ↓ ← → |
Mover o cursor (e sair para a barra lateral / botões nas bordas) |
⌥ ← / ⌥ → (or ^← / ^→) |
Mover por palavra |
⌘ ← / ⌘ →, Home / End |
Pular para o início / fim da linha (^A / ^E também) |
⌘ ↑ / ⌘ ↓ |
Pular para o início / fim de toda a consulta |
⏎ |
Inserir uma nova linha |
⌫ |
Apagar um caractere; ⌥ ⌫ apaga a palavra anterior |
^R |
Executar a consulta (de qualquer lugar) |
^R, ou o botãoRun ▶, executa todo o editor.Formatarimprime bonitamente a consulta no lugar: palavras-chave são maiúsculas, cada cláusula começa em uma nova linha, itens de lista estão indentados e subconsultas são aninhadas - enquanto literais de string e comentários permanecem exatamente como escritos.Limparesvazia o editor.- Autocompletar: à medida que você digita um identificador, uma lista suspensa de palavras-chave SQL,
tabelas/visualizações e colunas (do banco de dados conectado) aparece sob
o editor.
↑ ↓seleciona,⏎ouTabinsere,Escfecha. As sugestões são ciente do contexto - tabelas vêm em primeiro lugar apósFROM/JOIN, colunas após um qualificador. - A escolha de uma declaração de
Hista carrega de volta ao editor para edição ou execução novamente.
O swsql entende as sequências de escape padrão que os terminais enviam para essas teclas, então funciona em qualquer terminal que possa enviá-las - mas duas coisas dependem do seu terminal, não do swsql:
⌘(Command) nunca é entregue a um programa de terminal - o macOS mantém para atráves de atalhos de menu. Para usar⌘R,⌘←/→,⌘↑/↓, remapeie-os no seu terminal para enviar as sequências que o swsql lê. O Apple's Terminal.app não pode remapear⌘; use as teclas de^/⌥lá em vez disso. iTerm2, WezTerm, Kitty e Ghostty podem.⌥(Option) deve ser enviado como Meta para que⌥←/→e⌥⌫funcionem (no Terminal.app: Perfis → Teclado → Use Option como tecla Meta).
local act = require('wezterm').action
return {
keys = {
{ key = 'r', mods = 'CMD', action = act.SendString('\x12') }, -- ⌘R → run
{ key = 'LeftArrow', mods = 'CMD', action = act.SendString('\x1b[H') }, -- ⌘← → line start
{ key = 'RightArrow', mods = 'CMD', action = act.SendString('\x1b[F') }, -- ⌘→ → line end
{ key = 'UpArrow', mods = 'CMD', action = act.SendString('\x1b[1;5H')}, -- ⌘↑ → query start{ key = 'DownArrow', mods = 'CMD', action = act.SendString('\x1b[1;5F')}, -- ⌘↓ → query end
{ key = 'LeftArrow', mods = 'OPT', action = act.SendString('\x1b[1;3D')}, -- ⌥← → word left
{ key = 'RightArrow', mods = 'OPT', action = act.SendString('\x1b[1;3C')}, -- ⌥→ → word right
{ key = 'Backspace', mods = 'OPT', action = act.SendString('\x1b\x7f') }, -- ⌥⌫ → delete word
},
}
In ~/.tmux.conf, let modified keys through (and keep the prefix off ^R):
set -g xterm-keys on
set -s extended-keys on
Click a table, a result row or any button to focus and activate it; click a text field to type into it. The scroll wheel moves the focus up and down, which is how you scroll the sidebar or the result grid.
Mouse reporting is on whenever swsql is running, so selecting text to copy it works the way it does in any terminal program that tracks the mouse: hold ⌥ or ⇧ while dragging. This is why swsql links a small fork of SwiftTUI - the upstream library is keyboard-only, and the fork adds the SGR mouse handling.
- Os resultados são limitados a 10.000 linhas, e as prévias de tabelas recuperam 500. Um cliente não deve falhar porque alguém selecionou bilhões de linhas.
- A grade constrói uma tela cheia de linhas por vez. A paginação é explícita para que os botões abaixo da grade estejam sempre a um pressionar de tecla abaixo da última linha visível.
- As larguras das colunas são medidas uma vez por resultado com base nas primeiras 200 linhas e limitadas a 44 caracteres, então um único blob JSON não pode empurrar todas as outras colunas para fora da tela.
- O editor SQL tem autocompletamento mas nenhuma destaque de sintaxe, e o formataador visa SQL legível do dia-a-dia em vez de cobrir todos os cantos de um dialecto.
- A contagem de larguras de caracteres é feita por cluster gráfico, correspondendo ao modo como SwiftTUI desenha células. Glifos CJK amplos e emojis irão portanto ficar um pouco soltos em uma coluna.
Sources/
CLibPQ/ sistema library target exposto libpq-fe.h
SWSQLCore/ tudo testável e independente do terminal
LibPQ/ conexão, resultados, erros, nomes de tipo
Layout/ tamanho de coluna, renderização de span, regiões da tela
Catalog.swift consultas de introspecção e seu parsing
swsql/ a aplicação SwiftTUI: modelo e visualizações
Tests/SWSQLCoreTests/
SWSQLCore não tem dependência em SwiftTUI, e o código de layout não tem dependência em uma conexão ativa, então os casos difíceis - uma coluna mais larga que a tela, um viewport com zero largura, um conjunto de resultados que muda de forma sob um redimensionamento - são todos cobertos por testes unitários normais.
Dois comportamentos da biblioteca moldam o código e valem a pena saber se você estende:
Button.updateNodenunca atualiza a clausula de ação do controle, então uma ação de botão é congelada no momento em que é construído pela primeira vez. Cada ação aqui, portanto, toma um índice ou nenhum argumento e resolve o que atua a partir do modelo ao pressionar.- Teclas seta são consumidas por navegação de foco e nunca alcançam um controle, então qualquer coisa que seria um atalho em outro lugar é um botão aqui.

