Sunday, March 25, 2018

How to write interactive pager

When I wrote pspg, I had to solve few issues. The main problem was switch stdin stream from input data to keyboard to ensure user input. The pspg is pager. Pager input is stdin usually. But input for ncurses event loop is stdin too. There are few solution, but, unfortunately, some solution in some environments doesn't work.

I have a function when data is read from input:
void
readfile(FILE *fp, ...)
{
    if (fp == NULL)
       /* use stdin */
       fp = stdin;

    while (read = getline(&line, &len, fp))
    {
    }
}

/* when I process a option -f I opening the file */
fp = fopen(optarg, "r");
if (fp == NULL)
{
    fprintf(stderr, "cannot to open file: %s", optarg);
    exit(1);
}

/* now I can read data from input */
readfile(fp, ...);

/* when file was explicitly opened, close it */
if (fp != NULL)
   fclose(fp);

And the real important part started:
if (!isatty(fileno(stdin)))
{
    /* 
     * We should to try reopen some terminal device as stdin.
     * Start with /dev/tty. If it is not possible, try to use
     * device attached to stdout
     */
    if (freopen("/dev/tty", "r", stdin) != NULL)
        noatty = false;
    else if (freopen(ttyname(fileno(stdout)), "r", stdin) != NULL)
        noatty = false;
    else
    {
        /*
         * freopen fails. We can try to use device attached to
         * stderr. Check if stderr is joined with terminal and
         * and close stdin against some artefacts.
         */
        if (!isatty(fileno(stderr)))
        {
            fprintf(stderr, "no terminal device..");
            exit(1);
        }
        noatty = true;
        close(stdin);
    }
}
else
{
    /* all is done, stdin is joined to terminal */
    noatty = false;
}   

if (!noatty)
    /* usual ncurses start */
    initscr();
else
    /* use stderr as input stream - fallback solution used by less pager */
    newterm(termname(), stdout, stderr);
That is all. It is not too complicate code, but tuning this code needed few months to work inside wide set of environments: ssh, screen, ...

Note: how to detect if terminal uses UTF8 encoding? This question is much simpler:
/* init all locale variables */
setlocale(LC_ALL, ""); 
/* check LC_CTYPE */
force8bit = strcmp(nl_langinfo(CODESET), "UTF-8") != 0;

Tuesday, March 20, 2018

some update about schema variables

I sent new patch to mailing list - can be composite, scalar or array - and it is working like plpgsql variables (but are placed to schema, and content is session limited) - that was my target:
postgres=# create variable foo as numeric default 0;
CREATE VARIABLE
postgres=# select foo;
┌─────┐
│ foo │
╞═════╡
│   0 │
└─────┘
(1 row)

postgres=# let foo = pi();
LET 
postgres=# select foo;
┌──────────────────┐
│       foo        │
╞══════════════════╡
│ 3.14159265358979 │
└──────────────────┘
(1 row)

postgres=# create variable boo as (x numeric default 0, y numeric default 0);
CREATE VARIABLE
postgres=# let boo.x = 100;
LET 
postgres=# select boo;
┌─────────┐
│   boo   │
╞═════════╡
│ (100,0) │
└─────────┘
(1 row)

postgres=# select boo.x;
┌─────┐
│  x  │
╞═════╡
│ 100 │
└─────┘
(1 row)
In new session, the variables are available - but the content was initialized:
[pavel@nemesis src]$ psql
psql (11devel)
Type "help" for help.

postgres=# \dV
             List of relations
┌────────┬──────┬─────────────────┬───────┐
│ Schema │ Name │      Type       │ Owner │
╞════════╪══════╪═════════════════╪═══════╡
│ public │ boo  │ schema variable │ pavel │
│ public │ foo  │ schema variable │ pavel │
└────────┴──────┴─────────────────┴───────┘
(2 rows)

postgres=# select boo;
┌───────┐
│  boo  │
╞═══════╡
│ (0,0) │
└───────┘
(1 row)
These variables can be used like package variables in PL/SQL:
postgres=# create schema mypackage;
CREATE SCHEMA
postgres=# create variable mypackage.state as (name text, last_access timestamp, debug boolean default false);
CREATE VARIABLE
postgres=# let mypackage.state.name = 'Pavel';
LET 
postgres=# let mypackage.state.last_access = current_timestamp;
LET 
postgres=# select mypackage.state;
┌───────────────────────────────────────┐
│                 state                 │
╞═══════════════════════════════════════╡
│ (Pavel,"2018-03-20 19:12:18.29888",f) │
└───────────────────────────────────────┘
(1 row)

postgres=# select mypackage.state.name;
┌───────┐
│ name  │
╞═══════╡
│ Pavel │
└───────┘
(1 row)

Thursday, March 15, 2018

Release 1.0.0 of tabular data optimized pager - pspg

I released version 1.0.0 of pspg pager. It supports psql, mysql, vertica, pgcli output formats, and can be used with these databases.

Thursday, March 1, 2018

pspg pager is available from Debian testing packages

The availability of pspg will be higher - it is part of Debian packages - https://packages.debian.org/buster/pspg