| | @@ -466,20 +466,22 @@ |
| 466 | 466 | ** is true. Otherwise, assume stdin is connected to a file or pipe. |
| 467 | 467 | */ |
| 468 | 468 | static int stdin_is_interactive = 1; |
| 469 | 469 | |
| 470 | 470 | /* |
| 471 | | -** If build is for Windows, without 3rd-party line editing, Console |
| 472 | | -** input and output may be done in a UTF-8 compatible way. This is |
| 473 | | -** determined by invocation option and OS installed capability. |
| 471 | +** If build is for non-RT Windows, without 3rd-party line editing, |
| 472 | +** console input and output may be done in a UTF-8 compatible way, |
| 473 | +** if the OS is capable of it and the --no-utf8 option is not seen. |
| 474 | 474 | */ |
| 475 | 475 | #if (defined(_WIN32) || defined(WIN32)) && SHELL_USE_LOCAL_GETLINE \ |
| 476 | | - && !defined(SHELL_OMIT_WIN_UTF8) |
| 476 | + && !defined(SHELL_OMIT_WIN_UTF8) && !SQLITE_OS_WINRT |
| 477 | 477 | # define SHELL_WIN_UTF8_OPT 1 |
| 478 | +/* Record whether to do UTF-8 console I/O translation per stream. */ |
| 478 | 479 | static int console_utf8_in = 0; |
| 479 | 480 | static int console_utf8_out = 0; |
| 480 | | - static int mbcs_opted = 0; |
| 481 | +/* Record whether can do UTF-8 or --no-utf8 seen in invocation. */ |
| 482 | + static int mbcs_opted = 1; /* Assume cannot do until shown otherwise. */ |
| 481 | 483 | #else |
| 482 | 484 | # define console_utf8_in 0 |
| 483 | 485 | # define console_utf8_out 0 |
| 484 | 486 | # define SHELL_WIN_UTF8_OPT 0 |
| 485 | 487 | #endif |
| | @@ -615,11 +617,11 @@ |
| 615 | 617 | return dynPrompt.dynamicPrompt; |
| 616 | 618 | } |
| 617 | 619 | #endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */ |
| 618 | 620 | |
| 619 | 621 | #if SHELL_WIN_UTF8_OPT |
| 620 | | -/* Following struct is used for UTF-8 operation. */ |
| 622 | +/* Following struct is used for UTF-8 console I/O. */ |
| 621 | 623 | static struct ConsoleState { |
| 622 | 624 | int stdinEof; /* EOF has been seen on console input */ |
| 623 | 625 | int infsMode; /* Input file stream mode upon shell start */ |
| 624 | 626 | UINT inCodePage; /* Input code page upon shell start */ |
| 625 | 627 | UINT outCodePage; /* Output code page upon shell start */ |
| | @@ -629,110 +631,109 @@ |
| 629 | 631 | |
| 630 | 632 | #ifndef _O_U16TEXT /* For build environments lacking this constant: */ |
| 631 | 633 | # define _O_U16TEXT 0x20000 |
| 632 | 634 | #endif |
| 633 | 635 | |
| 634 | | - |
| 635 | | -#if !SQLITE_OS_WINRT |
| 636 | | -/* |
| 637 | | -** Check Windows major version against given value, returning |
| 638 | | -** 1 if the OS major version is no less than the argument. |
| 639 | | -** This check uses very late binding to the registry access |
| 640 | | -** API so that it can operate gracefully on OS versions that |
| 641 | | -** do not have that API. The Windows NT registry, for versions |
| 642 | | -** through Windows 11 (at least, as of October 2023), keeps |
| 643 | | -** the actual major version number at registry key/value |
| 644 | | -** HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentMajorVersionNumber |
| 645 | | -** where it can be read more reliably than allowed by various |
| 646 | | -** version info APIs which "process" the result in a manner |
| 647 | | -** incompatible with the purpose of the CLI's version check. |
| 648 | | -** |
| 649 | | -** If the registry API is unavailable, or the location of |
| 650 | | -** the above registry value changes, or the OS major version |
| 651 | | -** is less than the argument, this function returns 0. |
| 652 | | -*/ |
| 653 | | -static int CheckAtLeastWinX(DWORD major_version){ |
| 654 | | - typedef LONG (WINAPI *REG_OPEN)(HKEY,LPCSTR,DWORD,REGSAM,PHKEY); |
| 655 | | - typedef LSTATUS (WINAPI *REG_READ)(HKEY,LPCSTR,LPCSTR,DWORD, |
| 656 | | - LPDWORD,PVOID,LPDWORD); |
| 657 | | - typedef LSTATUS (WINAPI *REG_CLOSE)(HKEY); |
| 658 | | - int rv = 0; |
| 659 | | - HINSTANCE hLib = LoadLibrary(TEXT("Advapi32.dll")); |
| 660 | | - if( NULL != hLib ){ |
| 661 | | - REG_OPEN rkOpen = (REG_OPEN)GetProcAddress(hLib, "RegOpenKeyExA"); |
| 662 | | - REG_READ rkRead = (REG_READ)GetProcAddress(hLib, "RegGetValueA"); |
| 663 | | - REG_CLOSE rkFree = (REG_CLOSE)GetProcAddress(hLib, "RegCloseKey"); |
| 664 | | - if( rkOpen != NULL && rkRead != NULL && rkFree != NULL ){ |
| 665 | | - HKEY hk; |
| 666 | | - const char *zsk = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; |
| 667 | | - if( ERROR_SUCCESS == rkOpen(HKEY_LOCAL_MACHINE, zsk, 0, KEY_READ, &hk) ){ |
| 668 | | - DWORD kv = 0, kvsize = sizeof(kv); |
| 669 | | - if( ERROR_SUCCESS == rkRead(hk, 0, "CurrentMajorVersionNumber", |
| 670 | | - RRF_RT_REG_DWORD, 0, &kv, &kvsize) ){ |
| 671 | | - rv = (kv >= major_version); |
| 672 | | - } |
| 673 | | - rkFree(hk); |
| 674 | | - } |
| 675 | | - } |
| 676 | | - FreeLibrary(hLib); |
| 677 | | - } |
| 678 | | - return rv; |
| 679 | | -} |
| 680 | | -# define IS_WIN10_OR_LATER() CheckAtLeastWinX(10) |
| 681 | | -#else /* defined(SQLITE_OS_WINRT) */ |
| 682 | | -# define IS_WIN10_OR_LATER() 0 |
| 683 | | -#endif |
| 684 | | - |
| 685 | | -/* |
| 686 | | -** Prepare console, (if known to be a WIN32 console), for UTF-8 input |
| 687 | | -** (from either typing or suitable paste operations) and/or for UTF-8 |
| 688 | | -** output rendering. This may "fail" with a message to stderr, where |
| 689 | | -** the preparation is not done and common "code page" issues occur. |
| 636 | +/* |
| 637 | +** If given stream number is a console, return 1 and get some attributes, |
| 638 | +** else return 0 and set the output attributes to invalid values. |
| 639 | +*/ |
| 640 | +static short console_attrs(unsigned stnum, HANDLE *pH, DWORD *pConsMode){ |
| 641 | + static int stid[3] = { STD_INPUT_HANDLE,STD_OUTPUT_HANDLE,STD_ERROR_HANDLE }; |
| 642 | + HANDLE h; |
| 643 | + *pH = INVALID_HANDLE_VALUE; |
| 644 | + *pConsMode = 0; |
| 645 | + if( stnum > 2 ) return 0; |
| 646 | + h = GetStdHandle(stid[stnum]); |
| 647 | + if( h!=*pH && GetFileType(h)==FILE_TYPE_CHAR && GetConsoleMode(h,pConsMode) ){ |
| 648 | + *pH = h; |
| 649 | + return 1; |
| 650 | + } |
| 651 | + return 0; |
| 652 | +} |
| 653 | + |
| 654 | +/* |
| 655 | +** Perform a runtime test of Windows console to determine if it can |
| 656 | +** do char-stream I/O correctly when the code page is set to CP_UTF8. |
| 657 | +** Returns are: 1 => yes it can, 0 => no it cannot |
| 658 | +** |
| 659 | +** The console's output code page is momentarily set, then restored. |
| 660 | +** So this should only be run when the process is given use of the |
| 661 | +** console for either input or output. |
| 662 | +*/ |
| 663 | +static short ConsoleDoesUTF8(void){ |
| 664 | + UINT ocp = GetConsoleOutputCP(); |
| 665 | + const char TrialUtf8[] = { '\xC8', '\xAB' }; /* "ȫ" or 2 MBCS characters */ |
| 666 | + WCHAR aReadBack[1] = { 0 }; /* Read back as 0x022B when decoded as UTF-8. */ |
| 667 | + CONSOLE_SCREEN_BUFFER_INFO csbInfo = {0}; |
| 668 | + /* Create an inactive screen buffer with which to do the experiment. */ |
| 669 | + HANDLE hCSB = CreateConsoleScreenBuffer(GENERIC_READ|GENERIC_WRITE, 0, 0, |
| 670 | + CONSOLE_TEXTMODE_BUFFER, NULL); |
| 671 | + if( hCSB!=INVALID_HANDLE_VALUE ){ |
| 672 | + COORD cpos = {0,0}; |
| 673 | + DWORD rbc; |
| 674 | + SetConsoleCursorPosition(hCSB, cpos); |
| 675 | + SetConsoleOutputCP(CP_UTF8); |
| 676 | + /* Write 2 chars which are a single character in UTF-8 but more in MBCS. */ |
| 677 | + WriteConsoleA(hCSB, TrialUtf8, sizeof(TrialUtf8), NULL, NULL); |
| 678 | + ReadConsoleOutputCharacterW(hCSB, &aReadBack[0], 1, cpos, &rbc); |
| 679 | + GetConsoleScreenBufferInfo(hCSB, &csbInfo); |
| 680 | + SetConsoleOutputCP(ocp); |
| 681 | + CloseHandle(hCSB); |
| 682 | + } |
| 683 | + /* Return 1 if cursor advanced by 1 position, else 0. */ |
| 684 | + return (short)(csbInfo.dwCursorPosition.X == 1 && aReadBack[0] == 0x022B); |
| 685 | +} |
| 686 | + |
| 687 | +static short in_console = 0; |
| 688 | +static short out_console = 0; |
| 689 | + |
| 690 | +/* |
| 691 | +** Determine whether either normal I/O stream is the console, |
| 692 | +** and whether it can do UTF-8 translation, setting globals |
| 693 | +** in_console, out_console and mbcs_opted accordingly. |
| 694 | +*/ |
| 695 | +static void probe_console(void){ |
| 696 | + HANDLE h; |
| 697 | + DWORD cMode; |
| 698 | + in_console = console_attrs(0, &h, &cMode); |
| 699 | + out_console = console_attrs(1, &h, &cMode); |
| 700 | + if( in_console || out_console ) mbcs_opted = !ConsoleDoesUTF8(); |
| 701 | +} |
| 702 | + |
| 703 | +/* |
| 704 | +** If console is used for normal I/O, absent a --no-utf8 option, |
| 705 | +** prepare console for UTF-8 input (from either typing or suitable |
| 706 | +** paste operations) and/or for UTF-8 output rendering. |
| 690 | 707 | ** |
| 691 | 708 | ** The console state upon entry is preserved, in conState, so that |
| 692 | 709 | ** console_restore() can later restore the same console state. |
| 693 | 710 | ** |
| 694 | 711 | ** The globals console_utf8_in and console_utf8_out are set, for |
| 695 | 712 | ** later use in selecting UTF-8 or MBCS console I/O translations. |
| 713 | +** This routine depends upon globals set by probe_console(). |
| 696 | 714 | */ |
| 697 | 715 | static void console_prepare_utf8(void){ |
| 698 | | - HANDLE hCI = GetStdHandle(STD_INPUT_HANDLE); |
| 699 | | - HANDLE hCO = GetStdHandle(STD_OUTPUT_HANDLE); |
| 700 | | - HANDLE hCC = INVALID_HANDLE_VALUE; |
| 701 | | - DWORD consoleMode = 0; |
| 702 | | - u8 conI = 0, conO = 0; |
| 703 | 716 | struct ConsoleState csWork = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 }; |
| 704 | 717 | |
| 705 | 718 | console_utf8_in = console_utf8_out = 0; |
| 706 | | - if( isatty(0) && GetFileType(hCI)==FILE_TYPE_CHAR ) conI = 1; |
| 707 | | - if( isatty(1) && GetFileType(hCO)==FILE_TYPE_CHAR ) conO = 1; |
| 708 | | - if( (!conI && !conO) || mbcs_opted ) return; |
| 709 | | - if( conI ) hCC = hCI; |
| 710 | | - else hCC = hCO; |
| 711 | | - if( !IsValidCodePage(CP_UTF8) || !GetConsoleMode( hCC, &consoleMode) ){ |
| 712 | | - bail: |
| 713 | | - fprintf(stderr, "Cannot use UTF-8 code page.\n"); |
| 714 | | - return; |
| 715 | | - } |
| 716 | | - csWork.hConsole = hCC; |
| 717 | | - csWork.consoleMode = consoleMode; |
| 718 | | - csWork.inCodePage = GetConsoleCP(); |
| 719 | | - csWork.outCodePage = GetConsoleOutputCP(); |
| 720 | | - if( conI ){ |
| 721 | | - if( !SetConsoleCP(CP_UTF8) ) goto bail; |
| 722 | | - consoleMode |= ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; |
| 723 | | - SetConsoleMode(conState.hConsole, consoleMode); |
| 724 | | - csWork.infsMode = _setmode(_fileno(stdin), _O_U16TEXT); |
| 725 | | - } |
| 726 | | - if( conO ){ |
| 727 | | - /* Here, it is assumed that if conI is true, this call will also |
| 728 | | - ** succeed, so there is no need to undo above setup upon failure. */ |
| 729 | | - if( !SetConsoleOutputCP(CP_UTF8) ) goto bail; |
| 730 | | - } |
| 731 | | - console_utf8_in = conI; |
| 732 | | - console_utf8_out = conO; |
| 733 | | - conState = csWork; |
| 719 | + if( (!in_console && !out_console) || mbcs_opted ) return; |
| 720 | + console_attrs((in_console)? 0 : 1, &conState.hConsole, &conState.consoleMode); |
| 721 | + conState.inCodePage = GetConsoleCP(); |
| 722 | + conState.outCodePage = GetConsoleOutputCP(); |
| 723 | + if( in_console ){ |
| 724 | + SetConsoleCP(CP_UTF8); |
| 725 | + DWORD newConsoleMode = conState.consoleMode |
| 726 | + | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; |
| 727 | + SetConsoleMode(conState.hConsole, newConsoleMode); |
| 728 | + conState.infsMode = _setmode(_fileno(stdin), _O_U16TEXT); |
| 729 | + console_utf8_in = 1; |
| 730 | + } |
| 731 | + if( out_console ){ |
| 732 | + SetConsoleOutputCP(CP_UTF8); |
| 733 | + console_utf8_out = 1; |
| 734 | + } |
| 734 | 735 | } |
| 735 | 736 | |
| 736 | 737 | /* |
| 737 | 738 | ** Undo the effects of console_prepare_utf8(), if any. |
| 738 | 739 | */ |
| | @@ -19328,11 +19329,11 @@ |
| 19328 | 19329 | const char *z = 0; |
| 19329 | 19330 | int n = 0; |
| 19330 | 19331 | if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){ |
| 19331 | 19332 | break; |
| 19332 | 19333 | } |
| 19333 | | - n = strlen(z) + scanStatsHeight(p, ii)*3; |
| 19334 | + n = (int)strlen(z) + scanStatsHeight(p, ii)*3; |
| 19334 | 19335 | if( n>nWidth ) nWidth = n; |
| 19335 | 19336 | } |
| 19336 | 19337 | nWidth += 4; |
| 19337 | 19338 | |
| 19338 | 19339 | sqlite3_stmt_scanstatus_v2(p, -1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal); |
| | @@ -19340,16 +19341,16 @@ |
| 19340 | 19341 | i64 nLoop = 0; |
| 19341 | 19342 | i64 nRow = 0; |
| 19342 | 19343 | i64 nCycle = 0; |
| 19343 | 19344 | int iId = 0; |
| 19344 | 19345 | int iPid = 0; |
| 19345 | | - const char *z = 0; |
| 19346 | + const char *zo = 0; |
| 19346 | 19347 | const char *zName = 0; |
| 19347 | 19348 | char *zText = 0; |
| 19348 | 19349 | double rEst = 0.0; |
| 19349 | 19350 | |
| 19350 | | - if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){ |
| 19351 | + if( sqlite3_stmt_scanstatus_v2(p,ii,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&zo) ){ |
| 19351 | 19352 | break; |
| 19352 | 19353 | } |
| 19353 | 19354 | sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_EST,f,(void*)&rEst); |
| 19354 | 19355 | sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NLOOP,f,(void*)&nLoop); |
| 19355 | 19356 | sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NVISIT,f,(void*)&nRow); |
| | @@ -19356,11 +19357,11 @@ |
| 19356 | 19357 | sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NCYCLE,f,(void*)&nCycle); |
| 19357 | 19358 | sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_SELECTID,f,(void*)&iId); |
| 19358 | 19359 | sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid); |
| 19359 | 19360 | sqlite3_stmt_scanstatus_v2(p, ii, SQLITE_SCANSTAT_NAME,f,(void*)&zName); |
| 19360 | 19361 | |
| 19361 | | - zText = sqlite3_mprintf("%s", z); |
| 19362 | + zText = sqlite3_mprintf("%s", zo); |
| 19362 | 19363 | if( nCycle>=0 || nLoop>=0 || nRow>=0 ){ |
| 19363 | 19364 | char *z = 0; |
| 19364 | 19365 | if( nCycle>=0 && nTotal>0 ){ |
| 19365 | 19366 | z = sqlite3_mprintf("%zcycles=%lld [%d%%]", z, |
| 19366 | 19367 | nCycle, ((nCycle*100)+nTotal/2) / nTotal |
| | @@ -28075,11 +28076,12 @@ |
| 28075 | 28076 | #else |
| 28076 | 28077 | stdin_is_interactive = isatty(0); |
| 28077 | 28078 | stdout_is_console = isatty(1); |
| 28078 | 28079 | #endif |
| 28079 | 28080 | #if SHELL_WIN_UTF8_OPT |
| 28080 | | - atexit(console_restore); /* Needs revision for CLI as library call */ |
| 28081 | + probe_console(); /* Check for console I/O and UTF-8 capability. */ |
| 28082 | + if( !mbcs_opted ) atexit(console_restore); |
| 28081 | 28083 | #endif |
| 28082 | 28084 | atexit(sayAbnormalExit); |
| 28083 | 28085 | #ifdef SQLITE_DEBUG |
| 28084 | 28086 | mem_main_enter = sqlite3_memory_used(); |
| 28085 | 28087 | #endif |
| | @@ -28160,20 +28162,10 @@ |
| 28160 | 28162 | SQLITE_SHELL_DBNAME_PROC(&data.pAuxDb->zDbFilename); |
| 28161 | 28163 | warnInmemoryDb = 0; |
| 28162 | 28164 | } |
| 28163 | 28165 | #endif |
| 28164 | 28166 | |
| 28165 | | -#if SHELL_WIN_UTF8_OPT |
| 28166 | | - /* If Windows build and not RT, set default MBCS/UTF-8 translation for |
| 28167 | | - ** console according to detected Windows version. This default may be |
| 28168 | | - ** overridden by a -no-utf8 or (undocumented) -utf8 invocation option. |
| 28169 | | - ** If a runtime check for UTF-8 console I/O capability is devised, |
| 28170 | | - ** that should be preferred over this version check. |
| 28171 | | - */ |
| 28172 | | - mbcs_opted = (IS_WIN10_OR_LATER())? 0 : 1; |
| 28173 | | -#endif |
| 28174 | | - |
| 28175 | 28167 | /* Do an initial pass through the command-line argument to locate |
| 28176 | 28168 | ** the name of the database file, the name of the initialization file, |
| 28177 | 28169 | ** the size of the alternative malloc heap, options affecting commands |
| 28178 | 28170 | ** or SQL run from the command line, and the first command to execute. |
| 28179 | 28171 | */ |
| | @@ -28220,12 +28212,12 @@ |
| 28220 | 28212 | ** we do the actual processing of arguments later in a second pass. |
| 28221 | 28213 | */ |
| 28222 | 28214 | stdin_is_interactive = 0; |
| 28223 | 28215 | }else if( cli_strcmp(z,"-utf8")==0 ){ |
| 28224 | 28216 | #if SHELL_WIN_UTF8_OPT |
| 28225 | | - /* Option accepted, but just specifies default UTF-8 console I/O. */ |
| 28226 | | - mbcs_opted = 0; |
| 28217 | + /* Option accepted, but is ignored except for this diagnostic. */ |
| 28218 | + if( mbcs_opted ) fprintf(stderr, "Cannot do UTF-8 at this console.\n"); |
| 28227 | 28219 | #endif /* SHELL_WIN_UTF8_OPT */ |
| 28228 | 28220 | }else if( cli_strcmp(z,"-no-utf8")==0 ){ |
| 28229 | 28221 | #if SHELL_WIN_UTF8_OPT |
| 28230 | 28222 | mbcs_opted = 1; |
| 28231 | 28223 | #endif /* SHELL_WIN_UTF8_OPT */ |
| | @@ -28367,14 +28359,14 @@ |
| 28367 | 28359 | exit(1); |
| 28368 | 28360 | } |
| 28369 | 28361 | } |
| 28370 | 28362 | #if SHELL_WIN_UTF8_OPT |
| 28371 | 28363 | /* Get indicated Windows console setup done before running invocation commands. */ |
| 28372 | | - if( stdin_is_interactive || stdout_is_console ){ |
| 28364 | + if( in_console || out_console ){ |
| 28373 | 28365 | console_prepare_utf8(); |
| 28374 | 28366 | } |
| 28375 | | - if( !stdin_is_interactive ){ |
| 28367 | + if( !in_console ){ |
| 28376 | 28368 | setBinaryMode(stdin, 0); |
| 28377 | 28369 | } |
| 28378 | 28370 | #endif |
| 28379 | 28371 | |
| 28380 | 28372 | if( data.pAuxDb->zDbFilename==0 ){ |
| 28381 | 28373 | |