惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
WordPress大学
WordPress大学
T
Tailwind CSS Blog
V
Visual Studio Blog
月光博客
月光博客
Hugging Face - Blog
Hugging Face - Blog
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - Franky
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Last Week in AI
Last Week in AI
阮一峰的网络日志
阮一峰的网络日志
量子位
有赞技术团队
有赞技术团队
酷 壳 – CoolShell
酷 壳 – CoolShell
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
Jina AI
Jina AI
雷峰网
雷峰网
博客园 - 【当耐特】
博客园 - 叶小钗
美团技术团队
宝玉的分享
宝玉的分享
IT之家
IT之家

alexwlchan’s notes

What is WS11 1DB? Blocking referrers with Caddy How to type a Spanish question mark (¿) on a Mac Non-overlapping type comparisons and Python type checkers Why does t.Setenv panic after t.Parallel? Use Path.glob() and Path.rglob() for typed versions of glob.glob() Curious clocks and colourful eyes Track which templates are used by Jinja2 Archeologists distinguish between “sherds” and “shards” A single command to test all my changed Go packages Disable the new message animations in WhatsApp Finding high-churn folders that bother Backblaze Always-on SSH agent forwarding with my Git pushes Managing the caption of a photo with AppleScript (but not PhotoKit) Goodhart’s and Campbell’s Law are different Notes from The Cornishman No. 176 (Spring 2026) Notes from The Cornishman No. 176 (Spring 2026) GitUp can’t diff text files larger than 8MB Home Testing the width of a page on a mobile device using Playwright Disable AirPods charging notifications Start a Caddy server in a subprocess during a Python session Filter a list of JSON object based on a list of tags HOME_GET_ME_HOME is a Citymapper Shortcuts action The FileExistsError exception exposes a filename attribute The red-lined bubble snail Why can’t Python connect to example.com? Useful type hints for Python How to truncate the middle of long command output AirPlay Receiver can interfere with Flask apps
The SQLite command line shell will count your unclosed pa...
2026-02-20 · via alexwlchan’s notes

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:

406/*

407** Prompt strings. Initialized in main. Settable with

408** .prompt main continue

409*/

410#define PROMPT_LEN_MAX 128

411/* First line prompt. default: "sqlite> " */

412static char mainPrompt[PROMPT_LEN_MAX];

413/* Continuation prompt. default: " ...> " */

414static char continuePrompt[PROMPT_LEN_MAX];

Lines 406–414 of src/shell.c.in in the SQLite source repository. All SQLite code is in the public domain.

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

444/*

445** Optionally disable dynamic continuation prompt.

446** Unless disabled, the continuation prompt shows open SQL lexemes if any,

447** or open parentheses level if non-zero, or continuation prompt as set.

448** This facility interacts with the scanner and process_input() where the

449** below 5 macros are used.

450*/

451#ifdef SQLITE_OMIT_DYNAPROMPT

452# define CONTINUATION_PROMPT continuePrompt

460#else

461# define CONTINUATION_PROMPT dynamicContinuePrompt()

Lines 444–461 of src/shell.c.in in the SQLite source repository.

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:

499/* Upon demand, derive the continuation prompt to display. */

500static char *dynamicContinuePrompt(void){

501 if( continuePrompt[0]==0

502 || (dynPrompt.zScannerAwaits==0 && dynPrompt.inParenLevel == 0) ){

503 return continuePrompt;

504 }else{

505 if( dynPrompt.zScannerAwaits ){

506 size_t ncp = strlen(continuePrompt);

507 size_t ndp = strlen(dynPrompt.zScannerAwaits);

508 if( ndp > ncp-3 ) return continuePrompt;

509 shell_strcpy(dynPrompt.dynamicPrompt, dynPrompt.zScannerAwaits);

510 while( ndp<3 ) dynPrompt.dynamicPrompt[ndp++] = ' ';

511 shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3,

512 PROMPT_LEN_MAX-4);

513 }else{

514 if( dynPrompt.inParenLevel>9 ){

515 shell_strncpy(dynPrompt.dynamicPrompt, "(..", 4);

516 }else if( dynPrompt.inParenLevel<0 ){

517 shell_strncpy(dynPrompt.dynamicPrompt, ")x!", 4);

518 }else{

519 shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4);

520 dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel);

521 }

522 shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3,

523 PROMPT_LEN_MAX-4);

524 }

525 }

526 return dynPrompt.dynamicPrompt;

527}

528#endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */

Lines 499–528 of src/shell.c.in in the SQLite source repository.

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:

  • If you have between 1 to 9 unclosed parentheses, the prompt starts with (x and the number of unclosed parens:
    sqlite> (
    (x1...>
    sqlite> (((((((((
    (x9...>
  • If you have 10 or more unclosed parentheses, the prompt starts with prints (..:
    sqlite> ((((((((((
    (x.....>
  • If you have more closed parentheses than you've opened, the prompt starts with prints )x!:

    sqlite> )))
    )x!...>
  • If you’re in this state, I’m not sure if it's ever possible to get back to a valid SQL expression?

  • If you have an unfinished string, square bracket, or multi-line comment, the prompt starts with the quote character you need to close the string:

    sqlite> SELECT '
    '  ...> hello world
    '  ...> '
    
    hello world
    
    sqlite> SELECT "
    "  ...> hello world
    "  ...> "
    
    hello world
    
    sqlite> [
    [  ...> 
    
    sqlite> /*
    /* ...> 
    

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.