{"name":"psql.nu","source":"// examples/psql.nu — a small but real `psql`-style PostgreSQL client.\n//\n// Built entirely on stdlib/ext/postgres.nu (direct libpq FFI). Behaves\n// like psql for everyday use:\n//\n//   * Connects from a conninfo argument, $PG_CONNINFO, or libpq's own\n//     PG* env-var defaults when neither is given.\n//   * Reads SQL from stdin, one statement at a time (terminated by `;`),\n//     across multiple lines, and prints result sets as aligned tables —\n//     headers, a rule, rows, and an \"(N rows)\" footer — exactly like\n//     psql's default aligned output.\n//   * Reports the command tag (\"INSERT 0 1\", \"CREATE TABLE\", …) for\n//     non-SELECT statements and \"ERROR:  …\" with the server message on\n//     failure.\n//   * Backslash meta-commands: \\dt \\d <table> \\l \\du \\conninfo \\? \\q\n//   * One-shot mode: `psql [conninfo] -c \"SQL\"` runs a single command.\n//   * Prompts are shown only on an interactive terminal, so piping a\n//     script through it produces clean, prompt-free output.\n//\n// Build:  ./nurl.sh examples/psql.nu psql\n// Run:    ./psql \"host=/tmp port=5433 user=postgres dbname=postgres\"\n//         echo 'SELECT 1 AS one;' | ./psql \"$PG_CONNINFO\"\n//         ./psql \"$PG_CONNINFO\" -c '\\dt'\n\n$ `stdlib/core/string.nu`\n$ `stdlib/core/vec.nu`\n$ `stdlib/core/io.nu`\n$ `stdlib/ext/env.nu`\n$ `stdlib/ext/postgres.nu`\n\n& `c` @ isatty i32 fd → i32\n\n// ── Small Vec[i] accessor with a 0 default ───────────────────────\n@ veci ( Vec i ) w i idx → i {\n    ?? ( vec_get [i] w idx ) { T x → ^ x F _ → ^ 0 }\n}\n\n// Print `str` then pad with spaces out to column width `w`.\n@ print_padded s str i w → v {\n    ( nurl_print str )\n    : ~ i pad - w ( nurl_str_len str )\n    ~ > pad 0 { ( nurl_print ` ` ) = pad - pad 1 }\n}\n\n// ── Result rendering ─────────────────────────────────────────────\n\n// Display length of a cell: 0 for SQL NULL (rendered blank), else the\n// text length.\n@ cell_len PgResult r i row i col → i {\n    ? ( pg_get_is_null r row col ) { ^ 0 } {}\n    : String v ( pg_get_value r row col )\n    : i l ( string_len v )\n    ( string_free v )\n    ^ l\n}\n\n@ print_header PgResult r ( Vec i ) w i nf → v {\n    : ~ i ci 0\n    ~ < ci nf {\n        ? > ci 0 { ( nurl_print `|` ) } {}\n        : String hn ( pg_field_name r ci )\n        ( nurl_print ` ` )\n        ( print_padded ( string_data hn ) ( veci w ci ) )\n        ( nurl_print ` ` )\n        ( string_free hn )\n        = ci + ci 1\n    }\n    ( nurl_print `\\n` )\n}\n\n@ print_rule ( Vec i ) w i nf → v {\n    : ~ i ci 0\n    ~ < ci nf {\n        ? > ci 0 { ( nurl_print `+` ) } {}\n        : ~ i d + ( veci w ci ) 2\n        ~ > d 0 { ( nurl_print `-` ) = d - d 1 }\n        = ci + ci 1\n    }\n    ( nurl_print `\\n` )\n}\n\n@ print_data_row PgResult r ( Vec i ) w i nf i row → v {\n    : ~ i ci 0\n    ~ < ci nf {\n        ? > ci 0 { ( nurl_print `|` ) } {}\n        ( nurl_print ` ` )\n        ? ( pg_get_is_null r row ci ) {\n            ( print_padded `` ( veci w ci ) )\n        } {\n            : String v ( pg_get_value r row ci )\n            ( print_padded ( string_data v ) ( veci w ci ) )\n            ( string_free v )\n        }\n        ( nurl_print ` ` )\n        = ci + ci 1\n    }\n    ( nurl_print `\\n` )\n}\n\n// Render a PgResult: an aligned table for row-returning statements, or\n// the command tag otherwise.\n@ render_result PgResult r → v {\n    : i nf ( pg_nfields r )\n    ? == nf 0 {\n        : String cs ( pg_cmd_status r )\n        ( nurl_print ( string_data cs ) ) ( nurl_print `\\n` )\n        ( string_free cs )\n        ^ v\n    } {}\n    : i nt ( pg_ntuples r )\n    // Column widths: header length widened by each cell.\n    : ( Vec i ) w ( vec_with_cap [i] nf )\n    : ~ i ci 0\n    ~ < ci nf {\n        : String hn ( pg_field_name r ci )\n        ( vec_push [i] w ( string_len hn ) )\n        ( string_free hn )\n        = ci + ci 1\n    }\n    : ~ i ri 0\n    ~ < ri nt {\n        = ci 0\n        ~ < ci nf {\n            : i cl ( cell_len r ri ci )\n            ? > cl ( veci w ci ) { ( vec_set [i] w ci cl ) } {}\n            = ci + ci 1\n        }\n        = ri + ri 1\n    }\n    ( print_header r w nf )\n    ( print_rule w nf )\n    = ri 0\n    ~ < ri nt {\n        ( print_data_row r w nf ri )\n        = ri + ri 1\n    }\n    ( nurl_print `(` ) ( nurl_print ( nurl_str_int nt ) )\n    ( nurl_print ? == nt 1 ` row)\\n\\n` ` rows)\\n\\n` )\n    ( vec_free [i] w )\n}\n\n// Execute `sql` and render whatever comes back (rows, tag, or error).\n@ exec_and_render Connection c s sql → v {\n    : !PgResult PgErr r ( pg_exec c sql )\n    ?? r {\n        F e → {\n            // libpq's message already carries its own \"ERROR:  \" prefix\n            // and trailing newline — print it verbatim.\n            : String em ( pg_err_msg c )\n            ( nurl_print ( string_data em ) )\n            ( string_free em )\n        }\n        T res → {\n            ( render_result res )\n            ( pg_clear res )\n        }\n    }\n}\n\n// ── Meta-commands ────────────────────────────────────────────────\n\n@ print_help → v {\n    ( nurl_print `Meta-commands:\\n` )\n    ( nurl_print `  \\\\dt          list tables\\n` )\n    ( nurl_print `  \\\\d  TABLE    describe a table's columns\\n` )\n    ( nurl_print `  \\\\l           list databases\\n` )\n    ( nurl_print `  \\\\du          list roles\\n` )\n    ( nurl_print `  \\\\conninfo    show connection info\\n` )\n    ( nurl_print `  \\\\?           this help\\n` )\n    ( nurl_print `  \\\\q           quit\\n` )\n    ( nurl_print `Anything else is sent to the server as SQL (end with ';').\\n` )\n}\n\n@ print_conninfo Connection c → v {\n    : String db ( pg_db c )\n    : String usr ( pg_user c )\n    : String host ( pg_host c )\n    ( nurl_print `Connected to database \"` ) ( nurl_print ( string_data db ) )\n    ( nurl_print `\" as user \"` ) ( nurl_print ( string_data usr ) )\n    ( nurl_print `\" on host \"` ) ( nurl_print ( string_data host ) ) ( nurl_print `\".\\n` )\n    ( string_free db ) ( string_free usr ) ( string_free host )\n}\n\n// Describe a table: run an information_schema query with the table name\n// safely quoted as a SQL literal.\n@ describe_table Connection c s tbl → v {\n    : String esc ( pg_escape_literal c tbl )\n    : String q ( string_new )\n    ( string_push_str q `SELECT column_name AS column, data_type AS type, is_nullable AS nullable, column_default AS default FROM information_schema.columns WHERE table_name = ` )\n    ( string_push_str q ( string_data esc ) )\n    ( string_push_str q ` ORDER BY ordinal_position` )\n    ( exec_and_render c ( string_data q ) )\n    ( string_free q )\n    ( string_free esc )\n}\n\n// Returns 1 to quit, 0 to continue. `cmd` is the trimmed line, already\n// known to start with '\\'.\n@ handle_meta Connection c s cmd → i {\n    ? ( nurl_str_eq cmd `\\q` ) { ^ 1 } {}\n    ? ( nurl_str_eq cmd `\\dt` ) {\n        ( exec_and_render c `SELECT schemaname AS schema, tablename AS name, tableowner AS owner FROM pg_catalog.pg_tables WHERE schemaname NOT IN ('pg_catalog','information_schema') ORDER BY 1,2` )\n        ^ 0\n    } {}\n    ? ( nurl_str_eq cmd `\\l` ) {\n        ( exec_and_render c `SELECT datname AS name FROM pg_database WHERE datistemplate = false ORDER BY 1` )\n        ^ 0\n    } {}\n    ? ( nurl_str_eq cmd `\\du` ) {\n        ( exec_and_render c `SELECT rolname AS role, rolsuper AS superuser, rolcanlogin AS login FROM pg_roles ORDER BY 1` )\n        ^ 0\n    } {}\n    ? ( nurl_str_eq cmd `\\conninfo` ) { ( print_conninfo c ) ^ 0 } {}\n    ? ( nurl_str_eq cmd `\\?` ) { ( print_help ) ^ 0 } {}\n    ? ( nurl_str_starts cmd `\\d ` ) {\n        : s tbl ( nurl_str_slice cmd 3 - ( nurl_str_len cmd ) 3 )\n        ( describe_table c tbl )\n        ^ 0\n    } {}\n    ( nurl_print `invalid command: ` ) ( nurl_print cmd ) ( nurl_print `\\nTry \\\\? for help.\\n` )\n    ^ 0\n}\n\n// ── REPL ─────────────────────────────────────────────────────────\n\n// Process one input line. Accumulates into `buf` until a `;` terminator,\n// then executes. Meta-commands act only when no statement is pending.\n// Returns 1 to quit, 0 to continue.\n@ process_line Connection c String buf String line → i {\n    : String t ( string_trim line )\n    : i tl ( string_len t )\n    ? == 0 tl { ( string_free t ) ^ 0 } {}\n    ? & == 0 ( string_len buf ) == ( string_get t 0 ) 92 {\n        : i q ( handle_meta c ( string_data t ) )\n        ( string_free t )\n        ^ q\n    } {}\n    ( string_push_str buf ( string_data line ) )\n    ( string_push_char buf 32 )\n    : i done ( string_ends_with t `;` )\n    ( string_free t )\n    ? done {\n        ( exec_and_render c ( string_data buf ) )\n        ( string_clear buf )\n    } {}\n    ^ 0\n}\n\n@ run_repl Connection c i tty → v {\n    : String buf ( string_new )\n    : ~ i running 1\n    ~ != running 0 {\n        ? != 0 tty {\n            ( nurl_print ? == 0 ( string_len buf ) `nurl=> ` `nurl-> ` )\n        } {}\n        : String line ( read_line )\n        ? & == 0 ( string_len line ) ( stdin_eof ) {\n            = running 0\n            ? != 0 tty { ( nurl_print `\\n` ) } {}\n        } {\n            : i q ( process_line c buf line )\n            ? != 0 q { = running 0 } {}\n        }\n        ( string_free line )\n    }\n    ( string_free buf )\n}\n\n// ── Banner + entry point ─────────────────────────────────────────\n\n@ print_banner Connection c → v {\n    : String db ( pg_db c )\n    : String usr ( pg_user c )\n    ( nurl_print `psql (NURL) — connected to \"` ) ( nurl_print ( string_data db ) )\n    ( nurl_print `\" as \"` ) ( nurl_print ( string_data usr ) )\n    ( nurl_print `\" (server ` ) ( nurl_print ( nurl_str_int ( pg_server_version c ) ) )\n    ( nurl_print `)\\nType \\\\? for help, \\\\q to quit.\\n\\n` )\n    ( string_free db ) ( string_free usr )\n}\n\n@ main → i {\n    : ( Vec String ) args ( env_args_list )\n    : i argc ( vec_len [String] args )\n    : ~ s conninfo ``\n    : ~ s command ``\n    : ~ i has_cmd 0\n    : ~ i i 1\n    ~ < i argc {\n        ?? ( vec_get [String] args i ) {\n            T av → {\n                : s as ( string_data av )\n                ? ( nurl_str_eq as `-c` ) {\n                    = i + i 1\n                    ?? ( vec_get [String] args i ) {\n                        T cv → { = command ( string_data cv ) = has_cmd 1 }\n                        F _ → {}\n                    }\n                } {\n                    ? ( nurl_str_starts as `-` ) {} { = conninfo as }\n                }\n            }\n            F _ → {}\n        }\n        = i + i 1\n    }\n    // Fall back to $PG_CONNINFO, then to libpq's own env/defaults (\"\").\n    ? == 0 ( nurl_str_len conninfo ) {\n        ?? ( env_get `PG_CONNINFO` ) {\n            T ev → = conninfo ( string_data ev )\n            F _ → {}\n        }\n    } {}\n\n    : Connection c ( pg_connect conninfo )\n    ? ! ( pg_ok c ) {\n        : String em ( pg_err_msg c )\n        ( nurl_print `psql: error: ` ) ( nurl_print ( string_data em ) )\n        ( string_free em )\n        ( pg_close c )\n        ^ 1\n    } {}\n\n    ? != 0 has_cmd {\n        : String t ( string_trim ( string_from command ) )\n        ? & > ( string_len t ) 0 == ( string_get t 0 ) 92 {\n            ( handle_meta c ( string_data t ) )\n        } {\n            ( exec_and_render c command )\n        }\n        ( string_free t )\n        ( pg_close c )\n        ^ 0\n    } {}\n\n    : i tty # i ( isatty # i32 0 )\n    ? != 0 tty { ( print_banner c ) } {}\n    ( run_repl c tty )\n    ( pg_close c )\n    ^ 0\n}\n","bytes":11688}