Skip to main content

The SQLite command line shell will count your unclosed parentheses

  • Posted

If the prompt starts with (x1 or (x2, it means you’ve opened some parentheses and not closed them yet.

While writing my previous note, I noticed an unexpected prefix in the SQLite shell:

sqlite> CREATE TABLE KeyValuePairs (
(x1...>     Key   TEXT NOT NULL PRIMARY KEY,
(x1...>     Value TEXT NOT NULL
(x1...> );

What does that (x1 prefix mean? I couldn’t find any reference to it online, so I had to read the SQLite source code.

The relevant code is in shell.c.in. First I found the default prompts, and two variables where the main and continuation prompts are stored:

/*
** Prompt strings. Initialized in main. Settable with
**   .prompt main continue
*/
#define PROMPT_LEN_MAX 128
/* First line prompt.   default: "sqlite> " */
static char mainPrompt[PROMPT_LEN_MAX];
/* Continuation prompt. default: "   ...> " */
static char continuePrompt[PROMPT_LEN_MAX];
Lines 406–414 of src/shell.c.in at 15285c21cc

Looking at where those variables get used leads to another interesting snippet, which names this feature as “dynamic continuation prompt”:

/*
** Optionally disable dynamic continuation prompt.
** Unless disabled, the continuation prompt shows open SQL lexemes if any,
** or open parentheses level if non-zero, or continuation prompt as set.
** This facility interacts with the scanner and process_input() where the
** below 5 macros are used.
*/
#ifdef SQLITE_OMIT_DYNAPROMPT
# define CONTINUATION_PROMPT continuePrompt

#else
# define CONTINUATION_PROMPT dynamicContinuePrompt()
Lines 444–461 of src/shell.c.in at 15285c21cc

And looking for the definition of that dynamicContinuePrompt function, I can see it updating a dynPrompt.dynamicPrompt variable with expressions like the (x1 I saw in my SQLite shell:

/* Upon demand, derive the continuation prompt to display. */
static char *dynamicContinuePrompt(void){
  if( continuePrompt[0]==0
      || (dynPrompt.zScannerAwaits==0 && dynPrompt.inParenLevel == 0) ){
    return continuePrompt;
  }else{
    if( dynPrompt.zScannerAwaits ){
      size_t ncp = strlen(continuePrompt);
      size_t ndp = strlen(dynPrompt.zScannerAwaits);
      if( ndp > ncp-3 ) return continuePrompt;
      shell_strcpy(dynPrompt.dynamicPrompt, dynPrompt.zScannerAwaits);
      while( ndp<3 ) dynPrompt.dynamicPrompt[ndp++] = ' ';
      shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3,
              PROMPT_LEN_MAX-4);
    }else{
      if( dynPrompt.inParenLevel>9 ){
        shell_strncpy(dynPrompt.dynamicPrompt, "(..", 4);
      }else if( dynPrompt.inParenLevel<0 ){
        shell_strncpy(dynPrompt.dynamicPrompt, ")x!", 4);
      }else{
        shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4);
        dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel);
      }
      shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3,
                    PROMPT_LEN_MAX-4);
    }
  }
  return dynPrompt.dynamicPrompt;
}
#endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */
Lines 499–528 of src/shell.c.in at 15285c21cc

I don’t completely understand this function, but I think I get the general gist. The first branch is looking for “open SQL lexemes”, or unterminated strings, while the second branch is counting open parentheses. I can compare this to what I see in the SQLite shell:

I wonder where this behaviour came from? It feels like the sort of thing that might have come from Lisp, which is famous for having lots of brackets and exactly where this sort of indicator might be useful, whereas I imagine writing a heavily nested expression in the SQLite shell interface is comparatively rare.