Fossil SCM

Update cache-speedups to latest trunk for testing

danshearer 2026-09-28 12:48 UTC cache-speedups merge
Commit 62e4b3a879c706637fe25cd456fd7f0c0e027e5558073c49de79274ca3d0a397
+344 -145
--- extsrc/shell.c
+++ extsrc/shell.c
@@ -634,10 +634,11 @@
634634
va_list ap;
635635
636636
va_start(ap, zFormat);
637637
z = sqlite3_vmprintf(zFormat, ap);
638638
va_end(ap);
639
+ if( z==0 ) return -1;
639640
sqlite3_fputs(z, out);
640641
rc = (int)strlen(z);
641642
sqlite3_free(z);
642643
}else{
643644
/* Writing to a file or other destination, just write bytes without
@@ -655,10 +656,11 @@
655656
/* When writing to the command-prompt in Windows, it is necessary
656657
** to use _O_WTEXT input mode and write UTF-16 characters.
657658
*/
658659
char *z;
659660
z = sqlite3_vmprintf(zFormat, ap);
661
+ if( z==0 ) return -1;
660662
sqlite3_fputs(z, out);
661663
rc = (int)strlen(z);
662664
sqlite3_free(z);
663665
}else{
664666
/* Writing to a file or other destination, just write bytes without
@@ -1328,11 +1330,11 @@
13281330
const char *z = 0;
13291331
int n = 0;
13301332
if( sqlite3_stmt_scanstatus_v2(pS,i,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){
13311333
break;
13321334
}
1333
- n = (int)strlen(z) + qrfStatsHeight(pS,i)*3;
1335
+ n = (z ? (int)strlen(z) : 0) + qrfStatsHeight(pS,i)*3;
13341336
if( n>nWidth ) nWidth = n;
13351337
}
13361338
nWidth += 2;
13371339
13381340
sqlite3_stmt_scanstatus_v2(pS,-1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal);
@@ -25019,10 +25021,24 @@
2501925021
2502025022
/* Flags for Mode.mFlags */
2502125023
#define MFLG_ECHO 0x01 /* Echo inputs to output */
2502225024
#define MFLG_CRLF 0x02 /* Use CR/LF output line endings */
2502325025
#define MFLG_HDR 0x04 /* .header used to change headers on/off */
25026
+
25027
+/* Bit values for ShellState.mStats
25028
+*/
25029
+#define CLISTAT_VMSTEP 0x01 /* Show vmsteps */
25030
+#define CLISTAT_STMT 0x02 /* Stats for each statement */
25031
+#define CLISTAT_MEM 0x04 /* Memory stats */
25032
+#define CLISTAT_XSTMT 0x08 /* Extra statement stats */
25033
+#define CLISTAT_XMEM 0x10 /* Extra memory stats */
25034
+#define CLISTAT_IO 0x20 /* I/O stats (Linux only) */
25035
+#define CLISTAT_ZIPVFS 0x40 /* ZIPVFS stats */
25036
+#define CLISTAT_ON 0x17 /* Usual stats */
25037
+#define CLISTAT_ALL 0x7f /* All stats */
25038
+#define CLISTAT_RESET 0x80 /* Reset stat counters each time */
25039
+
2502425040
2502525041
/* A file that needs to be deleted, but only after a delay.
2502625042
*/
2502725043
typedef struct Unlink {
2502825044
sqlite3_int64 tm; /* Unlink after this time */
@@ -25041,11 +25057,11 @@
2504125057
u8 nEqpLevel; /* Depth of the EQP output graph */
2504225058
u8 eTraceType; /* SHELL_TRACE_* value for type of trace */
2504325059
u8 bSafeMode; /* True to prohibit unsafe operations */
2504425060
u8 bSafeModePersist; /* The long-term value of bSafeMode */
2504525061
u8 eRestoreState; /* See comments above doAutoDetectRestore() */
25046
- unsigned statsOn; /* True to display memory stats before each finalize */
25062
+ u8 mStats; /* Which stats to display (if any) */
2504725063
unsigned mEqpLines; /* Mask of vertical lines in the EQP output graph */
2504825064
u8 nPopOutput; /* Revert .output settings when reaching zero */
2504925065
u8 nPopMode; /* Revert .mode settings when reaching zero */
2505025066
u8 enableTimer; /* Enable the timer. 2: permanently 1: only once */
2505125067
u8 bDelimitNonprint; /* Add \001...\002 around non-printing in prompts */
@@ -25069,10 +25085,12 @@
2506925085
unsigned nTestErr; /* Number of test cases that failed */
2507025086
sqlite3_int64 szMax; /* --maxsize argument to .open */
2507125087
char *zDestTable; /* Name of destination table when MODE_Insert */
2507225088
char *zTempFile; /* Temporary file that might need deleting */
2507325089
char *zErrPrefix; /* Alternative error message prefix */
25090
+ char *zAuthIgnore; /* Authorizer returns SQLITE_IGNORE for glob matches */
25091
+ char *zAuthDeny; /* Authorizer returns SQLITE_DENY for glob matches */
2507425092
sqlite3_stmt *pStmt; /* Current statement if any. */
2507525093
FILE *pLog; /* Write log output here */
2507625094
char *azPrompt[2]; /* Main and continuation prompt strings */
2507725095
Mode mode; /* Current display mode */
2507825096
Mode *aModeStack; /* Backups */
@@ -27477,28 +27495,24 @@
2747727495
"TRANSACTION", "UPDATE", "ATTACH",
2747827496
"DETACH", "ALTER_TABLE", "REINDEX",
2747927497
"ANALYZE", "CREATE_VTABLE", "DROP_VTABLE",
2748027498
"FUNCTION", "SAVEPOINT", "RECURSIVE"
2748127499
};
27482
- int i;
27483
- const char *az[4];
27484
- az[0] = zA1;
27485
- az[1] = zA2;
27486
- az[2] = zA3;
27487
- az[3] = zA4;
27488
- cli_printf(p->out, "authorizer: %s", azAction[op]);
27489
- for(i=0; i<4; i++){
27490
- cli_puts(" ", p->out);
27491
- if( az[i] ){
27492
- output_c_string(p->out, az[i]);
27493
- }else{
27494
- cli_puts("NULL", p->out);
27495
- }
27496
- }
27497
- cli_puts("\n", p->out);
27500
+ char *zResult = "OK";
27501
+ int rc = SQLITE_OK;
27502
+ char *zMsg = sqlite3_mprintf("%s %Q %Q %Q %Q", azAction[op],zA1,zA2,zA3,zA4);
27503
+ if( p->zAuthDeny && sqlite3_strglob(p->zAuthDeny, zMsg)==0 ){
27504
+ zResult = "DENY";
27505
+ rc = SQLITE_DENY;
27506
+ }else if( p->zAuthIgnore && sqlite3_strglob(p->zAuthIgnore, zMsg)==0 ){
27507
+ zResult = "IGNORE";
27508
+ rc = SQLITE_IGNORE;
27509
+ }
27510
+ cli_printf(p->out, "authorizer: %s -> %s\n", zMsg, zResult);
27511
+ sqlite3_free(zMsg);
2749827512
if( p->bSafeMode ) (void)safeModeAuth(pClientData, op, zA1, zA2, zA3, zA4);
27499
- return SQLITE_OK;
27513
+ return rc;
2750027514
}
2750127515
#endif
2750227516
2750327517
/*
2750427518
** Print a schema statement. This is a helper routine to dump_callback().
@@ -27978,20 +27992,22 @@
2797827992
/*
2797927993
** Display memory stats.
2798027994
*/
2798127995
static int display_stats(
2798227996
sqlite3 *db, /* Database to query */
27983
- ShellState *pArg, /* Pointer to ShellState */
27984
- int bReset /* True to reset the stats */
27997
+ ShellState *pArg /* Pointer to ShellState */
2798527998
){
2798627999
int iCur, iHiwtr;
2798728000
sqlite3_int64 iCur64, iHiwtr64;
2798828001
FILE *out;
28002
+ int bReset;
28003
+
2798928004
if( pArg==0 || pArg->out==0 ) return 0;
2799028005
out = pArg->out;
28006
+ bReset = (pArg->mStats & CLISTAT_RESET)!=0;
2799128007
27992
- if( pArg->pStmt && pArg->statsOn==2 ){
28008
+ if( pArg->pStmt && (pArg->mStats & CLISTAT_XSTMT)!=0 ){
2799328009
int nCol, i, x;
2799428010
sqlite3_stmt *pStmt = pArg->pStmt;
2799528011
char z[100];
2799628012
nCol = sqlite3_column_count(pStmt);
2799728013
cli_printf(out, "%-36s %d\n", "Number of output columns:", nCol);
@@ -28012,95 +28028,96 @@
2801228028
cli_printf(out, "%-36s %s\n", z,sqlite3_column_origin_name(pStmt,i));
2801328029
#endif
2801428030
}
2801528031
}
2801628032
28017
- if( pArg->statsOn==3 ){
28018
- if( pArg->pStmt ){
28019
- iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP,bReset);
28020
- cli_printf(out, "VM-steps: %d\n", iCur);
28021
- }
28022
- return 0;
28023
- }
28024
-
28025
- displayStatLine(out, "Memory Used:",
28026
- "%lld (max %lld) bytes", SQLITE_STATUS_MEMORY_USED, bReset);
28027
- displayStatLine(out, "Number of Outstanding Allocations:",
28028
- "%lld (max %lld)", SQLITE_STATUS_MALLOC_COUNT, bReset);
28029
- if( pArg->shellFlgs & SHFLG_Pagecache ){
28030
- displayStatLine(out, "Number of Pcache Pages Used:",
28031
- "%lld (max %lld) pages", SQLITE_STATUS_PAGECACHE_USED, bReset);
28032
- }
28033
- displayStatLine(out, "Number of Pcache Overflow Bytes:",
28034
- "%lld (max %lld) bytes", SQLITE_STATUS_PAGECACHE_OVERFLOW, bReset);
28035
- displayStatLine(out, "Largest Allocation:",
28036
- "%lld bytes", SQLITE_STATUS_MALLOC_SIZE, bReset);
28037
- displayStatLine(out, "Largest Pcache Allocation:",
28038
- "%lld bytes", SQLITE_STATUS_PAGECACHE_SIZE, bReset);
28033
+ if( pArg->pStmt && (pArg->mStats & CLISTAT_VMSTEP)!=0 ){
28034
+ iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP,bReset);
28035
+ cli_printf(out, "VM-steps: %d\n", iCur);
28036
+ }
28037
+
28038
+ if( (pArg->mStats & CLISTAT_MEM)!=0 ){
28039
+ displayStatLine(out, "Memory Used:",
28040
+ "%lld (max %lld) bytes", SQLITE_STATUS_MEMORY_USED, bReset);
28041
+ displayStatLine(out, "Number of Outstanding Allocations:",
28042
+ "%lld (max %lld)", SQLITE_STATUS_MALLOC_COUNT, bReset);
28043
+ }
28044
+ if( (pArg->mStats & CLISTAT_XMEM)!=0 ){
28045
+ if( pArg->shellFlgs & SHFLG_Pagecache ){
28046
+ displayStatLine(out, "Number of Pcache Pages Used:",
28047
+ "%lld (max %lld) pages", SQLITE_STATUS_PAGECACHE_USED, bReset);
28048
+ }
28049
+ displayStatLine(out, "Number of Pcache Overflow Bytes:",
28050
+ "%lld (max %lld) bytes", SQLITE_STATUS_PAGECACHE_OVERFLOW, bReset);
28051
+ displayStatLine(out, "Largest Allocation:",
28052
+ "%lld bytes", SQLITE_STATUS_MALLOC_SIZE, bReset);
28053
+ displayStatLine(out, "Largest Pcache Allocation:",
28054
+ "%lld bytes", SQLITE_STATUS_PAGECACHE_SIZE, bReset);
2803928055
#ifdef YYTRACKMAXSTACKDEPTH
28040
- displayStatLine(out, "Deepest Parser Stack:",
28041
- "%lld (max %lld)", SQLITE_STATUS_PARSER_STACK, bReset);
28042
-#endif
28043
-
28044
- if( db ){
28045
- if( pArg->shellFlgs & SHFLG_Lookaside ){
28046
- iHiwtr = iCur = -1;
28047
- sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED,
28048
- &iCur, &iHiwtr, bReset);
28049
- cli_printf(out,
28050
- "Lookaside Slots Used: %d (max %d)\n", iCur, iHiwtr);
28051
- sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT,
28052
- &iCur, &iHiwtr, bReset);
28053
- cli_printf(out,
28054
- "Successful lookaside attempts: %d\n", iHiwtr);
28055
- sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE,
28056
- &iCur, &iHiwtr, bReset);
28057
- cli_printf(out,
28058
- "Lookaside failures due to size: %d\n", iHiwtr);
28059
- sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL,
28060
- &iCur, &iHiwtr, bReset);
28061
- cli_printf(out,
28062
- "Lookaside failures due to OOM: %d\n", iHiwtr);
28063
- }
28064
- iHiwtr = iCur = -1;
28065
- sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset);
28066
- cli_printf(out,
28067
- "Pager Heap Usage: %d bytes\n", iCur);
28068
- iHiwtr = iCur = -1;
28069
- sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1);
28070
- cli_printf(out,
28071
- "Page cache hits: %d\n", iCur);
28072
- iHiwtr = iCur = -1;
28073
- sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1);
28074
- cli_printf(out,
28075
- "Page cache misses: %d\n", iCur);
28076
- iHiwtr64 = iCur64 = -1;
28077
- sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64,
28078
- 0);
28079
- iHiwtr = iCur = -1;
28080
- sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1);
28081
- cli_printf(out,
28082
- "Page cache writes: %d\n", iCur);
28083
- iHiwtr = iCur = -1;
28084
- sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1);
28085
- cli_printf(out,
28086
- "Page cache spills: %d\n", iCur);
28087
- cli_printf(out,
28088
- "Temporary data spilled to disk: %lld\n", iCur64);
28089
- sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64,
28090
- 1);
28091
- iHiwtr = iCur = -1;
28092
- sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset);
28093
- cli_printf(out,
28094
- "Schema Heap Usage: %d bytes\n", iCur);
28095
- iHiwtr = iCur = -1;
28096
- sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset);
28097
- cli_printf(out,
28098
- "Statement Heap/Lookaside Usage: %d bytes\n", iCur);
28099
- }
28100
-
28101
- if( pArg->pStmt ){
28056
+ displayStatLine(out, "Deepest Parser Stack:",
28057
+ "%lld (max %lld)", SQLITE_STATUS_PARSER_STACK, bReset);
28058
+#endif
28059
+
28060
+ if( db ){
28061
+ if( pArg->shellFlgs & SHFLG_Lookaside ){
28062
+ iHiwtr = iCur = -1;
28063
+ sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED,
28064
+ &iCur, &iHiwtr, bReset);
28065
+ cli_printf(out,
28066
+ "Lookaside Slots Used: %d (max %d)\n", iCur, iHiwtr);
28067
+ sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT,
28068
+ &iCur, &iHiwtr, bReset);
28069
+ cli_printf(out,
28070
+ "Successful lookaside attempts: %d\n", iHiwtr);
28071
+ sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE,
28072
+ &iCur, &iHiwtr, bReset);
28073
+ cli_printf(out,
28074
+ "Lookaside failures due to size: %d\n", iHiwtr);
28075
+ sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL,
28076
+ &iCur, &iHiwtr, bReset);
28077
+ cli_printf(out,
28078
+ "Lookaside failures due to OOM: %d\n", iHiwtr);
28079
+ }
28080
+ iHiwtr = iCur = -1;
28081
+ sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset);
28082
+ cli_printf(out,
28083
+ "Pager Heap Usage: %d bytes\n", iCur);
28084
+ iHiwtr = iCur = -1;
28085
+ sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1);
28086
+ cli_printf(out,
28087
+ "Page cache hits: %d\n", iCur);
28088
+ iHiwtr = iCur = -1;
28089
+ sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1);
28090
+ cli_printf(out,
28091
+ "Page cache misses: %d\n", iCur);
28092
+ iHiwtr64 = iCur64 = -1;
28093
+ sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64,
28094
+ 0);
28095
+ iHiwtr = iCur = -1;
28096
+ sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1);
28097
+ cli_printf(out,
28098
+ "Page cache writes: %d\n", iCur);
28099
+ iHiwtr = iCur = -1;
28100
+ sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1);
28101
+ cli_printf(out,
28102
+ "Page cache spills: %d\n", iCur);
28103
+ cli_printf(out,
28104
+ "Temporary data spilled to disk: %lld\n", iCur64);
28105
+ sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64,
28106
+ 1);
28107
+ iHiwtr = iCur = -1;
28108
+ sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset);
28109
+ cli_printf(out,
28110
+ "Schema Heap Usage: %d bytes\n", iCur);
28111
+ iHiwtr = iCur = -1;
28112
+ sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset);
28113
+ cli_printf(out,
28114
+ "Statement Heap/Lookaside Usage: %d bytes\n", iCur);
28115
+ }
28116
+ }
28117
+
28118
+ if( pArg->pStmt && (pArg->mStats & CLISTAT_STMT)!=0 ){
2810228119
int iHit, iMiss;
2810328120
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP,
2810428121
bReset);
2810528122
cli_printf(out,
2810628123
"Fullscan Steps: %d\n", iCur);
@@ -28116,13 +28133,10 @@
2811628133
bReset);
2811728134
if( iHit || iMiss ){
2811828135
cli_printf(out,
2811928136
"Bloom filter bypass taken: %d/%d\n", iHit, iHit+iMiss);
2812028137
}
28121
- iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
28122
- cli_printf(out,
28123
- "Virtual Machine Steps: %d\n", iCur);
2812428138
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset);
2812528139
cli_printf(out,
2812628140
"Reprepare operations: %d\n", iCur);
2812728141
iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_RUN, bReset);
2812828142
cli_printf(out,
@@ -28131,14 +28145,20 @@
2813128145
cli_printf(out,
2813228146
"Memory used by prepared stmt: %d\n", iCur);
2813328147
}
2813428148
2813528149
#ifdef __linux__
28136
- displayLinuxIoStats(pArg->out);
28150
+ if( (pArg->mStats & CLISTAT_IO)!=0 ){
28151
+ displayLinuxIoStats(pArg->out);
28152
+ }
2813728153
#endif
2813828154
28139
- /* Do not remove this machine readable comment: extra-stats-output-here */
28155
+#ifdef SQLITE_ENABLE_ZIPVFS_VTAB
28156
+ if( (pArg->mStats & CLISTAT_ZIPVFS)!=0 ){
28157
+ /* Do not remove this machine readable comment: extra-stats-output-here */
28158
+ }
28159
+#endif
2814028160
2814128161
return 0;
2814228162
}
2814328163
2814428164
/*
@@ -28540,12 +28560,12 @@
2854028560
spec.eStyle = eStyle;
2854128561
sqlite3_format_query_result(pStmt, &spec, pzErrMsg);
2854228562
}
2854328563
2854428564
/* print usage stats if stats on */
28545
- if( pArg && pArg->statsOn ){
28546
- display_stats(db, pArg, 0);
28565
+ if( pArg && pArg->mStats!=0 ){
28566
+ display_stats(db, pArg);
2854728567
}
2854828568
2854928569
/* print loop-counters if required */
2855028570
if( pArg && pArg->mode.scanstatsOn ){
2855128571
char *zErr = 0;
@@ -28928,10 +28948,13 @@
2892828948
" See also:",
2892928949
" http://sqlite.org/cli.html#sqlite_archive_support",
2893028950
#endif
2893128951
#ifndef SQLITE_OMIT_AUTHORIZATION
2893228952
".auth ON|OFF Show authorizer callbacks",
28953
+ " Options:",
28954
+ " --deny GLOB Return SQLITE_DENY if pattern matches",
28955
+ " --ignore GLOB Return SQLITE_IGNORE if pattern matches",
2893328956
#endif
2893428957
#ifndef SQLITE_SHELL_FIDDLE
2893528958
".backup ?DB? FILE Backup DB (default \"main\") to FILE",
2893628959
" Options:",
2893728960
" --append Use the appendvfs",
@@ -29212,11 +29235,13 @@
2921229235
{ ".mode",
2921329236
"USAGE: .mode [MODE] [OPTIONS]\n"
2921429237
"\n"
2921529238
"Change the output mode to MODE and/or apply OPTIONS to the output mode.\n"
2921629239
"Arguments are processed from left to right. If no arguments, show the\n"
29217
-"current output mode and relevant options.\n"
29240
+"current output mode and relevant options. Use the --list option to get\n"
29241
+"a list of available MODE values. Use -v to show all the current output\n"
29242
+"mode settings.\n"
2921829243
"\n"
2921929244
"Options:\n"
2922029245
" --align STRING Set the alignment of text in columnar modes\n"
2922129246
" String consists of characters 'L', 'C', 'R'\n"
2922229247
" meaning \"left\", \"centered\", and \"right\", with\n"
@@ -29262,11 +29287,11 @@
2926229287
" to be N characters. An attempt may be made to\n"
2926329288
" wrap output text to fit within this limit. Zero\n"
2926429289
" means \"no limit\". Or N can be \"auto\" to set the\n"
2926529290
" width automatically.\n"
2926629291
" --tablename NAME Set the name of the table for \"insert\" mode.\n"
29267
-" --tag NAME Save mode to the left as NAME.\n"
29292
+" --tag NAME Save current settings as a new mode called NAME.\n"
2926829293
" --textjsonb BOOLEAN If enabled, JSONB text is displayed as text JSON.\n"
2926929294
" --title ARG Whether or not to show column headers, and if so\n"
2927029295
" how to encode them. ARG can be \"off\", \"on\",\n"
2927129296
" \"always\", \"sql\", \"csv\", \"html\", \"tcl\", or \"json\".\n"
2927229297
" --titlelimit N Limit the length of column titles to N characters.\n"
@@ -29348,10 +29373,31 @@
2934829373
" --keep Do not reset the testcase. More .check commands\n"
2934929374
" will follow.\n"
2935029375
" --notglob Output should not match PATTERN\n"
2935129376
" --show Write testcase output to the screen, for debugging.\n"
2935229377
},
29378
+ { ".stats",
29379
+"USAGE: .stats [OPTIONS] ...\n"
29380
+"\n"
29381
+"Show status information about the database and database connection.\n"
29382
+"The stats setting is persistent, unless the --once option is used.\n"
29383
+"Options are cumulative and are processed from left to right.\n"
29384
+"If no options are given, that is the same as \".stats all --once\".\n"
29385
+"\n"
29386
+"Options:\n"
29387
+" all Show all available stats.\n"
29388
+" io Show I/O stats (Linux only).\n"
29389
+" mem Show basic memory usage.\n"
29390
+" off Show nothing. Turn off stat display.\n"
29391
+" on Shorthand for \"stmt xmem\".\n"
29392
+" --once Show the stats once. Do not change the default stats display.\n"
29393
+" reset Reset \"max\" values after next display of each that value.\n"
29394
+" stmt Show information about the last SQL statement.\n"
29395
+" vmstep Show the number of virtual machine steps taken.\n"
29396
+" xmem Show detailed memory status information\n"
29397
+" xstmt Show details about each SQL statement\n"
29398
+ },
2935329399
{ ".testcase",
2935429400
"USAGE: .testcase [OPTIONS] NAME\n"
2935529401
"\n"
2935629402
"Start a new test case identified by NAME. All output\n"
2935729403
"through the next \".check\" command is captured for comparison. See the\n"
@@ -33495,11 +33541,13 @@
3349533541
**
3349633542
** USAGE: .mode [MODE] [OPTIONS]
3349733543
**
3349833544
** Change the output mode to MODE and/or apply OPTIONS to the output mode.
3349933545
** Arguments are processed from left to right. If no arguments, show the
33500
-** current output mode and relevant options.
33546
+** current output mode and relevant options. Use the --list option to get
33547
+** a list of available MODE values. Use -v to show all the current output
33548
+** mode settings.
3350133549
**
3350233550
** Options:
3350333551
** --align STRING Set the alignment of text in columnar modes
3350433552
** String consists of characters 'L', 'C', 'R'
3350533553
** meaning "left", "centered", and "right", with
@@ -33545,11 +33593,11 @@
3354533593
** to be N characters. An attempt may be made to
3354633594
** wrap output text to fit within this limit. Zero
3354733595
** means "no limit". Or N can be "auto" to set the
3354833596
** width automatically.
3354933597
** --tablename NAME Set the name of the table for "insert" mode.
33550
-** --tag NAME Save mode to the left as NAME.
33598
+** --tag NAME Save current settings as a new mode called NAME.
3355133599
** --textjsonb BOOLEAN If enabled, JSONB text is displayed as text JSON.
3355233600
** --title ARG Whether or not to show column headers, and if so
3355333601
** how to encode them. ARG can be "off", "on",
3355433602
** "always", "sql", "csv", "html", "tcl", or "json".
3355533603
** --titlelimit N Limit the length of column titles to N characters.
@@ -34530,10 +34578,101 @@
3453034578
output_reset(p);
3453134579
p->zTestcase[0] = 0;
3453234580
}
3453334581
return 0;
3453434582
}
34583
+
34584
+/*
34585
+** DOT-COMMAND: .stats
34586
+** USAGE: .stats [OPTIONS] ...
34587
+**
34588
+** Show status information about the database and database connection.
34589
+** The stats setting is persistent, unless the --once option is used.
34590
+** Options are cumulative and are processed from left to right.
34591
+** If no options are given, that is the same as ".stats all --once".
34592
+**
34593
+** Options:
34594
+** all Show all available stats.
34595
+** io Show I/O stats (Linux only).
34596
+** mem Show basic memory usage.
34597
+** off Show nothing. Turn off stat display.
34598
+** on Shorthand for "stmt xmem".
34599
+** --once Show the stats once. Do not change the default stats display.
34600
+** reset Reset "max" values after next display of each that value.
34601
+** stmt Show information about the last SQL statement.
34602
+** vmstep Show the number of virtual machine steps taken.
34603
+** xmem Show detailed memory status information
34604
+** xstmt Show details about each SQL statement
34605
+*/
34606
+static int dotCmdStats(ShellState *p){
34607
+ int nArg = p->dot.nArg; /* Number of arguments */
34608
+ char **azArg = p->dot.azArg; /* Text of the arguments */
34609
+ int ii; /* Loop counter */
34610
+ int bOnce = 0; /* --once option seen */
34611
+ u8 mNew = 0; /* New status setting */
34612
+ u8 savedStats = p->mStats; /* Current stats setting */
34613
+
34614
+ if( nArg==1 ){
34615
+ p->mStats = CLISTAT_ALL;
34616
+ display_stats(p->db, p);
34617
+ p->mStats = savedStats;
34618
+ return 0;
34619
+ }
34620
+ for(ii=1; ii<nArg; ii++){
34621
+ const char *z = azArg[ii];
34622
+ if( z[0]=='-' ){
34623
+ z++;
34624
+ if( z[0]=='-' && z[1]!=0 ) z++;
34625
+ }
34626
+ if( cli_strcmp(z,"stmt")==0 ){
34627
+ mNew |= CLISTAT_STMT|CLISTAT_VMSTEP;
34628
+ }else if( cli_strcmp(z,"xstmt")==0 ){
34629
+ mNew |= CLISTAT_STMT|CLISTAT_XSTMT|CLISTAT_VMSTEP;
34630
+ }else if( cli_strcmp(z,"vmstep")==0 ){
34631
+ mNew |= CLISTAT_VMSTEP;
34632
+ }else if( cli_strcmp(z,"mem")==0 ){
34633
+ mNew |= CLISTAT_MEM;
34634
+ }else if( cli_strcmp(z,"xmem")==0 ){
34635
+ mNew |= CLISTAT_XMEM|CLISTAT_MEM;
34636
+ }else if( cli_strcmp(z,"io")==0 ){
34637
+ mNew |= CLISTAT_IO;
34638
+ }else if( cli_strcmp(z,"all")==0 ){
34639
+ mNew |= CLISTAT_ALL;
34640
+#ifndef __linux__
34641
+ dotCmdError(p, ii, "available only on Linux", 0);
34642
+ return 1;
34643
+#endif
34644
+#ifdef SQLITE_ENABLE_ZIPVFS_VTAB
34645
+ }else if( cli_strcmp(z,"zipvfs")==0 ){
34646
+ mNew |= CLISTAT_ZIPVFS;
34647
+#endif
34648
+ }else if( cli_strcmp(z,"reset")==0 ){
34649
+ mNew |= CLISTAT_RESET;
34650
+ }else if( cli_strcmp(z,"once")==0 ){
34651
+ bOnce = 1;
34652
+ }else if( IsDigit(z[0]) ){
34653
+ if( booleanValue(z) ){
34654
+ mNew |= CLISTAT_ON;
34655
+ }else{
34656
+ mNew = 0;
34657
+ }
34658
+ }else if( pickStr(z, 0, "on", "yes", "ON", "YES", "")>=0 ){
34659
+ mNew |= CLISTAT_ON;
34660
+ }else if( pickStr(z, 0, "off", "no", "OFF", "NO", "")>=0 ){
34661
+ mNew = 0;
34662
+ }else{
34663
+ dotCmdError(p, ii, "unknown argument", 0);
34664
+ return 1;
34665
+ }
34666
+ }
34667
+ p->mStats = mNew;
34668
+ if( bOnce ){
34669
+ display_stats(p->db, p);
34670
+ p->mStats = savedStats;
34671
+ }
34672
+ return 0;
34673
+}
3453534674
3453634675
/*
3453734676
** DOT-COMMAND: .testcase
3453834677
** USAGE: .testcase [OPTIONS] NAME
3453934678
**
@@ -34682,17 +34821,61 @@
3468234821
c = azArg[0][0];
3468334822
clearTempFile(p,0,0);
3468434823
3468534824
#ifndef SQLITE_OMIT_AUTHORIZATION
3468634825
if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){
34687
- if( nArg!=2 ){
34688
- cli_printf(stderr, "Usage: .auth ON|OFF\n");
34826
+ int ii;
34827
+ int eOnOff = -1;
34828
+ open_db(p, 0);
34829
+ for(ii=1; ii<nArg && rc==0; ii++){
34830
+ const char *z = azArg[ii];
34831
+ if( z[0]=='-' && z[1]=='-' ) z++;
34832
+ if( strcmp(z,"-deny")==0 ){
34833
+ ii++;
34834
+ if( ii>=nArg ){
34835
+ dotCmdError(p, ii-1, "missing argument", 0);
34836
+ rc = 1;
34837
+ goto meta_command_exit;
34838
+ }else{
34839
+ free(p->zAuthDeny);
34840
+ p->zAuthDeny = 0;
34841
+ if( azArg[ii][0] ){
34842
+ p->zAuthDeny = strdup(azArg[ii]);
34843
+ shell_check_oom(p->zAuthDeny);
34844
+ }
34845
+ }
34846
+ }else
34847
+ if( strcmp(z,"-ignore")==0 ){
34848
+ ii++;
34849
+ if( ii>=nArg ){
34850
+ dotCmdError(p, ii-1, "missing argument", 0);
34851
+ rc = 1;
34852
+ goto meta_command_exit;
34853
+ }else{
34854
+ free(p->zAuthIgnore);
34855
+ p->zAuthIgnore = 0;
34856
+ if( azArg[ii][0] ){
34857
+ p->zAuthIgnore = strdup(azArg[ii]);
34858
+ shell_check_oom(p->zAuthIgnore);
34859
+ }
34860
+ }
34861
+ }else
34862
+ if( eOnOff<0 ){
34863
+ eOnOff = booleanValue(z);
34864
+ }else
34865
+ {
34866
+ dotCmdError(p, ii, "unknown argument", 0);
34867
+ rc = 1;
34868
+ goto meta_command_exit;
34869
+ }
34870
+ }
34871
+ if( eOnOff<0 ){
34872
+ cli_printf(stderr, "Usage: .auth ON|OFF [--deny GLOB] [--ignore]\n");
3468934873
rc = 1;
3469034874
goto meta_command_exit;
3469134875
}
34692
- open_db(p, 0);
34693
- if( booleanValue(azArg[1]) ){
34876
+ if( eOnOff ){
3469434877
sqlite3_set_authorizer(p->db, shellAuth, p);
3469534878
}else if( p->bSafeModePersist ){
3469634879
sqlite3_set_authorizer(p->db, safeModeAuth, p);
3469734880
}else{
3469834881
sqlite3_set_authorizer(p->db, 0, 0);
@@ -37044,11 +37227,10 @@
3704437227
}else
3704537228
#endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) */
3704637229
3704737230
if( c=='s' && cli_strncmp(azArg[0], "show", n)==0 ){
3704837231
static const char *azBool[] = { "off", "on", "trigger", "full"};
37049
- const char *zOut;
3705037232
int i;
3705137233
if( nArg!=1 ){
3705237234
eputz("Usage: .show\n");
3705337235
rc = 1;
3705437236
goto meta_command_exit;
@@ -37084,17 +37266,46 @@
3708437266
output_c_string(p->out, p->mode.spec.zColumnSep);
3708537267
cli_puts("\n", p->out);
3708637268
cli_printf(p->out, "%12.12s: ", "rowseparator");
3708737269
output_c_string(p->out, p->mode.spec.zRowSep);
3708837270
cli_puts("\n", p->out);
37089
- switch( p->statsOn ){
37090
- case 0: zOut = "off"; break;
37091
- default: zOut = "on"; break;
37092
- case 2: zOut = "stmt"; break;
37093
- case 3: zOut = "vmstep"; break;
37271
+ cli_printf(p->out, "%12.12s:", "stats");
37272
+ if( p->mStats==0 ){
37273
+ cli_printf(p->out, " off\n");
37274
+ }else{
37275
+ u8 mStats = p->mStats;
37276
+ if( (mStats & CLISTAT_ALL)==CLISTAT_ALL ){
37277
+ cli_printf(p->out, " all");
37278
+ mStats &= ~CLISTAT_ALL;
37279
+ }
37280
+ if( (mStats & CLISTAT_ON)==CLISTAT_ON ){
37281
+ cli_printf(p->out, " on");
37282
+ mStats &= ~CLISTAT_ON;
37283
+ }
37284
+ if( mStats & CLISTAT_XSTMT ){
37285
+ cli_printf(p->out, " xstmt");
37286
+ }else if( mStats & CLISTAT_STMT ){
37287
+ cli_printf(p->out, " stmt");
37288
+ }else if( mStats & CLISTAT_VMSTEP ){
37289
+ cli_printf(p->out, " vmstep");
37290
+ }
37291
+ if( mStats & CLISTAT_XMEM ){
37292
+ cli_printf(p->out, " xmem");
37293
+ }else if( mStats & CLISTAT_MEM ){
37294
+ cli_printf(p->out, " mem");
37295
+ }
37296
+ if( mStats & CLISTAT_IO ){
37297
+ cli_printf(p->out, " io");
37298
+ }
37299
+ if( mStats & CLISTAT_ZIPVFS ){
37300
+ cli_printf(p->out, " zipvfs");
37301
+ }
37302
+ if( mStats & CLISTAT_RESET ){
37303
+ cli_printf(p->out, " reset");
37304
+ }
37305
+ cli_printf(p->out,"\n");
3709437306
}
37095
- cli_printf(p->out, "%12.12s: %s\n","stats", zOut);
3709637307
cli_printf(p->out, "%12.12s: ", "width");
3709737308
for(i=0; i<p->mode.spec.nWidth; i++){
3709837309
cli_printf(p->out, "%d ", (int)p->mode.spec.aWidth[i]);
3709937310
}
3710037311
cli_puts("\n", p->out);
@@ -37101,24 +37312,11 @@
3710137312
cli_printf(p->out, "%12.12s: %s\n", "filename",
3710237313
p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : "");
3710337314
}else
3710437315
3710537316
if( c=='s' && cli_strncmp(azArg[0], "stats", n)==0 ){
37106
- if( nArg==2 ){
37107
- if( cli_strcmp(azArg[1],"stmt")==0 ){
37108
- p->statsOn = 2;
37109
- }else if( cli_strcmp(azArg[1],"vmstep")==0 ){
37110
- p->statsOn = 3;
37111
- }else{
37112
- p->statsOn = (u8)booleanValue(azArg[1]);
37113
- }
37114
- }else if( nArg==1 ){
37115
- display_stats(p->db, p, 0);
37116
- }else{
37117
- eputz("Usage: .stats ?on|off|stmt|vmstep?\n");
37118
- rc = 1;
37119
- }
37317
+ rc = dotCmdStats(p);
3712037318
}else
3712137319
3712237320
if( (c=='t' && n>1 && cli_strncmp(azArg[0], "tables", n)==0) ){
3712337321
sqlite3_stmt *pStmt;
3712437322
sqlite3_str *pSql;
@@ -37273,11 +37471,10 @@
3727337471
} aLabel[] = {
3727437472
{ 0x00000001, 1, "QueryFlattener" },
3727537473
{ 0x00000001, 0, "Flatten" },
3727637474
{ 0x00000002, 1, "WindowFunc" },
3727737475
{ 0x00000004, 1, "GroupByOrder" },
37278
- { 0x00000008, 1, "FactorOutConst" },
3727937476
{ 0x00000010, 1, "DistinctOpt" },
3728037477
{ 0x00000020, 1, "CoverIdxScan" },
3728137478
{ 0x00000040, 1, "OrderByIdxJoin" },
3728237479
{ 0x00000080, 1, "Transitive" },
3728337480
{ 0x00000100, 1, "OmitNoopJoin" },
@@ -37290,11 +37487,10 @@
3729037487
{ 0x00008000, 1, "PropagateConst" },
3729137488
{ 0x00010000, 1, "MinMaxOpt" },
3729237489
{ 0x00020000, 1, "SeekScan" },
3729337490
{ 0x00040000, 1, "OmitOrderBy" },
3729437491
{ 0x00080000, 1, "BloomFilter" },
37295
- { 0x00100000, 1, "BloomPulldown" },
3729637492
{ 0x00200000, 1, "BalancedMerge" },
3729737493
{ 0x00400000, 1, "ReleaseReg" },
3729837494
{ 0x00800000, 1, "FlttnUnionAll" },
3729937495
{ 0x01000000, 1, "IndexedEXpr" },
3730037496
{ 0x02000000, 1, "Coroutines" },
@@ -37301,10 +37497,11 @@
3730137497
{ 0x04000000, 1, "NullUnusedCols" },
3730237498
{ 0x08000000, 1, "OnePass" },
3730337499
{ 0x10000000, 1, "OrderBySubq" },
3730437500
{ 0x20000000, 1, "StarQuery" },
3730537501
{ 0x40000000, 1, "ExistsToJoin" },
37502
+ { 0x80000000, 1, "UnionLimit" },
3730637503
{ 0xffffffff, 0, "All" },
3730737504
};
3730837505
unsigned int curOpt;
3730937506
unsigned int newOpt;
3731037507
unsigned int m;
@@ -39151,11 +39348,11 @@
3915139348
}else if( cli_strcmp(z,"-eqp")==0 ){
3915239349
data.mode.autoEQP = AUTOEQP_on;
3915339350
}else if( cli_strcmp(z,"-eqpfull")==0 ){
3915439351
data.mode.autoEQP = AUTOEQP_full;
3915539352
}else if( cli_strcmp(z,"-stats")==0 ){
39156
- data.statsOn = 1;
39353
+ data.mStats = CLISTAT_ON;
3915739354
}else if( cli_strcmp(z,"-scanstats")==0 ){
3915839355
data.mode.scanstatsOn = 1;
3915939356
}else if( cli_strcmp(z,"-backslash")==0 ){
3916039357
/* Undocumented command-line option: -backslash
3916139358
** Causes C-style backslash escapes to be evaluated in SQL statements
@@ -39410,10 +39607,12 @@
3941039607
free(data.dot.azArg);
3941139608
free(data.dot.aiOfst);
3941239609
free(data.dot.abQuot);
3941339610
free(data.azPrompt[0]);
3941439611
free(data.azPrompt[1]);
39612
+ free(data.zAuthDeny);
39613
+ free(data.zAuthIgnore);
3941539614
if( data.nTestRun ){
3941639615
sqlite3_fprintf(stdout, "%d test%s run with %d error%s\n",
3941739616
data.nTestRun, data.nTestRun==1 ? "" : "s",
3941839617
data.nTestErr, data.nTestErr==1 ? "" : "s");
3941939618
fflush(stdout);
3942039619
--- extsrc/shell.c
+++ extsrc/shell.c
@@ -634,10 +634,11 @@
634 va_list ap;
635
636 va_start(ap, zFormat);
637 z = sqlite3_vmprintf(zFormat, ap);
638 va_end(ap);
 
639 sqlite3_fputs(z, out);
640 rc = (int)strlen(z);
641 sqlite3_free(z);
642 }else{
643 /* Writing to a file or other destination, just write bytes without
@@ -655,10 +656,11 @@
655 /* When writing to the command-prompt in Windows, it is necessary
656 ** to use _O_WTEXT input mode and write UTF-16 characters.
657 */
658 char *z;
659 z = sqlite3_vmprintf(zFormat, ap);
 
660 sqlite3_fputs(z, out);
661 rc = (int)strlen(z);
662 sqlite3_free(z);
663 }else{
664 /* Writing to a file or other destination, just write bytes without
@@ -1328,11 +1330,11 @@
1328 const char *z = 0;
1329 int n = 0;
1330 if( sqlite3_stmt_scanstatus_v2(pS,i,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){
1331 break;
1332 }
1333 n = (int)strlen(z) + qrfStatsHeight(pS,i)*3;
1334 if( n>nWidth ) nWidth = n;
1335 }
1336 nWidth += 2;
1337
1338 sqlite3_stmt_scanstatus_v2(pS,-1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal);
@@ -25019,10 +25021,24 @@
25019
25020 /* Flags for Mode.mFlags */
25021 #define MFLG_ECHO 0x01 /* Echo inputs to output */
25022 #define MFLG_CRLF 0x02 /* Use CR/LF output line endings */
25023 #define MFLG_HDR 0x04 /* .header used to change headers on/off */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25024
25025 /* A file that needs to be deleted, but only after a delay.
25026 */
25027 typedef struct Unlink {
25028 sqlite3_int64 tm; /* Unlink after this time */
@@ -25041,11 +25057,11 @@
25041 u8 nEqpLevel; /* Depth of the EQP output graph */
25042 u8 eTraceType; /* SHELL_TRACE_* value for type of trace */
25043 u8 bSafeMode; /* True to prohibit unsafe operations */
25044 u8 bSafeModePersist; /* The long-term value of bSafeMode */
25045 u8 eRestoreState; /* See comments above doAutoDetectRestore() */
25046 unsigned statsOn; /* True to display memory stats before each finalize */
25047 unsigned mEqpLines; /* Mask of vertical lines in the EQP output graph */
25048 u8 nPopOutput; /* Revert .output settings when reaching zero */
25049 u8 nPopMode; /* Revert .mode settings when reaching zero */
25050 u8 enableTimer; /* Enable the timer. 2: permanently 1: only once */
25051 u8 bDelimitNonprint; /* Add \001...\002 around non-printing in prompts */
@@ -25069,10 +25085,12 @@
25069 unsigned nTestErr; /* Number of test cases that failed */
25070 sqlite3_int64 szMax; /* --maxsize argument to .open */
25071 char *zDestTable; /* Name of destination table when MODE_Insert */
25072 char *zTempFile; /* Temporary file that might need deleting */
25073 char *zErrPrefix; /* Alternative error message prefix */
 
 
25074 sqlite3_stmt *pStmt; /* Current statement if any. */
25075 FILE *pLog; /* Write log output here */
25076 char *azPrompt[2]; /* Main and continuation prompt strings */
25077 Mode mode; /* Current display mode */
25078 Mode *aModeStack; /* Backups */
@@ -27477,28 +27495,24 @@
27477 "TRANSACTION", "UPDATE", "ATTACH",
27478 "DETACH", "ALTER_TABLE", "REINDEX",
27479 "ANALYZE", "CREATE_VTABLE", "DROP_VTABLE",
27480 "FUNCTION", "SAVEPOINT", "RECURSIVE"
27481 };
27482 int i;
27483 const char *az[4];
27484 az[0] = zA1;
27485 az[1] = zA2;
27486 az[2] = zA3;
27487 az[3] = zA4;
27488 cli_printf(p->out, "authorizer: %s", azAction[op]);
27489 for(i=0; i<4; i++){
27490 cli_puts(" ", p->out);
27491 if( az[i] ){
27492 output_c_string(p->out, az[i]);
27493 }else{
27494 cli_puts("NULL", p->out);
27495 }
27496 }
27497 cli_puts("\n", p->out);
27498 if( p->bSafeMode ) (void)safeModeAuth(pClientData, op, zA1, zA2, zA3, zA4);
27499 return SQLITE_OK;
27500 }
27501 #endif
27502
27503 /*
27504 ** Print a schema statement. This is a helper routine to dump_callback().
@@ -27978,20 +27992,22 @@
27978 /*
27979 ** Display memory stats.
27980 */
27981 static int display_stats(
27982 sqlite3 *db, /* Database to query */
27983 ShellState *pArg, /* Pointer to ShellState */
27984 int bReset /* True to reset the stats */
27985 ){
27986 int iCur, iHiwtr;
27987 sqlite3_int64 iCur64, iHiwtr64;
27988 FILE *out;
 
 
27989 if( pArg==0 || pArg->out==0 ) return 0;
27990 out = pArg->out;
 
27991
27992 if( pArg->pStmt && pArg->statsOn==2 ){
27993 int nCol, i, x;
27994 sqlite3_stmt *pStmt = pArg->pStmt;
27995 char z[100];
27996 nCol = sqlite3_column_count(pStmt);
27997 cli_printf(out, "%-36s %d\n", "Number of output columns:", nCol);
@@ -28012,95 +28028,96 @@
28012 cli_printf(out, "%-36s %s\n", z,sqlite3_column_origin_name(pStmt,i));
28013 #endif
28014 }
28015 }
28016
28017 if( pArg->statsOn==3 ){
28018 if( pArg->pStmt ){
28019 iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP,bReset);
28020 cli_printf(out, "VM-steps: %d\n", iCur);
28021 }
28022 return 0;
28023 }
28024
28025 displayStatLine(out, "Memory Used:",
28026 "%lld (max %lld) bytes", SQLITE_STATUS_MEMORY_USED, bReset);
28027 displayStatLine(out, "Number of Outstanding Allocations:",
28028 "%lld (max %lld)", SQLITE_STATUS_MALLOC_COUNT, bReset);
28029 if( pArg->shellFlgs & SHFLG_Pagecache ){
28030 displayStatLine(out, "Number of Pcache Pages Used:",
28031 "%lld (max %lld) pages", SQLITE_STATUS_PAGECACHE_USED, bReset);
28032 }
28033 displayStatLine(out, "Number of Pcache Overflow Bytes:",
28034 "%lld (max %lld) bytes", SQLITE_STATUS_PAGECACHE_OVERFLOW, bReset);
28035 displayStatLine(out, "Largest Allocation:",
28036 "%lld bytes", SQLITE_STATUS_MALLOC_SIZE, bReset);
28037 displayStatLine(out, "Largest Pcache Allocation:",
28038 "%lld bytes", SQLITE_STATUS_PAGECACHE_SIZE, bReset);
28039 #ifdef YYTRACKMAXSTACKDEPTH
28040 displayStatLine(out, "Deepest Parser Stack:",
28041 "%lld (max %lld)", SQLITE_STATUS_PARSER_STACK, bReset);
28042 #endif
28043
28044 if( db ){
28045 if( pArg->shellFlgs & SHFLG_Lookaside ){
28046 iHiwtr = iCur = -1;
28047 sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED,
28048 &iCur, &iHiwtr, bReset);
28049 cli_printf(out,
28050 "Lookaside Slots Used: %d (max %d)\n", iCur, iHiwtr);
28051 sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT,
28052 &iCur, &iHiwtr, bReset);
28053 cli_printf(out,
28054 "Successful lookaside attempts: %d\n", iHiwtr);
28055 sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE,
28056 &iCur, &iHiwtr, bReset);
28057 cli_printf(out,
28058 "Lookaside failures due to size: %d\n", iHiwtr);
28059 sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL,
28060 &iCur, &iHiwtr, bReset);
28061 cli_printf(out,
28062 "Lookaside failures due to OOM: %d\n", iHiwtr);
28063 }
28064 iHiwtr = iCur = -1;
28065 sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset);
28066 cli_printf(out,
28067 "Pager Heap Usage: %d bytes\n", iCur);
28068 iHiwtr = iCur = -1;
28069 sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1);
28070 cli_printf(out,
28071 "Page cache hits: %d\n", iCur);
28072 iHiwtr = iCur = -1;
28073 sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1);
28074 cli_printf(out,
28075 "Page cache misses: %d\n", iCur);
28076 iHiwtr64 = iCur64 = -1;
28077 sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64,
28078 0);
28079 iHiwtr = iCur = -1;
28080 sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1);
28081 cli_printf(out,
28082 "Page cache writes: %d\n", iCur);
28083 iHiwtr = iCur = -1;
28084 sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1);
28085 cli_printf(out,
28086 "Page cache spills: %d\n", iCur);
28087 cli_printf(out,
28088 "Temporary data spilled to disk: %lld\n", iCur64);
28089 sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64,
28090 1);
28091 iHiwtr = iCur = -1;
28092 sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset);
28093 cli_printf(out,
28094 "Schema Heap Usage: %d bytes\n", iCur);
28095 iHiwtr = iCur = -1;
28096 sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset);
28097 cli_printf(out,
28098 "Statement Heap/Lookaside Usage: %d bytes\n", iCur);
28099 }
28100
28101 if( pArg->pStmt ){
 
28102 int iHit, iMiss;
28103 iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP,
28104 bReset);
28105 cli_printf(out,
28106 "Fullscan Steps: %d\n", iCur);
@@ -28116,13 +28133,10 @@
28116 bReset);
28117 if( iHit || iMiss ){
28118 cli_printf(out,
28119 "Bloom filter bypass taken: %d/%d\n", iHit, iHit+iMiss);
28120 }
28121 iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
28122 cli_printf(out,
28123 "Virtual Machine Steps: %d\n", iCur);
28124 iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset);
28125 cli_printf(out,
28126 "Reprepare operations: %d\n", iCur);
28127 iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_RUN, bReset);
28128 cli_printf(out,
@@ -28131,14 +28145,20 @@
28131 cli_printf(out,
28132 "Memory used by prepared stmt: %d\n", iCur);
28133 }
28134
28135 #ifdef __linux__
28136 displayLinuxIoStats(pArg->out);
 
 
28137 #endif
28138
28139 /* Do not remove this machine readable comment: extra-stats-output-here */
 
 
 
 
28140
28141 return 0;
28142 }
28143
28144 /*
@@ -28540,12 +28560,12 @@
28540 spec.eStyle = eStyle;
28541 sqlite3_format_query_result(pStmt, &spec, pzErrMsg);
28542 }
28543
28544 /* print usage stats if stats on */
28545 if( pArg && pArg->statsOn ){
28546 display_stats(db, pArg, 0);
28547 }
28548
28549 /* print loop-counters if required */
28550 if( pArg && pArg->mode.scanstatsOn ){
28551 char *zErr = 0;
@@ -28928,10 +28948,13 @@
28928 " See also:",
28929 " http://sqlite.org/cli.html#sqlite_archive_support",
28930 #endif
28931 #ifndef SQLITE_OMIT_AUTHORIZATION
28932 ".auth ON|OFF Show authorizer callbacks",
 
 
 
28933 #endif
28934 #ifndef SQLITE_SHELL_FIDDLE
28935 ".backup ?DB? FILE Backup DB (default \"main\") to FILE",
28936 " Options:",
28937 " --append Use the appendvfs",
@@ -29212,11 +29235,13 @@
29212 { ".mode",
29213 "USAGE: .mode [MODE] [OPTIONS]\n"
29214 "\n"
29215 "Change the output mode to MODE and/or apply OPTIONS to the output mode.\n"
29216 "Arguments are processed from left to right. If no arguments, show the\n"
29217 "current output mode and relevant options.\n"
 
 
29218 "\n"
29219 "Options:\n"
29220 " --align STRING Set the alignment of text in columnar modes\n"
29221 " String consists of characters 'L', 'C', 'R'\n"
29222 " meaning \"left\", \"centered\", and \"right\", with\n"
@@ -29262,11 +29287,11 @@
29262 " to be N characters. An attempt may be made to\n"
29263 " wrap output text to fit within this limit. Zero\n"
29264 " means \"no limit\". Or N can be \"auto\" to set the\n"
29265 " width automatically.\n"
29266 " --tablename NAME Set the name of the table for \"insert\" mode.\n"
29267 " --tag NAME Save mode to the left as NAME.\n"
29268 " --textjsonb BOOLEAN If enabled, JSONB text is displayed as text JSON.\n"
29269 " --title ARG Whether or not to show column headers, and if so\n"
29270 " how to encode them. ARG can be \"off\", \"on\",\n"
29271 " \"always\", \"sql\", \"csv\", \"html\", \"tcl\", or \"json\".\n"
29272 " --titlelimit N Limit the length of column titles to N characters.\n"
@@ -29348,10 +29373,31 @@
29348 " --keep Do not reset the testcase. More .check commands\n"
29349 " will follow.\n"
29350 " --notglob Output should not match PATTERN\n"
29351 " --show Write testcase output to the screen, for debugging.\n"
29352 },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29353 { ".testcase",
29354 "USAGE: .testcase [OPTIONS] NAME\n"
29355 "\n"
29356 "Start a new test case identified by NAME. All output\n"
29357 "through the next \".check\" command is captured for comparison. See the\n"
@@ -33495,11 +33541,13 @@
33495 **
33496 ** USAGE: .mode [MODE] [OPTIONS]
33497 **
33498 ** Change the output mode to MODE and/or apply OPTIONS to the output mode.
33499 ** Arguments are processed from left to right. If no arguments, show the
33500 ** current output mode and relevant options.
 
 
33501 **
33502 ** Options:
33503 ** --align STRING Set the alignment of text in columnar modes
33504 ** String consists of characters 'L', 'C', 'R'
33505 ** meaning "left", "centered", and "right", with
@@ -33545,11 +33593,11 @@
33545 ** to be N characters. An attempt may be made to
33546 ** wrap output text to fit within this limit. Zero
33547 ** means "no limit". Or N can be "auto" to set the
33548 ** width automatically.
33549 ** --tablename NAME Set the name of the table for "insert" mode.
33550 ** --tag NAME Save mode to the left as NAME.
33551 ** --textjsonb BOOLEAN If enabled, JSONB text is displayed as text JSON.
33552 ** --title ARG Whether or not to show column headers, and if so
33553 ** how to encode them. ARG can be "off", "on",
33554 ** "always", "sql", "csv", "html", "tcl", or "json".
33555 ** --titlelimit N Limit the length of column titles to N characters.
@@ -34530,10 +34578,101 @@
34530 output_reset(p);
34531 p->zTestcase[0] = 0;
34532 }
34533 return 0;
34534 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34535
34536 /*
34537 ** DOT-COMMAND: .testcase
34538 ** USAGE: .testcase [OPTIONS] NAME
34539 **
@@ -34682,17 +34821,61 @@
34682 c = azArg[0][0];
34683 clearTempFile(p,0,0);
34684
34685 #ifndef SQLITE_OMIT_AUTHORIZATION
34686 if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){
34687 if( nArg!=2 ){
34688 cli_printf(stderr, "Usage: .auth ON|OFF\n");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34689 rc = 1;
34690 goto meta_command_exit;
34691 }
34692 open_db(p, 0);
34693 if( booleanValue(azArg[1]) ){
34694 sqlite3_set_authorizer(p->db, shellAuth, p);
34695 }else if( p->bSafeModePersist ){
34696 sqlite3_set_authorizer(p->db, safeModeAuth, p);
34697 }else{
34698 sqlite3_set_authorizer(p->db, 0, 0);
@@ -37044,11 +37227,10 @@
37044 }else
37045 #endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) */
37046
37047 if( c=='s' && cli_strncmp(azArg[0], "show", n)==0 ){
37048 static const char *azBool[] = { "off", "on", "trigger", "full"};
37049 const char *zOut;
37050 int i;
37051 if( nArg!=1 ){
37052 eputz("Usage: .show\n");
37053 rc = 1;
37054 goto meta_command_exit;
@@ -37084,17 +37266,46 @@
37084 output_c_string(p->out, p->mode.spec.zColumnSep);
37085 cli_puts("\n", p->out);
37086 cli_printf(p->out, "%12.12s: ", "rowseparator");
37087 output_c_string(p->out, p->mode.spec.zRowSep);
37088 cli_puts("\n", p->out);
37089 switch( p->statsOn ){
37090 case 0: zOut = "off"; break;
37091 default: zOut = "on"; break;
37092 case 2: zOut = "stmt"; break;
37093 case 3: zOut = "vmstep"; break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37094 }
37095 cli_printf(p->out, "%12.12s: %s\n","stats", zOut);
37096 cli_printf(p->out, "%12.12s: ", "width");
37097 for(i=0; i<p->mode.spec.nWidth; i++){
37098 cli_printf(p->out, "%d ", (int)p->mode.spec.aWidth[i]);
37099 }
37100 cli_puts("\n", p->out);
@@ -37101,24 +37312,11 @@
37101 cli_printf(p->out, "%12.12s: %s\n", "filename",
37102 p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : "");
37103 }else
37104
37105 if( c=='s' && cli_strncmp(azArg[0], "stats", n)==0 ){
37106 if( nArg==2 ){
37107 if( cli_strcmp(azArg[1],"stmt")==0 ){
37108 p->statsOn = 2;
37109 }else if( cli_strcmp(azArg[1],"vmstep")==0 ){
37110 p->statsOn = 3;
37111 }else{
37112 p->statsOn = (u8)booleanValue(azArg[1]);
37113 }
37114 }else if( nArg==1 ){
37115 display_stats(p->db, p, 0);
37116 }else{
37117 eputz("Usage: .stats ?on|off|stmt|vmstep?\n");
37118 rc = 1;
37119 }
37120 }else
37121
37122 if( (c=='t' && n>1 && cli_strncmp(azArg[0], "tables", n)==0) ){
37123 sqlite3_stmt *pStmt;
37124 sqlite3_str *pSql;
@@ -37273,11 +37471,10 @@
37273 } aLabel[] = {
37274 { 0x00000001, 1, "QueryFlattener" },
37275 { 0x00000001, 0, "Flatten" },
37276 { 0x00000002, 1, "WindowFunc" },
37277 { 0x00000004, 1, "GroupByOrder" },
37278 { 0x00000008, 1, "FactorOutConst" },
37279 { 0x00000010, 1, "DistinctOpt" },
37280 { 0x00000020, 1, "CoverIdxScan" },
37281 { 0x00000040, 1, "OrderByIdxJoin" },
37282 { 0x00000080, 1, "Transitive" },
37283 { 0x00000100, 1, "OmitNoopJoin" },
@@ -37290,11 +37487,10 @@
37290 { 0x00008000, 1, "PropagateConst" },
37291 { 0x00010000, 1, "MinMaxOpt" },
37292 { 0x00020000, 1, "SeekScan" },
37293 { 0x00040000, 1, "OmitOrderBy" },
37294 { 0x00080000, 1, "BloomFilter" },
37295 { 0x00100000, 1, "BloomPulldown" },
37296 { 0x00200000, 1, "BalancedMerge" },
37297 { 0x00400000, 1, "ReleaseReg" },
37298 { 0x00800000, 1, "FlttnUnionAll" },
37299 { 0x01000000, 1, "IndexedEXpr" },
37300 { 0x02000000, 1, "Coroutines" },
@@ -37301,10 +37497,11 @@
37301 { 0x04000000, 1, "NullUnusedCols" },
37302 { 0x08000000, 1, "OnePass" },
37303 { 0x10000000, 1, "OrderBySubq" },
37304 { 0x20000000, 1, "StarQuery" },
37305 { 0x40000000, 1, "ExistsToJoin" },
 
37306 { 0xffffffff, 0, "All" },
37307 };
37308 unsigned int curOpt;
37309 unsigned int newOpt;
37310 unsigned int m;
@@ -39151,11 +39348,11 @@
39151 }else if( cli_strcmp(z,"-eqp")==0 ){
39152 data.mode.autoEQP = AUTOEQP_on;
39153 }else if( cli_strcmp(z,"-eqpfull")==0 ){
39154 data.mode.autoEQP = AUTOEQP_full;
39155 }else if( cli_strcmp(z,"-stats")==0 ){
39156 data.statsOn = 1;
39157 }else if( cli_strcmp(z,"-scanstats")==0 ){
39158 data.mode.scanstatsOn = 1;
39159 }else if( cli_strcmp(z,"-backslash")==0 ){
39160 /* Undocumented command-line option: -backslash
39161 ** Causes C-style backslash escapes to be evaluated in SQL statements
@@ -39410,10 +39607,12 @@
39410 free(data.dot.azArg);
39411 free(data.dot.aiOfst);
39412 free(data.dot.abQuot);
39413 free(data.azPrompt[0]);
39414 free(data.azPrompt[1]);
 
 
39415 if( data.nTestRun ){
39416 sqlite3_fprintf(stdout, "%d test%s run with %d error%s\n",
39417 data.nTestRun, data.nTestRun==1 ? "" : "s",
39418 data.nTestErr, data.nTestErr==1 ? "" : "s");
39419 fflush(stdout);
39420
--- extsrc/shell.c
+++ extsrc/shell.c
@@ -634,10 +634,11 @@
634 va_list ap;
635
636 va_start(ap, zFormat);
637 z = sqlite3_vmprintf(zFormat, ap);
638 va_end(ap);
639 if( z==0 ) return -1;
640 sqlite3_fputs(z, out);
641 rc = (int)strlen(z);
642 sqlite3_free(z);
643 }else{
644 /* Writing to a file or other destination, just write bytes without
@@ -655,10 +656,11 @@
656 /* When writing to the command-prompt in Windows, it is necessary
657 ** to use _O_WTEXT input mode and write UTF-16 characters.
658 */
659 char *z;
660 z = sqlite3_vmprintf(zFormat, ap);
661 if( z==0 ) return -1;
662 sqlite3_fputs(z, out);
663 rc = (int)strlen(z);
664 sqlite3_free(z);
665 }else{
666 /* Writing to a file or other destination, just write bytes without
@@ -1328,11 +1330,11 @@
1330 const char *z = 0;
1331 int n = 0;
1332 if( sqlite3_stmt_scanstatus_v2(pS,i,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){
1333 break;
1334 }
1335 n = (z ? (int)strlen(z) : 0) + qrfStatsHeight(pS,i)*3;
1336 if( n>nWidth ) nWidth = n;
1337 }
1338 nWidth += 2;
1339
1340 sqlite3_stmt_scanstatus_v2(pS,-1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal);
@@ -25019,10 +25021,24 @@
25021
25022 /* Flags for Mode.mFlags */
25023 #define MFLG_ECHO 0x01 /* Echo inputs to output */
25024 #define MFLG_CRLF 0x02 /* Use CR/LF output line endings */
25025 #define MFLG_HDR 0x04 /* .header used to change headers on/off */
25026
25027 /* Bit values for ShellState.mStats
25028 */
25029 #define CLISTAT_VMSTEP 0x01 /* Show vmsteps */
25030 #define CLISTAT_STMT 0x02 /* Stats for each statement */
25031 #define CLISTAT_MEM 0x04 /* Memory stats */
25032 #define CLISTAT_XSTMT 0x08 /* Extra statement stats */
25033 #define CLISTAT_XMEM 0x10 /* Extra memory stats */
25034 #define CLISTAT_IO 0x20 /* I/O stats (Linux only) */
25035 #define CLISTAT_ZIPVFS 0x40 /* ZIPVFS stats */
25036 #define CLISTAT_ON 0x17 /* Usual stats */
25037 #define CLISTAT_ALL 0x7f /* All stats */
25038 #define CLISTAT_RESET 0x80 /* Reset stat counters each time */
25039
25040
25041 /* A file that needs to be deleted, but only after a delay.
25042 */
25043 typedef struct Unlink {
25044 sqlite3_int64 tm; /* Unlink after this time */
@@ -25041,11 +25057,11 @@
25057 u8 nEqpLevel; /* Depth of the EQP output graph */
25058 u8 eTraceType; /* SHELL_TRACE_* value for type of trace */
25059 u8 bSafeMode; /* True to prohibit unsafe operations */
25060 u8 bSafeModePersist; /* The long-term value of bSafeMode */
25061 u8 eRestoreState; /* See comments above doAutoDetectRestore() */
25062 u8 mStats; /* Which stats to display (if any) */
25063 unsigned mEqpLines; /* Mask of vertical lines in the EQP output graph */
25064 u8 nPopOutput; /* Revert .output settings when reaching zero */
25065 u8 nPopMode; /* Revert .mode settings when reaching zero */
25066 u8 enableTimer; /* Enable the timer. 2: permanently 1: only once */
25067 u8 bDelimitNonprint; /* Add \001...\002 around non-printing in prompts */
@@ -25069,10 +25085,12 @@
25085 unsigned nTestErr; /* Number of test cases that failed */
25086 sqlite3_int64 szMax; /* --maxsize argument to .open */
25087 char *zDestTable; /* Name of destination table when MODE_Insert */
25088 char *zTempFile; /* Temporary file that might need deleting */
25089 char *zErrPrefix; /* Alternative error message prefix */
25090 char *zAuthIgnore; /* Authorizer returns SQLITE_IGNORE for glob matches */
25091 char *zAuthDeny; /* Authorizer returns SQLITE_DENY for glob matches */
25092 sqlite3_stmt *pStmt; /* Current statement if any. */
25093 FILE *pLog; /* Write log output here */
25094 char *azPrompt[2]; /* Main and continuation prompt strings */
25095 Mode mode; /* Current display mode */
25096 Mode *aModeStack; /* Backups */
@@ -27477,28 +27495,24 @@
27495 "TRANSACTION", "UPDATE", "ATTACH",
27496 "DETACH", "ALTER_TABLE", "REINDEX",
27497 "ANALYZE", "CREATE_VTABLE", "DROP_VTABLE",
27498 "FUNCTION", "SAVEPOINT", "RECURSIVE"
27499 };
27500 char *zResult = "OK";
27501 int rc = SQLITE_OK;
27502 char *zMsg = sqlite3_mprintf("%s %Q %Q %Q %Q", azAction[op],zA1,zA2,zA3,zA4);
27503 if( p->zAuthDeny && sqlite3_strglob(p->zAuthDeny, zMsg)==0 ){
27504 zResult = "DENY";
27505 rc = SQLITE_DENY;
27506 }else if( p->zAuthIgnore && sqlite3_strglob(p->zAuthIgnore, zMsg)==0 ){
27507 zResult = "IGNORE";
27508 rc = SQLITE_IGNORE;
27509 }
27510 cli_printf(p->out, "authorizer: %s -> %s\n", zMsg, zResult);
27511 sqlite3_free(zMsg);
 
 
 
 
27512 if( p->bSafeMode ) (void)safeModeAuth(pClientData, op, zA1, zA2, zA3, zA4);
27513 return rc;
27514 }
27515 #endif
27516
27517 /*
27518 ** Print a schema statement. This is a helper routine to dump_callback().
@@ -27978,20 +27992,22 @@
27992 /*
27993 ** Display memory stats.
27994 */
27995 static int display_stats(
27996 sqlite3 *db, /* Database to query */
27997 ShellState *pArg /* Pointer to ShellState */
 
27998 ){
27999 int iCur, iHiwtr;
28000 sqlite3_int64 iCur64, iHiwtr64;
28001 FILE *out;
28002 int bReset;
28003
28004 if( pArg==0 || pArg->out==0 ) return 0;
28005 out = pArg->out;
28006 bReset = (pArg->mStats & CLISTAT_RESET)!=0;
28007
28008 if( pArg->pStmt && (pArg->mStats & CLISTAT_XSTMT)!=0 ){
28009 int nCol, i, x;
28010 sqlite3_stmt *pStmt = pArg->pStmt;
28011 char z[100];
28012 nCol = sqlite3_column_count(pStmt);
28013 cli_printf(out, "%-36s %d\n", "Number of output columns:", nCol);
@@ -28012,95 +28028,96 @@
28028 cli_printf(out, "%-36s %s\n", z,sqlite3_column_origin_name(pStmt,i));
28029 #endif
28030 }
28031 }
28032
28033 if( pArg->pStmt && (pArg->mStats & CLISTAT_VMSTEP)!=0 ){
28034 iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP,bReset);
28035 cli_printf(out, "VM-steps: %d\n", iCur);
28036 }
28037
28038 if( (pArg->mStats & CLISTAT_MEM)!=0 ){
28039 displayStatLine(out, "Memory Used:",
28040 "%lld (max %lld) bytes", SQLITE_STATUS_MEMORY_USED, bReset);
28041 displayStatLine(out, "Number of Outstanding Allocations:",
28042 "%lld (max %lld)", SQLITE_STATUS_MALLOC_COUNT, bReset);
28043 }
28044 if( (pArg->mStats & CLISTAT_XMEM)!=0 ){
28045 if( pArg->shellFlgs & SHFLG_Pagecache ){
28046 displayStatLine(out, "Number of Pcache Pages Used:",
28047 "%lld (max %lld) pages", SQLITE_STATUS_PAGECACHE_USED, bReset);
28048 }
28049 displayStatLine(out, "Number of Pcache Overflow Bytes:",
28050 "%lld (max %lld) bytes", SQLITE_STATUS_PAGECACHE_OVERFLOW, bReset);
28051 displayStatLine(out, "Largest Allocation:",
28052 "%lld bytes", SQLITE_STATUS_MALLOC_SIZE, bReset);
28053 displayStatLine(out, "Largest Pcache Allocation:",
28054 "%lld bytes", SQLITE_STATUS_PAGECACHE_SIZE, bReset);
28055 #ifdef YYTRACKMAXSTACKDEPTH
28056 displayStatLine(out, "Deepest Parser Stack:",
28057 "%lld (max %lld)", SQLITE_STATUS_PARSER_STACK, bReset);
28058 #endif
28059
28060 if( db ){
28061 if( pArg->shellFlgs & SHFLG_Lookaside ){
28062 iHiwtr = iCur = -1;
28063 sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED,
28064 &iCur, &iHiwtr, bReset);
28065 cli_printf(out,
28066 "Lookaside Slots Used: %d (max %d)\n", iCur, iHiwtr);
28067 sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT,
28068 &iCur, &iHiwtr, bReset);
28069 cli_printf(out,
28070 "Successful lookaside attempts: %d\n", iHiwtr);
28071 sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE,
28072 &iCur, &iHiwtr, bReset);
28073 cli_printf(out,
28074 "Lookaside failures due to size: %d\n", iHiwtr);
28075 sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL,
28076 &iCur, &iHiwtr, bReset);
28077 cli_printf(out,
28078 "Lookaside failures due to OOM: %d\n", iHiwtr);
28079 }
28080 iHiwtr = iCur = -1;
28081 sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset);
28082 cli_printf(out,
28083 "Pager Heap Usage: %d bytes\n", iCur);
28084 iHiwtr = iCur = -1;
28085 sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1);
28086 cli_printf(out,
28087 "Page cache hits: %d\n", iCur);
28088 iHiwtr = iCur = -1;
28089 sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1);
28090 cli_printf(out,
28091 "Page cache misses: %d\n", iCur);
28092 iHiwtr64 = iCur64 = -1;
28093 sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64,
28094 0);
28095 iHiwtr = iCur = -1;
28096 sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1);
28097 cli_printf(out,
28098 "Page cache writes: %d\n", iCur);
28099 iHiwtr = iCur = -1;
28100 sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_SPILL, &iCur, &iHiwtr, 1);
28101 cli_printf(out,
28102 "Page cache spills: %d\n", iCur);
28103 cli_printf(out,
28104 "Temporary data spilled to disk: %lld\n", iCur64);
28105 sqlite3_db_status64(db, SQLITE_DBSTATUS_TEMPBUF_SPILL, &iCur64, &iHiwtr64,
28106 1);
28107 iHiwtr = iCur = -1;
28108 sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset);
28109 cli_printf(out,
28110 "Schema Heap Usage: %d bytes\n", iCur);
28111 iHiwtr = iCur = -1;
28112 sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset);
28113 cli_printf(out,
28114 "Statement Heap/Lookaside Usage: %d bytes\n", iCur);
28115 }
28116 }
28117
28118 if( pArg->pStmt && (pArg->mStats & CLISTAT_STMT)!=0 ){
28119 int iHit, iMiss;
28120 iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP,
28121 bReset);
28122 cli_printf(out,
28123 "Fullscan Steps: %d\n", iCur);
@@ -28116,13 +28133,10 @@
28133 bReset);
28134 if( iHit || iMiss ){
28135 cli_printf(out,
28136 "Bloom filter bypass taken: %d/%d\n", iHit, iHit+iMiss);
28137 }
 
 
 
28138 iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_REPREPARE,bReset);
28139 cli_printf(out,
28140 "Reprepare operations: %d\n", iCur);
28141 iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_RUN, bReset);
28142 cli_printf(out,
@@ -28131,14 +28145,20 @@
28145 cli_printf(out,
28146 "Memory used by prepared stmt: %d\n", iCur);
28147 }
28148
28149 #ifdef __linux__
28150 if( (pArg->mStats & CLISTAT_IO)!=0 ){
28151 displayLinuxIoStats(pArg->out);
28152 }
28153 #endif
28154
28155 #ifdef SQLITE_ENABLE_ZIPVFS_VTAB
28156 if( (pArg->mStats & CLISTAT_ZIPVFS)!=0 ){
28157 /* Do not remove this machine readable comment: extra-stats-output-here */
28158 }
28159 #endif
28160
28161 return 0;
28162 }
28163
28164 /*
@@ -28540,12 +28560,12 @@
28560 spec.eStyle = eStyle;
28561 sqlite3_format_query_result(pStmt, &spec, pzErrMsg);
28562 }
28563
28564 /* print usage stats if stats on */
28565 if( pArg && pArg->mStats!=0 ){
28566 display_stats(db, pArg);
28567 }
28568
28569 /* print loop-counters if required */
28570 if( pArg && pArg->mode.scanstatsOn ){
28571 char *zErr = 0;
@@ -28928,10 +28948,13 @@
28948 " See also:",
28949 " http://sqlite.org/cli.html#sqlite_archive_support",
28950 #endif
28951 #ifndef SQLITE_OMIT_AUTHORIZATION
28952 ".auth ON|OFF Show authorizer callbacks",
28953 " Options:",
28954 " --deny GLOB Return SQLITE_DENY if pattern matches",
28955 " --ignore GLOB Return SQLITE_IGNORE if pattern matches",
28956 #endif
28957 #ifndef SQLITE_SHELL_FIDDLE
28958 ".backup ?DB? FILE Backup DB (default \"main\") to FILE",
28959 " Options:",
28960 " --append Use the appendvfs",
@@ -29212,11 +29235,13 @@
29235 { ".mode",
29236 "USAGE: .mode [MODE] [OPTIONS]\n"
29237 "\n"
29238 "Change the output mode to MODE and/or apply OPTIONS to the output mode.\n"
29239 "Arguments are processed from left to right. If no arguments, show the\n"
29240 "current output mode and relevant options. Use the --list option to get\n"
29241 "a list of available MODE values. Use -v to show all the current output\n"
29242 "mode settings.\n"
29243 "\n"
29244 "Options:\n"
29245 " --align STRING Set the alignment of text in columnar modes\n"
29246 " String consists of characters 'L', 'C', 'R'\n"
29247 " meaning \"left\", \"centered\", and \"right\", with\n"
@@ -29262,11 +29287,11 @@
29287 " to be N characters. An attempt may be made to\n"
29288 " wrap output text to fit within this limit. Zero\n"
29289 " means \"no limit\". Or N can be \"auto\" to set the\n"
29290 " width automatically.\n"
29291 " --tablename NAME Set the name of the table for \"insert\" mode.\n"
29292 " --tag NAME Save current settings as a new mode called NAME.\n"
29293 " --textjsonb BOOLEAN If enabled, JSONB text is displayed as text JSON.\n"
29294 " --title ARG Whether or not to show column headers, and if so\n"
29295 " how to encode them. ARG can be \"off\", \"on\",\n"
29296 " \"always\", \"sql\", \"csv\", \"html\", \"tcl\", or \"json\".\n"
29297 " --titlelimit N Limit the length of column titles to N characters.\n"
@@ -29348,10 +29373,31 @@
29373 " --keep Do not reset the testcase. More .check commands\n"
29374 " will follow.\n"
29375 " --notglob Output should not match PATTERN\n"
29376 " --show Write testcase output to the screen, for debugging.\n"
29377 },
29378 { ".stats",
29379 "USAGE: .stats [OPTIONS] ...\n"
29380 "\n"
29381 "Show status information about the database and database connection.\n"
29382 "The stats setting is persistent, unless the --once option is used.\n"
29383 "Options are cumulative and are processed from left to right.\n"
29384 "If no options are given, that is the same as \".stats all --once\".\n"
29385 "\n"
29386 "Options:\n"
29387 " all Show all available stats.\n"
29388 " io Show I/O stats (Linux only).\n"
29389 " mem Show basic memory usage.\n"
29390 " off Show nothing. Turn off stat display.\n"
29391 " on Shorthand for \"stmt xmem\".\n"
29392 " --once Show the stats once. Do not change the default stats display.\n"
29393 " reset Reset \"max\" values after next display of each that value.\n"
29394 " stmt Show information about the last SQL statement.\n"
29395 " vmstep Show the number of virtual machine steps taken.\n"
29396 " xmem Show detailed memory status information\n"
29397 " xstmt Show details about each SQL statement\n"
29398 },
29399 { ".testcase",
29400 "USAGE: .testcase [OPTIONS] NAME\n"
29401 "\n"
29402 "Start a new test case identified by NAME. All output\n"
29403 "through the next \".check\" command is captured for comparison. See the\n"
@@ -33495,11 +33541,13 @@
33541 **
33542 ** USAGE: .mode [MODE] [OPTIONS]
33543 **
33544 ** Change the output mode to MODE and/or apply OPTIONS to the output mode.
33545 ** Arguments are processed from left to right. If no arguments, show the
33546 ** current output mode and relevant options. Use the --list option to get
33547 ** a list of available MODE values. Use -v to show all the current output
33548 ** mode settings.
33549 **
33550 ** Options:
33551 ** --align STRING Set the alignment of text in columnar modes
33552 ** String consists of characters 'L', 'C', 'R'
33553 ** meaning "left", "centered", and "right", with
@@ -33545,11 +33593,11 @@
33593 ** to be N characters. An attempt may be made to
33594 ** wrap output text to fit within this limit. Zero
33595 ** means "no limit". Or N can be "auto" to set the
33596 ** width automatically.
33597 ** --tablename NAME Set the name of the table for "insert" mode.
33598 ** --tag NAME Save current settings as a new mode called NAME.
33599 ** --textjsonb BOOLEAN If enabled, JSONB text is displayed as text JSON.
33600 ** --title ARG Whether or not to show column headers, and if so
33601 ** how to encode them. ARG can be "off", "on",
33602 ** "always", "sql", "csv", "html", "tcl", or "json".
33603 ** --titlelimit N Limit the length of column titles to N characters.
@@ -34530,10 +34578,101 @@
34578 output_reset(p);
34579 p->zTestcase[0] = 0;
34580 }
34581 return 0;
34582 }
34583
34584 /*
34585 ** DOT-COMMAND: .stats
34586 ** USAGE: .stats [OPTIONS] ...
34587 **
34588 ** Show status information about the database and database connection.
34589 ** The stats setting is persistent, unless the --once option is used.
34590 ** Options are cumulative and are processed from left to right.
34591 ** If no options are given, that is the same as ".stats all --once".
34592 **
34593 ** Options:
34594 ** all Show all available stats.
34595 ** io Show I/O stats (Linux only).
34596 ** mem Show basic memory usage.
34597 ** off Show nothing. Turn off stat display.
34598 ** on Shorthand for "stmt xmem".
34599 ** --once Show the stats once. Do not change the default stats display.
34600 ** reset Reset "max" values after next display of each that value.
34601 ** stmt Show information about the last SQL statement.
34602 ** vmstep Show the number of virtual machine steps taken.
34603 ** xmem Show detailed memory status information
34604 ** xstmt Show details about each SQL statement
34605 */
34606 static int dotCmdStats(ShellState *p){
34607 int nArg = p->dot.nArg; /* Number of arguments */
34608 char **azArg = p->dot.azArg; /* Text of the arguments */
34609 int ii; /* Loop counter */
34610 int bOnce = 0; /* --once option seen */
34611 u8 mNew = 0; /* New status setting */
34612 u8 savedStats = p->mStats; /* Current stats setting */
34613
34614 if( nArg==1 ){
34615 p->mStats = CLISTAT_ALL;
34616 display_stats(p->db, p);
34617 p->mStats = savedStats;
34618 return 0;
34619 }
34620 for(ii=1; ii<nArg; ii++){
34621 const char *z = azArg[ii];
34622 if( z[0]=='-' ){
34623 z++;
34624 if( z[0]=='-' && z[1]!=0 ) z++;
34625 }
34626 if( cli_strcmp(z,"stmt")==0 ){
34627 mNew |= CLISTAT_STMT|CLISTAT_VMSTEP;
34628 }else if( cli_strcmp(z,"xstmt")==0 ){
34629 mNew |= CLISTAT_STMT|CLISTAT_XSTMT|CLISTAT_VMSTEP;
34630 }else if( cli_strcmp(z,"vmstep")==0 ){
34631 mNew |= CLISTAT_VMSTEP;
34632 }else if( cli_strcmp(z,"mem")==0 ){
34633 mNew |= CLISTAT_MEM;
34634 }else if( cli_strcmp(z,"xmem")==0 ){
34635 mNew |= CLISTAT_XMEM|CLISTAT_MEM;
34636 }else if( cli_strcmp(z,"io")==0 ){
34637 mNew |= CLISTAT_IO;
34638 }else if( cli_strcmp(z,"all")==0 ){
34639 mNew |= CLISTAT_ALL;
34640 #ifndef __linux__
34641 dotCmdError(p, ii, "available only on Linux", 0);
34642 return 1;
34643 #endif
34644 #ifdef SQLITE_ENABLE_ZIPVFS_VTAB
34645 }else if( cli_strcmp(z,"zipvfs")==0 ){
34646 mNew |= CLISTAT_ZIPVFS;
34647 #endif
34648 }else if( cli_strcmp(z,"reset")==0 ){
34649 mNew |= CLISTAT_RESET;
34650 }else if( cli_strcmp(z,"once")==0 ){
34651 bOnce = 1;
34652 }else if( IsDigit(z[0]) ){
34653 if( booleanValue(z) ){
34654 mNew |= CLISTAT_ON;
34655 }else{
34656 mNew = 0;
34657 }
34658 }else if( pickStr(z, 0, "on", "yes", "ON", "YES", "")>=0 ){
34659 mNew |= CLISTAT_ON;
34660 }else if( pickStr(z, 0, "off", "no", "OFF", "NO", "")>=0 ){
34661 mNew = 0;
34662 }else{
34663 dotCmdError(p, ii, "unknown argument", 0);
34664 return 1;
34665 }
34666 }
34667 p->mStats = mNew;
34668 if( bOnce ){
34669 display_stats(p->db, p);
34670 p->mStats = savedStats;
34671 }
34672 return 0;
34673 }
34674
34675 /*
34676 ** DOT-COMMAND: .testcase
34677 ** USAGE: .testcase [OPTIONS] NAME
34678 **
@@ -34682,17 +34821,61 @@
34821 c = azArg[0][0];
34822 clearTempFile(p,0,0);
34823
34824 #ifndef SQLITE_OMIT_AUTHORIZATION
34825 if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){
34826 int ii;
34827 int eOnOff = -1;
34828 open_db(p, 0);
34829 for(ii=1; ii<nArg && rc==0; ii++){
34830 const char *z = azArg[ii];
34831 if( z[0]=='-' && z[1]=='-' ) z++;
34832 if( strcmp(z,"-deny")==0 ){
34833 ii++;
34834 if( ii>=nArg ){
34835 dotCmdError(p, ii-1, "missing argument", 0);
34836 rc = 1;
34837 goto meta_command_exit;
34838 }else{
34839 free(p->zAuthDeny);
34840 p->zAuthDeny = 0;
34841 if( azArg[ii][0] ){
34842 p->zAuthDeny = strdup(azArg[ii]);
34843 shell_check_oom(p->zAuthDeny);
34844 }
34845 }
34846 }else
34847 if( strcmp(z,"-ignore")==0 ){
34848 ii++;
34849 if( ii>=nArg ){
34850 dotCmdError(p, ii-1, "missing argument", 0);
34851 rc = 1;
34852 goto meta_command_exit;
34853 }else{
34854 free(p->zAuthIgnore);
34855 p->zAuthIgnore = 0;
34856 if( azArg[ii][0] ){
34857 p->zAuthIgnore = strdup(azArg[ii]);
34858 shell_check_oom(p->zAuthIgnore);
34859 }
34860 }
34861 }else
34862 if( eOnOff<0 ){
34863 eOnOff = booleanValue(z);
34864 }else
34865 {
34866 dotCmdError(p, ii, "unknown argument", 0);
34867 rc = 1;
34868 goto meta_command_exit;
34869 }
34870 }
34871 if( eOnOff<0 ){
34872 cli_printf(stderr, "Usage: .auth ON|OFF [--deny GLOB] [--ignore]\n");
34873 rc = 1;
34874 goto meta_command_exit;
34875 }
34876 if( eOnOff ){
 
34877 sqlite3_set_authorizer(p->db, shellAuth, p);
34878 }else if( p->bSafeModePersist ){
34879 sqlite3_set_authorizer(p->db, safeModeAuth, p);
34880 }else{
34881 sqlite3_set_authorizer(p->db, 0, 0);
@@ -37044,11 +37227,10 @@
37227 }else
37228 #endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) */
37229
37230 if( c=='s' && cli_strncmp(azArg[0], "show", n)==0 ){
37231 static const char *azBool[] = { "off", "on", "trigger", "full"};
 
37232 int i;
37233 if( nArg!=1 ){
37234 eputz("Usage: .show\n");
37235 rc = 1;
37236 goto meta_command_exit;
@@ -37084,17 +37266,46 @@
37266 output_c_string(p->out, p->mode.spec.zColumnSep);
37267 cli_puts("\n", p->out);
37268 cli_printf(p->out, "%12.12s: ", "rowseparator");
37269 output_c_string(p->out, p->mode.spec.zRowSep);
37270 cli_puts("\n", p->out);
37271 cli_printf(p->out, "%12.12s:", "stats");
37272 if( p->mStats==0 ){
37273 cli_printf(p->out, " off\n");
37274 }else{
37275 u8 mStats = p->mStats;
37276 if( (mStats & CLISTAT_ALL)==CLISTAT_ALL ){
37277 cli_printf(p->out, " all");
37278 mStats &= ~CLISTAT_ALL;
37279 }
37280 if( (mStats & CLISTAT_ON)==CLISTAT_ON ){
37281 cli_printf(p->out, " on");
37282 mStats &= ~CLISTAT_ON;
37283 }
37284 if( mStats & CLISTAT_XSTMT ){
37285 cli_printf(p->out, " xstmt");
37286 }else if( mStats & CLISTAT_STMT ){
37287 cli_printf(p->out, " stmt");
37288 }else if( mStats & CLISTAT_VMSTEP ){
37289 cli_printf(p->out, " vmstep");
37290 }
37291 if( mStats & CLISTAT_XMEM ){
37292 cli_printf(p->out, " xmem");
37293 }else if( mStats & CLISTAT_MEM ){
37294 cli_printf(p->out, " mem");
37295 }
37296 if( mStats & CLISTAT_IO ){
37297 cli_printf(p->out, " io");
37298 }
37299 if( mStats & CLISTAT_ZIPVFS ){
37300 cli_printf(p->out, " zipvfs");
37301 }
37302 if( mStats & CLISTAT_RESET ){
37303 cli_printf(p->out, " reset");
37304 }
37305 cli_printf(p->out,"\n");
37306 }
 
37307 cli_printf(p->out, "%12.12s: ", "width");
37308 for(i=0; i<p->mode.spec.nWidth; i++){
37309 cli_printf(p->out, "%d ", (int)p->mode.spec.aWidth[i]);
37310 }
37311 cli_puts("\n", p->out);
@@ -37101,24 +37312,11 @@
37312 cli_printf(p->out, "%12.12s: %s\n", "filename",
37313 p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : "");
37314 }else
37315
37316 if( c=='s' && cli_strncmp(azArg[0], "stats", n)==0 ){
37317 rc = dotCmdStats(p);
 
 
 
 
 
 
 
 
 
 
 
 
 
37318 }else
37319
37320 if( (c=='t' && n>1 && cli_strncmp(azArg[0], "tables", n)==0) ){
37321 sqlite3_stmt *pStmt;
37322 sqlite3_str *pSql;
@@ -37273,11 +37471,10 @@
37471 } aLabel[] = {
37472 { 0x00000001, 1, "QueryFlattener" },
37473 { 0x00000001, 0, "Flatten" },
37474 { 0x00000002, 1, "WindowFunc" },
37475 { 0x00000004, 1, "GroupByOrder" },
 
37476 { 0x00000010, 1, "DistinctOpt" },
37477 { 0x00000020, 1, "CoverIdxScan" },
37478 { 0x00000040, 1, "OrderByIdxJoin" },
37479 { 0x00000080, 1, "Transitive" },
37480 { 0x00000100, 1, "OmitNoopJoin" },
@@ -37290,11 +37487,10 @@
37487 { 0x00008000, 1, "PropagateConst" },
37488 { 0x00010000, 1, "MinMaxOpt" },
37489 { 0x00020000, 1, "SeekScan" },
37490 { 0x00040000, 1, "OmitOrderBy" },
37491 { 0x00080000, 1, "BloomFilter" },
 
37492 { 0x00200000, 1, "BalancedMerge" },
37493 { 0x00400000, 1, "ReleaseReg" },
37494 { 0x00800000, 1, "FlttnUnionAll" },
37495 { 0x01000000, 1, "IndexedEXpr" },
37496 { 0x02000000, 1, "Coroutines" },
@@ -37301,10 +37497,11 @@
37497 { 0x04000000, 1, "NullUnusedCols" },
37498 { 0x08000000, 1, "OnePass" },
37499 { 0x10000000, 1, "OrderBySubq" },
37500 { 0x20000000, 1, "StarQuery" },
37501 { 0x40000000, 1, "ExistsToJoin" },
37502 { 0x80000000, 1, "UnionLimit" },
37503 { 0xffffffff, 0, "All" },
37504 };
37505 unsigned int curOpt;
37506 unsigned int newOpt;
37507 unsigned int m;
@@ -39151,11 +39348,11 @@
39348 }else if( cli_strcmp(z,"-eqp")==0 ){
39349 data.mode.autoEQP = AUTOEQP_on;
39350 }else if( cli_strcmp(z,"-eqpfull")==0 ){
39351 data.mode.autoEQP = AUTOEQP_full;
39352 }else if( cli_strcmp(z,"-stats")==0 ){
39353 data.mStats = CLISTAT_ON;
39354 }else if( cli_strcmp(z,"-scanstats")==0 ){
39355 data.mode.scanstatsOn = 1;
39356 }else if( cli_strcmp(z,"-backslash")==0 ){
39357 /* Undocumented command-line option: -backslash
39358 ** Causes C-style backslash escapes to be evaluated in SQL statements
@@ -39410,10 +39607,12 @@
39607 free(data.dot.azArg);
39608 free(data.dot.aiOfst);
39609 free(data.dot.abQuot);
39610 free(data.azPrompt[0]);
39611 free(data.azPrompt[1]);
39612 free(data.zAuthDeny);
39613 free(data.zAuthIgnore);
39614 if( data.nTestRun ){
39615 sqlite3_fprintf(stdout, "%d test%s run with %d error%s\n",
39616 data.nTestRun, data.nTestRun==1 ? "" : "s",
39617 data.nTestErr, data.nTestErr==1 ? "" : "s");
39618 fflush(stdout);
39619
+464 -175
--- extsrc/sqlite3.c
+++ extsrc/sqlite3.c
@@ -16,11 +16,11 @@
1616
** if you want a wrapper to interface SQLite with your choice of programming
1717
** language. The code for the "sqlite3" command-line shell is also in a
1818
** separate file. This file contains only code for the core SQLite library.
1919
**
2020
** The content in this amalgamation comes from Fossil check-in
21
-** 3bd5456e50ef321f2cafcf67261d556a7606 with changes in files:
21
+** 555f31c64d3fcc55df2715a7c8c1cb28503b with changes in files:
2222
**
2323
**
2424
*/
2525
#ifndef SQLITE_AMALGAMATION
2626
#define SQLITE_CORE 1
@@ -467,14 +467,14 @@
467467
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
468468
** [sqlite_version()] and [sqlite_source_id()].
469469
*/
470470
#define SQLITE_VERSION "3.54.0"
471471
#define SQLITE_VERSION_NUMBER 3054000
472
-#define SQLITE_SOURCE_ID "2026-08-20 13:25:12 3bd5456e50ef321f2cafcf67261d556a760624ce0caab430ed9454cebec233e7"
472
+#define SQLITE_SOURCE_ID "2026-08-27 10:58:16 555f31c64d3fcc55df2715a7c8c1cb28503b2c0eb21d950ccdcbf6f577593dd5"
473473
#define SQLITE_SCM_BRANCH "trunk"
474474
#define SQLITE_SCM_TAGS ""
475
-#define SQLITE_SCM_DATETIME "2026-08-20T13:25:12.207Z"
475
+#define SQLITE_SCM_DATETIME "2026-08-27T10:58:16.680Z"
476476
477477
/*
478478
** CAPI3REF: Run-Time Library Version Numbers
479479
** KEYWORDS: sqlite3_version sqlite3_sourceid
480480
**
@@ -18208,11 +18208,11 @@
1820818208
SQLITE_PRIVATE int sqlite3VdbeUsesDoubleQuotedString(Vdbe*,const char*);
1820918209
#endif
1821018210
SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe*,Vdbe*);
1821118211
SQLITE_PRIVATE VdbeOp *sqlite3VdbeTakeOpArray(Vdbe*, int*, int*);
1821218212
SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe*, int, u8);
18213
-SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe*, int);
18213
+SQLITE_PRIVATE void sqlite3VdbeReprepareOnBind(Vdbe*, int, int);
1821418214
#ifndef SQLITE_OMIT_TRACE
1821518215
SQLITE_PRIVATE char *sqlite3VdbeExpandSql(Vdbe*, const char*);
1821618216
#endif
1821718217
SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*);
1821818218
SQLITE_PRIVATE int sqlite3BlobCompare(const Mem*, const Mem*);
@@ -19088,41 +19088,43 @@
1908819088
** selectively disable various optimizations.
1908919089
*/
1909019090
#define SQLITE_QueryFlattener 0x00000001 /* Query flattening */
1909119091
#define SQLITE_WindowFunc 0x00000002 /* Use xInverse for window functions */
1909219092
#define SQLITE_GroupByOrder 0x00000004 /* GROUPBY cover of ORDERBY */
19093
-#define SQLITE_FactorOutConst 0x00000008 /* Constant factoring */
19093
+ /* 0x00000008 -- Available for reuse */
1909419094
#define SQLITE_DistinctOpt 0x00000010 /* DISTINCT using indexes */
1909519095
#define SQLITE_CoverIdxScan 0x00000020 /* Covering index scans */
1909619096
#define SQLITE_OrderByIdxJoin 0x00000040 /* ORDER BY of joins via index */
1909719097
#define SQLITE_Transitive 0x00000080 /* Transitive constraints */
1909819098
#define SQLITE_OmitNoopJoin 0x00000100 /* Omit unused tables in joins */
1909919099
#define SQLITE_CountOfView 0x00000200 /* The count-of-view optimization */
1910019100
#define SQLITE_CursorHints 0x00000400 /* Add OP_CursorHint opcodes */
1910119101
#define SQLITE_Stat4 0x00000800 /* Use STAT4 data */
19102
- /* TH3 expects this value ^^^^^^^^^^ to be 0x0000800. Don't change it */
19102
+ /* TH3 expects this value ^^^^^^^^^^ */
1910319103
#define SQLITE_PushDown 0x00001000 /* WHERE-clause push-down opt */
1910419104
#define SQLITE_SimplifyJoin 0x00002000 /* Convert LEFT JOIN to JOIN */
1910519105
#define SQLITE_SkipScan 0x00004000 /* Skip-scans */
1910619106
#define SQLITE_PropagateConst 0x00008000 /* The constant propagation opt */
1910719107
#define SQLITE_MinMaxOpt 0x00010000 /* The min/max optimization */
1910819108
#define SQLITE_SeekScan 0x00020000 /* The OP_SeekScan optimization */
1910919109
#define SQLITE_OmitOrderBy 0x00040000 /* Omit pointless ORDER BY */
19110
- /* TH3 expects this value ^^^^^^^^^^ to be 0x40000. Coordinate any change */
19111
-#define SQLITE_BloomFilter 0x00080000 /* Use a Bloom filter on searches */
19112
-#define SQLITE_BloomPulldown 0x00100000 /* Run Bloom filters early */
19110
+ /* TH3 expects this value ^^^^^^^^^^ */
19111
+#define SQLITE_BloomFilter 0x00080000 /* Use a Bloom filters */
19112
+ /* TH3 expects this value ^^^^^^^^^^ */
19113
+ /* 0x00100000 -- Available for reuse */
1911319114
#define SQLITE_BalancedMerge 0x00200000 /* Balance multi-way merges */
1911419115
#define SQLITE_ReleaseReg 0x00400000 /* Use OP_ReleaseReg for testing */
1911519116
#define SQLITE_FlttnUnionAll 0x00800000 /* Disable the UNION ALL flattener */
19116
- /* TH3 expects this value ^^^^^^^^^^ See flatten04.test */
19117
+ /* TH3 expects this value ^^^^^^^^^^ */
1911719118
#define SQLITE_IndexedExpr 0x01000000 /* Pull exprs from index when able */
1911819119
#define SQLITE_Coroutines 0x02000000 /* Co-routines for subqueries */
1911919120
#define SQLITE_NullUnusedCols 0x04000000 /* NULL unused columns in subqueries */
1912019121
#define SQLITE_OnePass 0x08000000 /* Single-pass DELETE and UPDATE */
1912119122
#define SQLITE_OrderBySubq 0x10000000 /* ORDER BY in subquery helps outer */
1912219123
#define SQLITE_StarQuery 0x20000000 /* Heurists for star queries */
1912319124
#define SQLITE_ExistsToJoin 0x40000000 /* The EXISTS-to-JOIN optimization */
19125
+#define SQLITE_UnionLimit 0x80000000 /* Optimizations for UNION + LIMIT */
1912419126
#define SQLITE_AllOpts 0xffffffff /* All optimizations */
1912519127
1912619128
/*
1912719129
** Macros for testing whether or not optimizations are enabled or disabled.
1912819130
*/
@@ -22226,10 +22228,11 @@
2222622228
SQLITE_PRIVATE void sqlite3ResetOneSchema(sqlite3*,int);
2222722229
SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3*);
2222822230
SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3*);
2222922231
SQLITE_PRIVATE void sqlite3ColumnSetExpr(Parse*,Table*,Column*,Expr*);
2223022232
SQLITE_PRIVATE Expr *sqlite3ColumnExpr(Table*,Column*);
22233
+SQLITE_PRIVATE Expr *sqlite3ColumnExprAuth(Table*,Column*,Parse*);
2223122234
SQLITE_PRIVATE void sqlite3ColumnSetColl(sqlite3*,Column*,const char*zColl);
2223222235
SQLITE_PRIVATE const char *sqlite3ColumnColl(Column*);
2223322236
SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3*,Table*);
2223422237
SQLITE_PRIVATE void sqlite3GenerateColumnNames(Parse *pParse, Select *pSelect);
2223522238
SQLITE_PRIVATE int sqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**);
@@ -22439,11 +22442,11 @@
2243922442
SQLITE_PRIVATE int sqlite3ExprIsSingleTableConstraint(Expr*,const SrcList*,int,int);
2244022443
SQLITE_PRIVATE int sqlite3ExprListIsConstant(Parse *pParse, ExprList *pList, int bNoIs);
2244122444
#ifdef SQLITE_ENABLE_CURSOR_HINTS
2244222445
SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr*);
2244322446
#endif
22444
-SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr*, int*, Parse*);
22447
+SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr*, int*, Parse*, int);
2244522448
SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*);
2244622449
SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char);
2244722450
SQLITE_PRIVATE int sqlite3ExprIsLikeOperator(const Expr*);
2244822451
SQLITE_PRIVATE int sqlite3IsRowid(const char*);
2244922452
SQLITE_PRIVATE const char *sqlite3RowidAlias(Table *pTab);
@@ -25047,11 +25050,13 @@
2504725050
#endif
2504825051
void *pFree; /* Free this when deleting the vdbe */
2504925052
VdbeFrame *pFrame; /* Parent frame */
2505025053
VdbeFrame *pDelFrame; /* List of frame objects to free on VM reset */
2505125054
int nFrame; /* Number of frames in pFrame list */
25052
- u32 expmask; /* Binding to these vars invalidates VM */
25055
+ u32 expmask; /* Binding to these vars might invalidate VM */
25056
+ u32 smimask; /* Only invalid if changing to/from small integer */
25057
+ /* Note: smimask is always a subset of expmask */
2505325058
SubProgram *pProgram; /* Linked list of all sub-programs used by VM */
2505425059
AuxData *pAuxData; /* Linked list of auxdata allocations */
2505525060
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
2505625061
int nScan; /* Entries in aScan[] */
2505725062
ScanStatus *aScan; /* Scan definitions for sqlite3_stmt_scanstatus() */
@@ -33849,14 +33854,17 @@
3384933854
/*
3385033855
** If pExpr has a byte offset for the start of a token, record that as
3385133856
** as the error offset.
3385233857
*/
3385333858
SQLITE_PRIVATE void sqlite3RecordErrorOffsetOfExpr(sqlite3 *db, const Expr *pExpr){
33854
- while( pExpr
33855
- && (ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0)
33856
- ){
33857
- pExpr = pExpr->pLeft;
33859
+ while( pExpr ){
33860
+ if( ExprHasProperty(pExpr, EP_Reduced|EP_TokenOnly) ) return;
33861
+ if( ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0 ){
33862
+ pExpr = pExpr->pLeft;
33863
+ }else{
33864
+ break;
33865
+ }
3385833866
}
3385933867
if( pExpr==0 ) return;
3386033868
if( ExprHasProperty(pExpr, EP_FromDDL) ) return;
3386133869
db->errByteOffset = pExpr->w.iOfst;
3386233870
}
@@ -86910,11 +86918,34 @@
8691086918
const Expr *pExpr, /* The expression to evaluate */
8691186919
u8 enc, /* Encoding to use */
8691286920
u8 affinity, /* Affinity to use */
8691386921
sqlite3_value **ppVal /* Write the new value here */
8691486922
){
86915
- return pExpr ? valueFromExpr(db, pExpr, enc, affinity, ppVal, 0) : 0;
86923
+ int rc = SQLITE_OK;
86924
+ sqlite3_value *pVal = 0;
86925
+ if( pExpr ){
86926
+ rc = valueFromExpr(db, pExpr, enc, affinity, &pVal, 0);
86927
+ if( rc==SQLITE_OK && pVal
86928
+ && affinity==SQLITE_AFF_REAL
86929
+ && (pVal->flags & MEM_Int)
86930
+ && (u64)(pVal->u.i<0 ? pVal->u.i : pVal->u.i)>140737488355327LL
86931
+ ){
86932
+ /* If the integer value is too large to fit in a 6-byte integer and
86933
+ ** the affinity is REAL, convert it to a real value now. In most
86934
+ ** cases an OP_RealAffinity opcode will be used to convert the
86935
+ ** value to an actual real, but this opcode is omitted if the values
86936
+ ** are being read directly from a table in order to create an index
86937
+ ** key. It is important to get a "real" real value for the larger
86938
+ ** magnitude integer values so that comparisons work correctly -
86939
+ ** SQLite by default will convert real values to integers before
86940
+ ** doing the comparison, which is different from converting to real
86941
+ ** first. */
86942
+ sqlite3VdbeMemRealify(pVal);
86943
+ }
86944
+ }
86945
+ *ppVal = pVal;
86946
+ return rc;
8691686947
}
8691786948
8691886949
#ifdef SQLITE_ENABLE_STAT4
8691986950
/*
8692086951
** Attempt to extract a value from pExpr and use it to construct *ppVal.
@@ -86955,11 +86986,11 @@
8695586986
sqlite3VdbeMemSetNull((Mem*)pVal);
8695686987
}
8695786988
}else if( pExpr->op==TK_VARIABLE && (db->flags & SQLITE_EnableQPSG)==0 ){
8695886989
Vdbe *v;
8695986990
int iBindVar = pExpr->iColumn;
86960
- sqlite3VdbeSetVarmask(pParse->pVdbe, iBindVar);
86991
+ sqlite3VdbeReprepareOnBind(pParse->pVdbe, iBindVar, 0);
8696186992
if( (v = pParse->pReprepare)!=0 ){
8696286993
pVal = valueNew(db, pAlloc);
8696386994
if( pVal ){
8696486995
rc = sqlite3VdbeMemCopy((Mem*)pVal, &v->aVar[iBindVar-1]);
8696586996
sqlite3ValueApplyAffinity(pVal, affinity, ENC(db));
@@ -87332,11 +87363,12 @@
8733287363
#ifdef SQLITE_ENABLE_NORMALIZE
8733387364
zTmp = pA->zNormSql;
8733487365
pA->zNormSql = pB->zNormSql;
8733587366
pB->zNormSql = zTmp;
8733687367
#endif
87337
- pB->expmask = pA->expmask;
87368
+ pB->expmask |= pA->expmask;
87369
+ pB->smimask |= pA->smimask;
8733887370
pB->prepFlags = pA->prepFlags;
8733987371
memcpy(pB->aCounter, pA->aCounter, sizeof(pB->aCounter));
8734087372
pB->aCounter[SQLITE_STMTSTATUS_REPREPARE]++;
8734187373
}
8734287374
@@ -92520,21 +92552,32 @@
9252092552
}
9252192553
9252292554
/*
9252392555
** Configure SQL variable iVar so that binding a new value to it signals
9252492556
** to sqlite3_reoptimize() that re-preparing the statement may result
92525
-** in a better query plan.
92557
+** in a better query plan. If parameter bSmallint is true, then the
92558
+** statement is only re-prepared if the new value is integer value 0 or 1.
92559
+**
92560
+** The v->expmask bit is always set. expmask means that a reprepare is
92561
+** possible. The v->smimask bit is only set if we want to restrict
92562
+** reprepare when the value changes from (0,1) to something else, or from
92563
+** something else to (0,1).
9252692564
*/
92527
-SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){
92565
+SQLITE_PRIVATE void sqlite3VdbeReprepareOnBind(Vdbe *v, int iVar, int bSmallint){
92566
+ u32 m;
9252892567
assert( iVar>0 );
9252992568
assert( (v->db->flags & SQLITE_EnableQPSG)==0
9253092569
|| (v->db->mDbFlags & DBFLAG_InternalFunc)!=0 );
92531
- if( iVar>=32 ){
92532
- v->expmask |= 0x80000000;
92533
- }else{
92534
- v->expmask |= ((u32)1 << (iVar-1));
92570
+
92571
+ m = (iVar>=32) ? 0x80000000 : ((u32)1 << (iVar-1));
92572
+ v->expmask |= m;
92573
+ if( bSmallint ){
92574
+ v->smimask |= m;
9253592575
}
92576
+
92577
+ /* smimask is always a subset of expmask */
92578
+ assert( (v->smimask & v->expmask) == v->smimask );
9253692579
}
9253792580
9253892581
/*
9253992582
** Helper function for vdbeIsMatchingIndexKey(). Return true if column
9254092583
** iCol should be ignored when comparing a record with a record from
@@ -94695,11 +94738,15 @@
9469594738
** for a statement, then the statement will be automatically recompiled,
9469694739
** as if there had been a schema change, on the first sqlite3_step() call
9469794740
** following any change to the bindings of that parameter.
9469894741
*/
9469994742
assert( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || p->expmask==0 );
94743
+ assert( (p->expmask & p->smimask)==p->smimask );
9470094744
if( p->expmask!=0 && (p->expmask & (i>=31 ? 0x80000000 : (u32)1<<i))!=0 ){
94745
+ /* We might avoid a reprepare here if p->smimask is set and the old
94746
+ ** value is an integer other than (0,1). But that is such a corner
94747
+ ** case that it does not seem worth the extra code to implement. */
9470194748
p->expired = 1;
9470294749
}
9470394750
return SQLITE_OK;
9470494751
}
9470594752
@@ -94794,19 +94841,50 @@
9479494841
}
9479594842
SQLITE_API int sqlite3_bind_int(sqlite3_stmt *p, int i, int iValue){
9479694843
return sqlite3_bind_int64(p, i, (i64)iValue);
9479794844
}
9479894845
SQLITE_API int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){
94799
- int rc;
94800
- Vdbe *p = (Vdbe *)pStmt;
94801
- rc = vdbeUnbind(p, (u32)(i-1));
94802
- if( rc==SQLITE_OK ){
94803
- assert( p!=0 && p->aVar!=0 && i>0 && i<=p->nVar ); /* tag-20240917-01 */
94804
- sqlite3VdbeMemSetInt64(&p->aVar[i-1], iValue);
94846
+ Mem *pVar;
94847
+ Vdbe *p = (Vdbe*)pStmt;
94848
+ if( vdbeSafetyNotNull(p) ){
94849
+ return SQLITE_MISUSE_BKPT;
94850
+ }
94851
+ sqlite3_mutex_enter(p->db->mutex);
94852
+ if( p->eVdbeState!=VDBE_READY_STATE ){
94853
+ sqlite3Error(p->db, SQLITE_MISUSE_BKPT);
94854
+ sqlite3_mutex_leave(p->db->mutex);
94855
+ sqlite3_log(SQLITE_MISUSE,
94856
+ "bind on a busy prepared statement: [%s]", p->zSql);
94857
+ return SQLITE_MISUSE_BKPT;
94858
+ }
94859
+ if( i<=0 || (--i)>=p->nVar ){
94860
+ sqlite3Error(p->db, SQLITE_RANGE);
9480594861
sqlite3_mutex_leave(p->db->mutex);
94862
+ return SQLITE_RANGE;
9480694863
}
94807
- return rc;
94864
+ pVar = &p->aVar[i];
94865
+ if( p->expmask!=0 ){
94866
+ u32 expireMask = i>=32 ? 0x80000000 : (u32)1 << i;
94867
+ assert( (p->expmask & p->smimask)==p->smimask );
94868
+ if( (p->expmask & expireMask)!=0 ){
94869
+ if( (p->smimask & expireMask)!=0 ){
94870
+ /* If the smimask bit is set, only expire the prepared statement
94871
+ ** if the value is changing to or from (0,1) and something else */
94872
+ int sm1 = (pVar->flags & MEM_Int)!=0 && pVar->u.i>=0 && pVar->u.i<=1;
94873
+ int sm2 = iValue>=0 && iValue<=1;
94874
+ if( sm1!=sm2 ){
94875
+ p->expired = 1;
94876
+ }
94877
+ }else if( (pVar->flags & MEM_Int)==0 || pVar->u.i!=iValue ){
94878
+ /* Always expire if the value really is changing */
94879
+ p->expired = 1;
94880
+ }
94881
+ }
94882
+ }
94883
+ sqlite3VdbeMemSetInt64(pVar, iValue);
94884
+ sqlite3_mutex_leave(p->db->mutex);
94885
+ return SQLITE_OK;
9480894886
}
9480994887
SQLITE_API int sqlite3_bind_null(sqlite3_stmt *pStmt, int i){
9481094888
int rc;
9481194889
Vdbe *p = (Vdbe*)pStmt;
9481294890
rc = vdbeUnbind(p, (u32)(i-1));
@@ -111721,11 +111799,11 @@
111721111799
NameContext nc; /* Name context for resolving pE */
111722111800
sqlite3 *db; /* Database connection */
111723111801
int rc; /* Return code from subprocedures */
111724111802
u8 savedSuppErr; /* Saved value of db->suppressErr */
111725111803
111726
- assert( sqlite3ExprIsInteger(pE, &i, 0)==0 );
111804
+ assert( sqlite3ExprIsInteger(pE, &i, 0, 0)==0 );
111727111805
pEList = pSelect->pEList;
111728111806
111729111807
/* Resolve all names in the ORDER BY term expression
111730111808
*/
111731111809
memset(&nc, 0, sizeof(nc));
@@ -111820,11 +111898,11 @@
111820111898
int iCol = -1;
111821111899
Expr *pE, *pDup;
111822111900
if( pItem->fg.done ) continue;
111823111901
pE = sqlite3ExprSkipCollateAndLikely(pItem->pExpr);
111824111902
if( NEVER(pE==0) ) continue;
111825
- if( sqlite3ExprIsInteger(pE, &iCol, 0) ){
111903
+ if( sqlite3ExprIsInteger(pE, &iCol, 0, 0) ){
111826111904
if( iCol<=0 || iCol>pEList->nExpr ){
111827111905
resolveOutOfRangeError(pParse, "ORDER", i+1, pEList->nExpr, pE);
111828111906
return 1;
111829111907
}
111830111908
}else{
@@ -112003,11 +112081,11 @@
112003112081
** copy of the iCol-th result-set expression. */
112004112082
pItem->u.x.iOrderByCol = (u16)iCol;
112005112083
continue;
112006112084
}
112007112085
}
112008
- if( sqlite3ExprIsInteger(pE2, &iCol, 0) ){
112086
+ if( sqlite3ExprIsInteger(pE2, &iCol, 0, 0) ){
112009112087
/* The ORDER BY term is an integer constant. Again, set the column
112010112088
** number so that sqlite3ResolveOrderGroupBy() will convert the
112011112089
** order-by term to a copy of the result-set expression */
112012112090
if( iCol<1 || iCol>0xffff ){
112013112091
resolveOutOfRangeError(pParse, zType, i+1, nResult, pE2);
@@ -115041,11 +115119,11 @@
115041115119
db = pWalker->pParse->db;
115042115120
pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0);
115043115121
if( pDef==0
115044115122
|| pDef->xFinalize!=0
115045115123
|| (pDef->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0
115046
- || ExprHasProperty(pExpr, EP_WinFunc)
115124
+ || NEVER(ExprHasProperty(pExpr, EP_WinFunc))
115047115125
){
115048115126
pWalker->eCode = 0;
115049115127
return WRC_Abort;
115050115128
}
115051115129
return WRC_Prune;
@@ -115089,23 +115167,29 @@
115089115167
pWalker->eCode = 0;
115090115168
return WRC_Abort;
115091115169
}
115092115170
115093115171
switch( pExpr->op ){
115094
- /* Consider functions to be constant if all their arguments are constant
115095
- ** and either pWalker->eCode==4 or 5 or the function has the
115096
- ** SQLITE_FUNC_CONST flag. */
115097115172
case TK_FUNCTION:
115098
- if( (pWalker->eCode>=4 || ExprHasProperty(pExpr,EP_ConstFunc))
115099
- && !ExprHasProperty(pExpr, EP_WinFunc)
115100
- ){
115173
+ if( ExprHasProperty(pExpr, EP_WinFunc) ){
115174
+ pWalker->eCode = 0; /* Window functions are never constant */
115175
+ return WRC_Abort;
115176
+ }else if( pWalker->eCode>=4 && pWalker->eCode<=5 ){
115177
+ /* Functions used within a CREATE TABLE statement are constant as
115178
+ ** long as all of their arguments are constant */
115101115179
if( pWalker->eCode==5 ) ExprSetProperty(pExpr, EP_FromDDL);
115102115180
return WRC_Continue;
115181
+ }else if( ExprHasProperty(pExpr,EP_ConstFunc) ){
115182
+ /* Functions marked with EP_ConstFunc are constant as long as
115183
+ ** all their arugments are constant and they are not window functions */
115184
+ return WRC_Continue;
115103115185
}else if( pWalker->pParse ){
115186
+ /* Functions are constant if they have SQLITE_FUNC_CONSTANT or
115187
+ ** SQLITE_FUNC_SLOCHNG and if all arguments are constant */
115104115188
return exprNodeIsConstantFunction(pWalker, pExpr);
115105115189
}else{
115106
- pWalker->eCode = 0;
115190
+ pWalker->eCode = 0; /* Function call is not constant */
115107115191
return WRC_Abort;
115108115192
}
115109115193
case TK_ID:
115110115194
/* Convert "true" or "false" in a DEFAULT clause into the
115111115195
** appropriate TK_TRUEFALSE operator */
@@ -115449,69 +115533,79 @@
115449115533
return w.eCode==0;
115450115534
}
115451115535
#endif
115452115536
115453115537
/*
115454
-** If the expression p codes a constant integer that is small enough
115455
-** to fit in a 32-bit integer, return 1 and put the value of the integer
115456
-** in *pValue. If the expression is not an integer or if it is too big
115457
-** to fit in a signed 32-bit integer, return 0 and leave *pValue unchanged.
115538
+** If the expression p codes a constant integer between 0 and 0x7fffffff,
115539
+** then return 1 and put the value of the integer in *pValue. If the
115540
+** expression is not an integer or if it is an integer that is out side
115541
+** the range of 0...0x7fffffff, then return 0 and leave *pValue unchanged.
115458115542
**
115459115543
** If the pParse pointer is provided, then allow the expression p to be
115460
-** a parameter (TK_VARIABLE) that is bound to an integer.
115461
-** But if pParse is NULL, then p must be a pure integer literal.
115544
+** a parameter (TK_VARIABLE) that is bound to an integer between 0 and
115545
+** 0x7fffffff. Variables that hold anything other than integers, or that
115546
+** hold integers outside the range of 0..0x7fffffff are not seen.
115547
+** But if pParse is NULL, then p must be a pure integer literal between
115548
+** 0 and 0x7fffffff.
115549
+**
115550
+** If pParse is not NULL and expression p is a variable, then the variable
115551
+** is marked so as to cause the statement to be reprepared each time a new
115552
+** value is bound to it. Except, if parameter bRSI is true, then the statement
115553
+** will only be reprepared if the rebind changes the value to or from a
115554
+** "small integer" (either 0 or 1). Note that if p is a variable then
115555
+** reprepare is always enabled for that variable, regardless of its current
115556
+** binding. The RSI is only enabled if the current binding is a small
115557
+** integer. "RSI" stands for "Reprepare Small Integers".
115462115558
*/
115463
-SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr *p, int *pValue, Parse *pParse){
115464
- int rc = 0;
115465
- if( NEVER(p==0) ) return 0; /* Used to only happen following on OOM */
115466
-
115467
- /* If an expression is an integer literal that fits in a signed 32-bit
115468
- ** integer, then the EP_IntValue flag will have already been set */
115469
- assert( p->op!=TK_INTEGER || (p->flags & EP_IntValue)!=0
115470
- || sqlite3GetInt32(p->u.zToken, &rc)==0 );
115471
-
115472
- if( p->flags & EP_IntValue ){
115473
- *pValue = p->u.iValue;
115474
- return 1;
115475
- }
115476
- switch( p->op ){
115477
- case TK_UPLUS: {
115478
- rc = sqlite3ExprIsInteger(p->pLeft, pValue, 0);
115479
- break;
115480
- }
115481
- case TK_UMINUS: {
115482
- int v = 0;
115483
- if( sqlite3ExprIsInteger(p->pLeft, &v, 0) ){
115484
- assert( ((unsigned int)v)!=0x80000000 );
115485
- *pValue = -v;
115486
- rc = 1;
115487
- }
115488
- break;
115489
- }
115490
- case TK_VARIABLE: {
115491
- sqlite3_value *pVal;
115492
- if( pParse==0 ) break;
115559
+SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr *p, int *pValue, Parse *pParse, int bRSI){
115560
+ int iSign = 1; /* Either +1 or -1. */
115561
+ while( 1/*exit-by-break*/ ){
115562
+ if( NEVER(p==0) ) return 0;
115563
+ if( ExprUseUValue(p) ){
115564
+ *pValue = p->u.iValue*iSign;
115565
+ return 1;
115566
+ }
115567
+ if( p->op==TK_UPLUS ){
115568
+ p = p->pLeft;
115569
+ pParse = 0;
115570
+ continue;
115571
+ }
115572
+ if( p->op==TK_UMINUS ){
115573
+ iSign = -iSign;
115574
+ p = p->pLeft;
115575
+ pParse = 0;
115576
+ continue;
115577
+ }
115578
+ if( p->op==TK_VARIABLE && pParse!=0 ){
115579
+ sqlite3_value *pVal; /* The variable */
115580
+ int isSmall = 0; /* Only reprepare if change to/from small integer */
115581
+ int rc = 0; /* 1 if successful, 0 if failed */
115582
+ assert( iSign==1 );
115493115583
if( NEVER(pParse->pVdbe==0) ) break;
115494115584
if( (pParse->db->flags & SQLITE_EnableQPSG)!=0 ) break;
115495
- sqlite3VdbeSetVarmask(pParse->pVdbe, p->iColumn);
115496115585
pVal = sqlite3VdbeGetBoundValue(pParse->pReprepare, p->iColumn,
115497115586
SQLITE_AFF_BLOB);
115498115587
if( pVal ){
115499
- if( sqlite3_value_type(pVal)==SQLITE_INTEGER ){
115500
- sqlite3_int64 vv = sqlite3_value_int64(pVal);
115501
- if( vv == (vv & 0x7fffffff) ){ /* non-negative numbers only */
115502
- *pValue = (int)vv;
115503
- rc = 1;
115588
+ i64 vv;
115589
+ if( sqlite3_value_type(pVal)==SQLITE_INTEGER
115590
+ && (vv = sqlite3_value_int64(pVal))>=0
115591
+ && vv<=0x7fffffff
115592
+ ){
115593
+ *pValue = (int)vv;
115594
+ if( bRSI ){
115595
+ isSmall = vv<=1;
115504115596
}
115597
+ rc = 1;
115505115598
}
115506115599
sqlite3ValueFree(pVal);
115507115600
}
115508
- break;
115601
+ sqlite3VdbeReprepareOnBind(pParse->pVdbe, p->iColumn, isSmall);
115602
+ return rc;
115509115603
}
115510
- default: break;
115604
+ break;
115511115605
}
115512
- return rc;
115606
+ return 0;
115513115607
}
115514115608
115515115609
/*
115516115610
** Return FALSE if there is no chance that the expression can be NULL.
115517115611
**
@@ -115674,17 +115768,28 @@
115674115768
** Generate code that checks the left-most column of index table iCur to see if
115675115769
** it contains any NULL entries. Cause the register at regHasNull to be set
115676115770
** to a non-NULL value if iCur contains no NULLs. Cause register regHasNull
115677115771
** to be set to NULL if iCur contains one or more NULL values.
115678115772
*/
115679
-static void sqlite3SetHasNullFlag(Vdbe *v, int iCur, int regHasNull){
115773
+static void sqlite3SetHasNullFlag(
115774
+ Vdbe *v, /* Write new code into this statement under construction */
115775
+ int iCur, /* Cursor for the index */
115776
+ int regHasNull, /* Register in which to store hasNull flag */
115777
+ int eSortOrder /* SQLITE_SO_ASC or SQLITE_SO_DESC */
115778
+){
115680115779
int addr1;
115780
+ int op;
115681115781
sqlite3VdbeAddOp2(v, OP_Integer, 0, regHasNull);
115682
- addr1 = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v);
115782
+ if( eSortOrder==SQLITE_SO_ASC ){
115783
+ op = OP_Rewind;
115784
+ }else{
115785
+ op = OP_Last;
115786
+ }
115787
+ addr1 = sqlite3VdbeAddOp1(v, op, iCur); VdbeCoverage(v);
115683115788
sqlite3VdbeAddOp3(v, OP_Column, iCur, 0, regHasNull);
115684115789
sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG);
115685
- VdbeComment((v, "first_entry_in(%d)", iCur));
115790
+ VdbeComment((v, op==OP_Last?"last_entry_in(%d)":"first_entry_in(%d)", iCur));
115686115791
sqlite3VdbeJumpHere(v, addr1);
115687115792
}
115688115793
#endif
115689115794
115690115795
@@ -115947,11 +116052,12 @@
115947116052
sqlite3VdbeAddOp3(v, OP_ColumnsUsed,
115948116053
iTab, LOWER32(mask), UPPER32(mask));
115949116054
#endif
115950116055
*prRhsHasNull = ++pParse->nMem;
115951116056
if( nExpr==1 ){
115952
- sqlite3SetHasNullFlag(v, iTab, *prRhsHasNull);
116057
+ sqlite3SetHasNullFlag(v, iTab, *prRhsHasNull,
116058
+ pIdx->aSortOrder[0]);
115953116059
}
115954116060
}
115955116061
sqlite3VdbeJumpHere(v, iAddr);
115956116062
}
115957116063
} /* End loop over indexes */
@@ -115996,11 +116102,11 @@
115996116102
){
115997116103
bloomOk = 1;
115998116104
}
115999116105
sqlite3CodeRhsOfIN(pParse, pX, iTab, bloomOk);
116000116106
if( rMayHaveNull ){
116001
- sqlite3SetHasNullFlag(v, iTab, rMayHaveNull);
116107
+ sqlite3SetHasNullFlag(v, iTab, rMayHaveNull, SQLITE_SO_ASC);
116002116108
}
116003116109
pParse->nQueryLoop = savedNQueryLoop;
116004116110
}
116005116111
116006116112
if( aiMap && eType!=IN_INDEX_INDEX_ASC && eType!=IN_INDEX_INDEX_DESC ){
@@ -116829,11 +116935,17 @@
116829116935
**
116830116936
** For a scalar LHS, it is sufficient to check just the first row
116831116937
** of the RHS.
116832116938
*/
116833116939
if( destStep6 ) sqlite3VdbeResolveLabel(v, destStep6);
116834
- addrTop = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, destIfFalse);
116940
+ if( eType==IN_INDEX_INDEX_DESC ){
116941
+ addrTop = sqlite3VdbeAddOp2(v, OP_Last, iTab, destIfFalse);
116942
+ }else{
116943
+ testcase( eType==IN_INDEX_EPH );
116944
+ testcase( eType==IN_INDEX_ROWID );
116945
+ addrTop = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, destIfFalse);
116946
+ }
116835116947
VdbeCoverage(v);
116836116948
if( nVector>1 ){
116837116949
destNotNull = sqlite3VdbeMakeLabel(pParse);
116838116950
}else{
116839116951
/* For nVector==1, combine steps 6 and 7 by immediately returning
@@ -117019,16 +117131,16 @@
117019117131
assert( pTab!=0 );
117020117132
assert( iCol!=XN_EXPR );
117021117133
if( iCol<0 || iCol==pTab->iPKey ){
117022117134
sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut);
117023117135
VdbeComment((v, "%s.rowid", pTab->zName));
117024
- }else{
117025
- int op;
117136
+ }else{
117026117137
int x;
117027117138
if( IsVirtual(pTab) ){
117028
- op = OP_VColumn;
117029117139
x = iCol;
117140
+ sqlite3VdbeAddOp3(v, OP_VColumn, iTabCur, x, regOut);
117141
+ return;
117030117142
#ifndef SQLITE_OMIT_GENERATED_COLUMNS
117031117143
}else if( (pCol = &pTab->aCol[iCol])->colFlags & COLFLAG_VIRTUAL ){
117032117144
Parse *pParse = sqlite3VdbeParser(v);
117033117145
if( pCol->colFlags & COLFLAG_BUSY ){
117034117146
sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"",
@@ -117044,17 +117156,15 @@
117044117156
return;
117045117157
#endif
117046117158
}else if( !HasRowid(pTab) ){
117047117159
testcase( iCol!=sqlite3TableColumnToStorage(pTab, iCol) );
117048117160
x = sqlite3TableColumnToIndex(sqlite3PrimaryKeyIndex(pTab), iCol);
117049
- op = OP_Column;
117050117161
}else{
117051117162
x = sqlite3TableColumnToStorage(pTab,iCol);
117052117163
testcase( x!=iCol );
117053
- op = OP_Column;
117054117164
}
117055
- sqlite3VdbeAddOp3(v, op, iTabCur, x, regOut);
117165
+ sqlite3VdbeAddOp3(v, OP_Column, iTabCur, x, regOut);
117056117166
sqlite3ColumnDefault(v, pTab, iCol, regOut);
117057117167
}
117058117168
}
117059117169
117060117170
/*
@@ -117433,10 +117543,15 @@
117433117543
addr = sqlite3VdbeAddOp1(v, OP_IfNullRow, p->iIdxCur);
117434117544
}
117435117545
ret = sqlite3ExprCodeTarget(pParse, p->pExpr, iTarget);
117436117546
sqlite3VdbeAddOp4(pParse->pVdbe, OP_Affinity, ret, 1, 0,
117437117547
(const char*)&p->aff, 1);
117548
+ if( sqlite3ExprCanReturnSubtype(pParse, p->pExpr) ){
117549
+ /* If the expression value may have a sub-type, clear it. Values
117550
+ ** read from columns do not have subtypes. */
117551
+ sqlite3VdbeAddOp1(pParse->pVdbe, OP_ClrSubtype, ret);
117552
+ }
117438117553
if( addr ){
117439117554
sqlite3VdbeJumpHere(v, addr);
117440117555
sqlite3VdbeChangeP3(v, addr, ret);
117441117556
}
117442117557
return ret;
@@ -119107,11 +119222,11 @@
119107119222
}
119108119223
if( (pParse->db->flags & SQLITE_EnableQPSG)!=0 ) return 2;
119109119224
sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR);
119110119225
if( pR ){
119111119226
iVar = pVar->iColumn;
119112
- sqlite3VdbeSetVarmask(pParse->pVdbe, iVar);
119227
+ sqlite3VdbeReprepareOnBind(pParse->pVdbe, iVar, 0);
119113119228
pL = sqlite3VdbeGetBoundValue(pParse->pReprepare, iVar, SQLITE_AFF_BLOB);
119114119229
if( pL ){
119115119230
if( sqlite3_value_type(pL)==SQLITE_TEXT ){
119116119231
sqlite3_value_text(pL); /* Make sure the encoding is UTF-8 */
119117119232
}
@@ -119363,11 +119478,11 @@
119363119478
static int sqlite3ExprIsNotTrue(Expr *pExpr){
119364119479
int v;
119365119480
if( pExpr->op==TK_NULL ) return 1;
119366119481
if( pExpr->op==TK_TRUEFALSE && sqlite3ExprTruthValue(pExpr)==0 ) return 1;
119367119482
v = 1;
119368
- if( sqlite3ExprIsInteger(pExpr, &v, 0) && v==0 ) return 1;
119483
+ if( sqlite3ExprIsInteger(pExpr, &v, 0, 0) && v==0 ) return 1;
119369119484
return 0;
119370119485
}
119371119486
119372119487
/*
119373119488
** Return true if the expression is one of the following:
@@ -120613,10 +120728,34 @@
120613120728
sqlite3NestedParse(pParse,
120614120729
"SELECT raise(ABORT,%Q) FROM \"%w\".\"%w\"",
120615120730
zErr, zDb, zTab
120616120731
);
120617120732
}
120733
+
120734
+/*
120735
+** zCol is a column name used in an ALTER TABLE DROP, ADD or RENAME COLUMN
120736
+** operation. zOp identifies the specific operation - "drop", "add", "rename
120737
+** to" or "rename from". pTab is the table being altered.
120738
+**
120739
+** If pTab has a rowid and zCol is a rowid alias, then SQLITE_ERROR is
120740
+** returned and an error message left in pParse. Or, if zCol is not an alias
120741
+** for "rowid" or pTab is not an intkey table, then SQLITE_OK is returned.
120742
+*/
120743
+static int isRowidAlias(
120744
+ Parse *pParse,
120745
+ Table *pTab,
120746
+ const char *zCol,
120747
+ const char *zOp
120748
+){
120749
+ if( HasRowid(pTab) && sqlite3IsRowid(zCol) ){
120750
+ sqlite3ErrorMsg(pParse, "cannot %s rowid alias: %s", zOp, zCol);
120751
+ return SQLITE_ERROR;
120752
+ }
120753
+ return SQLITE_OK;
120754
+}
120755
+
120756
+
120618120757
120619120758
/*
120620120759
** This function is called after an "ALTER TABLE ... ADD" statement
120621120760
** has been parsed. Argument pColDef contains the text of the new
120622120761
** column definition.
@@ -120659,13 +120798,13 @@
120659120798
return;
120660120799
}
120661120800
#endif
120662120801
120663120802
120664
- /* Check that the new column is not specified as PRIMARY KEY or UNIQUE.
120665
- ** If there is a NOT NULL constraint, then the default value for the
120666
- ** column must not be NULL.
120803
+ /* Check that the new column is not specified as PRIMARY KEY or UNIQUE,
120804
+ ** or a rowid alias. If there is a NOT NULL constraint, then the default
120805
+ ** value for the column must not be NULL.
120667120806
*/
120668120807
if( pCol->colFlags & COLFLAG_PRIMKEY ){
120669120808
sqlite3ErrorMsg(pParse, "Cannot add a PRIMARY KEY column");
120670120809
return;
120671120810
}
@@ -120672,10 +120811,11 @@
120672120811
if( pNew->pIndex ){
120673120812
sqlite3ErrorMsg(pParse,
120674120813
"Cannot add a UNIQUE column");
120675120814
return;
120676120815
}
120816
+ if( isRowidAlias(pParse, pTab, pCol->zCnName, "add") ) return;
120677120817
if( (pCol->colFlags & COLFLAG_GENERATED)==0 ){
120678120818
/* If the default value for the new column was specified with a
120679120819
** literal NULL, then set pDflt to 0. This simplifies checking
120680120820
** for an SQL NULL default below.
120681120821
*/
@@ -120964,10 +121104,12 @@
120964121104
** CREATE statement text for the sqlite_schema table.
120965121105
*/
120966121106
sqlite3MayAbort(pParse);
120967121107
zNew = sqlite3NameFromToken(db, pNew);
120968121108
if( !zNew ) goto exit_rename_column;
121109
+ if( isRowidAlias(pParse, pTab, zOld, "rename from") ) goto exit_rename_column;
121110
+ if( isRowidAlias(pParse, pTab, zNew, "rename to") ) goto exit_rename_column;
120969121111
assert( pNew->n>0 );
120970121112
bQuote = sqlite3Isquote(pNew->z[0]);
120971121113
sqlite3NestedParse(pParse,
120972121114
"UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET "
120973121115
"sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, %d) "
@@ -122590,10 +122732,11 @@
122590122732
iCol = sqlite3ColumnIndex(pTab, zCol);
122591122733
if( iCol<0 ){
122592122734
sqlite3ErrorMsg(pParse, "no such column: \"%T\"", pName);
122593122735
goto exit_drop_column;
122594122736
}
122737
+ if( isRowidAlias(pParse, pTab, zCol, "drop") ) goto exit_drop_column;
122595122738
122596122739
/* Do not allow the user to drop a PRIMARY KEY column or a column
122597122740
** constrained by a UNIQUE constraint. */
122598122741
if( pTab->aCol[iCol].colFlags & (COLFLAG_PRIMKEY|COLFLAG_UNIQUE) ){
122599122742
sqlite3ErrorMsg(pParse, "cannot drop %s column: \"%s\"",
@@ -127037,21 +127180,61 @@
127037127180
sqlite3ExprDelete(pParse->db, pList->a[pCol->iDflt-1].pExpr);
127038127181
pList->a[pCol->iDflt-1].pExpr = pExpr;
127039127182
}
127040127183
}
127041127184
127185
+#ifndef SQLITE_OMIT_AUTHORIZATION
127186
+/*
127187
+** If the expression contains any TK_FUNCTION, send it to the authorizer
127188
+** and if the authorization fails then raise an error.
127189
+*/
127190
+static int authFunctions(Walker *pWalker, Expr *p){
127191
+ if( p->op==TK_FUNCTION ){
127192
+ Parse *pParse = pWalker->pParse;
127193
+ int rc = sqlite3AuthCheck(pParse, SQLITE_FUNCTION, 0, p->u.zToken, 0);
127194
+ if( rc ){
127195
+ sqlite3ErrorMsg(pParse, "not authorized to use function: %#T", p);
127196
+ return WRC_Abort;
127197
+ }
127198
+ }
127199
+ return WRC_Continue;
127200
+}
127201
+static void sqlite3FuncAuth(Parse *pParse, Expr *pExpr){
127202
+ Walker w;
127203
+ memset(&w, 0, sizeof(w));
127204
+ w.xExprCallback = authFunctions;
127205
+ w.pParse = pParse;
127206
+ sqlite3WalkExpr(&w,pExpr);
127207
+}
127208
+#endif /* SQLITE_OMIT_AUTHORIZATION */
127209
+
127042127210
/*
127043127211
** Return the expression associated with a column. The expression might be
127044127212
** the DEFAULT clause or the AS clause of a generated column.
127045127213
** Return NULL if the column has no associated expression.
127214
+**
127215
+** There are two variants of this routine. sqlite3ColumnExpr() just
127216
+** returns the expression with no side effects. sqlite3ColumnExprAuth()
127217
+** takes the extra step of invoking the authorizer (if one is defined)
127218
+** and raising an error if the returned expression uses any unauthorized
127219
+** SQL function.
127046127220
*/
127047127221
SQLITE_PRIVATE Expr *sqlite3ColumnExpr(Table *pTab, Column *pCol){
127048127222
if( pCol->iDflt==0 ) return 0;
127049127223
if( !IsOrdinaryTable(pTab) ) return 0;
127050127224
if( NEVER(pTab->u.tab.pDfltList==0) ) return 0;
127051127225
if( NEVER(pTab->u.tab.pDfltList->nExpr<pCol->iDflt) ) return 0;
127052127226
return pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr;
127227
+}
127228
+SQLITE_PRIVATE Expr *sqlite3ColumnExprAuth(Table *pTab, Column *pCol, Parse *pParse){
127229
+ Expr *pExpr = sqlite3ColumnExpr(pTab,pCol);
127230
+#ifndef SQLITE_OMIT_AUTHORIZATION
127231
+ if( pParse->db->xAuth!=0 ){
127232
+ sqlite3FuncAuth(pParse, pExpr);
127233
+ }
127234
+#endif
127235
+ return pExpr;
127053127236
}
127054127237
127055127238
/*
127056127239
** Suppress false-positive warning message generated with -O3 in GCC
127057127240
** on the second call to sqlite3Strlen30() in the sqlite3ColumnSetColl()
@@ -128115,10 +128298,17 @@
128115128298
x.pLeft = pExpr;
128116128299
x.flags = EP_Skip;
128117128300
pDfltExpr = sqlite3ExprDup(db, &x, EXPRDUP_REDUCE);
128118128301
sqlite3DbFree(db, x.u.zToken);
128119128302
sqlite3ColumnSetExpr(pParse, p, pCol, pDfltExpr);
128303
+#ifndef SQLITE_OMIT_AUTHORIZATION
128304
+ /* Reject unauthorized functions from DEFAULT clauses */
128305
+ if( db->init.busy==0 && db->xAuth!=0 ){
128306
+ sqlite3FuncAuth(pParse, pExpr);
128307
+ }
128308
+#endif /* SQLITE_OMIT_AUTHORIZER */
128309
+
128120128310
}
128121128311
}
128122128312
if( IN_RENAME_OBJECT ){
128123128313
sqlite3RenameExprUnmap(pParse, pExpr);
128124128314
}
@@ -128980,11 +129170,10 @@
128980129170
}
128981129171
}
128982129172
#else
128983129173
#define markExprListImmutable(X) /* no-op */
128984129174
#endif /* SQLITE_DEBUG */
128985
-
128986129175
128987129176
/*
128988129177
** This routine is called to report the final ")" that terminates
128989129178
** a CREATE TABLE statement.
128990129179
**
@@ -138593,11 +138782,11 @@
138593138782
if( pCol->colFlags & COLFLAG_GENERATED ){
138594138783
testcase( pCol->colFlags & COLFLAG_VIRTUAL );
138595138784
testcase( pCol->colFlags & COLFLAG_STORED );
138596138785
pDflt = 0;
138597138786
}else{
138598
- pDflt = sqlite3ColumnExpr(pFKey->pFrom, pCol);
138787
+ pDflt = sqlite3ColumnExprAuth(pFKey->pFrom, pCol, pParse);
138599138788
}
138600138789
if( pDflt ){
138601138790
pNew = sqlite3ExprDup(db, pDflt, 0);
138602138791
}else{
138603138792
pNew = sqlite3ExprAlloc(db, TK_NULL, 0, 0);
@@ -140165,11 +140354,11 @@
140165140354
continue;
140166140355
}else if( pColumn==0 ){
140167140356
/* Hidden columns that are not explicitly named in the INSERT
140168140357
** get their default value */
140169140358
sqlite3ExprCodeFactorable(pParse,
140170
- sqlite3ColumnExpr(pTab, &pTab->aCol[i]),
140359
+ sqlite3ColumnExprAuth(pTab, &pTab->aCol[i], pParse),
140171140360
iRegStore);
140172140361
continue;
140173140362
}
140174140363
}
140175140364
if( pColumn ){
@@ -140177,19 +140366,19 @@
140177140366
assert( j>=0 && j<=pColumn->nId );
140178140367
if( j==0 ){
140179140368
/* A column not named in the insert column list gets its
140180140369
** default value */
140181140370
sqlite3ExprCodeFactorable(pParse,
140182
- sqlite3ColumnExpr(pTab, &pTab->aCol[i]),
140371
+ sqlite3ColumnExprAuth(pTab, &pTab->aCol[i], pParse),
140183140372
iRegStore);
140184140373
continue;
140185140374
}
140186140375
k = j - 1;
140187140376
}else if( nColumn==0 ){
140188140377
/* This is INSERT INTO ... DEFAULT VALUES. Load the default value. */
140189140378
sqlite3ExprCodeFactorable(pParse,
140190
- sqlite3ColumnExpr(pTab, &pTab->aCol[i]),
140379
+ sqlite3ColumnExprAuth(pTab, &pTab->aCol[i], pParse),
140191140380
iRegStore);
140192140381
continue;
140193140382
}else{
140194140383
k = i - nHidden;
140195140384
}
@@ -140783,11 +140972,11 @@
140783140972
int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, iReg);
140784140973
VdbeCoverage(v);
140785140974
assert( (pCol->colFlags & COLFLAG_GENERATED)==0 );
140786140975
nSeenReplace++;
140787140976
sqlite3ExprCodeCopy(pParse,
140788
- sqlite3ColumnExpr(pTab, pCol), iReg);
140977
+ sqlite3ColumnExprAuth(pTab, pCol, pParse), iReg);
140789140978
sqlite3VdbeJumpHere(v, addr1);
140790140979
break;
140791140980
}
140792140981
case OE_Abort:
140793140982
sqlite3MayAbort(pParse);
@@ -141439,11 +141628,11 @@
141439141628
VdbeOp x; /* Conflict check opcode to copy */
141440141629
/* The sqlite3VdbeAddOp4() call might reallocate the opcode array.
141441141630
** Hence, make a complete copy of the opcode, rather than using
141442141631
** a pointer to the opcode. */
141443141632
x = *sqlite3VdbeGetOp(v, addrConflictCk);
141444
- if( x.opcode!=OP_IdxRowid ){
141633
+ if( x.opcode!=OP_IdxRowid || isUpdate ){
141445141634
int p2; /* New P2 value for copied conflict check opcode */
141446141635
const char *zP4;
141447141636
if( sqlite3OpcodeProperty[x.opcode]&OPFLG_JUMP ){
141448141637
p2 = lblRecheckOk;
141449141638
}else{
@@ -146693,12 +146882,12 @@
146693146882
p1 = -1;
146694146883
p3 = 3;
146695146884
}else{
146696146885
if( pCol->iDflt ){
146697146886
sqlite3_value *pDfltValue = 0;
146698
- sqlite3ValueFromExpr(db, sqlite3ColumnExpr(pTab,pCol), ENC(db),
146699
- pCol->affinity, &pDfltValue);
146887
+ sqlite3ValueFromExpr(db, sqlite3ColumnExprAuth(pTab,pCol,pParse),
146888
+ ENC(db), pCol->affinity, &pDfltValue);
146700146889
if( pDfltValue ){
146701146890
p4 = sqlite3_value_type(pDfltValue);
146702146891
sqlite3ValueFree(pDfltValue);
146703146892
}
146704146893
}
@@ -151506,13 +151695,11 @@
151506151695
*/
151507151696
SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse *pParse){
151508151697
if( pParse->pVdbe ){
151509151698
return pParse->pVdbe;
151510151699
}
151511
- if( pParse->pToplevel==0
151512
- && OptimizationEnabled(pParse->db,SQLITE_FactorOutConst)
151513
- ){
151700
+ if( pParse->pToplevel==0 ){
151514151701
pParse->okConstFactor = 1;
151515151702
}
151516151703
return sqlite3VdbeCreate(pParse);
151517151704
}
151518151705
@@ -151559,11 +151746,11 @@
151559151746
assert( pLimit->op==TK_LIMIT );
151560151747
assert( pLimit->pLeft!=0 );
151561151748
p->iLimit = iLimit = ++pParse->nMem;
151562151749
v = sqlite3GetVdbe(pParse);
151563151750
assert( v!=0 );
151564
- if( sqlite3ExprIsInteger(pLimit->pLeft, &n, pParse) ){
151751
+ if( sqlite3ExprIsInteger(pLimit->pLeft, &n, pParse, 0) ){
151565151752
sqlite3VdbeAddOp2(v, OP_Integer, n, iLimit);
151566151753
VdbeComment((v, "LIMIT counter"));
151567151754
if( n==0 ){
151568151755
sqlite3VdbeGoto(v, iBreak);
151569151756
}else if( n>=0 && p->nSelectRow>sqlite3LogEst((u64)n) ){
@@ -151926,15 +152113,37 @@
151926152113
while( p && (p->selFlags & SF_Recursive)!=0 ){ p = p->pPrior; }
151927152114
return p!=0;
151928152115
}
151929152116
151930152117
/*
151931
-** This routine is called to process a compound query form from
152118
+** Return TRUE if p is a UNION with a LIMIT of exactly 1 and no OFFSET
152119
+** clause. This is a precondition for a couple of related optimizations.
152120
+**
152121
+** False negatives are harmless (apart from resulting in a slower query).
152122
+** False positives can result in incorrect answers, however. To provoke
152123
+** false negatives for testing purposes, disable the SQLITE_UnionLimit
152124
+** optimization.
152125
+*/
152126
+static int unionWithLimitOne(sqlite3 *db, Select *p){
152127
+ int v;
152128
+ if( p->op!=TK_UNION ) return 0;
152129
+ if( p->pLimit==0 ) return 0;
152130
+ if( p->pLimit->pRight ) return 0; /* No OFFSET */
152131
+ v = 0;
152132
+ assert( p->pLimit->pLeft!=0 );
152133
+ if( sqlite3ExprIsInteger(p->pLimit->pLeft, &v, 0, 0)==0 ) return 0;
152134
+ if( v!=1 ) return 0;
152135
+ if( OptimizationDisabled(db, SQLITE_UnionLimit) ) return 0;
152136
+ return 1;
152137
+}
152138
+
152139
+/*
152140
+** This routine is called to process a compound query formed from
151932152141
** two or more separate queries using UNION, UNION ALL, EXCEPT, or
151933152142
** INTERSECT
151934152143
**
151935
-** "p" points to the right-most of the two queries. the query on the
152144
+** "p" points to the right-most of the two queries. The query on the
151936152145
** left is p->pPrior. The left query could also be a compound query
151937152146
** in which case this routine will be called recursively.
151938152147
**
151939152148
** The results of the total query are to be written into a destination
151940152149
** of type eDest with parameter iParm.
@@ -152014,11 +152223,11 @@
152014152223
#endif
152015152224
if( p->pOrderBy ){
152016152225
/* If the compound has an ORDER BY clause, then always use the merge
152017152226
** algorithm. */
152018152227
return multiSelectByMerge(pParse, p, pDest);
152019
- }else if( p->op!=TK_ALL ){
152228
+ }else if( p->op!=TK_ALL && !unionWithLimitOne(db,p) ){
152020152229
/* If the compound is EXCEPT, INTERSECT, or UNION (anything other than
152021152230
** UNION ALL) then also always use the merge algorithm. However, the
152022152231
** multiSelectByMerge() routine requires that the compound have an
152023152232
** ORDER BY clause, and it doesn't right now. So invent one first. */
152024152233
Expr *pOne = sqlite3ExprInt32(db, 1);
@@ -152026,11 +152235,12 @@
152026152235
if( pParse->nErr ) goto multi_select_end;
152027152236
assert( p->pOrderBy!=0 );
152028152237
p->pOrderBy->a[0].u.x.iOrderByCol = 1;
152029152238
return multiSelectByMerge(pParse, p, pDest);
152030152239
}else{
152031
- /* For a UNION ALL compound without ORDER BY, simply run the left
152240
+ /* For a UNION ALL compound without ORDER BY, or for a UNION with a
152241
+ ** LIMIT of exactly 1 and no OFFSET and no ORDER BY, simply run the left
152032152242
** query, then run the right query */
152033152243
int addr = 0;
152034152244
int nLimit = 0; /* Initialize to suppress harmless compiler warning */
152035152245
152036152246
#ifndef SQLITE_OMIT_EXPLAIN
@@ -152067,11 +152277,11 @@
152067152277
testcase( rc!=SQLITE_OK );
152068152278
pDelete = p->pPrior;
152069152279
p->pPrior = pPrior;
152070152280
p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
152071152281
if( p->pLimit
152072
- && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit, pParse)
152282
+ && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit, pParse, 0)
152073152283
&& nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit)
152074152284
){
152075152285
p->nSelectRow = sqlite3LogEst((u64)nLimit);
152076152286
}
152077152287
if( addr ){
@@ -152577,11 +152787,11 @@
152577152787
sqlite3ResolveOrderGroupBy(pParse, p, p->pOrderBy, "ORDER");
152578152788
sqlite3ResolveOrderGroupBy(pParse, pPrior, pPrior->pOrderBy, "ORDER");
152579152789
152580152790
/* Compute the limit registers */
152581152791
computeLimitRegisters(pParse, p, labelEnd);
152582
- if( p->iLimit && op==TK_ALL ){
152792
+ if( p->iLimit && (op==TK_ALL || unionWithLimitOne(db,p)) ){
152583152793
regLimitA = ++pParse->nMem;
152584152794
regLimitB = ++pParse->nMem;
152585152795
sqlite3VdbeAddOp2(v, OP_Copy, p->iOffset ? p->iOffset+1 : p->iLimit,
152586152796
regLimitA);
152587152797
sqlite3VdbeAddOp2(v, OP_Copy, regLimitA, regLimitB);
@@ -159864,30 +160074,47 @@
159864160074
** If column as REAL affinity and the table is an ordinary b-tree table
159865160075
** (not a virtual table) then the value might have been stored as an
159866160076
** integer. In that case, add an OP_RealAffinity opcode to make sure
159867160077
** it has been converted into REAL.
159868160078
*/
159869
-SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){
160079
+static SQLITE_NOINLINE void columnDefaultUncommonCase(
160080
+ Vdbe *v, /* Byte code under construction */
160081
+ Table *pTab, /* The table */
160082
+ Column *pCol, /* Which column of the table */
160083
+ int iReg /* Register in which results are stored */
160084
+){
160085
+ sqlite3_value *pValue = 0;
160086
+ u8 enc = ENC(sqlite3VdbeDb(v));
160087
+ Parse *pParse = sqlite3VdbeParser(v);
160088
+ assert( !IsView(pTab) );
160089
+ sqlite3ValueFromExpr(sqlite3VdbeDb(v),
160090
+ sqlite3ColumnExprAuth(pTab,pCol,pParse), enc,
160091
+ pCol->affinity, &pValue);
160092
+ if( pValue ){
160093
+ sqlite3VdbeAppendP4(v, pValue, P4_MEM);
160094
+ }
160095
+#ifndef SQLITE_OMIT_FLOATING_POINT
160096
+ if( pCol->affinity==SQLITE_AFF_REAL ){
160097
+ sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg);
160098
+ }
160099
+#endif
160100
+}
160101
+SQLITE_PRIVATE void sqlite3ColumnDefault(
160102
+ Vdbe *v, /* Byte code under construction */
160103
+ Table *pTab, /* The table */
160104
+ int i, /* Which column of the table */
160105
+ int iReg /* Register in which results are stored */
160106
+){
159870160107
Column *pCol;
159871160108
assert( pTab!=0 );
159872160109
assert( pTab->nCol>i );
159873160110
pCol = &pTab->aCol[i];
159874160111
if( pCol->iDflt ){
159875
- sqlite3_value *pValue = 0;
159876
- u8 enc = ENC(sqlite3VdbeDb(v));
159877
- assert( !IsView(pTab) );
159878
- VdbeComment((v, "%s.%s", pTab->zName, pCol->zCnName));
159879
- assert( i<pTab->nCol );
159880
- sqlite3ValueFromExpr(sqlite3VdbeDb(v),
159881
- sqlite3ColumnExpr(pTab,pCol), enc,
159882
- pCol->affinity, &pValue);
159883
- if( pValue ){
159884
- sqlite3VdbeAppendP4(v, pValue, P4_MEM);
159885
- }
160112
+ columnDefaultUncommonCase(v,pTab,pCol,iReg);
159886160113
}
159887160114
#ifndef SQLITE_OMIT_FLOATING_POINT
159888
- if( pCol->affinity==SQLITE_AFF_REAL && !IsVirtual(pTab) ){
160115
+ if( pCol->affinity==SQLITE_AFF_REAL ){
159889160116
sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg);
159890160117
}
159891160118
#endif
159892160119
}
159893160120
@@ -164579,10 +164806,11 @@
164579164806
ExprList *pOrigRhs; /* Original unmodified RHS */
164580164807
ExprList *pOrigLhs = 0; /* Original unmodified LHS */
164581164808
ExprList *pRhs = 0; /* New RHS after modifications */
164582164809
ExprList *pLhs = 0; /* New LHS after mods */
164583164810
int i; /* Loop counter */
164811
+ int nRhs = 0; /* Number of RHS terms added so far */
164584164812
164585164813
assert( ExprUseXSelect(pNew) );
164586164814
pOrigRhs = pSelect->pEList;
164587164815
assert( pNew->pLeft!=0 );
164588164816
assert( ExprUseXList(pNew->pLeft) );
@@ -164592,10 +164820,13 @@
164592164820
for(i=iEq; i<pLoop->nLTerm; i++){
164593164821
if( pLoop->aLTerm[i]->pExpr==pX ){
164594164822
int iField;
164595164823
assert( (pLoop->aLTerm[i]->eOperator & (WO_OR|WO_AND))==0 );
164596164824
iField = pLoop->aLTerm[i]->u.x.iField - 1;
164825
+ if( iField!=nRhs ){
164826
+ ExprClearProperty(pNew, EP_Subrtn);
164827
+ }
164597164828
if( NEVER(pOrigRhs->a[iField].pExpr==0) ){
164598164829
continue; /* Duplicate PK column */
164599164830
}
164600164831
pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr);
164601164832
pOrigRhs->a[iField].pExpr = 0;
@@ -164603,10 +164834,11 @@
164603164834
if( pOrigLhs ){
164604164835
assert( pOrigLhs->a[iField].pExpr!=0 );
164605164836
pLhs = sqlite3ExprListAppend(pParse,pLhs,pOrigLhs->a[iField].pExpr);
164606164837
pOrigLhs->a[iField].pExpr = 0;
164607164838
}
164839
+ nRhs++;
164608164840
}
164609164841
}
164610164842
sqlite3ExprListDelete(db, pOrigRhs);
164611164843
if( pOrigLhs ){
164612164844
sqlite3ExprListDelete(db, pOrigLhs);
@@ -164714,11 +164946,12 @@
164714164946
** in sorted order (requires checking asc/desc, and rejecting cases
164715164947
** where the indexed columns are not in the right order). */
164716164948
pLoop->wsFlags &= ~WHERE_IN_SEEKSCAN;
164717164949
}
164718164950
if( !db->mallocFailed ){
164719
- aiMap = (int*)sqlite3DbMallocZero(db, sizeof(int)*nEq);
164951
+ int nCol = pX->x.pSelect->pEList->nExpr;
164952
+ aiMap = (int*)sqlite3DbMallocZero(db, sizeof(int)*nCol);
164720164953
eType = sqlite3FindInIndex(pParse, pXMod, IN_INDEX_LOOP, 0, aiMap, &iTab);
164721164954
}
164722164955
sqlite3ExprDelete(db, pXMod);
164723164956
}
164724164957
@@ -165468,10 +165701,53 @@
165468165701
}
165469165702
return 1;
165470165703
}
165471165704
return 0;
165472165705
}
165706
+
165707
+/*
165708
+** This is called while coding loop pLevel, which scans FROM clause item
165709
+** pTabItem. pE is an expression for which all the prerequisites are
165710
+** available. This function tests if pE can be coded as part of the current
165711
+** loop, or whether it needs to be deferred to ensure outer joins are
165712
+** processed correctly. This function returns non-zero if the expression
165713
+** can be coded as part of the loop, or 0 if it must be deferred.
165714
+**
165715
+** The expression must be deferred if:
165716
+**
165717
+** * There are any RIGHT joins in the FROM clause and the expression
165718
+** was not part of an ON clause, or was part of an ON clause to the
165719
+** right of the join.
165720
+**
165721
+** * The table is the RHS of a LEFT JOIN and the expression was not
165722
+** part of an ON clause on an OUTER join, or was part of an ON clause
165723
+** on an OUTER join to the right of the join.
165724
+*/
165725
+static int whereExprIsReady(
165726
+ WhereInfo *pWInfo,
165727
+ WhereLevel *pLevel,
165728
+ SrcItem *pTabItem,
165729
+ Expr *pE
165730
+){
165731
+ u8 jtype = pTabItem->fg.jointype;
165732
+ if( jtype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ){
165733
+ if( !ExprHasProperty(pE,EP_OuterON|EP_InnerON) ){
165734
+ /* Defer processing WHERE clause constraints until after outer
165735
+ ** join processing. tag-20220513a */
165736
+ return 0;
165737
+ }else if( (jtype & JT_LEFT) && !ExprHasProperty(pE,EP_OuterON) ){
165738
+ return 0;
165739
+ }else{
165740
+ Bitmask m = sqlite3WhereGetMask(&pWInfo->sMaskSet, pE->w.iJoin);
165741
+ if( m & pLevel->notReady ){
165742
+ /* An ON clause that is not ripe */
165743
+ return 0;
165744
+ }
165745
+ }
165746
+ }
165747
+ return 1;
165748
+}
165473165749
165474165750
/*
165475165751
** Generate code for the start of the iLevel-th loop in the WHERE clause
165476165752
** implementation described by pWInfo.
165477165753
*/
@@ -166399,10 +166675,11 @@
166399166675
if( (pWC->a[iTerm].wtFlags & (TERM_VIRTUAL|TERM_CODED|TERM_SLICE))!=0 ){
166400166676
continue;
166401166677
}
166402166678
if( (pWC->a[iTerm].eOperator & WO_ALL)==0 ) continue;
166403166679
if( ExprHasProperty(pExpr, EP_Subquery) ) continue; /* tag-20220303a */
166680
+ if( whereExprIsReady(pWInfo, pLevel, pTabItem, pExpr)==0 ) continue;
166404166681
pExpr = sqlite3ExprDup(db, pExpr, 0);
166405166682
pAndExpr = sqlite3ExprAnd(pParse, pAndExpr, pExpr);
166406166683
}
166407166684
if( pAndExpr ){
166408166685
/* The extra 0x10000 bit on the opcode is masked off and does not
@@ -166639,26 +166916,11 @@
166639166916
pWInfo->untestedTerms = 1;
166640166917
continue;
166641166918
}
166642166919
pE = pTerm->pExpr;
166643166920
assert( pE!=0 );
166644
- if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ){
166645
- if( !ExprHasProperty(pE,EP_OuterON|EP_InnerON) ){
166646
- /* Defer processing WHERE clause constraints until after outer
166647
- ** join processing. tag-20220513a */
166648
- continue;
166649
- }else if( (pTabItem->fg.jointype & JT_LEFT)==JT_LEFT
166650
- && !ExprHasProperty(pE,EP_OuterON) ){
166651
- continue;
166652
- }else{
166653
- Bitmask m = sqlite3WhereGetMask(&pWInfo->sMaskSet, pE->w.iJoin);
166654
- if( m & pLevel->notReady ){
166655
- /* An ON clause that is not ripe */
166656
- continue;
166657
- }
166658
- }
166659
- }
166921
+ if( whereExprIsReady(pWInfo, pLevel, pTabItem, pE)==0 ) continue;
166660166922
if( iLoop==1 && !sqlite3ExprCoveredByIndex(pE, pLevel->iTabCur, pIdx) ){
166661166923
iNext = 2;
166662166924
continue;
166663166925
}
166664166926
if( iLoop<3 && (pTerm->wtFlags & TERM_VARSELECT) ){
@@ -166807,13 +167069,15 @@
166807167069
** of the RIGHT JOIN. During normal operation, the subroutine will
166808167070
** be in-line with the rest of the code. But at the end, a separate
166809167071
** loop will run that invokes this subroutine for unmatched rows
166810167072
** of pTab, with all tables to left begin set to NULL.
166811167073
*/
166812
- WhereRightJoin *pRJ = pLevel->pRJ;
166813
- sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pRJ->regReturn);
166814
- pRJ->addrSubrtn = sqlite3VdbeCurrentAddr(v);
167074
+ {
167075
+ WhereRightJoin *pRJ = pLevel->pRJ;
167076
+ sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pRJ->regReturn);
167077
+ pRJ->addrSubrtn = sqlite3VdbeCurrentAddr(v);
167078
+ }
166815167079
assert( pParse->withinRJSubrtn < 255 );
166816167080
pParse->withinRJSubrtn++;
166817167081
166818167082
/* WHERE clause constraints must be deferred until after outer join
166819167083
** row elimination has completed, since WHERE clause constraints apply
@@ -167209,11 +167473,11 @@
167209167473
int iCol = pRight->iColumn;
167210167474
pVal = sqlite3VdbeGetBoundValue(pReprepare, iCol, SQLITE_AFF_BLOB);
167211167475
if( pVal && sqlite3_value_type(pVal)==SQLITE_TEXT ){
167212167476
z = sqlite3_value_text(pVal);
167213167477
}
167214
- sqlite3VdbeSetVarmask(pParse->pVdbe, iCol);
167478
+ sqlite3VdbeReprepareOnBind(pParse->pVdbe, iCol, 0);
167215167479
assert( pRight->op==TK_VARIABLE || pRight->op==TK_REGISTER );
167216167480
}else if( op==TK_STRING ){
167217167481
assert( !ExprHasProperty(pRight, EP_IntValue) );
167218167482
z = (u8*)pRight->u.zToken;
167219167483
}
@@ -167314,11 +167578,11 @@
167314167578
167315167579
/* If the RHS pattern is a bound parameter, make arrangements to
167316167580
** reprepare the statement when that parameter is rebound */
167317167581
if( op==TK_VARIABLE ){
167318167582
Vdbe *v = pParse->pVdbe;
167319
- sqlite3VdbeSetVarmask(v, pRight->iColumn);
167583
+ sqlite3VdbeReprepareOnBind(v, pRight->iColumn, 0);
167320167584
assert( !ExprHasProperty(pRight, EP_IntValue) );
167321167585
if( *pisComplete && pRight->u.zToken[1] ){
167322167586
/* If the rhs of the LIKE expression is a variable, and the current
167323167587
** value of the variable means there is no need to invoke the LIKE
167324167588
** function, then no OP_Variable will be added to the program.
@@ -168665,11 +168929,11 @@
168665168929
Parse *pParse = pWC->pWInfo->pParse;
168666168930
sqlite3 *db = pParse->db;
168667168931
Expr *pNew;
168668168932
int iVal = 0;
168669168933
168670
- if( sqlite3ExprIsInteger(pExpr, &iVal, pParse) && iVal>=0 ){
168934
+ if( sqlite3ExprIsInteger(pExpr, &iVal, pParse, 0) && iVal>=0 ){
168671168935
Expr *pVal = sqlite3ExprInt32(db, iVal);
168672168936
if( pVal==0 ) return;
168673168937
pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal);
168674168938
}else{
168675168939
Expr *pVal = sqlite3ExprAlloc(db, TK_REGISTER, 0, 0);
@@ -170248,13 +170512,11 @@
170248170512
/*
170249170513
** Generate bytecode that will initialize a Bloom filter that is appropriate
170250170514
** for pLevel.
170251170515
**
170252170516
** If there are inner loops within pLevel that have the WHERE_BLOOMFILTER
170253
-** flag set, initialize a Bloomfilter for them as well. Except don't do
170254
-** this recursive initialization if the SQLITE_BloomPulldown optimization has
170255
-** been turned off.
170517
+** flag set, initialize a Bloomfilter for them as well.
170256170518
**
170257170519
** When the Bloom filter is initialized, the WHERE_BLOOMFILTER flag is cleared
170258170520
** from the loop, but the regFilter value is set to a register that implements
170259170521
** the Bloom filter. When regFilter is positive, the
170260170522
** sqlite3WhereCodeOneLoopStart() will generate code to test the Bloom filter
@@ -170359,11 +170621,10 @@
170359170621
sqlite3VdbeResolveLabel(v, addrCont);
170360170622
sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1);
170361170623
VdbeCoverage(v);
170362170624
sqlite3VdbeJumpHere(v, addrTop);
170363170625
pLoop->wsFlags &= ~WHERE_BLOOMFILTER;
170364
- if( OptimizationDisabled(pParse->db, SQLITE_BloomPulldown) ) break;
170365170626
while( ++iLevel < pWInfo->nLevel ){
170366170627
const SrcItem *pTabItem;
170367170628
pLevel = &pWInfo->a[iLevel];
170368170629
pTabItem = &pWInfo->pTabList->a[pLevel->iFrom];
170369170630
if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ) ) continue;
@@ -172038,10 +172299,17 @@
172038172299
){
172039172300
WhereTerm *pTerm, *pX;
172040172301
Bitmask notAllowed = ~(pLoop->prereq|pLoop->maskSelf);
172041172302
int i, j;
172042172303
LogEst iReduce = 0; /* pLoop->nOut should not exceed nRow-iReduce */
172304
+
172305
+ /* Skip all this if the FROM clause of the query is a single table and
172306
+ ** there is no ORDER BY. In this case it doesn't matter how accurate
172307
+ ** the WhereLoop.nOut values are. */
172308
+ if( pWC->pWInfo->pTabList->nSrc<=1 && pWC->pWInfo->pOrderBy==0 ){
172309
+ return;
172310
+ }
172043172311
172044172312
assert( (pLoop->wsFlags & WHERE_AUTO_INDEX)==0 );
172045172313
for(i=pWC->nBase, pTerm=pWC->a; i>0; i--, pTerm++){
172046172314
assert( pTerm!=0 );
172047172315
if( (pTerm->prereqAll & notAllowed)!=0 ) continue;
@@ -172086,11 +172354,11 @@
172086172354
){
172087172355
Expr *pRight = pOpExpr->pRight;
172088172356
Parse *pParse = pWC->pWInfo->pParse;
172089172357
int k = 0;
172090172358
testcase( pOpExpr->op==TK_IS );
172091
- if( sqlite3ExprIsInteger(pRight, &k, pParse) && k>=(-1) && k<=1 ){
172359
+ if( sqlite3ExprIsInteger(pRight, &k, pParse, 1) && k>=(-1) && k<=1 ){
172092172360
k = 10;
172093172361
}else{
172094172362
k = 20;
172095172363
}
172096172364
if( iReduce<k ){
@@ -174256,16 +174524,24 @@
174256174524
if( j>=pLoop->nLTerm ) continue;
174257174525
}
174258174526
if( (pTerm->eOperator&(WO_EQ|WO_IS))!=0 && pOBExpr->iColumn>=0 ){
174259174527
Parse *pParse = pWInfo->pParse;
174260174528
CollSeq *pColl1 = sqlite3ExprNNCollSeq(pParse, pOrderBy->a[i].pExpr);
174261
- CollSeq *pColl2 = sqlite3ExprCompareCollSeq(pParse, pTerm->pExpr);
174529
+ const Expr *pCExpr = pTerm->pExpr;
174530
+ CollSeq *pColl2 = sqlite3ExprCompareCollSeq(pParse, pCExpr);
174531
+ char affRight;
174262174532
assert( pColl1 );
174263174533
if( pColl2==0 || sqlite3StrICmp(pColl1->zName, pColl2->zName) ){
174264174534
continue;
174265174535
}
174266
- testcase( pTerm->pExpr->op==TK_IS );
174536
+ affRight = sqlite3ExprAffinity(pCExpr->pRight);
174537
+ /* Left operand must be a column, hence always has affinity */
174538
+ assert( sqlite3ExprAffinity(pCExpr->pLeft)!=0 );
174539
+ if( affRight!=0 && affRight!=sqlite3ExprAffinity(pCExpr->pLeft) ){
174540
+ continue; /* An affinity conversion would be required */
174541
+ }
174542
+ testcase( pCExpr->op==TK_IS );
174267174543
}
174268174544
obSat |= MASKBIT(i);
174269174545
}
174270174546
174271174547
if( (pLoop->wsFlags & WHERE_ONEROW)==0 ){
@@ -177803,11 +178079,11 @@
177803178079
}
177804178080
if( bIntToNull ){
177805178081
int iDummy;
177806178082
Expr *pSub;
177807178083
pSub = sqlite3ExprSkipCollateAndLikely(pDup);
177808
- if( sqlite3ExprIsInteger(pSub, &iDummy, 0) ){
178084
+ if( sqlite3ExprIsInteger(pSub, &iDummy, 0, 0) ){
177809178085
pSub->op = TK_NULL;
177810178086
pSub->flags &= ~(EP_IntValue|EP_IsTrue|EP_IsFalse);
177811178087
pSub->u.zToken = 0;
177812178088
}
177813178089
}
@@ -195326,11 +195602,14 @@
195326195602
/* Loop through the returned columns. Set nStr to the number of bytes of
195327195603
** space required to store a copy of each column name, including the
195328195604
** nul-terminator byte. */
195329195605
nCol = sqlite3_column_count(pStmt);
195330195606
for(i=0; i<nCol; i++){
195607
+ /* This call to sqlite3_column_name() cannot fail, as no conversion
195608
+ ** between encodings, and therefore no malloc() call, is required. */
195331195609
const char *zCol = sqlite3_column_name(pStmt, i);
195610
+ assert( zCol );
195332195611
nStr += strlen(zCol) + 1;
195333195612
}
195334195613
195335195614
/* Allocate and populate the array to return. */
195336195615
azCol = (const char **)sqlite3_malloc64(sizeof(char *) * nCol + nStr);
@@ -195339,10 +195618,11 @@
195339195618
}else{
195340195619
char *p = (char *)&azCol[nCol];
195341195620
for(i=0; i<nCol; i++){
195342195621
const char *zCol = sqlite3_column_name(pStmt, i);
195343195622
int n = (int)strlen(zCol)+1;
195623
+ assert( zCol ); /* no malloc() required, cannot fail */
195344195624
memcpy(p, zCol, n);
195345195625
azCol[i] = p;
195346195626
p += n;
195347195627
}
195348195628
}
@@ -255110,10 +255390,11 @@
255110255390
** against no columns at all).
255111255391
*/
255112255392
static void fts5IterSetOutputs_ZeroColset(Fts5Iter *pIter, Fts5SegIter *pSeg){
255113255393
UNUSED_PARAM(pSeg);
255114255394
pIter->base.nData = 0;
255395
+ pIter->base.bEof = 1;
255115255396
}
255116255397
255117255398
/*
255118255399
** xSetOutputs callback used by detail=col when there is a column filter
255119255400
** and there are 100 or more columns. Also called as a fallback from
@@ -257019,11 +257300,10 @@
257019257300
&& !fts5FlushSecureDelete(p, pStruct, zTerm, nTerm, iRowid)
257020257301
){
257021257302
iOff++;
257022257303
if( iOff<nDoclist && pDoclist[iOff]==0x00 ){
257023257304
iOff++;
257024
- nDoclist = 0;
257025257305
}else{
257026257306
continue;
257027257307
}
257028257308
}
257029257309
}else if( (pDoclist[iOff] & 0x01)
@@ -257335,13 +257615,13 @@
257335257615
257336257616
assert( pIter->aPoslist || (p==0 && pIter->aPoslist==0) );
257337257617
if( p>=pIter->aEof ){
257338257618
pIter->aPoslist = 0;
257339257619
}else{
257340
- i64 iDelta;
257620
+ u64 iDelta;
257341257621
257342
- p += fts5GetVarint(p, (u64*)&iDelta);
257622
+ p += fts5GetVarint(p, &iDelta);
257343257623
pIter->iRowid += iDelta;
257344257624
257345257625
/* Read position list size */
257346257626
if( p[0] & 0x80 ){
257347257627
int nPos;
@@ -264533,11 +264813,11 @@
264533264813
int nArg, /* Number of args */
264534264814
sqlite3_value **apUnused /* Function arguments */
264535264815
){
264536264816
assert( nArg==0 );
264537264817
UNUSED_PARAM2(nArg, apUnused);
264538
- sqlite3_result_text(pCtx, "fts5: 2026-08-20 13:25:12 3bd5456e50ef321f2cafcf67261d556a760624ce0caab430ed9454cebec233e7", -1, SQLITE_TRANSIENT);
264818
+ sqlite3_result_text(pCtx, "fts5: 2026-08-27 10:58:16 555f31c64d3fcc55df2715a7c8c1cb28503b2c0eb21d950ccdcbf6f577593dd5", -1, SQLITE_TRANSIENT);
264539264819
}
264540264820
264541264821
/*
264542264822
** Implementation of fts5_locale(LOCALE, TEXT) function.
264543264823
**
@@ -269637,13 +269917,22 @@
269637269917
sqlite3_value *pLe = 0;
269638269918
269639269919
UNUSED_PARAM2(zUnused, nUnused);
269640269920
269641269921
fts5VocabResetCursor(pCsr);
269642
- if( idxNum & FTS5_VOCAB_TERM_EQ ) pEq = apVal[iVal++];
269643
- if( idxNum & FTS5_VOCAB_TERM_GE ) pGe = apVal[iVal++];
269644
- if( idxNum & FTS5_VOCAB_TERM_LE ) pLe = apVal[iVal++];
269922
+ if( idxNum & FTS5_VOCAB_TERM_EQ ){
269923
+ pEq = apVal[iVal++];
269924
+ if( sqlite3_value_type(pEq)!=SQLITE_TEXT ) pEq = 0;
269925
+ }
269926
+ if( idxNum & FTS5_VOCAB_TERM_GE ){
269927
+ pGe = apVal[iVal++];
269928
+ if( sqlite3_value_type(pGe)!=SQLITE_TEXT ) pGe = 0;
269929
+ }
269930
+ if( idxNum & FTS5_VOCAB_TERM_LE ){
269931
+ pLe = apVal[iVal++];
269932
+ if( sqlite3_value_type(pLe)!=SQLITE_TEXT ) pLe = 0;
269933
+ }
269645269934
pCsr->colUsed = (idxNum & FTS5_VOCAB_COLUSED_MASK);
269646269935
269647269936
if( pEq ){
269648269937
zTerm = fts5VocabValueText(pEq, &nTerm);
269649269938
f = FTS5INDEX_QUERY_NOTOKENDATA;
269650269939
--- extsrc/sqlite3.c
+++ extsrc/sqlite3.c
@@ -16,11 +16,11 @@
16 ** if you want a wrapper to interface SQLite with your choice of programming
17 ** language. The code for the "sqlite3" command-line shell is also in a
18 ** separate file. This file contains only code for the core SQLite library.
19 **
20 ** The content in this amalgamation comes from Fossil check-in
21 ** 3bd5456e50ef321f2cafcf67261d556a7606 with changes in files:
22 **
23 **
24 */
25 #ifndef SQLITE_AMALGAMATION
26 #define SQLITE_CORE 1
@@ -467,14 +467,14 @@
467 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
468 ** [sqlite_version()] and [sqlite_source_id()].
469 */
470 #define SQLITE_VERSION "3.54.0"
471 #define SQLITE_VERSION_NUMBER 3054000
472 #define SQLITE_SOURCE_ID "2026-08-20 13:25:12 3bd5456e50ef321f2cafcf67261d556a760624ce0caab430ed9454cebec233e7"
473 #define SQLITE_SCM_BRANCH "trunk"
474 #define SQLITE_SCM_TAGS ""
475 #define SQLITE_SCM_DATETIME "2026-08-20T13:25:12.207Z"
476
477 /*
478 ** CAPI3REF: Run-Time Library Version Numbers
479 ** KEYWORDS: sqlite3_version sqlite3_sourceid
480 **
@@ -18208,11 +18208,11 @@
18208 SQLITE_PRIVATE int sqlite3VdbeUsesDoubleQuotedString(Vdbe*,const char*);
18209 #endif
18210 SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe*,Vdbe*);
18211 SQLITE_PRIVATE VdbeOp *sqlite3VdbeTakeOpArray(Vdbe*, int*, int*);
18212 SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe*, int, u8);
18213 SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe*, int);
18214 #ifndef SQLITE_OMIT_TRACE
18215 SQLITE_PRIVATE char *sqlite3VdbeExpandSql(Vdbe*, const char*);
18216 #endif
18217 SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*);
18218 SQLITE_PRIVATE int sqlite3BlobCompare(const Mem*, const Mem*);
@@ -19088,41 +19088,43 @@
19088 ** selectively disable various optimizations.
19089 */
19090 #define SQLITE_QueryFlattener 0x00000001 /* Query flattening */
19091 #define SQLITE_WindowFunc 0x00000002 /* Use xInverse for window functions */
19092 #define SQLITE_GroupByOrder 0x00000004 /* GROUPBY cover of ORDERBY */
19093 #define SQLITE_FactorOutConst 0x00000008 /* Constant factoring */
19094 #define SQLITE_DistinctOpt 0x00000010 /* DISTINCT using indexes */
19095 #define SQLITE_CoverIdxScan 0x00000020 /* Covering index scans */
19096 #define SQLITE_OrderByIdxJoin 0x00000040 /* ORDER BY of joins via index */
19097 #define SQLITE_Transitive 0x00000080 /* Transitive constraints */
19098 #define SQLITE_OmitNoopJoin 0x00000100 /* Omit unused tables in joins */
19099 #define SQLITE_CountOfView 0x00000200 /* The count-of-view optimization */
19100 #define SQLITE_CursorHints 0x00000400 /* Add OP_CursorHint opcodes */
19101 #define SQLITE_Stat4 0x00000800 /* Use STAT4 data */
19102 /* TH3 expects this value ^^^^^^^^^^ to be 0x0000800. Don't change it */
19103 #define SQLITE_PushDown 0x00001000 /* WHERE-clause push-down opt */
19104 #define SQLITE_SimplifyJoin 0x00002000 /* Convert LEFT JOIN to JOIN */
19105 #define SQLITE_SkipScan 0x00004000 /* Skip-scans */
19106 #define SQLITE_PropagateConst 0x00008000 /* The constant propagation opt */
19107 #define SQLITE_MinMaxOpt 0x00010000 /* The min/max optimization */
19108 #define SQLITE_SeekScan 0x00020000 /* The OP_SeekScan optimization */
19109 #define SQLITE_OmitOrderBy 0x00040000 /* Omit pointless ORDER BY */
19110 /* TH3 expects this value ^^^^^^^^^^ to be 0x40000. Coordinate any change */
19111 #define SQLITE_BloomFilter 0x00080000 /* Use a Bloom filter on searches */
19112 #define SQLITE_BloomPulldown 0x00100000 /* Run Bloom filters early */
 
19113 #define SQLITE_BalancedMerge 0x00200000 /* Balance multi-way merges */
19114 #define SQLITE_ReleaseReg 0x00400000 /* Use OP_ReleaseReg for testing */
19115 #define SQLITE_FlttnUnionAll 0x00800000 /* Disable the UNION ALL flattener */
19116 /* TH3 expects this value ^^^^^^^^^^ See flatten04.test */
19117 #define SQLITE_IndexedExpr 0x01000000 /* Pull exprs from index when able */
19118 #define SQLITE_Coroutines 0x02000000 /* Co-routines for subqueries */
19119 #define SQLITE_NullUnusedCols 0x04000000 /* NULL unused columns in subqueries */
19120 #define SQLITE_OnePass 0x08000000 /* Single-pass DELETE and UPDATE */
19121 #define SQLITE_OrderBySubq 0x10000000 /* ORDER BY in subquery helps outer */
19122 #define SQLITE_StarQuery 0x20000000 /* Heurists for star queries */
19123 #define SQLITE_ExistsToJoin 0x40000000 /* The EXISTS-to-JOIN optimization */
 
19124 #define SQLITE_AllOpts 0xffffffff /* All optimizations */
19125
19126 /*
19127 ** Macros for testing whether or not optimizations are enabled or disabled.
19128 */
@@ -22226,10 +22228,11 @@
22226 SQLITE_PRIVATE void sqlite3ResetOneSchema(sqlite3*,int);
22227 SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3*);
22228 SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3*);
22229 SQLITE_PRIVATE void sqlite3ColumnSetExpr(Parse*,Table*,Column*,Expr*);
22230 SQLITE_PRIVATE Expr *sqlite3ColumnExpr(Table*,Column*);
 
22231 SQLITE_PRIVATE void sqlite3ColumnSetColl(sqlite3*,Column*,const char*zColl);
22232 SQLITE_PRIVATE const char *sqlite3ColumnColl(Column*);
22233 SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3*,Table*);
22234 SQLITE_PRIVATE void sqlite3GenerateColumnNames(Parse *pParse, Select *pSelect);
22235 SQLITE_PRIVATE int sqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**);
@@ -22439,11 +22442,11 @@
22439 SQLITE_PRIVATE int sqlite3ExprIsSingleTableConstraint(Expr*,const SrcList*,int,int);
22440 SQLITE_PRIVATE int sqlite3ExprListIsConstant(Parse *pParse, ExprList *pList, int bNoIs);
22441 #ifdef SQLITE_ENABLE_CURSOR_HINTS
22442 SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr*);
22443 #endif
22444 SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr*, int*, Parse*);
22445 SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*);
22446 SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char);
22447 SQLITE_PRIVATE int sqlite3ExprIsLikeOperator(const Expr*);
22448 SQLITE_PRIVATE int sqlite3IsRowid(const char*);
22449 SQLITE_PRIVATE const char *sqlite3RowidAlias(Table *pTab);
@@ -25047,11 +25050,13 @@
25047 #endif
25048 void *pFree; /* Free this when deleting the vdbe */
25049 VdbeFrame *pFrame; /* Parent frame */
25050 VdbeFrame *pDelFrame; /* List of frame objects to free on VM reset */
25051 int nFrame; /* Number of frames in pFrame list */
25052 u32 expmask; /* Binding to these vars invalidates VM */
 
 
25053 SubProgram *pProgram; /* Linked list of all sub-programs used by VM */
25054 AuxData *pAuxData; /* Linked list of auxdata allocations */
25055 #ifdef SQLITE_ENABLE_STMT_SCANSTATUS
25056 int nScan; /* Entries in aScan[] */
25057 ScanStatus *aScan; /* Scan definitions for sqlite3_stmt_scanstatus() */
@@ -33849,14 +33854,17 @@
33849 /*
33850 ** If pExpr has a byte offset for the start of a token, record that as
33851 ** as the error offset.
33852 */
33853 SQLITE_PRIVATE void sqlite3RecordErrorOffsetOfExpr(sqlite3 *db, const Expr *pExpr){
33854 while( pExpr
33855 && (ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0)
33856 ){
33857 pExpr = pExpr->pLeft;
 
 
 
33858 }
33859 if( pExpr==0 ) return;
33860 if( ExprHasProperty(pExpr, EP_FromDDL) ) return;
33861 db->errByteOffset = pExpr->w.iOfst;
33862 }
@@ -86910,11 +86918,34 @@
86910 const Expr *pExpr, /* The expression to evaluate */
86911 u8 enc, /* Encoding to use */
86912 u8 affinity, /* Affinity to use */
86913 sqlite3_value **ppVal /* Write the new value here */
86914 ){
86915 return pExpr ? valueFromExpr(db, pExpr, enc, affinity, ppVal, 0) : 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86916 }
86917
86918 #ifdef SQLITE_ENABLE_STAT4
86919 /*
86920 ** Attempt to extract a value from pExpr and use it to construct *ppVal.
@@ -86955,11 +86986,11 @@
86955 sqlite3VdbeMemSetNull((Mem*)pVal);
86956 }
86957 }else if( pExpr->op==TK_VARIABLE && (db->flags & SQLITE_EnableQPSG)==0 ){
86958 Vdbe *v;
86959 int iBindVar = pExpr->iColumn;
86960 sqlite3VdbeSetVarmask(pParse->pVdbe, iBindVar);
86961 if( (v = pParse->pReprepare)!=0 ){
86962 pVal = valueNew(db, pAlloc);
86963 if( pVal ){
86964 rc = sqlite3VdbeMemCopy((Mem*)pVal, &v->aVar[iBindVar-1]);
86965 sqlite3ValueApplyAffinity(pVal, affinity, ENC(db));
@@ -87332,11 +87363,12 @@
87332 #ifdef SQLITE_ENABLE_NORMALIZE
87333 zTmp = pA->zNormSql;
87334 pA->zNormSql = pB->zNormSql;
87335 pB->zNormSql = zTmp;
87336 #endif
87337 pB->expmask = pA->expmask;
 
87338 pB->prepFlags = pA->prepFlags;
87339 memcpy(pB->aCounter, pA->aCounter, sizeof(pB->aCounter));
87340 pB->aCounter[SQLITE_STMTSTATUS_REPREPARE]++;
87341 }
87342
@@ -92520,21 +92552,32 @@
92520 }
92521
92522 /*
92523 ** Configure SQL variable iVar so that binding a new value to it signals
92524 ** to sqlite3_reoptimize() that re-preparing the statement may result
92525 ** in a better query plan.
 
 
 
 
 
 
92526 */
92527 SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){
 
92528 assert( iVar>0 );
92529 assert( (v->db->flags & SQLITE_EnableQPSG)==0
92530 || (v->db->mDbFlags & DBFLAG_InternalFunc)!=0 );
92531 if( iVar>=32 ){
92532 v->expmask |= 0x80000000;
92533 }else{
92534 v->expmask |= ((u32)1 << (iVar-1));
 
92535 }
 
 
 
92536 }
92537
92538 /*
92539 ** Helper function for vdbeIsMatchingIndexKey(). Return true if column
92540 ** iCol should be ignored when comparing a record with a record from
@@ -94695,11 +94738,15 @@
94695 ** for a statement, then the statement will be automatically recompiled,
94696 ** as if there had been a schema change, on the first sqlite3_step() call
94697 ** following any change to the bindings of that parameter.
94698 */
94699 assert( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || p->expmask==0 );
 
94700 if( p->expmask!=0 && (p->expmask & (i>=31 ? 0x80000000 : (u32)1<<i))!=0 ){
 
 
 
94701 p->expired = 1;
94702 }
94703 return SQLITE_OK;
94704 }
94705
@@ -94794,19 +94841,50 @@
94794 }
94795 SQLITE_API int sqlite3_bind_int(sqlite3_stmt *p, int i, int iValue){
94796 return sqlite3_bind_int64(p, i, (i64)iValue);
94797 }
94798 SQLITE_API int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){
94799 int rc;
94800 Vdbe *p = (Vdbe *)pStmt;
94801 rc = vdbeUnbind(p, (u32)(i-1));
94802 if( rc==SQLITE_OK ){
94803 assert( p!=0 && p->aVar!=0 && i>0 && i<=p->nVar ); /* tag-20240917-01 */
94804 sqlite3VdbeMemSetInt64(&p->aVar[i-1], iValue);
 
 
 
 
 
 
 
 
 
94805 sqlite3_mutex_leave(p->db->mutex);
 
94806 }
94807 return rc;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94808 }
94809 SQLITE_API int sqlite3_bind_null(sqlite3_stmt *pStmt, int i){
94810 int rc;
94811 Vdbe *p = (Vdbe*)pStmt;
94812 rc = vdbeUnbind(p, (u32)(i-1));
@@ -111721,11 +111799,11 @@
111721 NameContext nc; /* Name context for resolving pE */
111722 sqlite3 *db; /* Database connection */
111723 int rc; /* Return code from subprocedures */
111724 u8 savedSuppErr; /* Saved value of db->suppressErr */
111725
111726 assert( sqlite3ExprIsInteger(pE, &i, 0)==0 );
111727 pEList = pSelect->pEList;
111728
111729 /* Resolve all names in the ORDER BY term expression
111730 */
111731 memset(&nc, 0, sizeof(nc));
@@ -111820,11 +111898,11 @@
111820 int iCol = -1;
111821 Expr *pE, *pDup;
111822 if( pItem->fg.done ) continue;
111823 pE = sqlite3ExprSkipCollateAndLikely(pItem->pExpr);
111824 if( NEVER(pE==0) ) continue;
111825 if( sqlite3ExprIsInteger(pE, &iCol, 0) ){
111826 if( iCol<=0 || iCol>pEList->nExpr ){
111827 resolveOutOfRangeError(pParse, "ORDER", i+1, pEList->nExpr, pE);
111828 return 1;
111829 }
111830 }else{
@@ -112003,11 +112081,11 @@
112003 ** copy of the iCol-th result-set expression. */
112004 pItem->u.x.iOrderByCol = (u16)iCol;
112005 continue;
112006 }
112007 }
112008 if( sqlite3ExprIsInteger(pE2, &iCol, 0) ){
112009 /* The ORDER BY term is an integer constant. Again, set the column
112010 ** number so that sqlite3ResolveOrderGroupBy() will convert the
112011 ** order-by term to a copy of the result-set expression */
112012 if( iCol<1 || iCol>0xffff ){
112013 resolveOutOfRangeError(pParse, zType, i+1, nResult, pE2);
@@ -115041,11 +115119,11 @@
115041 db = pWalker->pParse->db;
115042 pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0);
115043 if( pDef==0
115044 || pDef->xFinalize!=0
115045 || (pDef->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0
115046 || ExprHasProperty(pExpr, EP_WinFunc)
115047 ){
115048 pWalker->eCode = 0;
115049 return WRC_Abort;
115050 }
115051 return WRC_Prune;
@@ -115089,23 +115167,29 @@
115089 pWalker->eCode = 0;
115090 return WRC_Abort;
115091 }
115092
115093 switch( pExpr->op ){
115094 /* Consider functions to be constant if all their arguments are constant
115095 ** and either pWalker->eCode==4 or 5 or the function has the
115096 ** SQLITE_FUNC_CONST flag. */
115097 case TK_FUNCTION:
115098 if( (pWalker->eCode>=4 || ExprHasProperty(pExpr,EP_ConstFunc))
115099 && !ExprHasProperty(pExpr, EP_WinFunc)
115100 ){
 
 
 
115101 if( pWalker->eCode==5 ) ExprSetProperty(pExpr, EP_FromDDL);
115102 return WRC_Continue;
 
 
 
 
115103 }else if( pWalker->pParse ){
 
 
115104 return exprNodeIsConstantFunction(pWalker, pExpr);
115105 }else{
115106 pWalker->eCode = 0;
115107 return WRC_Abort;
115108 }
115109 case TK_ID:
115110 /* Convert "true" or "false" in a DEFAULT clause into the
115111 ** appropriate TK_TRUEFALSE operator */
@@ -115449,69 +115533,79 @@
115449 return w.eCode==0;
115450 }
115451 #endif
115452
115453 /*
115454 ** If the expression p codes a constant integer that is small enough
115455 ** to fit in a 32-bit integer, return 1 and put the value of the integer
115456 ** in *pValue. If the expression is not an integer or if it is too big
115457 ** to fit in a signed 32-bit integer, return 0 and leave *pValue unchanged.
115458 **
115459 ** If the pParse pointer is provided, then allow the expression p to be
115460 ** a parameter (TK_VARIABLE) that is bound to an integer.
115461 ** But if pParse is NULL, then p must be a pure integer literal.
 
 
 
 
 
 
 
 
 
 
 
 
115462 */
115463 SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr *p, int *pValue, Parse *pParse){
115464 int rc = 0;
115465 if( NEVER(p==0) ) return 0; /* Used to only happen following on OOM */
115466
115467 /* If an expression is an integer literal that fits in a signed 32-bit
115468 ** integer, then the EP_IntValue flag will have already been set */
115469 assert( p->op!=TK_INTEGER || (p->flags & EP_IntValue)!=0
115470 || sqlite3GetInt32(p->u.zToken, &rc)==0 );
115471
115472 if( p->flags & EP_IntValue ){
115473 *pValue = p->u.iValue;
115474 return 1;
115475 }
115476 switch( p->op ){
115477 case TK_UPLUS: {
115478 rc = sqlite3ExprIsInteger(p->pLeft, pValue, 0);
115479 break;
115480 }
115481 case TK_UMINUS: {
115482 int v = 0;
115483 if( sqlite3ExprIsInteger(p->pLeft, &v, 0) ){
115484 assert( ((unsigned int)v)!=0x80000000 );
115485 *pValue = -v;
115486 rc = 1;
115487 }
115488 break;
115489 }
115490 case TK_VARIABLE: {
115491 sqlite3_value *pVal;
115492 if( pParse==0 ) break;
115493 if( NEVER(pParse->pVdbe==0) ) break;
115494 if( (pParse->db->flags & SQLITE_EnableQPSG)!=0 ) break;
115495 sqlite3VdbeSetVarmask(pParse->pVdbe, p->iColumn);
115496 pVal = sqlite3VdbeGetBoundValue(pParse->pReprepare, p->iColumn,
115497 SQLITE_AFF_BLOB);
115498 if( pVal ){
115499 if( sqlite3_value_type(pVal)==SQLITE_INTEGER ){
115500 sqlite3_int64 vv = sqlite3_value_int64(pVal);
115501 if( vv == (vv & 0x7fffffff) ){ /* non-negative numbers only */
115502 *pValue = (int)vv;
115503 rc = 1;
 
 
 
115504 }
 
115505 }
115506 sqlite3ValueFree(pVal);
115507 }
115508 break;
 
115509 }
115510 default: break;
115511 }
115512 return rc;
115513 }
115514
115515 /*
115516 ** Return FALSE if there is no chance that the expression can be NULL.
115517 **
@@ -115674,17 +115768,28 @@
115674 ** Generate code that checks the left-most column of index table iCur to see if
115675 ** it contains any NULL entries. Cause the register at regHasNull to be set
115676 ** to a non-NULL value if iCur contains no NULLs. Cause register regHasNull
115677 ** to be set to NULL if iCur contains one or more NULL values.
115678 */
115679 static void sqlite3SetHasNullFlag(Vdbe *v, int iCur, int regHasNull){
 
 
 
 
 
115680 int addr1;
 
115681 sqlite3VdbeAddOp2(v, OP_Integer, 0, regHasNull);
115682 addr1 = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v);
 
 
 
 
 
115683 sqlite3VdbeAddOp3(v, OP_Column, iCur, 0, regHasNull);
115684 sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG);
115685 VdbeComment((v, "first_entry_in(%d)", iCur));
115686 sqlite3VdbeJumpHere(v, addr1);
115687 }
115688 #endif
115689
115690
@@ -115947,11 +116052,12 @@
115947 sqlite3VdbeAddOp3(v, OP_ColumnsUsed,
115948 iTab, LOWER32(mask), UPPER32(mask));
115949 #endif
115950 *prRhsHasNull = ++pParse->nMem;
115951 if( nExpr==1 ){
115952 sqlite3SetHasNullFlag(v, iTab, *prRhsHasNull);
 
115953 }
115954 }
115955 sqlite3VdbeJumpHere(v, iAddr);
115956 }
115957 } /* End loop over indexes */
@@ -115996,11 +116102,11 @@
115996 ){
115997 bloomOk = 1;
115998 }
115999 sqlite3CodeRhsOfIN(pParse, pX, iTab, bloomOk);
116000 if( rMayHaveNull ){
116001 sqlite3SetHasNullFlag(v, iTab, rMayHaveNull);
116002 }
116003 pParse->nQueryLoop = savedNQueryLoop;
116004 }
116005
116006 if( aiMap && eType!=IN_INDEX_INDEX_ASC && eType!=IN_INDEX_INDEX_DESC ){
@@ -116829,11 +116935,17 @@
116829 **
116830 ** For a scalar LHS, it is sufficient to check just the first row
116831 ** of the RHS.
116832 */
116833 if( destStep6 ) sqlite3VdbeResolveLabel(v, destStep6);
116834 addrTop = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, destIfFalse);
 
 
 
 
 
 
116835 VdbeCoverage(v);
116836 if( nVector>1 ){
116837 destNotNull = sqlite3VdbeMakeLabel(pParse);
116838 }else{
116839 /* For nVector==1, combine steps 6 and 7 by immediately returning
@@ -117019,16 +117131,16 @@
117019 assert( pTab!=0 );
117020 assert( iCol!=XN_EXPR );
117021 if( iCol<0 || iCol==pTab->iPKey ){
117022 sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut);
117023 VdbeComment((v, "%s.rowid", pTab->zName));
117024 }else{
117025 int op;
117026 int x;
117027 if( IsVirtual(pTab) ){
117028 op = OP_VColumn;
117029 x = iCol;
 
 
117030 #ifndef SQLITE_OMIT_GENERATED_COLUMNS
117031 }else if( (pCol = &pTab->aCol[iCol])->colFlags & COLFLAG_VIRTUAL ){
117032 Parse *pParse = sqlite3VdbeParser(v);
117033 if( pCol->colFlags & COLFLAG_BUSY ){
117034 sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"",
@@ -117044,17 +117156,15 @@
117044 return;
117045 #endif
117046 }else if( !HasRowid(pTab) ){
117047 testcase( iCol!=sqlite3TableColumnToStorage(pTab, iCol) );
117048 x = sqlite3TableColumnToIndex(sqlite3PrimaryKeyIndex(pTab), iCol);
117049 op = OP_Column;
117050 }else{
117051 x = sqlite3TableColumnToStorage(pTab,iCol);
117052 testcase( x!=iCol );
117053 op = OP_Column;
117054 }
117055 sqlite3VdbeAddOp3(v, op, iTabCur, x, regOut);
117056 sqlite3ColumnDefault(v, pTab, iCol, regOut);
117057 }
117058 }
117059
117060 /*
@@ -117433,10 +117543,15 @@
117433 addr = sqlite3VdbeAddOp1(v, OP_IfNullRow, p->iIdxCur);
117434 }
117435 ret = sqlite3ExprCodeTarget(pParse, p->pExpr, iTarget);
117436 sqlite3VdbeAddOp4(pParse->pVdbe, OP_Affinity, ret, 1, 0,
117437 (const char*)&p->aff, 1);
 
 
 
 
 
117438 if( addr ){
117439 sqlite3VdbeJumpHere(v, addr);
117440 sqlite3VdbeChangeP3(v, addr, ret);
117441 }
117442 return ret;
@@ -119107,11 +119222,11 @@
119107 }
119108 if( (pParse->db->flags & SQLITE_EnableQPSG)!=0 ) return 2;
119109 sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR);
119110 if( pR ){
119111 iVar = pVar->iColumn;
119112 sqlite3VdbeSetVarmask(pParse->pVdbe, iVar);
119113 pL = sqlite3VdbeGetBoundValue(pParse->pReprepare, iVar, SQLITE_AFF_BLOB);
119114 if( pL ){
119115 if( sqlite3_value_type(pL)==SQLITE_TEXT ){
119116 sqlite3_value_text(pL); /* Make sure the encoding is UTF-8 */
119117 }
@@ -119363,11 +119478,11 @@
119363 static int sqlite3ExprIsNotTrue(Expr *pExpr){
119364 int v;
119365 if( pExpr->op==TK_NULL ) return 1;
119366 if( pExpr->op==TK_TRUEFALSE && sqlite3ExprTruthValue(pExpr)==0 ) return 1;
119367 v = 1;
119368 if( sqlite3ExprIsInteger(pExpr, &v, 0) && v==0 ) return 1;
119369 return 0;
119370 }
119371
119372 /*
119373 ** Return true if the expression is one of the following:
@@ -120613,10 +120728,34 @@
120613 sqlite3NestedParse(pParse,
120614 "SELECT raise(ABORT,%Q) FROM \"%w\".\"%w\"",
120615 zErr, zDb, zTab
120616 );
120617 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120618
120619 /*
120620 ** This function is called after an "ALTER TABLE ... ADD" statement
120621 ** has been parsed. Argument pColDef contains the text of the new
120622 ** column definition.
@@ -120659,13 +120798,13 @@
120659 return;
120660 }
120661 #endif
120662
120663
120664 /* Check that the new column is not specified as PRIMARY KEY or UNIQUE.
120665 ** If there is a NOT NULL constraint, then the default value for the
120666 ** column must not be NULL.
120667 */
120668 if( pCol->colFlags & COLFLAG_PRIMKEY ){
120669 sqlite3ErrorMsg(pParse, "Cannot add a PRIMARY KEY column");
120670 return;
120671 }
@@ -120672,10 +120811,11 @@
120672 if( pNew->pIndex ){
120673 sqlite3ErrorMsg(pParse,
120674 "Cannot add a UNIQUE column");
120675 return;
120676 }
 
120677 if( (pCol->colFlags & COLFLAG_GENERATED)==0 ){
120678 /* If the default value for the new column was specified with a
120679 ** literal NULL, then set pDflt to 0. This simplifies checking
120680 ** for an SQL NULL default below.
120681 */
@@ -120964,10 +121104,12 @@
120964 ** CREATE statement text for the sqlite_schema table.
120965 */
120966 sqlite3MayAbort(pParse);
120967 zNew = sqlite3NameFromToken(db, pNew);
120968 if( !zNew ) goto exit_rename_column;
 
 
120969 assert( pNew->n>0 );
120970 bQuote = sqlite3Isquote(pNew->z[0]);
120971 sqlite3NestedParse(pParse,
120972 "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET "
120973 "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, %d) "
@@ -122590,10 +122732,11 @@
122590 iCol = sqlite3ColumnIndex(pTab, zCol);
122591 if( iCol<0 ){
122592 sqlite3ErrorMsg(pParse, "no such column: \"%T\"", pName);
122593 goto exit_drop_column;
122594 }
 
122595
122596 /* Do not allow the user to drop a PRIMARY KEY column or a column
122597 ** constrained by a UNIQUE constraint. */
122598 if( pTab->aCol[iCol].colFlags & (COLFLAG_PRIMKEY|COLFLAG_UNIQUE) ){
122599 sqlite3ErrorMsg(pParse, "cannot drop %s column: \"%s\"",
@@ -127037,21 +127180,61 @@
127037 sqlite3ExprDelete(pParse->db, pList->a[pCol->iDflt-1].pExpr);
127038 pList->a[pCol->iDflt-1].pExpr = pExpr;
127039 }
127040 }
127041
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127042 /*
127043 ** Return the expression associated with a column. The expression might be
127044 ** the DEFAULT clause or the AS clause of a generated column.
127045 ** Return NULL if the column has no associated expression.
 
 
 
 
 
 
127046 */
127047 SQLITE_PRIVATE Expr *sqlite3ColumnExpr(Table *pTab, Column *pCol){
127048 if( pCol->iDflt==0 ) return 0;
127049 if( !IsOrdinaryTable(pTab) ) return 0;
127050 if( NEVER(pTab->u.tab.pDfltList==0) ) return 0;
127051 if( NEVER(pTab->u.tab.pDfltList->nExpr<pCol->iDflt) ) return 0;
127052 return pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr;
 
 
 
 
 
 
 
 
 
127053 }
127054
127055 /*
127056 ** Suppress false-positive warning message generated with -O3 in GCC
127057 ** on the second call to sqlite3Strlen30() in the sqlite3ColumnSetColl()
@@ -128115,10 +128298,17 @@
128115 x.pLeft = pExpr;
128116 x.flags = EP_Skip;
128117 pDfltExpr = sqlite3ExprDup(db, &x, EXPRDUP_REDUCE);
128118 sqlite3DbFree(db, x.u.zToken);
128119 sqlite3ColumnSetExpr(pParse, p, pCol, pDfltExpr);
 
 
 
 
 
 
 
128120 }
128121 }
128122 if( IN_RENAME_OBJECT ){
128123 sqlite3RenameExprUnmap(pParse, pExpr);
128124 }
@@ -128980,11 +129170,10 @@
128980 }
128981 }
128982 #else
128983 #define markExprListImmutable(X) /* no-op */
128984 #endif /* SQLITE_DEBUG */
128985
128986
128987 /*
128988 ** This routine is called to report the final ")" that terminates
128989 ** a CREATE TABLE statement.
128990 **
@@ -138593,11 +138782,11 @@
138593 if( pCol->colFlags & COLFLAG_GENERATED ){
138594 testcase( pCol->colFlags & COLFLAG_VIRTUAL );
138595 testcase( pCol->colFlags & COLFLAG_STORED );
138596 pDflt = 0;
138597 }else{
138598 pDflt = sqlite3ColumnExpr(pFKey->pFrom, pCol);
138599 }
138600 if( pDflt ){
138601 pNew = sqlite3ExprDup(db, pDflt, 0);
138602 }else{
138603 pNew = sqlite3ExprAlloc(db, TK_NULL, 0, 0);
@@ -140165,11 +140354,11 @@
140165 continue;
140166 }else if( pColumn==0 ){
140167 /* Hidden columns that are not explicitly named in the INSERT
140168 ** get their default value */
140169 sqlite3ExprCodeFactorable(pParse,
140170 sqlite3ColumnExpr(pTab, &pTab->aCol[i]),
140171 iRegStore);
140172 continue;
140173 }
140174 }
140175 if( pColumn ){
@@ -140177,19 +140366,19 @@
140177 assert( j>=0 && j<=pColumn->nId );
140178 if( j==0 ){
140179 /* A column not named in the insert column list gets its
140180 ** default value */
140181 sqlite3ExprCodeFactorable(pParse,
140182 sqlite3ColumnExpr(pTab, &pTab->aCol[i]),
140183 iRegStore);
140184 continue;
140185 }
140186 k = j - 1;
140187 }else if( nColumn==0 ){
140188 /* This is INSERT INTO ... DEFAULT VALUES. Load the default value. */
140189 sqlite3ExprCodeFactorable(pParse,
140190 sqlite3ColumnExpr(pTab, &pTab->aCol[i]),
140191 iRegStore);
140192 continue;
140193 }else{
140194 k = i - nHidden;
140195 }
@@ -140783,11 +140972,11 @@
140783 int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, iReg);
140784 VdbeCoverage(v);
140785 assert( (pCol->colFlags & COLFLAG_GENERATED)==0 );
140786 nSeenReplace++;
140787 sqlite3ExprCodeCopy(pParse,
140788 sqlite3ColumnExpr(pTab, pCol), iReg);
140789 sqlite3VdbeJumpHere(v, addr1);
140790 break;
140791 }
140792 case OE_Abort:
140793 sqlite3MayAbort(pParse);
@@ -141439,11 +141628,11 @@
141439 VdbeOp x; /* Conflict check opcode to copy */
141440 /* The sqlite3VdbeAddOp4() call might reallocate the opcode array.
141441 ** Hence, make a complete copy of the opcode, rather than using
141442 ** a pointer to the opcode. */
141443 x = *sqlite3VdbeGetOp(v, addrConflictCk);
141444 if( x.opcode!=OP_IdxRowid ){
141445 int p2; /* New P2 value for copied conflict check opcode */
141446 const char *zP4;
141447 if( sqlite3OpcodeProperty[x.opcode]&OPFLG_JUMP ){
141448 p2 = lblRecheckOk;
141449 }else{
@@ -146693,12 +146882,12 @@
146693 p1 = -1;
146694 p3 = 3;
146695 }else{
146696 if( pCol->iDflt ){
146697 sqlite3_value *pDfltValue = 0;
146698 sqlite3ValueFromExpr(db, sqlite3ColumnExpr(pTab,pCol), ENC(db),
146699 pCol->affinity, &pDfltValue);
146700 if( pDfltValue ){
146701 p4 = sqlite3_value_type(pDfltValue);
146702 sqlite3ValueFree(pDfltValue);
146703 }
146704 }
@@ -151506,13 +151695,11 @@
151506 */
151507 SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse *pParse){
151508 if( pParse->pVdbe ){
151509 return pParse->pVdbe;
151510 }
151511 if( pParse->pToplevel==0
151512 && OptimizationEnabled(pParse->db,SQLITE_FactorOutConst)
151513 ){
151514 pParse->okConstFactor = 1;
151515 }
151516 return sqlite3VdbeCreate(pParse);
151517 }
151518
@@ -151559,11 +151746,11 @@
151559 assert( pLimit->op==TK_LIMIT );
151560 assert( pLimit->pLeft!=0 );
151561 p->iLimit = iLimit = ++pParse->nMem;
151562 v = sqlite3GetVdbe(pParse);
151563 assert( v!=0 );
151564 if( sqlite3ExprIsInteger(pLimit->pLeft, &n, pParse) ){
151565 sqlite3VdbeAddOp2(v, OP_Integer, n, iLimit);
151566 VdbeComment((v, "LIMIT counter"));
151567 if( n==0 ){
151568 sqlite3VdbeGoto(v, iBreak);
151569 }else if( n>=0 && p->nSelectRow>sqlite3LogEst((u64)n) ){
@@ -151926,15 +152113,37 @@
151926 while( p && (p->selFlags & SF_Recursive)!=0 ){ p = p->pPrior; }
151927 return p!=0;
151928 }
151929
151930 /*
151931 ** This routine is called to process a compound query form from
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151932 ** two or more separate queries using UNION, UNION ALL, EXCEPT, or
151933 ** INTERSECT
151934 **
151935 ** "p" points to the right-most of the two queries. the query on the
151936 ** left is p->pPrior. The left query could also be a compound query
151937 ** in which case this routine will be called recursively.
151938 **
151939 ** The results of the total query are to be written into a destination
151940 ** of type eDest with parameter iParm.
@@ -152014,11 +152223,11 @@
152014 #endif
152015 if( p->pOrderBy ){
152016 /* If the compound has an ORDER BY clause, then always use the merge
152017 ** algorithm. */
152018 return multiSelectByMerge(pParse, p, pDest);
152019 }else if( p->op!=TK_ALL ){
152020 /* If the compound is EXCEPT, INTERSECT, or UNION (anything other than
152021 ** UNION ALL) then also always use the merge algorithm. However, the
152022 ** multiSelectByMerge() routine requires that the compound have an
152023 ** ORDER BY clause, and it doesn't right now. So invent one first. */
152024 Expr *pOne = sqlite3ExprInt32(db, 1);
@@ -152026,11 +152235,12 @@
152026 if( pParse->nErr ) goto multi_select_end;
152027 assert( p->pOrderBy!=0 );
152028 p->pOrderBy->a[0].u.x.iOrderByCol = 1;
152029 return multiSelectByMerge(pParse, p, pDest);
152030 }else{
152031 /* For a UNION ALL compound without ORDER BY, simply run the left
 
152032 ** query, then run the right query */
152033 int addr = 0;
152034 int nLimit = 0; /* Initialize to suppress harmless compiler warning */
152035
152036 #ifndef SQLITE_OMIT_EXPLAIN
@@ -152067,11 +152277,11 @@
152067 testcase( rc!=SQLITE_OK );
152068 pDelete = p->pPrior;
152069 p->pPrior = pPrior;
152070 p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
152071 if( p->pLimit
152072 && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit, pParse)
152073 && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit)
152074 ){
152075 p->nSelectRow = sqlite3LogEst((u64)nLimit);
152076 }
152077 if( addr ){
@@ -152577,11 +152787,11 @@
152577 sqlite3ResolveOrderGroupBy(pParse, p, p->pOrderBy, "ORDER");
152578 sqlite3ResolveOrderGroupBy(pParse, pPrior, pPrior->pOrderBy, "ORDER");
152579
152580 /* Compute the limit registers */
152581 computeLimitRegisters(pParse, p, labelEnd);
152582 if( p->iLimit && op==TK_ALL ){
152583 regLimitA = ++pParse->nMem;
152584 regLimitB = ++pParse->nMem;
152585 sqlite3VdbeAddOp2(v, OP_Copy, p->iOffset ? p->iOffset+1 : p->iLimit,
152586 regLimitA);
152587 sqlite3VdbeAddOp2(v, OP_Copy, regLimitA, regLimitB);
@@ -159864,30 +160074,47 @@
159864 ** If column as REAL affinity and the table is an ordinary b-tree table
159865 ** (not a virtual table) then the value might have been stored as an
159866 ** integer. In that case, add an OP_RealAffinity opcode to make sure
159867 ** it has been converted into REAL.
159868 */
159869 SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159870 Column *pCol;
159871 assert( pTab!=0 );
159872 assert( pTab->nCol>i );
159873 pCol = &pTab->aCol[i];
159874 if( pCol->iDflt ){
159875 sqlite3_value *pValue = 0;
159876 u8 enc = ENC(sqlite3VdbeDb(v));
159877 assert( !IsView(pTab) );
159878 VdbeComment((v, "%s.%s", pTab->zName, pCol->zCnName));
159879 assert( i<pTab->nCol );
159880 sqlite3ValueFromExpr(sqlite3VdbeDb(v),
159881 sqlite3ColumnExpr(pTab,pCol), enc,
159882 pCol->affinity, &pValue);
159883 if( pValue ){
159884 sqlite3VdbeAppendP4(v, pValue, P4_MEM);
159885 }
159886 }
159887 #ifndef SQLITE_OMIT_FLOATING_POINT
159888 if( pCol->affinity==SQLITE_AFF_REAL && !IsVirtual(pTab) ){
159889 sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg);
159890 }
159891 #endif
159892 }
159893
@@ -164579,10 +164806,11 @@
164579 ExprList *pOrigRhs; /* Original unmodified RHS */
164580 ExprList *pOrigLhs = 0; /* Original unmodified LHS */
164581 ExprList *pRhs = 0; /* New RHS after modifications */
164582 ExprList *pLhs = 0; /* New LHS after mods */
164583 int i; /* Loop counter */
 
164584
164585 assert( ExprUseXSelect(pNew) );
164586 pOrigRhs = pSelect->pEList;
164587 assert( pNew->pLeft!=0 );
164588 assert( ExprUseXList(pNew->pLeft) );
@@ -164592,10 +164820,13 @@
164592 for(i=iEq; i<pLoop->nLTerm; i++){
164593 if( pLoop->aLTerm[i]->pExpr==pX ){
164594 int iField;
164595 assert( (pLoop->aLTerm[i]->eOperator & (WO_OR|WO_AND))==0 );
164596 iField = pLoop->aLTerm[i]->u.x.iField - 1;
 
 
 
164597 if( NEVER(pOrigRhs->a[iField].pExpr==0) ){
164598 continue; /* Duplicate PK column */
164599 }
164600 pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr);
164601 pOrigRhs->a[iField].pExpr = 0;
@@ -164603,10 +164834,11 @@
164603 if( pOrigLhs ){
164604 assert( pOrigLhs->a[iField].pExpr!=0 );
164605 pLhs = sqlite3ExprListAppend(pParse,pLhs,pOrigLhs->a[iField].pExpr);
164606 pOrigLhs->a[iField].pExpr = 0;
164607 }
 
164608 }
164609 }
164610 sqlite3ExprListDelete(db, pOrigRhs);
164611 if( pOrigLhs ){
164612 sqlite3ExprListDelete(db, pOrigLhs);
@@ -164714,11 +164946,12 @@
164714 ** in sorted order (requires checking asc/desc, and rejecting cases
164715 ** where the indexed columns are not in the right order). */
164716 pLoop->wsFlags &= ~WHERE_IN_SEEKSCAN;
164717 }
164718 if( !db->mallocFailed ){
164719 aiMap = (int*)sqlite3DbMallocZero(db, sizeof(int)*nEq);
 
164720 eType = sqlite3FindInIndex(pParse, pXMod, IN_INDEX_LOOP, 0, aiMap, &iTab);
164721 }
164722 sqlite3ExprDelete(db, pXMod);
164723 }
164724
@@ -165468,10 +165701,53 @@
165468 }
165469 return 1;
165470 }
165471 return 0;
165472 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165473
165474 /*
165475 ** Generate code for the start of the iLevel-th loop in the WHERE clause
165476 ** implementation described by pWInfo.
165477 */
@@ -166399,10 +166675,11 @@
166399 if( (pWC->a[iTerm].wtFlags & (TERM_VIRTUAL|TERM_CODED|TERM_SLICE))!=0 ){
166400 continue;
166401 }
166402 if( (pWC->a[iTerm].eOperator & WO_ALL)==0 ) continue;
166403 if( ExprHasProperty(pExpr, EP_Subquery) ) continue; /* tag-20220303a */
 
166404 pExpr = sqlite3ExprDup(db, pExpr, 0);
166405 pAndExpr = sqlite3ExprAnd(pParse, pAndExpr, pExpr);
166406 }
166407 if( pAndExpr ){
166408 /* The extra 0x10000 bit on the opcode is masked off and does not
@@ -166639,26 +166916,11 @@
166639 pWInfo->untestedTerms = 1;
166640 continue;
166641 }
166642 pE = pTerm->pExpr;
166643 assert( pE!=0 );
166644 if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ){
166645 if( !ExprHasProperty(pE,EP_OuterON|EP_InnerON) ){
166646 /* Defer processing WHERE clause constraints until after outer
166647 ** join processing. tag-20220513a */
166648 continue;
166649 }else if( (pTabItem->fg.jointype & JT_LEFT)==JT_LEFT
166650 && !ExprHasProperty(pE,EP_OuterON) ){
166651 continue;
166652 }else{
166653 Bitmask m = sqlite3WhereGetMask(&pWInfo->sMaskSet, pE->w.iJoin);
166654 if( m & pLevel->notReady ){
166655 /* An ON clause that is not ripe */
166656 continue;
166657 }
166658 }
166659 }
166660 if( iLoop==1 && !sqlite3ExprCoveredByIndex(pE, pLevel->iTabCur, pIdx) ){
166661 iNext = 2;
166662 continue;
166663 }
166664 if( iLoop<3 && (pTerm->wtFlags & TERM_VARSELECT) ){
@@ -166807,13 +167069,15 @@
166807 ** of the RIGHT JOIN. During normal operation, the subroutine will
166808 ** be in-line with the rest of the code. But at the end, a separate
166809 ** loop will run that invokes this subroutine for unmatched rows
166810 ** of pTab, with all tables to left begin set to NULL.
166811 */
166812 WhereRightJoin *pRJ = pLevel->pRJ;
166813 sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pRJ->regReturn);
166814 pRJ->addrSubrtn = sqlite3VdbeCurrentAddr(v);
 
 
166815 assert( pParse->withinRJSubrtn < 255 );
166816 pParse->withinRJSubrtn++;
166817
166818 /* WHERE clause constraints must be deferred until after outer join
166819 ** row elimination has completed, since WHERE clause constraints apply
@@ -167209,11 +167473,11 @@
167209 int iCol = pRight->iColumn;
167210 pVal = sqlite3VdbeGetBoundValue(pReprepare, iCol, SQLITE_AFF_BLOB);
167211 if( pVal && sqlite3_value_type(pVal)==SQLITE_TEXT ){
167212 z = sqlite3_value_text(pVal);
167213 }
167214 sqlite3VdbeSetVarmask(pParse->pVdbe, iCol);
167215 assert( pRight->op==TK_VARIABLE || pRight->op==TK_REGISTER );
167216 }else if( op==TK_STRING ){
167217 assert( !ExprHasProperty(pRight, EP_IntValue) );
167218 z = (u8*)pRight->u.zToken;
167219 }
@@ -167314,11 +167578,11 @@
167314
167315 /* If the RHS pattern is a bound parameter, make arrangements to
167316 ** reprepare the statement when that parameter is rebound */
167317 if( op==TK_VARIABLE ){
167318 Vdbe *v = pParse->pVdbe;
167319 sqlite3VdbeSetVarmask(v, pRight->iColumn);
167320 assert( !ExprHasProperty(pRight, EP_IntValue) );
167321 if( *pisComplete && pRight->u.zToken[1] ){
167322 /* If the rhs of the LIKE expression is a variable, and the current
167323 ** value of the variable means there is no need to invoke the LIKE
167324 ** function, then no OP_Variable will be added to the program.
@@ -168665,11 +168929,11 @@
168665 Parse *pParse = pWC->pWInfo->pParse;
168666 sqlite3 *db = pParse->db;
168667 Expr *pNew;
168668 int iVal = 0;
168669
168670 if( sqlite3ExprIsInteger(pExpr, &iVal, pParse) && iVal>=0 ){
168671 Expr *pVal = sqlite3ExprInt32(db, iVal);
168672 if( pVal==0 ) return;
168673 pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal);
168674 }else{
168675 Expr *pVal = sqlite3ExprAlloc(db, TK_REGISTER, 0, 0);
@@ -170248,13 +170512,11 @@
170248 /*
170249 ** Generate bytecode that will initialize a Bloom filter that is appropriate
170250 ** for pLevel.
170251 **
170252 ** If there are inner loops within pLevel that have the WHERE_BLOOMFILTER
170253 ** flag set, initialize a Bloomfilter for them as well. Except don't do
170254 ** this recursive initialization if the SQLITE_BloomPulldown optimization has
170255 ** been turned off.
170256 **
170257 ** When the Bloom filter is initialized, the WHERE_BLOOMFILTER flag is cleared
170258 ** from the loop, but the regFilter value is set to a register that implements
170259 ** the Bloom filter. When regFilter is positive, the
170260 ** sqlite3WhereCodeOneLoopStart() will generate code to test the Bloom filter
@@ -170359,11 +170621,10 @@
170359 sqlite3VdbeResolveLabel(v, addrCont);
170360 sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1);
170361 VdbeCoverage(v);
170362 sqlite3VdbeJumpHere(v, addrTop);
170363 pLoop->wsFlags &= ~WHERE_BLOOMFILTER;
170364 if( OptimizationDisabled(pParse->db, SQLITE_BloomPulldown) ) break;
170365 while( ++iLevel < pWInfo->nLevel ){
170366 const SrcItem *pTabItem;
170367 pLevel = &pWInfo->a[iLevel];
170368 pTabItem = &pWInfo->pTabList->a[pLevel->iFrom];
170369 if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ) ) continue;
@@ -172038,10 +172299,17 @@
172038 ){
172039 WhereTerm *pTerm, *pX;
172040 Bitmask notAllowed = ~(pLoop->prereq|pLoop->maskSelf);
172041 int i, j;
172042 LogEst iReduce = 0; /* pLoop->nOut should not exceed nRow-iReduce */
 
 
 
 
 
 
 
172043
172044 assert( (pLoop->wsFlags & WHERE_AUTO_INDEX)==0 );
172045 for(i=pWC->nBase, pTerm=pWC->a; i>0; i--, pTerm++){
172046 assert( pTerm!=0 );
172047 if( (pTerm->prereqAll & notAllowed)!=0 ) continue;
@@ -172086,11 +172354,11 @@
172086 ){
172087 Expr *pRight = pOpExpr->pRight;
172088 Parse *pParse = pWC->pWInfo->pParse;
172089 int k = 0;
172090 testcase( pOpExpr->op==TK_IS );
172091 if( sqlite3ExprIsInteger(pRight, &k, pParse) && k>=(-1) && k<=1 ){
172092 k = 10;
172093 }else{
172094 k = 20;
172095 }
172096 if( iReduce<k ){
@@ -174256,16 +174524,24 @@
174256 if( j>=pLoop->nLTerm ) continue;
174257 }
174258 if( (pTerm->eOperator&(WO_EQ|WO_IS))!=0 && pOBExpr->iColumn>=0 ){
174259 Parse *pParse = pWInfo->pParse;
174260 CollSeq *pColl1 = sqlite3ExprNNCollSeq(pParse, pOrderBy->a[i].pExpr);
174261 CollSeq *pColl2 = sqlite3ExprCompareCollSeq(pParse, pTerm->pExpr);
 
 
174262 assert( pColl1 );
174263 if( pColl2==0 || sqlite3StrICmp(pColl1->zName, pColl2->zName) ){
174264 continue;
174265 }
174266 testcase( pTerm->pExpr->op==TK_IS );
 
 
 
 
 
 
174267 }
174268 obSat |= MASKBIT(i);
174269 }
174270
174271 if( (pLoop->wsFlags & WHERE_ONEROW)==0 ){
@@ -177803,11 +178079,11 @@
177803 }
177804 if( bIntToNull ){
177805 int iDummy;
177806 Expr *pSub;
177807 pSub = sqlite3ExprSkipCollateAndLikely(pDup);
177808 if( sqlite3ExprIsInteger(pSub, &iDummy, 0) ){
177809 pSub->op = TK_NULL;
177810 pSub->flags &= ~(EP_IntValue|EP_IsTrue|EP_IsFalse);
177811 pSub->u.zToken = 0;
177812 }
177813 }
@@ -195326,11 +195602,14 @@
195326 /* Loop through the returned columns. Set nStr to the number of bytes of
195327 ** space required to store a copy of each column name, including the
195328 ** nul-terminator byte. */
195329 nCol = sqlite3_column_count(pStmt);
195330 for(i=0; i<nCol; i++){
 
 
195331 const char *zCol = sqlite3_column_name(pStmt, i);
 
195332 nStr += strlen(zCol) + 1;
195333 }
195334
195335 /* Allocate and populate the array to return. */
195336 azCol = (const char **)sqlite3_malloc64(sizeof(char *) * nCol + nStr);
@@ -195339,10 +195618,11 @@
195339 }else{
195340 char *p = (char *)&azCol[nCol];
195341 for(i=0; i<nCol; i++){
195342 const char *zCol = sqlite3_column_name(pStmt, i);
195343 int n = (int)strlen(zCol)+1;
 
195344 memcpy(p, zCol, n);
195345 azCol[i] = p;
195346 p += n;
195347 }
195348 }
@@ -255110,10 +255390,11 @@
255110 ** against no columns at all).
255111 */
255112 static void fts5IterSetOutputs_ZeroColset(Fts5Iter *pIter, Fts5SegIter *pSeg){
255113 UNUSED_PARAM(pSeg);
255114 pIter->base.nData = 0;
 
255115 }
255116
255117 /*
255118 ** xSetOutputs callback used by detail=col when there is a column filter
255119 ** and there are 100 or more columns. Also called as a fallback from
@@ -257019,11 +257300,10 @@
257019 && !fts5FlushSecureDelete(p, pStruct, zTerm, nTerm, iRowid)
257020 ){
257021 iOff++;
257022 if( iOff<nDoclist && pDoclist[iOff]==0x00 ){
257023 iOff++;
257024 nDoclist = 0;
257025 }else{
257026 continue;
257027 }
257028 }
257029 }else if( (pDoclist[iOff] & 0x01)
@@ -257335,13 +257615,13 @@
257335
257336 assert( pIter->aPoslist || (p==0 && pIter->aPoslist==0) );
257337 if( p>=pIter->aEof ){
257338 pIter->aPoslist = 0;
257339 }else{
257340 i64 iDelta;
257341
257342 p += fts5GetVarint(p, (u64*)&iDelta);
257343 pIter->iRowid += iDelta;
257344
257345 /* Read position list size */
257346 if( p[0] & 0x80 ){
257347 int nPos;
@@ -264533,11 +264813,11 @@
264533 int nArg, /* Number of args */
264534 sqlite3_value **apUnused /* Function arguments */
264535 ){
264536 assert( nArg==0 );
264537 UNUSED_PARAM2(nArg, apUnused);
264538 sqlite3_result_text(pCtx, "fts5: 2026-08-20 13:25:12 3bd5456e50ef321f2cafcf67261d556a760624ce0caab430ed9454cebec233e7", -1, SQLITE_TRANSIENT);
264539 }
264540
264541 /*
264542 ** Implementation of fts5_locale(LOCALE, TEXT) function.
264543 **
@@ -269637,13 +269917,22 @@
269637 sqlite3_value *pLe = 0;
269638
269639 UNUSED_PARAM2(zUnused, nUnused);
269640
269641 fts5VocabResetCursor(pCsr);
269642 if( idxNum & FTS5_VOCAB_TERM_EQ ) pEq = apVal[iVal++];
269643 if( idxNum & FTS5_VOCAB_TERM_GE ) pGe = apVal[iVal++];
269644 if( idxNum & FTS5_VOCAB_TERM_LE ) pLe = apVal[iVal++];
 
 
 
 
 
 
 
 
 
269645 pCsr->colUsed = (idxNum & FTS5_VOCAB_COLUSED_MASK);
269646
269647 if( pEq ){
269648 zTerm = fts5VocabValueText(pEq, &nTerm);
269649 f = FTS5INDEX_QUERY_NOTOKENDATA;
269650
--- extsrc/sqlite3.c
+++ extsrc/sqlite3.c
@@ -16,11 +16,11 @@
16 ** if you want a wrapper to interface SQLite with your choice of programming
17 ** language. The code for the "sqlite3" command-line shell is also in a
18 ** separate file. This file contains only code for the core SQLite library.
19 **
20 ** The content in this amalgamation comes from Fossil check-in
21 ** 555f31c64d3fcc55df2715a7c8c1cb28503b with changes in files:
22 **
23 **
24 */
25 #ifndef SQLITE_AMALGAMATION
26 #define SQLITE_CORE 1
@@ -467,14 +467,14 @@
467 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
468 ** [sqlite_version()] and [sqlite_source_id()].
469 */
470 #define SQLITE_VERSION "3.54.0"
471 #define SQLITE_VERSION_NUMBER 3054000
472 #define SQLITE_SOURCE_ID "2026-08-27 10:58:16 555f31c64d3fcc55df2715a7c8c1cb28503b2c0eb21d950ccdcbf6f577593dd5"
473 #define SQLITE_SCM_BRANCH "trunk"
474 #define SQLITE_SCM_TAGS ""
475 #define SQLITE_SCM_DATETIME "2026-08-27T10:58:16.680Z"
476
477 /*
478 ** CAPI3REF: Run-Time Library Version Numbers
479 ** KEYWORDS: sqlite3_version sqlite3_sourceid
480 **
@@ -18208,11 +18208,11 @@
18208 SQLITE_PRIVATE int sqlite3VdbeUsesDoubleQuotedString(Vdbe*,const char*);
18209 #endif
18210 SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe*,Vdbe*);
18211 SQLITE_PRIVATE VdbeOp *sqlite3VdbeTakeOpArray(Vdbe*, int*, int*);
18212 SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe*, int, u8);
18213 SQLITE_PRIVATE void sqlite3VdbeReprepareOnBind(Vdbe*, int, int);
18214 #ifndef SQLITE_OMIT_TRACE
18215 SQLITE_PRIVATE char *sqlite3VdbeExpandSql(Vdbe*, const char*);
18216 #endif
18217 SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*);
18218 SQLITE_PRIVATE int sqlite3BlobCompare(const Mem*, const Mem*);
@@ -19088,41 +19088,43 @@
19088 ** selectively disable various optimizations.
19089 */
19090 #define SQLITE_QueryFlattener 0x00000001 /* Query flattening */
19091 #define SQLITE_WindowFunc 0x00000002 /* Use xInverse for window functions */
19092 #define SQLITE_GroupByOrder 0x00000004 /* GROUPBY cover of ORDERBY */
19093 /* 0x00000008 -- Available for reuse */
19094 #define SQLITE_DistinctOpt 0x00000010 /* DISTINCT using indexes */
19095 #define SQLITE_CoverIdxScan 0x00000020 /* Covering index scans */
19096 #define SQLITE_OrderByIdxJoin 0x00000040 /* ORDER BY of joins via index */
19097 #define SQLITE_Transitive 0x00000080 /* Transitive constraints */
19098 #define SQLITE_OmitNoopJoin 0x00000100 /* Omit unused tables in joins */
19099 #define SQLITE_CountOfView 0x00000200 /* The count-of-view optimization */
19100 #define SQLITE_CursorHints 0x00000400 /* Add OP_CursorHint opcodes */
19101 #define SQLITE_Stat4 0x00000800 /* Use STAT4 data */
19102 /* TH3 expects this value ^^^^^^^^^^ */
19103 #define SQLITE_PushDown 0x00001000 /* WHERE-clause push-down opt */
19104 #define SQLITE_SimplifyJoin 0x00002000 /* Convert LEFT JOIN to JOIN */
19105 #define SQLITE_SkipScan 0x00004000 /* Skip-scans */
19106 #define SQLITE_PropagateConst 0x00008000 /* The constant propagation opt */
19107 #define SQLITE_MinMaxOpt 0x00010000 /* The min/max optimization */
19108 #define SQLITE_SeekScan 0x00020000 /* The OP_SeekScan optimization */
19109 #define SQLITE_OmitOrderBy 0x00040000 /* Omit pointless ORDER BY */
19110 /* TH3 expects this value ^^^^^^^^^^ */
19111 #define SQLITE_BloomFilter 0x00080000 /* Use a Bloom filters */
19112 /* TH3 expects this value ^^^^^^^^^^ */
19113 /* 0x00100000 -- Available for reuse */
19114 #define SQLITE_BalancedMerge 0x00200000 /* Balance multi-way merges */
19115 #define SQLITE_ReleaseReg 0x00400000 /* Use OP_ReleaseReg for testing */
19116 #define SQLITE_FlttnUnionAll 0x00800000 /* Disable the UNION ALL flattener */
19117 /* TH3 expects this value ^^^^^^^^^^ */
19118 #define SQLITE_IndexedExpr 0x01000000 /* Pull exprs from index when able */
19119 #define SQLITE_Coroutines 0x02000000 /* Co-routines for subqueries */
19120 #define SQLITE_NullUnusedCols 0x04000000 /* NULL unused columns in subqueries */
19121 #define SQLITE_OnePass 0x08000000 /* Single-pass DELETE and UPDATE */
19122 #define SQLITE_OrderBySubq 0x10000000 /* ORDER BY in subquery helps outer */
19123 #define SQLITE_StarQuery 0x20000000 /* Heurists for star queries */
19124 #define SQLITE_ExistsToJoin 0x40000000 /* The EXISTS-to-JOIN optimization */
19125 #define SQLITE_UnionLimit 0x80000000 /* Optimizations for UNION + LIMIT */
19126 #define SQLITE_AllOpts 0xffffffff /* All optimizations */
19127
19128 /*
19129 ** Macros for testing whether or not optimizations are enabled or disabled.
19130 */
@@ -22226,10 +22228,11 @@
22228 SQLITE_PRIVATE void sqlite3ResetOneSchema(sqlite3*,int);
22229 SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3*);
22230 SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3*);
22231 SQLITE_PRIVATE void sqlite3ColumnSetExpr(Parse*,Table*,Column*,Expr*);
22232 SQLITE_PRIVATE Expr *sqlite3ColumnExpr(Table*,Column*);
22233 SQLITE_PRIVATE Expr *sqlite3ColumnExprAuth(Table*,Column*,Parse*);
22234 SQLITE_PRIVATE void sqlite3ColumnSetColl(sqlite3*,Column*,const char*zColl);
22235 SQLITE_PRIVATE const char *sqlite3ColumnColl(Column*);
22236 SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3*,Table*);
22237 SQLITE_PRIVATE void sqlite3GenerateColumnNames(Parse *pParse, Select *pSelect);
22238 SQLITE_PRIVATE int sqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**);
@@ -22439,11 +22442,11 @@
22442 SQLITE_PRIVATE int sqlite3ExprIsSingleTableConstraint(Expr*,const SrcList*,int,int);
22443 SQLITE_PRIVATE int sqlite3ExprListIsConstant(Parse *pParse, ExprList *pList, int bNoIs);
22444 #ifdef SQLITE_ENABLE_CURSOR_HINTS
22445 SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr*);
22446 #endif
22447 SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr*, int*, Parse*, int);
22448 SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*);
22449 SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char);
22450 SQLITE_PRIVATE int sqlite3ExprIsLikeOperator(const Expr*);
22451 SQLITE_PRIVATE int sqlite3IsRowid(const char*);
22452 SQLITE_PRIVATE const char *sqlite3RowidAlias(Table *pTab);
@@ -25047,11 +25050,13 @@
25050 #endif
25051 void *pFree; /* Free this when deleting the vdbe */
25052 VdbeFrame *pFrame; /* Parent frame */
25053 VdbeFrame *pDelFrame; /* List of frame objects to free on VM reset */
25054 int nFrame; /* Number of frames in pFrame list */
25055 u32 expmask; /* Binding to these vars might invalidate VM */
25056 u32 smimask; /* Only invalid if changing to/from small integer */
25057 /* Note: smimask is always a subset of expmask */
25058 SubProgram *pProgram; /* Linked list of all sub-programs used by VM */
25059 AuxData *pAuxData; /* Linked list of auxdata allocations */
25060 #ifdef SQLITE_ENABLE_STMT_SCANSTATUS
25061 int nScan; /* Entries in aScan[] */
25062 ScanStatus *aScan; /* Scan definitions for sqlite3_stmt_scanstatus() */
@@ -33849,14 +33854,17 @@
33854 /*
33855 ** If pExpr has a byte offset for the start of a token, record that as
33856 ** as the error offset.
33857 */
33858 SQLITE_PRIVATE void sqlite3RecordErrorOffsetOfExpr(sqlite3 *db, const Expr *pExpr){
33859 while( pExpr ){
33860 if( ExprHasProperty(pExpr, EP_Reduced|EP_TokenOnly) ) return;
33861 if( ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0 ){
33862 pExpr = pExpr->pLeft;
33863 }else{
33864 break;
33865 }
33866 }
33867 if( pExpr==0 ) return;
33868 if( ExprHasProperty(pExpr, EP_FromDDL) ) return;
33869 db->errByteOffset = pExpr->w.iOfst;
33870 }
@@ -86910,11 +86918,34 @@
86918 const Expr *pExpr, /* The expression to evaluate */
86919 u8 enc, /* Encoding to use */
86920 u8 affinity, /* Affinity to use */
86921 sqlite3_value **ppVal /* Write the new value here */
86922 ){
86923 int rc = SQLITE_OK;
86924 sqlite3_value *pVal = 0;
86925 if( pExpr ){
86926 rc = valueFromExpr(db, pExpr, enc, affinity, &pVal, 0);
86927 if( rc==SQLITE_OK && pVal
86928 && affinity==SQLITE_AFF_REAL
86929 && (pVal->flags & MEM_Int)
86930 && (u64)(pVal->u.i<0 ? pVal->u.i : pVal->u.i)>140737488355327LL
86931 ){
86932 /* If the integer value is too large to fit in a 6-byte integer and
86933 ** the affinity is REAL, convert it to a real value now. In most
86934 ** cases an OP_RealAffinity opcode will be used to convert the
86935 ** value to an actual real, but this opcode is omitted if the values
86936 ** are being read directly from a table in order to create an index
86937 ** key. It is important to get a "real" real value for the larger
86938 ** magnitude integer values so that comparisons work correctly -
86939 ** SQLite by default will convert real values to integers before
86940 ** doing the comparison, which is different from converting to real
86941 ** first. */
86942 sqlite3VdbeMemRealify(pVal);
86943 }
86944 }
86945 *ppVal = pVal;
86946 return rc;
86947 }
86948
86949 #ifdef SQLITE_ENABLE_STAT4
86950 /*
86951 ** Attempt to extract a value from pExpr and use it to construct *ppVal.
@@ -86955,11 +86986,11 @@
86986 sqlite3VdbeMemSetNull((Mem*)pVal);
86987 }
86988 }else if( pExpr->op==TK_VARIABLE && (db->flags & SQLITE_EnableQPSG)==0 ){
86989 Vdbe *v;
86990 int iBindVar = pExpr->iColumn;
86991 sqlite3VdbeReprepareOnBind(pParse->pVdbe, iBindVar, 0);
86992 if( (v = pParse->pReprepare)!=0 ){
86993 pVal = valueNew(db, pAlloc);
86994 if( pVal ){
86995 rc = sqlite3VdbeMemCopy((Mem*)pVal, &v->aVar[iBindVar-1]);
86996 sqlite3ValueApplyAffinity(pVal, affinity, ENC(db));
@@ -87332,11 +87363,12 @@
87363 #ifdef SQLITE_ENABLE_NORMALIZE
87364 zTmp = pA->zNormSql;
87365 pA->zNormSql = pB->zNormSql;
87366 pB->zNormSql = zTmp;
87367 #endif
87368 pB->expmask |= pA->expmask;
87369 pB->smimask |= pA->smimask;
87370 pB->prepFlags = pA->prepFlags;
87371 memcpy(pB->aCounter, pA->aCounter, sizeof(pB->aCounter));
87372 pB->aCounter[SQLITE_STMTSTATUS_REPREPARE]++;
87373 }
87374
@@ -92520,21 +92552,32 @@
92552 }
92553
92554 /*
92555 ** Configure SQL variable iVar so that binding a new value to it signals
92556 ** to sqlite3_reoptimize() that re-preparing the statement may result
92557 ** in a better query plan. If parameter bSmallint is true, then the
92558 ** statement is only re-prepared if the new value is integer value 0 or 1.
92559 **
92560 ** The v->expmask bit is always set. expmask means that a reprepare is
92561 ** possible. The v->smimask bit is only set if we want to restrict
92562 ** reprepare when the value changes from (0,1) to something else, or from
92563 ** something else to (0,1).
92564 */
92565 SQLITE_PRIVATE void sqlite3VdbeReprepareOnBind(Vdbe *v, int iVar, int bSmallint){
92566 u32 m;
92567 assert( iVar>0 );
92568 assert( (v->db->flags & SQLITE_EnableQPSG)==0
92569 || (v->db->mDbFlags & DBFLAG_InternalFunc)!=0 );
92570
92571 m = (iVar>=32) ? 0x80000000 : ((u32)1 << (iVar-1));
92572 v->expmask |= m;
92573 if( bSmallint ){
92574 v->smimask |= m;
92575 }
92576
92577 /* smimask is always a subset of expmask */
92578 assert( (v->smimask & v->expmask) == v->smimask );
92579 }
92580
92581 /*
92582 ** Helper function for vdbeIsMatchingIndexKey(). Return true if column
92583 ** iCol should be ignored when comparing a record with a record from
@@ -94695,11 +94738,15 @@
94738 ** for a statement, then the statement will be automatically recompiled,
94739 ** as if there had been a schema change, on the first sqlite3_step() call
94740 ** following any change to the bindings of that parameter.
94741 */
94742 assert( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 || p->expmask==0 );
94743 assert( (p->expmask & p->smimask)==p->smimask );
94744 if( p->expmask!=0 && (p->expmask & (i>=31 ? 0x80000000 : (u32)1<<i))!=0 ){
94745 /* We might avoid a reprepare here if p->smimask is set and the old
94746 ** value is an integer other than (0,1). But that is such a corner
94747 ** case that it does not seem worth the extra code to implement. */
94748 p->expired = 1;
94749 }
94750 return SQLITE_OK;
94751 }
94752
@@ -94794,19 +94841,50 @@
94841 }
94842 SQLITE_API int sqlite3_bind_int(sqlite3_stmt *p, int i, int iValue){
94843 return sqlite3_bind_int64(p, i, (i64)iValue);
94844 }
94845 SQLITE_API int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){
94846 Mem *pVar;
94847 Vdbe *p = (Vdbe*)pStmt;
94848 if( vdbeSafetyNotNull(p) ){
94849 return SQLITE_MISUSE_BKPT;
94850 }
94851 sqlite3_mutex_enter(p->db->mutex);
94852 if( p->eVdbeState!=VDBE_READY_STATE ){
94853 sqlite3Error(p->db, SQLITE_MISUSE_BKPT);
94854 sqlite3_mutex_leave(p->db->mutex);
94855 sqlite3_log(SQLITE_MISUSE,
94856 "bind on a busy prepared statement: [%s]", p->zSql);
94857 return SQLITE_MISUSE_BKPT;
94858 }
94859 if( i<=0 || (--i)>=p->nVar ){
94860 sqlite3Error(p->db, SQLITE_RANGE);
94861 sqlite3_mutex_leave(p->db->mutex);
94862 return SQLITE_RANGE;
94863 }
94864 pVar = &p->aVar[i];
94865 if( p->expmask!=0 ){
94866 u32 expireMask = i>=32 ? 0x80000000 : (u32)1 << i;
94867 assert( (p->expmask & p->smimask)==p->smimask );
94868 if( (p->expmask & expireMask)!=0 ){
94869 if( (p->smimask & expireMask)!=0 ){
94870 /* If the smimask bit is set, only expire the prepared statement
94871 ** if the value is changing to or from (0,1) and something else */
94872 int sm1 = (pVar->flags & MEM_Int)!=0 && pVar->u.i>=0 && pVar->u.i<=1;
94873 int sm2 = iValue>=0 && iValue<=1;
94874 if( sm1!=sm2 ){
94875 p->expired = 1;
94876 }
94877 }else if( (pVar->flags & MEM_Int)==0 || pVar->u.i!=iValue ){
94878 /* Always expire if the value really is changing */
94879 p->expired = 1;
94880 }
94881 }
94882 }
94883 sqlite3VdbeMemSetInt64(pVar, iValue);
94884 sqlite3_mutex_leave(p->db->mutex);
94885 return SQLITE_OK;
94886 }
94887 SQLITE_API int sqlite3_bind_null(sqlite3_stmt *pStmt, int i){
94888 int rc;
94889 Vdbe *p = (Vdbe*)pStmt;
94890 rc = vdbeUnbind(p, (u32)(i-1));
@@ -111721,11 +111799,11 @@
111799 NameContext nc; /* Name context for resolving pE */
111800 sqlite3 *db; /* Database connection */
111801 int rc; /* Return code from subprocedures */
111802 u8 savedSuppErr; /* Saved value of db->suppressErr */
111803
111804 assert( sqlite3ExprIsInteger(pE, &i, 0, 0)==0 );
111805 pEList = pSelect->pEList;
111806
111807 /* Resolve all names in the ORDER BY term expression
111808 */
111809 memset(&nc, 0, sizeof(nc));
@@ -111820,11 +111898,11 @@
111898 int iCol = -1;
111899 Expr *pE, *pDup;
111900 if( pItem->fg.done ) continue;
111901 pE = sqlite3ExprSkipCollateAndLikely(pItem->pExpr);
111902 if( NEVER(pE==0) ) continue;
111903 if( sqlite3ExprIsInteger(pE, &iCol, 0, 0) ){
111904 if( iCol<=0 || iCol>pEList->nExpr ){
111905 resolveOutOfRangeError(pParse, "ORDER", i+1, pEList->nExpr, pE);
111906 return 1;
111907 }
111908 }else{
@@ -112003,11 +112081,11 @@
112081 ** copy of the iCol-th result-set expression. */
112082 pItem->u.x.iOrderByCol = (u16)iCol;
112083 continue;
112084 }
112085 }
112086 if( sqlite3ExprIsInteger(pE2, &iCol, 0, 0) ){
112087 /* The ORDER BY term is an integer constant. Again, set the column
112088 ** number so that sqlite3ResolveOrderGroupBy() will convert the
112089 ** order-by term to a copy of the result-set expression */
112090 if( iCol<1 || iCol>0xffff ){
112091 resolveOutOfRangeError(pParse, zType, i+1, nResult, pE2);
@@ -115041,11 +115119,11 @@
115119 db = pWalker->pParse->db;
115120 pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0);
115121 if( pDef==0
115122 || pDef->xFinalize!=0
115123 || (pDef->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0
115124 || NEVER(ExprHasProperty(pExpr, EP_WinFunc))
115125 ){
115126 pWalker->eCode = 0;
115127 return WRC_Abort;
115128 }
115129 return WRC_Prune;
@@ -115089,23 +115167,29 @@
115167 pWalker->eCode = 0;
115168 return WRC_Abort;
115169 }
115170
115171 switch( pExpr->op ){
 
 
 
115172 case TK_FUNCTION:
115173 if( ExprHasProperty(pExpr, EP_WinFunc) ){
115174 pWalker->eCode = 0; /* Window functions are never constant */
115175 return WRC_Abort;
115176 }else if( pWalker->eCode>=4 && pWalker->eCode<=5 ){
115177 /* Functions used within a CREATE TABLE statement are constant as
115178 ** long as all of their arguments are constant */
115179 if( pWalker->eCode==5 ) ExprSetProperty(pExpr, EP_FromDDL);
115180 return WRC_Continue;
115181 }else if( ExprHasProperty(pExpr,EP_ConstFunc) ){
115182 /* Functions marked with EP_ConstFunc are constant as long as
115183 ** all their arugments are constant and they are not window functions */
115184 return WRC_Continue;
115185 }else if( pWalker->pParse ){
115186 /* Functions are constant if they have SQLITE_FUNC_CONSTANT or
115187 ** SQLITE_FUNC_SLOCHNG and if all arguments are constant */
115188 return exprNodeIsConstantFunction(pWalker, pExpr);
115189 }else{
115190 pWalker->eCode = 0; /* Function call is not constant */
115191 return WRC_Abort;
115192 }
115193 case TK_ID:
115194 /* Convert "true" or "false" in a DEFAULT clause into the
115195 ** appropriate TK_TRUEFALSE operator */
@@ -115449,69 +115533,79 @@
115533 return w.eCode==0;
115534 }
115535 #endif
115536
115537 /*
115538 ** If the expression p codes a constant integer between 0 and 0x7fffffff,
115539 ** then return 1 and put the value of the integer in *pValue. If the
115540 ** expression is not an integer or if it is an integer that is out side
115541 ** the range of 0...0x7fffffff, then return 0 and leave *pValue unchanged.
115542 **
115543 ** If the pParse pointer is provided, then allow the expression p to be
115544 ** a parameter (TK_VARIABLE) that is bound to an integer between 0 and
115545 ** 0x7fffffff. Variables that hold anything other than integers, or that
115546 ** hold integers outside the range of 0..0x7fffffff are not seen.
115547 ** But if pParse is NULL, then p must be a pure integer literal between
115548 ** 0 and 0x7fffffff.
115549 **
115550 ** If pParse is not NULL and expression p is a variable, then the variable
115551 ** is marked so as to cause the statement to be reprepared each time a new
115552 ** value is bound to it. Except, if parameter bRSI is true, then the statement
115553 ** will only be reprepared if the rebind changes the value to or from a
115554 ** "small integer" (either 0 or 1). Note that if p is a variable then
115555 ** reprepare is always enabled for that variable, regardless of its current
115556 ** binding. The RSI is only enabled if the current binding is a small
115557 ** integer. "RSI" stands for "Reprepare Small Integers".
115558 */
115559 SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr *p, int *pValue, Parse *pParse, int bRSI){
115560 int iSign = 1; /* Either +1 or -1. */
115561 while( 1/*exit-by-break*/ ){
115562 if( NEVER(p==0) ) return 0;
115563 if( ExprUseUValue(p) ){
115564 *pValue = p->u.iValue*iSign;
115565 return 1;
115566 }
115567 if( p->op==TK_UPLUS ){
115568 p = p->pLeft;
115569 pParse = 0;
115570 continue;
115571 }
115572 if( p->op==TK_UMINUS ){
115573 iSign = -iSign;
115574 p = p->pLeft;
115575 pParse = 0;
115576 continue;
115577 }
115578 if( p->op==TK_VARIABLE && pParse!=0 ){
115579 sqlite3_value *pVal; /* The variable */
115580 int isSmall = 0; /* Only reprepare if change to/from small integer */
115581 int rc = 0; /* 1 if successful, 0 if failed */
115582 assert( iSign==1 );
 
 
 
 
 
 
115583 if( NEVER(pParse->pVdbe==0) ) break;
115584 if( (pParse->db->flags & SQLITE_EnableQPSG)!=0 ) break;
 
115585 pVal = sqlite3VdbeGetBoundValue(pParse->pReprepare, p->iColumn,
115586 SQLITE_AFF_BLOB);
115587 if( pVal ){
115588 i64 vv;
115589 if( sqlite3_value_type(pVal)==SQLITE_INTEGER
115590 && (vv = sqlite3_value_int64(pVal))>=0
115591 && vv<=0x7fffffff
115592 ){
115593 *pValue = (int)vv;
115594 if( bRSI ){
115595 isSmall = vv<=1;
115596 }
115597 rc = 1;
115598 }
115599 sqlite3ValueFree(pVal);
115600 }
115601 sqlite3VdbeReprepareOnBind(pParse->pVdbe, p->iColumn, isSmall);
115602 return rc;
115603 }
115604 break;
115605 }
115606 return 0;
115607 }
115608
115609 /*
115610 ** Return FALSE if there is no chance that the expression can be NULL.
115611 **
@@ -115674,17 +115768,28 @@
115768 ** Generate code that checks the left-most column of index table iCur to see if
115769 ** it contains any NULL entries. Cause the register at regHasNull to be set
115770 ** to a non-NULL value if iCur contains no NULLs. Cause register regHasNull
115771 ** to be set to NULL if iCur contains one or more NULL values.
115772 */
115773 static void sqlite3SetHasNullFlag(
115774 Vdbe *v, /* Write new code into this statement under construction */
115775 int iCur, /* Cursor for the index */
115776 int regHasNull, /* Register in which to store hasNull flag */
115777 int eSortOrder /* SQLITE_SO_ASC or SQLITE_SO_DESC */
115778 ){
115779 int addr1;
115780 int op;
115781 sqlite3VdbeAddOp2(v, OP_Integer, 0, regHasNull);
115782 if( eSortOrder==SQLITE_SO_ASC ){
115783 op = OP_Rewind;
115784 }else{
115785 op = OP_Last;
115786 }
115787 addr1 = sqlite3VdbeAddOp1(v, op, iCur); VdbeCoverage(v);
115788 sqlite3VdbeAddOp3(v, OP_Column, iCur, 0, regHasNull);
115789 sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG);
115790 VdbeComment((v, op==OP_Last?"last_entry_in(%d)":"first_entry_in(%d)", iCur));
115791 sqlite3VdbeJumpHere(v, addr1);
115792 }
115793 #endif
115794
115795
@@ -115947,11 +116052,12 @@
116052 sqlite3VdbeAddOp3(v, OP_ColumnsUsed,
116053 iTab, LOWER32(mask), UPPER32(mask));
116054 #endif
116055 *prRhsHasNull = ++pParse->nMem;
116056 if( nExpr==1 ){
116057 sqlite3SetHasNullFlag(v, iTab, *prRhsHasNull,
116058 pIdx->aSortOrder[0]);
116059 }
116060 }
116061 sqlite3VdbeJumpHere(v, iAddr);
116062 }
116063 } /* End loop over indexes */
@@ -115996,11 +116102,11 @@
116102 ){
116103 bloomOk = 1;
116104 }
116105 sqlite3CodeRhsOfIN(pParse, pX, iTab, bloomOk);
116106 if( rMayHaveNull ){
116107 sqlite3SetHasNullFlag(v, iTab, rMayHaveNull, SQLITE_SO_ASC);
116108 }
116109 pParse->nQueryLoop = savedNQueryLoop;
116110 }
116111
116112 if( aiMap && eType!=IN_INDEX_INDEX_ASC && eType!=IN_INDEX_INDEX_DESC ){
@@ -116829,11 +116935,17 @@
116935 **
116936 ** For a scalar LHS, it is sufficient to check just the first row
116937 ** of the RHS.
116938 */
116939 if( destStep6 ) sqlite3VdbeResolveLabel(v, destStep6);
116940 if( eType==IN_INDEX_INDEX_DESC ){
116941 addrTop = sqlite3VdbeAddOp2(v, OP_Last, iTab, destIfFalse);
116942 }else{
116943 testcase( eType==IN_INDEX_EPH );
116944 testcase( eType==IN_INDEX_ROWID );
116945 addrTop = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, destIfFalse);
116946 }
116947 VdbeCoverage(v);
116948 if( nVector>1 ){
116949 destNotNull = sqlite3VdbeMakeLabel(pParse);
116950 }else{
116951 /* For nVector==1, combine steps 6 and 7 by immediately returning
@@ -117019,16 +117131,16 @@
117131 assert( pTab!=0 );
117132 assert( iCol!=XN_EXPR );
117133 if( iCol<0 || iCol==pTab->iPKey ){
117134 sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut);
117135 VdbeComment((v, "%s.rowid", pTab->zName));
117136 }else{
 
117137 int x;
117138 if( IsVirtual(pTab) ){
 
117139 x = iCol;
117140 sqlite3VdbeAddOp3(v, OP_VColumn, iTabCur, x, regOut);
117141 return;
117142 #ifndef SQLITE_OMIT_GENERATED_COLUMNS
117143 }else if( (pCol = &pTab->aCol[iCol])->colFlags & COLFLAG_VIRTUAL ){
117144 Parse *pParse = sqlite3VdbeParser(v);
117145 if( pCol->colFlags & COLFLAG_BUSY ){
117146 sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"",
@@ -117044,17 +117156,15 @@
117156 return;
117157 #endif
117158 }else if( !HasRowid(pTab) ){
117159 testcase( iCol!=sqlite3TableColumnToStorage(pTab, iCol) );
117160 x = sqlite3TableColumnToIndex(sqlite3PrimaryKeyIndex(pTab), iCol);
 
117161 }else{
117162 x = sqlite3TableColumnToStorage(pTab,iCol);
117163 testcase( x!=iCol );
 
117164 }
117165 sqlite3VdbeAddOp3(v, OP_Column, iTabCur, x, regOut);
117166 sqlite3ColumnDefault(v, pTab, iCol, regOut);
117167 }
117168 }
117169
117170 /*
@@ -117433,10 +117543,15 @@
117543 addr = sqlite3VdbeAddOp1(v, OP_IfNullRow, p->iIdxCur);
117544 }
117545 ret = sqlite3ExprCodeTarget(pParse, p->pExpr, iTarget);
117546 sqlite3VdbeAddOp4(pParse->pVdbe, OP_Affinity, ret, 1, 0,
117547 (const char*)&p->aff, 1);
117548 if( sqlite3ExprCanReturnSubtype(pParse, p->pExpr) ){
117549 /* If the expression value may have a sub-type, clear it. Values
117550 ** read from columns do not have subtypes. */
117551 sqlite3VdbeAddOp1(pParse->pVdbe, OP_ClrSubtype, ret);
117552 }
117553 if( addr ){
117554 sqlite3VdbeJumpHere(v, addr);
117555 sqlite3VdbeChangeP3(v, addr, ret);
117556 }
117557 return ret;
@@ -119107,11 +119222,11 @@
119222 }
119223 if( (pParse->db->flags & SQLITE_EnableQPSG)!=0 ) return 2;
119224 sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR);
119225 if( pR ){
119226 iVar = pVar->iColumn;
119227 sqlite3VdbeReprepareOnBind(pParse->pVdbe, iVar, 0);
119228 pL = sqlite3VdbeGetBoundValue(pParse->pReprepare, iVar, SQLITE_AFF_BLOB);
119229 if( pL ){
119230 if( sqlite3_value_type(pL)==SQLITE_TEXT ){
119231 sqlite3_value_text(pL); /* Make sure the encoding is UTF-8 */
119232 }
@@ -119363,11 +119478,11 @@
119478 static int sqlite3ExprIsNotTrue(Expr *pExpr){
119479 int v;
119480 if( pExpr->op==TK_NULL ) return 1;
119481 if( pExpr->op==TK_TRUEFALSE && sqlite3ExprTruthValue(pExpr)==0 ) return 1;
119482 v = 1;
119483 if( sqlite3ExprIsInteger(pExpr, &v, 0, 0) && v==0 ) return 1;
119484 return 0;
119485 }
119486
119487 /*
119488 ** Return true if the expression is one of the following:
@@ -120613,10 +120728,34 @@
120728 sqlite3NestedParse(pParse,
120729 "SELECT raise(ABORT,%Q) FROM \"%w\".\"%w\"",
120730 zErr, zDb, zTab
120731 );
120732 }
120733
120734 /*
120735 ** zCol is a column name used in an ALTER TABLE DROP, ADD or RENAME COLUMN
120736 ** operation. zOp identifies the specific operation - "drop", "add", "rename
120737 ** to" or "rename from". pTab is the table being altered.
120738 **
120739 ** If pTab has a rowid and zCol is a rowid alias, then SQLITE_ERROR is
120740 ** returned and an error message left in pParse. Or, if zCol is not an alias
120741 ** for "rowid" or pTab is not an intkey table, then SQLITE_OK is returned.
120742 */
120743 static int isRowidAlias(
120744 Parse *pParse,
120745 Table *pTab,
120746 const char *zCol,
120747 const char *zOp
120748 ){
120749 if( HasRowid(pTab) && sqlite3IsRowid(zCol) ){
120750 sqlite3ErrorMsg(pParse, "cannot %s rowid alias: %s", zOp, zCol);
120751 return SQLITE_ERROR;
120752 }
120753 return SQLITE_OK;
120754 }
120755
120756
120757
120758 /*
120759 ** This function is called after an "ALTER TABLE ... ADD" statement
120760 ** has been parsed. Argument pColDef contains the text of the new
120761 ** column definition.
@@ -120659,13 +120798,13 @@
120798 return;
120799 }
120800 #endif
120801
120802
120803 /* Check that the new column is not specified as PRIMARY KEY or UNIQUE,
120804 ** or a rowid alias. If there is a NOT NULL constraint, then the default
120805 ** value for the column must not be NULL.
120806 */
120807 if( pCol->colFlags & COLFLAG_PRIMKEY ){
120808 sqlite3ErrorMsg(pParse, "Cannot add a PRIMARY KEY column");
120809 return;
120810 }
@@ -120672,10 +120811,11 @@
120811 if( pNew->pIndex ){
120812 sqlite3ErrorMsg(pParse,
120813 "Cannot add a UNIQUE column");
120814 return;
120815 }
120816 if( isRowidAlias(pParse, pTab, pCol->zCnName, "add") ) return;
120817 if( (pCol->colFlags & COLFLAG_GENERATED)==0 ){
120818 /* If the default value for the new column was specified with a
120819 ** literal NULL, then set pDflt to 0. This simplifies checking
120820 ** for an SQL NULL default below.
120821 */
@@ -120964,10 +121104,12 @@
121104 ** CREATE statement text for the sqlite_schema table.
121105 */
121106 sqlite3MayAbort(pParse);
121107 zNew = sqlite3NameFromToken(db, pNew);
121108 if( !zNew ) goto exit_rename_column;
121109 if( isRowidAlias(pParse, pTab, zOld, "rename from") ) goto exit_rename_column;
121110 if( isRowidAlias(pParse, pTab, zNew, "rename to") ) goto exit_rename_column;
121111 assert( pNew->n>0 );
121112 bQuote = sqlite3Isquote(pNew->z[0]);
121113 sqlite3NestedParse(pParse,
121114 "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET "
121115 "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, %d) "
@@ -122590,10 +122732,11 @@
122732 iCol = sqlite3ColumnIndex(pTab, zCol);
122733 if( iCol<0 ){
122734 sqlite3ErrorMsg(pParse, "no such column: \"%T\"", pName);
122735 goto exit_drop_column;
122736 }
122737 if( isRowidAlias(pParse, pTab, zCol, "drop") ) goto exit_drop_column;
122738
122739 /* Do not allow the user to drop a PRIMARY KEY column or a column
122740 ** constrained by a UNIQUE constraint. */
122741 if( pTab->aCol[iCol].colFlags & (COLFLAG_PRIMKEY|COLFLAG_UNIQUE) ){
122742 sqlite3ErrorMsg(pParse, "cannot drop %s column: \"%s\"",
@@ -127037,21 +127180,61 @@
127180 sqlite3ExprDelete(pParse->db, pList->a[pCol->iDflt-1].pExpr);
127181 pList->a[pCol->iDflt-1].pExpr = pExpr;
127182 }
127183 }
127184
127185 #ifndef SQLITE_OMIT_AUTHORIZATION
127186 /*
127187 ** If the expression contains any TK_FUNCTION, send it to the authorizer
127188 ** and if the authorization fails then raise an error.
127189 */
127190 static int authFunctions(Walker *pWalker, Expr *p){
127191 if( p->op==TK_FUNCTION ){
127192 Parse *pParse = pWalker->pParse;
127193 int rc = sqlite3AuthCheck(pParse, SQLITE_FUNCTION, 0, p->u.zToken, 0);
127194 if( rc ){
127195 sqlite3ErrorMsg(pParse, "not authorized to use function: %#T", p);
127196 return WRC_Abort;
127197 }
127198 }
127199 return WRC_Continue;
127200 }
127201 static void sqlite3FuncAuth(Parse *pParse, Expr *pExpr){
127202 Walker w;
127203 memset(&w, 0, sizeof(w));
127204 w.xExprCallback = authFunctions;
127205 w.pParse = pParse;
127206 sqlite3WalkExpr(&w,pExpr);
127207 }
127208 #endif /* SQLITE_OMIT_AUTHORIZATION */
127209
127210 /*
127211 ** Return the expression associated with a column. The expression might be
127212 ** the DEFAULT clause or the AS clause of a generated column.
127213 ** Return NULL if the column has no associated expression.
127214 **
127215 ** There are two variants of this routine. sqlite3ColumnExpr() just
127216 ** returns the expression with no side effects. sqlite3ColumnExprAuth()
127217 ** takes the extra step of invoking the authorizer (if one is defined)
127218 ** and raising an error if the returned expression uses any unauthorized
127219 ** SQL function.
127220 */
127221 SQLITE_PRIVATE Expr *sqlite3ColumnExpr(Table *pTab, Column *pCol){
127222 if( pCol->iDflt==0 ) return 0;
127223 if( !IsOrdinaryTable(pTab) ) return 0;
127224 if( NEVER(pTab->u.tab.pDfltList==0) ) return 0;
127225 if( NEVER(pTab->u.tab.pDfltList->nExpr<pCol->iDflt) ) return 0;
127226 return pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr;
127227 }
127228 SQLITE_PRIVATE Expr *sqlite3ColumnExprAuth(Table *pTab, Column *pCol, Parse *pParse){
127229 Expr *pExpr = sqlite3ColumnExpr(pTab,pCol);
127230 #ifndef SQLITE_OMIT_AUTHORIZATION
127231 if( pParse->db->xAuth!=0 ){
127232 sqlite3FuncAuth(pParse, pExpr);
127233 }
127234 #endif
127235 return pExpr;
127236 }
127237
127238 /*
127239 ** Suppress false-positive warning message generated with -O3 in GCC
127240 ** on the second call to sqlite3Strlen30() in the sqlite3ColumnSetColl()
@@ -128115,10 +128298,17 @@
128298 x.pLeft = pExpr;
128299 x.flags = EP_Skip;
128300 pDfltExpr = sqlite3ExprDup(db, &x, EXPRDUP_REDUCE);
128301 sqlite3DbFree(db, x.u.zToken);
128302 sqlite3ColumnSetExpr(pParse, p, pCol, pDfltExpr);
128303 #ifndef SQLITE_OMIT_AUTHORIZATION
128304 /* Reject unauthorized functions from DEFAULT clauses */
128305 if( db->init.busy==0 && db->xAuth!=0 ){
128306 sqlite3FuncAuth(pParse, pExpr);
128307 }
128308 #endif /* SQLITE_OMIT_AUTHORIZER */
128309
128310 }
128311 }
128312 if( IN_RENAME_OBJECT ){
128313 sqlite3RenameExprUnmap(pParse, pExpr);
128314 }
@@ -128980,11 +129170,10 @@
129170 }
129171 }
129172 #else
129173 #define markExprListImmutable(X) /* no-op */
129174 #endif /* SQLITE_DEBUG */
 
129175
129176 /*
129177 ** This routine is called to report the final ")" that terminates
129178 ** a CREATE TABLE statement.
129179 **
@@ -138593,11 +138782,11 @@
138782 if( pCol->colFlags & COLFLAG_GENERATED ){
138783 testcase( pCol->colFlags & COLFLAG_VIRTUAL );
138784 testcase( pCol->colFlags & COLFLAG_STORED );
138785 pDflt = 0;
138786 }else{
138787 pDflt = sqlite3ColumnExprAuth(pFKey->pFrom, pCol, pParse);
138788 }
138789 if( pDflt ){
138790 pNew = sqlite3ExprDup(db, pDflt, 0);
138791 }else{
138792 pNew = sqlite3ExprAlloc(db, TK_NULL, 0, 0);
@@ -140165,11 +140354,11 @@
140354 continue;
140355 }else if( pColumn==0 ){
140356 /* Hidden columns that are not explicitly named in the INSERT
140357 ** get their default value */
140358 sqlite3ExprCodeFactorable(pParse,
140359 sqlite3ColumnExprAuth(pTab, &pTab->aCol[i], pParse),
140360 iRegStore);
140361 continue;
140362 }
140363 }
140364 if( pColumn ){
@@ -140177,19 +140366,19 @@
140366 assert( j>=0 && j<=pColumn->nId );
140367 if( j==0 ){
140368 /* A column not named in the insert column list gets its
140369 ** default value */
140370 sqlite3ExprCodeFactorable(pParse,
140371 sqlite3ColumnExprAuth(pTab, &pTab->aCol[i], pParse),
140372 iRegStore);
140373 continue;
140374 }
140375 k = j - 1;
140376 }else if( nColumn==0 ){
140377 /* This is INSERT INTO ... DEFAULT VALUES. Load the default value. */
140378 sqlite3ExprCodeFactorable(pParse,
140379 sqlite3ColumnExprAuth(pTab, &pTab->aCol[i], pParse),
140380 iRegStore);
140381 continue;
140382 }else{
140383 k = i - nHidden;
140384 }
@@ -140783,11 +140972,11 @@
140972 int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, iReg);
140973 VdbeCoverage(v);
140974 assert( (pCol->colFlags & COLFLAG_GENERATED)==0 );
140975 nSeenReplace++;
140976 sqlite3ExprCodeCopy(pParse,
140977 sqlite3ColumnExprAuth(pTab, pCol, pParse), iReg);
140978 sqlite3VdbeJumpHere(v, addr1);
140979 break;
140980 }
140981 case OE_Abort:
140982 sqlite3MayAbort(pParse);
@@ -141439,11 +141628,11 @@
141628 VdbeOp x; /* Conflict check opcode to copy */
141629 /* The sqlite3VdbeAddOp4() call might reallocate the opcode array.
141630 ** Hence, make a complete copy of the opcode, rather than using
141631 ** a pointer to the opcode. */
141632 x = *sqlite3VdbeGetOp(v, addrConflictCk);
141633 if( x.opcode!=OP_IdxRowid || isUpdate ){
141634 int p2; /* New P2 value for copied conflict check opcode */
141635 const char *zP4;
141636 if( sqlite3OpcodeProperty[x.opcode]&OPFLG_JUMP ){
141637 p2 = lblRecheckOk;
141638 }else{
@@ -146693,12 +146882,12 @@
146882 p1 = -1;
146883 p3 = 3;
146884 }else{
146885 if( pCol->iDflt ){
146886 sqlite3_value *pDfltValue = 0;
146887 sqlite3ValueFromExpr(db, sqlite3ColumnExprAuth(pTab,pCol,pParse),
146888 ENC(db), pCol->affinity, &pDfltValue);
146889 if( pDfltValue ){
146890 p4 = sqlite3_value_type(pDfltValue);
146891 sqlite3ValueFree(pDfltValue);
146892 }
146893 }
@@ -151506,13 +151695,11 @@
151695 */
151696 SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse *pParse){
151697 if( pParse->pVdbe ){
151698 return pParse->pVdbe;
151699 }
151700 if( pParse->pToplevel==0 ){
 
 
151701 pParse->okConstFactor = 1;
151702 }
151703 return sqlite3VdbeCreate(pParse);
151704 }
151705
@@ -151559,11 +151746,11 @@
151746 assert( pLimit->op==TK_LIMIT );
151747 assert( pLimit->pLeft!=0 );
151748 p->iLimit = iLimit = ++pParse->nMem;
151749 v = sqlite3GetVdbe(pParse);
151750 assert( v!=0 );
151751 if( sqlite3ExprIsInteger(pLimit->pLeft, &n, pParse, 0) ){
151752 sqlite3VdbeAddOp2(v, OP_Integer, n, iLimit);
151753 VdbeComment((v, "LIMIT counter"));
151754 if( n==0 ){
151755 sqlite3VdbeGoto(v, iBreak);
151756 }else if( n>=0 && p->nSelectRow>sqlite3LogEst((u64)n) ){
@@ -151926,15 +152113,37 @@
152113 while( p && (p->selFlags & SF_Recursive)!=0 ){ p = p->pPrior; }
152114 return p!=0;
152115 }
152116
152117 /*
152118 ** Return TRUE if p is a UNION with a LIMIT of exactly 1 and no OFFSET
152119 ** clause. This is a precondition for a couple of related optimizations.
152120 **
152121 ** False negatives are harmless (apart from resulting in a slower query).
152122 ** False positives can result in incorrect answers, however. To provoke
152123 ** false negatives for testing purposes, disable the SQLITE_UnionLimit
152124 ** optimization.
152125 */
152126 static int unionWithLimitOne(sqlite3 *db, Select *p){
152127 int v;
152128 if( p->op!=TK_UNION ) return 0;
152129 if( p->pLimit==0 ) return 0;
152130 if( p->pLimit->pRight ) return 0; /* No OFFSET */
152131 v = 0;
152132 assert( p->pLimit->pLeft!=0 );
152133 if( sqlite3ExprIsInteger(p->pLimit->pLeft, &v, 0, 0)==0 ) return 0;
152134 if( v!=1 ) return 0;
152135 if( OptimizationDisabled(db, SQLITE_UnionLimit) ) return 0;
152136 return 1;
152137 }
152138
152139 /*
152140 ** This routine is called to process a compound query formed from
152141 ** two or more separate queries using UNION, UNION ALL, EXCEPT, or
152142 ** INTERSECT
152143 **
152144 ** "p" points to the right-most of the two queries. The query on the
152145 ** left is p->pPrior. The left query could also be a compound query
152146 ** in which case this routine will be called recursively.
152147 **
152148 ** The results of the total query are to be written into a destination
152149 ** of type eDest with parameter iParm.
@@ -152014,11 +152223,11 @@
152223 #endif
152224 if( p->pOrderBy ){
152225 /* If the compound has an ORDER BY clause, then always use the merge
152226 ** algorithm. */
152227 return multiSelectByMerge(pParse, p, pDest);
152228 }else if( p->op!=TK_ALL && !unionWithLimitOne(db,p) ){
152229 /* If the compound is EXCEPT, INTERSECT, or UNION (anything other than
152230 ** UNION ALL) then also always use the merge algorithm. However, the
152231 ** multiSelectByMerge() routine requires that the compound have an
152232 ** ORDER BY clause, and it doesn't right now. So invent one first. */
152233 Expr *pOne = sqlite3ExprInt32(db, 1);
@@ -152026,11 +152235,12 @@
152235 if( pParse->nErr ) goto multi_select_end;
152236 assert( p->pOrderBy!=0 );
152237 p->pOrderBy->a[0].u.x.iOrderByCol = 1;
152238 return multiSelectByMerge(pParse, p, pDest);
152239 }else{
152240 /* For a UNION ALL compound without ORDER BY, or for a UNION with a
152241 ** LIMIT of exactly 1 and no OFFSET and no ORDER BY, simply run the left
152242 ** query, then run the right query */
152243 int addr = 0;
152244 int nLimit = 0; /* Initialize to suppress harmless compiler warning */
152245
152246 #ifndef SQLITE_OMIT_EXPLAIN
@@ -152067,11 +152277,11 @@
152277 testcase( rc!=SQLITE_OK );
152278 pDelete = p->pPrior;
152279 p->pPrior = pPrior;
152280 p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow);
152281 if( p->pLimit
152282 && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit, pParse, 0)
152283 && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit)
152284 ){
152285 p->nSelectRow = sqlite3LogEst((u64)nLimit);
152286 }
152287 if( addr ){
@@ -152577,11 +152787,11 @@
152787 sqlite3ResolveOrderGroupBy(pParse, p, p->pOrderBy, "ORDER");
152788 sqlite3ResolveOrderGroupBy(pParse, pPrior, pPrior->pOrderBy, "ORDER");
152789
152790 /* Compute the limit registers */
152791 computeLimitRegisters(pParse, p, labelEnd);
152792 if( p->iLimit && (op==TK_ALL || unionWithLimitOne(db,p)) ){
152793 regLimitA = ++pParse->nMem;
152794 regLimitB = ++pParse->nMem;
152795 sqlite3VdbeAddOp2(v, OP_Copy, p->iOffset ? p->iOffset+1 : p->iLimit,
152796 regLimitA);
152797 sqlite3VdbeAddOp2(v, OP_Copy, regLimitA, regLimitB);
@@ -159864,30 +160074,47 @@
160074 ** If column as REAL affinity and the table is an ordinary b-tree table
160075 ** (not a virtual table) then the value might have been stored as an
160076 ** integer. In that case, add an OP_RealAffinity opcode to make sure
160077 ** it has been converted into REAL.
160078 */
160079 static SQLITE_NOINLINE void columnDefaultUncommonCase(
160080 Vdbe *v, /* Byte code under construction */
160081 Table *pTab, /* The table */
160082 Column *pCol, /* Which column of the table */
160083 int iReg /* Register in which results are stored */
160084 ){
160085 sqlite3_value *pValue = 0;
160086 u8 enc = ENC(sqlite3VdbeDb(v));
160087 Parse *pParse = sqlite3VdbeParser(v);
160088 assert( !IsView(pTab) );
160089 sqlite3ValueFromExpr(sqlite3VdbeDb(v),
160090 sqlite3ColumnExprAuth(pTab,pCol,pParse), enc,
160091 pCol->affinity, &pValue);
160092 if( pValue ){
160093 sqlite3VdbeAppendP4(v, pValue, P4_MEM);
160094 }
160095 #ifndef SQLITE_OMIT_FLOATING_POINT
160096 if( pCol->affinity==SQLITE_AFF_REAL ){
160097 sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg);
160098 }
160099 #endif
160100 }
160101 SQLITE_PRIVATE void sqlite3ColumnDefault(
160102 Vdbe *v, /* Byte code under construction */
160103 Table *pTab, /* The table */
160104 int i, /* Which column of the table */
160105 int iReg /* Register in which results are stored */
160106 ){
160107 Column *pCol;
160108 assert( pTab!=0 );
160109 assert( pTab->nCol>i );
160110 pCol = &pTab->aCol[i];
160111 if( pCol->iDflt ){
160112 columnDefaultUncommonCase(v,pTab,pCol,iReg);
 
 
 
 
 
 
 
 
 
 
160113 }
160114 #ifndef SQLITE_OMIT_FLOATING_POINT
160115 if( pCol->affinity==SQLITE_AFF_REAL ){
160116 sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg);
160117 }
160118 #endif
160119 }
160120
@@ -164579,10 +164806,11 @@
164806 ExprList *pOrigRhs; /* Original unmodified RHS */
164807 ExprList *pOrigLhs = 0; /* Original unmodified LHS */
164808 ExprList *pRhs = 0; /* New RHS after modifications */
164809 ExprList *pLhs = 0; /* New LHS after mods */
164810 int i; /* Loop counter */
164811 int nRhs = 0; /* Number of RHS terms added so far */
164812
164813 assert( ExprUseXSelect(pNew) );
164814 pOrigRhs = pSelect->pEList;
164815 assert( pNew->pLeft!=0 );
164816 assert( ExprUseXList(pNew->pLeft) );
@@ -164592,10 +164820,13 @@
164820 for(i=iEq; i<pLoop->nLTerm; i++){
164821 if( pLoop->aLTerm[i]->pExpr==pX ){
164822 int iField;
164823 assert( (pLoop->aLTerm[i]->eOperator & (WO_OR|WO_AND))==0 );
164824 iField = pLoop->aLTerm[i]->u.x.iField - 1;
164825 if( iField!=nRhs ){
164826 ExprClearProperty(pNew, EP_Subrtn);
164827 }
164828 if( NEVER(pOrigRhs->a[iField].pExpr==0) ){
164829 continue; /* Duplicate PK column */
164830 }
164831 pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr);
164832 pOrigRhs->a[iField].pExpr = 0;
@@ -164603,10 +164834,11 @@
164834 if( pOrigLhs ){
164835 assert( pOrigLhs->a[iField].pExpr!=0 );
164836 pLhs = sqlite3ExprListAppend(pParse,pLhs,pOrigLhs->a[iField].pExpr);
164837 pOrigLhs->a[iField].pExpr = 0;
164838 }
164839 nRhs++;
164840 }
164841 }
164842 sqlite3ExprListDelete(db, pOrigRhs);
164843 if( pOrigLhs ){
164844 sqlite3ExprListDelete(db, pOrigLhs);
@@ -164714,11 +164946,12 @@
164946 ** in sorted order (requires checking asc/desc, and rejecting cases
164947 ** where the indexed columns are not in the right order). */
164948 pLoop->wsFlags &= ~WHERE_IN_SEEKSCAN;
164949 }
164950 if( !db->mallocFailed ){
164951 int nCol = pX->x.pSelect->pEList->nExpr;
164952 aiMap = (int*)sqlite3DbMallocZero(db, sizeof(int)*nCol);
164953 eType = sqlite3FindInIndex(pParse, pXMod, IN_INDEX_LOOP, 0, aiMap, &iTab);
164954 }
164955 sqlite3ExprDelete(db, pXMod);
164956 }
164957
@@ -165468,10 +165701,53 @@
165701 }
165702 return 1;
165703 }
165704 return 0;
165705 }
165706
165707 /*
165708 ** This is called while coding loop pLevel, which scans FROM clause item
165709 ** pTabItem. pE is an expression for which all the prerequisites are
165710 ** available. This function tests if pE can be coded as part of the current
165711 ** loop, or whether it needs to be deferred to ensure outer joins are
165712 ** processed correctly. This function returns non-zero if the expression
165713 ** can be coded as part of the loop, or 0 if it must be deferred.
165714 **
165715 ** The expression must be deferred if:
165716 **
165717 ** * There are any RIGHT joins in the FROM clause and the expression
165718 ** was not part of an ON clause, or was part of an ON clause to the
165719 ** right of the join.
165720 **
165721 ** * The table is the RHS of a LEFT JOIN and the expression was not
165722 ** part of an ON clause on an OUTER join, or was part of an ON clause
165723 ** on an OUTER join to the right of the join.
165724 */
165725 static int whereExprIsReady(
165726 WhereInfo *pWInfo,
165727 WhereLevel *pLevel,
165728 SrcItem *pTabItem,
165729 Expr *pE
165730 ){
165731 u8 jtype = pTabItem->fg.jointype;
165732 if( jtype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ){
165733 if( !ExprHasProperty(pE,EP_OuterON|EP_InnerON) ){
165734 /* Defer processing WHERE clause constraints until after outer
165735 ** join processing. tag-20220513a */
165736 return 0;
165737 }else if( (jtype & JT_LEFT) && !ExprHasProperty(pE,EP_OuterON) ){
165738 return 0;
165739 }else{
165740 Bitmask m = sqlite3WhereGetMask(&pWInfo->sMaskSet, pE->w.iJoin);
165741 if( m & pLevel->notReady ){
165742 /* An ON clause that is not ripe */
165743 return 0;
165744 }
165745 }
165746 }
165747 return 1;
165748 }
165749
165750 /*
165751 ** Generate code for the start of the iLevel-th loop in the WHERE clause
165752 ** implementation described by pWInfo.
165753 */
@@ -166399,10 +166675,11 @@
166675 if( (pWC->a[iTerm].wtFlags & (TERM_VIRTUAL|TERM_CODED|TERM_SLICE))!=0 ){
166676 continue;
166677 }
166678 if( (pWC->a[iTerm].eOperator & WO_ALL)==0 ) continue;
166679 if( ExprHasProperty(pExpr, EP_Subquery) ) continue; /* tag-20220303a */
166680 if( whereExprIsReady(pWInfo, pLevel, pTabItem, pExpr)==0 ) continue;
166681 pExpr = sqlite3ExprDup(db, pExpr, 0);
166682 pAndExpr = sqlite3ExprAnd(pParse, pAndExpr, pExpr);
166683 }
166684 if( pAndExpr ){
166685 /* The extra 0x10000 bit on the opcode is masked off and does not
@@ -166639,26 +166916,11 @@
166916 pWInfo->untestedTerms = 1;
166917 continue;
166918 }
166919 pE = pTerm->pExpr;
166920 assert( pE!=0 );
166921 if( whereExprIsReady(pWInfo, pLevel, pTabItem, pE)==0 ) continue;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166922 if( iLoop==1 && !sqlite3ExprCoveredByIndex(pE, pLevel->iTabCur, pIdx) ){
166923 iNext = 2;
166924 continue;
166925 }
166926 if( iLoop<3 && (pTerm->wtFlags & TERM_VARSELECT) ){
@@ -166807,13 +167069,15 @@
167069 ** of the RIGHT JOIN. During normal operation, the subroutine will
167070 ** be in-line with the rest of the code. But at the end, a separate
167071 ** loop will run that invokes this subroutine for unmatched rows
167072 ** of pTab, with all tables to left begin set to NULL.
167073 */
167074 {
167075 WhereRightJoin *pRJ = pLevel->pRJ;
167076 sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pRJ->regReturn);
167077 pRJ->addrSubrtn = sqlite3VdbeCurrentAddr(v);
167078 }
167079 assert( pParse->withinRJSubrtn < 255 );
167080 pParse->withinRJSubrtn++;
167081
167082 /* WHERE clause constraints must be deferred until after outer join
167083 ** row elimination has completed, since WHERE clause constraints apply
@@ -167209,11 +167473,11 @@
167473 int iCol = pRight->iColumn;
167474 pVal = sqlite3VdbeGetBoundValue(pReprepare, iCol, SQLITE_AFF_BLOB);
167475 if( pVal && sqlite3_value_type(pVal)==SQLITE_TEXT ){
167476 z = sqlite3_value_text(pVal);
167477 }
167478 sqlite3VdbeReprepareOnBind(pParse->pVdbe, iCol, 0);
167479 assert( pRight->op==TK_VARIABLE || pRight->op==TK_REGISTER );
167480 }else if( op==TK_STRING ){
167481 assert( !ExprHasProperty(pRight, EP_IntValue) );
167482 z = (u8*)pRight->u.zToken;
167483 }
@@ -167314,11 +167578,11 @@
167578
167579 /* If the RHS pattern is a bound parameter, make arrangements to
167580 ** reprepare the statement when that parameter is rebound */
167581 if( op==TK_VARIABLE ){
167582 Vdbe *v = pParse->pVdbe;
167583 sqlite3VdbeReprepareOnBind(v, pRight->iColumn, 0);
167584 assert( !ExprHasProperty(pRight, EP_IntValue) );
167585 if( *pisComplete && pRight->u.zToken[1] ){
167586 /* If the rhs of the LIKE expression is a variable, and the current
167587 ** value of the variable means there is no need to invoke the LIKE
167588 ** function, then no OP_Variable will be added to the program.
@@ -168665,11 +168929,11 @@
168929 Parse *pParse = pWC->pWInfo->pParse;
168930 sqlite3 *db = pParse->db;
168931 Expr *pNew;
168932 int iVal = 0;
168933
168934 if( sqlite3ExprIsInteger(pExpr, &iVal, pParse, 0) && iVal>=0 ){
168935 Expr *pVal = sqlite3ExprInt32(db, iVal);
168936 if( pVal==0 ) return;
168937 pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal);
168938 }else{
168939 Expr *pVal = sqlite3ExprAlloc(db, TK_REGISTER, 0, 0);
@@ -170248,13 +170512,11 @@
170512 /*
170513 ** Generate bytecode that will initialize a Bloom filter that is appropriate
170514 ** for pLevel.
170515 **
170516 ** If there are inner loops within pLevel that have the WHERE_BLOOMFILTER
170517 ** flag set, initialize a Bloomfilter for them as well.
 
 
170518 **
170519 ** When the Bloom filter is initialized, the WHERE_BLOOMFILTER flag is cleared
170520 ** from the loop, but the regFilter value is set to a register that implements
170521 ** the Bloom filter. When regFilter is positive, the
170522 ** sqlite3WhereCodeOneLoopStart() will generate code to test the Bloom filter
@@ -170359,11 +170621,10 @@
170621 sqlite3VdbeResolveLabel(v, addrCont);
170622 sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1);
170623 VdbeCoverage(v);
170624 sqlite3VdbeJumpHere(v, addrTop);
170625 pLoop->wsFlags &= ~WHERE_BLOOMFILTER;
 
170626 while( ++iLevel < pWInfo->nLevel ){
170627 const SrcItem *pTabItem;
170628 pLevel = &pWInfo->a[iLevel];
170629 pTabItem = &pWInfo->pTabList->a[pLevel->iFrom];
170630 if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ) ) continue;
@@ -172038,10 +172299,17 @@
172299 ){
172300 WhereTerm *pTerm, *pX;
172301 Bitmask notAllowed = ~(pLoop->prereq|pLoop->maskSelf);
172302 int i, j;
172303 LogEst iReduce = 0; /* pLoop->nOut should not exceed nRow-iReduce */
172304
172305 /* Skip all this if the FROM clause of the query is a single table and
172306 ** there is no ORDER BY. In this case it doesn't matter how accurate
172307 ** the WhereLoop.nOut values are. */
172308 if( pWC->pWInfo->pTabList->nSrc<=1 && pWC->pWInfo->pOrderBy==0 ){
172309 return;
172310 }
172311
172312 assert( (pLoop->wsFlags & WHERE_AUTO_INDEX)==0 );
172313 for(i=pWC->nBase, pTerm=pWC->a; i>0; i--, pTerm++){
172314 assert( pTerm!=0 );
172315 if( (pTerm->prereqAll & notAllowed)!=0 ) continue;
@@ -172086,11 +172354,11 @@
172354 ){
172355 Expr *pRight = pOpExpr->pRight;
172356 Parse *pParse = pWC->pWInfo->pParse;
172357 int k = 0;
172358 testcase( pOpExpr->op==TK_IS );
172359 if( sqlite3ExprIsInteger(pRight, &k, pParse, 1) && k>=(-1) && k<=1 ){
172360 k = 10;
172361 }else{
172362 k = 20;
172363 }
172364 if( iReduce<k ){
@@ -174256,16 +174524,24 @@
174524 if( j>=pLoop->nLTerm ) continue;
174525 }
174526 if( (pTerm->eOperator&(WO_EQ|WO_IS))!=0 && pOBExpr->iColumn>=0 ){
174527 Parse *pParse = pWInfo->pParse;
174528 CollSeq *pColl1 = sqlite3ExprNNCollSeq(pParse, pOrderBy->a[i].pExpr);
174529 const Expr *pCExpr = pTerm->pExpr;
174530 CollSeq *pColl2 = sqlite3ExprCompareCollSeq(pParse, pCExpr);
174531 char affRight;
174532 assert( pColl1 );
174533 if( pColl2==0 || sqlite3StrICmp(pColl1->zName, pColl2->zName) ){
174534 continue;
174535 }
174536 affRight = sqlite3ExprAffinity(pCExpr->pRight);
174537 /* Left operand must be a column, hence always has affinity */
174538 assert( sqlite3ExprAffinity(pCExpr->pLeft)!=0 );
174539 if( affRight!=0 && affRight!=sqlite3ExprAffinity(pCExpr->pLeft) ){
174540 continue; /* An affinity conversion would be required */
174541 }
174542 testcase( pCExpr->op==TK_IS );
174543 }
174544 obSat |= MASKBIT(i);
174545 }
174546
174547 if( (pLoop->wsFlags & WHERE_ONEROW)==0 ){
@@ -177803,11 +178079,11 @@
178079 }
178080 if( bIntToNull ){
178081 int iDummy;
178082 Expr *pSub;
178083 pSub = sqlite3ExprSkipCollateAndLikely(pDup);
178084 if( sqlite3ExprIsInteger(pSub, &iDummy, 0, 0) ){
178085 pSub->op = TK_NULL;
178086 pSub->flags &= ~(EP_IntValue|EP_IsTrue|EP_IsFalse);
178087 pSub->u.zToken = 0;
178088 }
178089 }
@@ -195326,11 +195602,14 @@
195602 /* Loop through the returned columns. Set nStr to the number of bytes of
195603 ** space required to store a copy of each column name, including the
195604 ** nul-terminator byte. */
195605 nCol = sqlite3_column_count(pStmt);
195606 for(i=0; i<nCol; i++){
195607 /* This call to sqlite3_column_name() cannot fail, as no conversion
195608 ** between encodings, and therefore no malloc() call, is required. */
195609 const char *zCol = sqlite3_column_name(pStmt, i);
195610 assert( zCol );
195611 nStr += strlen(zCol) + 1;
195612 }
195613
195614 /* Allocate and populate the array to return. */
195615 azCol = (const char **)sqlite3_malloc64(sizeof(char *) * nCol + nStr);
@@ -195339,10 +195618,11 @@
195618 }else{
195619 char *p = (char *)&azCol[nCol];
195620 for(i=0; i<nCol; i++){
195621 const char *zCol = sqlite3_column_name(pStmt, i);
195622 int n = (int)strlen(zCol)+1;
195623 assert( zCol ); /* no malloc() required, cannot fail */
195624 memcpy(p, zCol, n);
195625 azCol[i] = p;
195626 p += n;
195627 }
195628 }
@@ -255110,10 +255390,11 @@
255390 ** against no columns at all).
255391 */
255392 static void fts5IterSetOutputs_ZeroColset(Fts5Iter *pIter, Fts5SegIter *pSeg){
255393 UNUSED_PARAM(pSeg);
255394 pIter->base.nData = 0;
255395 pIter->base.bEof = 1;
255396 }
255397
255398 /*
255399 ** xSetOutputs callback used by detail=col when there is a column filter
255400 ** and there are 100 or more columns. Also called as a fallback from
@@ -257019,11 +257300,10 @@
257300 && !fts5FlushSecureDelete(p, pStruct, zTerm, nTerm, iRowid)
257301 ){
257302 iOff++;
257303 if( iOff<nDoclist && pDoclist[iOff]==0x00 ){
257304 iOff++;
 
257305 }else{
257306 continue;
257307 }
257308 }
257309 }else if( (pDoclist[iOff] & 0x01)
@@ -257335,13 +257615,13 @@
257615
257616 assert( pIter->aPoslist || (p==0 && pIter->aPoslist==0) );
257617 if( p>=pIter->aEof ){
257618 pIter->aPoslist = 0;
257619 }else{
257620 u64 iDelta;
257621
257622 p += fts5GetVarint(p, &iDelta);
257623 pIter->iRowid += iDelta;
257624
257625 /* Read position list size */
257626 if( p[0] & 0x80 ){
257627 int nPos;
@@ -264533,11 +264813,11 @@
264813 int nArg, /* Number of args */
264814 sqlite3_value **apUnused /* Function arguments */
264815 ){
264816 assert( nArg==0 );
264817 UNUSED_PARAM2(nArg, apUnused);
264818 sqlite3_result_text(pCtx, "fts5: 2026-08-27 10:58:16 555f31c64d3fcc55df2715a7c8c1cb28503b2c0eb21d950ccdcbf6f577593dd5", -1, SQLITE_TRANSIENT);
264819 }
264820
264821 /*
264822 ** Implementation of fts5_locale(LOCALE, TEXT) function.
264823 **
@@ -269637,13 +269917,22 @@
269917 sqlite3_value *pLe = 0;
269918
269919 UNUSED_PARAM2(zUnused, nUnused);
269920
269921 fts5VocabResetCursor(pCsr);
269922 if( idxNum & FTS5_VOCAB_TERM_EQ ){
269923 pEq = apVal[iVal++];
269924 if( sqlite3_value_type(pEq)!=SQLITE_TEXT ) pEq = 0;
269925 }
269926 if( idxNum & FTS5_VOCAB_TERM_GE ){
269927 pGe = apVal[iVal++];
269928 if( sqlite3_value_type(pGe)!=SQLITE_TEXT ) pGe = 0;
269929 }
269930 if( idxNum & FTS5_VOCAB_TERM_LE ){
269931 pLe = apVal[iVal++];
269932 if( sqlite3_value_type(pLe)!=SQLITE_TEXT ) pLe = 0;
269933 }
269934 pCsr->colUsed = (idxNum & FTS5_VOCAB_COLUSED_MASK);
269935
269936 if( pEq ){
269937 zTerm = fts5VocabValueText(pEq, &nTerm);
269938 f = FTS5INDEX_QUERY_NOTOKENDATA;
269939
--- extsrc/sqlite3.h
+++ extsrc/sqlite3.h
@@ -146,14 +146,14 @@
146146
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
147147
** [sqlite_version()] and [sqlite_source_id()].
148148
*/
149149
#define SQLITE_VERSION "3.54.0"
150150
#define SQLITE_VERSION_NUMBER 3054000
151
-#define SQLITE_SOURCE_ID "2026-08-20 13:25:12 3bd5456e50ef321f2cafcf67261d556a760624ce0caab430ed9454cebec233e7"
151
+#define SQLITE_SOURCE_ID "2026-08-27 10:58:16 555f31c64d3fcc55df2715a7c8c1cb28503b2c0eb21d950ccdcbf6f577593dd5"
152152
#define SQLITE_SCM_BRANCH "trunk"
153153
#define SQLITE_SCM_TAGS ""
154
-#define SQLITE_SCM_DATETIME "2026-08-20T13:25:12.207Z"
154
+#define SQLITE_SCM_DATETIME "2026-08-27T10:58:16.680Z"
155155
156156
/*
157157
** CAPI3REF: Run-Time Library Version Numbers
158158
** KEYWORDS: sqlite3_version sqlite3_sourceid
159159
**
160160
--- extsrc/sqlite3.h
+++ extsrc/sqlite3.h
@@ -146,14 +146,14 @@
146 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
147 ** [sqlite_version()] and [sqlite_source_id()].
148 */
149 #define SQLITE_VERSION "3.54.0"
150 #define SQLITE_VERSION_NUMBER 3054000
151 #define SQLITE_SOURCE_ID "2026-08-20 13:25:12 3bd5456e50ef321f2cafcf67261d556a760624ce0caab430ed9454cebec233e7"
152 #define SQLITE_SCM_BRANCH "trunk"
153 #define SQLITE_SCM_TAGS ""
154 #define SQLITE_SCM_DATETIME "2026-08-20T13:25:12.207Z"
155
156 /*
157 ** CAPI3REF: Run-Time Library Version Numbers
158 ** KEYWORDS: sqlite3_version sqlite3_sourceid
159 **
160
--- extsrc/sqlite3.h
+++ extsrc/sqlite3.h
@@ -146,14 +146,14 @@
146 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
147 ** [sqlite_version()] and [sqlite_source_id()].
148 */
149 #define SQLITE_VERSION "3.54.0"
150 #define SQLITE_VERSION_NUMBER 3054000
151 #define SQLITE_SOURCE_ID "2026-08-27 10:58:16 555f31c64d3fcc55df2715a7c8c1cb28503b2c0eb21d950ccdcbf6f577593dd5"
152 #define SQLITE_SCM_BRANCH "trunk"
153 #define SQLITE_SCM_TAGS ""
154 #define SQLITE_SCM_DATETIME "2026-08-27T10:58:16.680Z"
155
156 /*
157 ** CAPI3REF: Run-Time Library Version Numbers
158 ** KEYWORDS: sqlite3_version sqlite3_sourceid
159 **
160
--- src/backlink.c
+++ src/backlink.c
@@ -283,10 +283,12 @@
283283
static int mkdn_noop_r_html_tag(Blob *b1, Blob *b2, void *v){ return 1; }
284284
static int (*mkdn_noop_tri_emphas)(Blob*, Blob*, char,
285285
void*) = mkdn_noop_emphasis;
286286
static int mkdn_noop_footnoteref(Blob *b1, const Blob *b2, const Blob *b3,
287287
int i1, int i2, void *v){ return 1; }
288
+static int mkdn_noop_tagref(Blob *b1,Blob *b2, enum mkd_tagspan t,
289
+ void *p){ return 1; }
288290
289291
/*
290292
** Scan markdown text and add self-hyperlinks to the BACKLINK table.
291293
*/
292294
void markdown_extract_links(
@@ -317,10 +319,11 @@
317319
/* emphasis */ mkdn_noop_emphasis,
318320
/* image */ mkdn_noop_image,
319321
/* linebreak */ mkdn_noop_linebreak,
320322
/* link */ backlink_md_link,
321323
/* r_html_tag */ mkdn_noop_r_html_tag,
324
+ /* #tags */ mkdn_noop_tagref,
322325
/* tri_emphas */ mkdn_noop_tri_emphas,
323326
/* footnoteref*/ mkdn_noop_footnoteref,
324327
325328
0, /* entity */
326329
0, /* normal_text */
327330
--- src/backlink.c
+++ src/backlink.c
@@ -283,10 +283,12 @@
283 static int mkdn_noop_r_html_tag(Blob *b1, Blob *b2, void *v){ return 1; }
284 static int (*mkdn_noop_tri_emphas)(Blob*, Blob*, char,
285 void*) = mkdn_noop_emphasis;
286 static int mkdn_noop_footnoteref(Blob *b1, const Blob *b2, const Blob *b3,
287 int i1, int i2, void *v){ return 1; }
 
 
288
289 /*
290 ** Scan markdown text and add self-hyperlinks to the BACKLINK table.
291 */
292 void markdown_extract_links(
@@ -317,10 +319,11 @@
317 /* emphasis */ mkdn_noop_emphasis,
318 /* image */ mkdn_noop_image,
319 /* linebreak */ mkdn_noop_linebreak,
320 /* link */ backlink_md_link,
321 /* r_html_tag */ mkdn_noop_r_html_tag,
 
322 /* tri_emphas */ mkdn_noop_tri_emphas,
323 /* footnoteref*/ mkdn_noop_footnoteref,
324
325 0, /* entity */
326 0, /* normal_text */
327
--- src/backlink.c
+++ src/backlink.c
@@ -283,10 +283,12 @@
283 static int mkdn_noop_r_html_tag(Blob *b1, Blob *b2, void *v){ return 1; }
284 static int (*mkdn_noop_tri_emphas)(Blob*, Blob*, char,
285 void*) = mkdn_noop_emphasis;
286 static int mkdn_noop_footnoteref(Blob *b1, const Blob *b2, const Blob *b3,
287 int i1, int i2, void *v){ return 1; }
288 static int mkdn_noop_tagref(Blob *b1,Blob *b2, enum mkd_tagspan t,
289 void *p){ return 1; }
290
291 /*
292 ** Scan markdown text and add self-hyperlinks to the BACKLINK table.
293 */
294 void markdown_extract_links(
@@ -317,10 +319,11 @@
319 /* emphasis */ mkdn_noop_emphasis,
320 /* image */ mkdn_noop_image,
321 /* linebreak */ mkdn_noop_linebreak,
322 /* link */ backlink_md_link,
323 /* r_html_tag */ mkdn_noop_r_html_tag,
324 /* #tags */ mkdn_noop_tagref,
325 /* tri_emphas */ mkdn_noop_tri_emphas,
326 /* footnoteref*/ mkdn_noop_footnoteref,
327
328 0, /* entity */
329 0, /* normal_text */
330
+1
--- src/chat.c
+++ src/chat.c
@@ -216,10 +216,11 @@
216216
@ </span>
217217
@ <span>Active users (sorted by last message time)</span>
218218
@ </div>
219219
@ <div id='chat-user-list'></div>
220220
@ </div>
221
+ @ <button id='chat-clear-filter' class='hidden'>Clear filter</button>
221222
@ <div id='chat-preview' class='hidden chat-view'>
222223
@ <header>Preview: (<a href='%R/md_rules' target='_blank'>markdown reference</a>)</header>
223224
@ <div id='chat-preview-content'></div>
224225
@ <div class='button-bar'><button class='action-close'>Close Preview</button></div>
225226
@ </div>
226227
--- src/chat.c
+++ src/chat.c
@@ -216,10 +216,11 @@
216 @ </span>
217 @ <span>Active users (sorted by last message time)</span>
218 @ </div>
219 @ <div id='chat-user-list'></div>
220 @ </div>
 
221 @ <div id='chat-preview' class='hidden chat-view'>
222 @ <header>Preview: (<a href='%R/md_rules' target='_blank'>markdown reference</a>)</header>
223 @ <div id='chat-preview-content'></div>
224 @ <div class='button-bar'><button class='action-close'>Close Preview</button></div>
225 @ </div>
226
--- src/chat.c
+++ src/chat.c
@@ -216,10 +216,11 @@
216 @ </span>
217 @ <span>Active users (sorted by last message time)</span>
218 @ </div>
219 @ <div id='chat-user-list'></div>
220 @ </div>
221 @ <button id='chat-clear-filter' class='hidden'>Clear filter</button>
222 @ <div id='chat-preview' class='hidden chat-view'>
223 @ <header>Preview: (<a href='%R/md_rules' target='_blank'>markdown reference</a>)</header>
224 @ <div id='chat-preview-content'></div>
225 @ <div class='button-bar'><button class='action-close'>Close Preview</button></div>
226 @ </div>
227
+9 -1
--- src/dispatch.c
+++ src/dispatch.c
@@ -315,16 +315,24 @@
315315
}
316316
317317
/*
318318
** Append text to pOut, adding formatting markup. Terms that
319319
** have all lower-case letters are within <tt>..</tt>. Terms
320
-** that have all upper-case letters are within <i>..</i>.
320
+** that have all upper-case letters are within <i>..</i>. Except,
321
+** if the entire string begins with % then put the whole thing
322
+** inside of <tt>..</tt>.
321323
*/
322324
static void appendMixedFont(Blob *pOut, const char *z, int n){
323325
const char *zEnd = "";
324326
int i = 0;
325327
int j;
328
+ if( n>0 && z[0]=='%' ){
329
+ blob_append(pOut, "<tt>", 4);
330
+ blob_append(pOut, z, n);
331
+ blob_append(pOut, "</tt>", 5);
332
+ return;
333
+ }
326334
while( i<n ){
327335
if( z[i]==' ' || z[i]=='=' ){
328336
for(j=i+1; j<n && (z[j]==' ' || z[j]=='='); j++){}
329337
appendLinked(pOut, z+i, j-i);
330338
i = j;
331339
--- src/dispatch.c
+++ src/dispatch.c
@@ -315,16 +315,24 @@
315 }
316
317 /*
318 ** Append text to pOut, adding formatting markup. Terms that
319 ** have all lower-case letters are within <tt>..</tt>. Terms
320 ** that have all upper-case letters are within <i>..</i>.
 
 
321 */
322 static void appendMixedFont(Blob *pOut, const char *z, int n){
323 const char *zEnd = "";
324 int i = 0;
325 int j;
 
 
 
 
 
 
326 while( i<n ){
327 if( z[i]==' ' || z[i]=='=' ){
328 for(j=i+1; j<n && (z[j]==' ' || z[j]=='='); j++){}
329 appendLinked(pOut, z+i, j-i);
330 i = j;
331
--- src/dispatch.c
+++ src/dispatch.c
@@ -315,16 +315,24 @@
315 }
316
317 /*
318 ** Append text to pOut, adding formatting markup. Terms that
319 ** have all lower-case letters are within <tt>..</tt>. Terms
320 ** that have all upper-case letters are within <i>..</i>. Except,
321 ** if the entire string begins with % then put the whole thing
322 ** inside of <tt>..</tt>.
323 */
324 static void appendMixedFont(Blob *pOut, const char *z, int n){
325 const char *zEnd = "";
326 int i = 0;
327 int j;
328 if( n>0 && z[0]=='%' ){
329 blob_append(pOut, "<tt>", 4);
330 blob_append(pOut, z, n);
331 blob_append(pOut, "</tt>", 5);
332 return;
333 }
334 while( i<n ){
335 if( z[i]==' ' || z[i]=='=' ){
336 for(j=i+1; j<n && (z[j]==' ' || z[j]=='='); j++){}
337 appendLinked(pOut, z+i, j-i);
338 i = j;
339
+3 -3
--- src/encode.c
+++ src/encode.c
@@ -482,13 +482,13 @@
482482
if( c>=0xc0 ){
483483
c = utf8Trans1[c-0xc0];
484484
while( (*(*pz) & 0xc0)==0x80 ){
485485
c = (c<<6) + (0x3f & *((*pz)++));
486486
}
487
- if( c<0x80
488
- || (c&0xFFFFF800)==0xD800
489
- || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; }
487
+ if( c<0x80 || (c&0xFFFFF800)==0xD800 ){
488
+ c = 0xFFFD;
489
+ }
490490
}
491491
return c;
492492
}
493493
494494
/*
495495
--- src/encode.c
+++ src/encode.c
@@ -482,13 +482,13 @@
482 if( c>=0xc0 ){
483 c = utf8Trans1[c-0xc0];
484 while( (*(*pz) & 0xc0)==0x80 ){
485 c = (c<<6) + (0x3f & *((*pz)++));
486 }
487 if( c<0x80
488 || (c&0xFFFFF800)==0xD800
489 || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; }
490 }
491 return c;
492 }
493
494 /*
495
--- src/encode.c
+++ src/encode.c
@@ -482,13 +482,13 @@
482 if( c>=0xc0 ){
483 c = utf8Trans1[c-0xc0];
484 while( (*(*pz) & 0xc0)==0x80 ){
485 c = (c<<6) + (0x3f & *((*pz)++));
486 }
487 if( c<0x80 || (c&0xFFFFF800)==0xD800 ){
488 c = 0xFFFD;
489 }
490 }
491 return c;
492 }
493
494 /*
495
+8 -6
--- src/forum.c
+++ src/forum.c
@@ -1554,21 +1554,22 @@
15541554
** <div class='forumSel'>...</div>. Javascript is used to move the
15551555
** selected posting into view after the page loads.
15561556
**
15571557
** Query parameters:
15581558
**
1559
-** name=X REQUIRED. The hash of the post to display.
1559
+** name=NAME Required. The NAME value is a hash prefix or timestamp
1560
+** for the post to be displayed.
15601561
** t=a Automatic display mode, i.e. hierarchical for
15611562
** desktop and chronological for mobile. This is the
15621563
** default if the "t" query parameter is omitted.
15631564
** t=c Show posts in the order they were written.
15641565
** t=h Show posts using hierarchical indenting.
15651566
** t=s Show only the post specified by "name=X".
1566
-** t=r Alias for "t=c&unf&hist".
1567
-** t=y Alias for "t=s&unf&hist".
1568
-** raw Alias for "t=s&unf". Additionally, omit the border
1569
-** around the post, and ignore "t" and "hist".
1567
+** t=r Alias for "t=c" with "unf" and "hist".
1568
+** t=y Alias for "t=s" with "unf" and "hist".
1569
+** raw Alias for "t=s" with "unf". Additionally, omit
1570
+** the border around the post, and ignore "t" and "hist".
15701571
** unf Show the original, unformatted source text.
15711572
** hist Show edit history in addition to current posts.
15721573
*/
15731574
void forumpost_page(void){
15741575
forumthread_page();
@@ -1581,11 +1582,12 @@
15811582
** The result is basically the same as /forumpost except that none of
15821583
** the postings in the thread are selected.
15831584
**
15841585
** Query parameters:
15851586
**
1586
-** name=X REQUIRED. The hash of any post of the thread.
1587
+** name=NAME Required. The NAME value is a hash prefix or timestamp
1588
+** for one of the posts in the thread.
15871589
** t=a Automatic display mode, i.e. hierarchical for
15881590
** desktop and chronological for mobile. This is the
15891591
** default if the "t" query parameter is omitted.
15901592
** t=c Show posts in the order they were written.
15911593
** t=h Show posts using hierarchical indenting.
15921594
--- src/forum.c
+++ src/forum.c
@@ -1554,21 +1554,22 @@
1554 ** <div class='forumSel'>...</div>. Javascript is used to move the
1555 ** selected posting into view after the page loads.
1556 **
1557 ** Query parameters:
1558 **
1559 ** name=X REQUIRED. The hash of the post to display.
 
1560 ** t=a Automatic display mode, i.e. hierarchical for
1561 ** desktop and chronological for mobile. This is the
1562 ** default if the "t" query parameter is omitted.
1563 ** t=c Show posts in the order they were written.
1564 ** t=h Show posts using hierarchical indenting.
1565 ** t=s Show only the post specified by "name=X".
1566 ** t=r Alias for "t=c&unf&hist".
1567 ** t=y Alias for "t=s&unf&hist".
1568 ** raw Alias for "t=s&unf". Additionally, omit the border
1569 ** around the post, and ignore "t" and "hist".
1570 ** unf Show the original, unformatted source text.
1571 ** hist Show edit history in addition to current posts.
1572 */
1573 void forumpost_page(void){
1574 forumthread_page();
@@ -1581,11 +1582,12 @@
1581 ** The result is basically the same as /forumpost except that none of
1582 ** the postings in the thread are selected.
1583 **
1584 ** Query parameters:
1585 **
1586 ** name=X REQUIRED. The hash of any post of the thread.
 
1587 ** t=a Automatic display mode, i.e. hierarchical for
1588 ** desktop and chronological for mobile. This is the
1589 ** default if the "t" query parameter is omitted.
1590 ** t=c Show posts in the order they were written.
1591 ** t=h Show posts using hierarchical indenting.
1592
--- src/forum.c
+++ src/forum.c
@@ -1554,21 +1554,22 @@
1554 ** <div class='forumSel'>...</div>. Javascript is used to move the
1555 ** selected posting into view after the page loads.
1556 **
1557 ** Query parameters:
1558 **
1559 ** name=NAME Required. The NAME value is a hash prefix or timestamp
1560 ** for the post to be displayed.
1561 ** t=a Automatic display mode, i.e. hierarchical for
1562 ** desktop and chronological for mobile. This is the
1563 ** default if the "t" query parameter is omitted.
1564 ** t=c Show posts in the order they were written.
1565 ** t=h Show posts using hierarchical indenting.
1566 ** t=s Show only the post specified by "name=X".
1567 ** t=r Alias for "t=c" with "unf" and "hist".
1568 ** t=y Alias for "t=s" with "unf" and "hist".
1569 ** raw Alias for "t=s" with "unf". Additionally, omit
1570 ** the border around the post, and ignore "t" and "hist".
1571 ** unf Show the original, unformatted source text.
1572 ** hist Show edit history in addition to current posts.
1573 */
1574 void forumpost_page(void){
1575 forumthread_page();
@@ -1581,11 +1582,12 @@
1582 ** The result is basically the same as /forumpost except that none of
1583 ** the postings in the thread are selected.
1584 **
1585 ** Query parameters:
1586 **
1587 ** name=NAME Required. The NAME value is a hash prefix or timestamp
1588 ** for one of the posts in the thread.
1589 ** t=a Automatic display mode, i.e. hierarchical for
1590 ** desktop and chronological for mobile. This is the
1591 ** default if the "t" query parameter is omitted.
1592 ** t=c Show posts in the order they were written.
1593 ** t=h Show posts using hierarchical indenting.
1594
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -283,19 +283,21 @@
283283
}
284284
return this;
285285
};
286286
287287
/**
288
- Sets the innerText of the page's TITLE tag to
289
- the given text and returns this object.
288
+ Sets the innerText of the page's TITLE tag to the given text and
289
+ returns this object. If passed a falsy value then the title is
290
+ reverted to its page-load-time value.
290291
*/
291
- F.page.setPageTitle = function(title){
292
+ F.page.setPageTitle = function f(title){
292293
const t = document.querySelector('title');
293
- if(t) t.innerText = title;
294
+ if(t) t.innerText = title || f.$orig;
294295
return this;
295296
};
296
-
297
+ F.onPageLoad(()=>F.page.setPageTitle.$orig
298
+ = document.querySelector('title').innerText);
297299
/**
298300
Returns a function, that, as long as it continues to be invoked,
299301
will not be triggered. The function will be called after it stops
300302
being called for N milliseconds. If `immediate` is passed, call
301303
the callback immediately and hinder future invocations until at
302304
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -283,19 +283,21 @@
283 }
284 return this;
285 };
286
287 /**
288 Sets the innerText of the page's TITLE tag to
289 the given text and returns this object.
 
290 */
291 F.page.setPageTitle = function(title){
292 const t = document.querySelector('title');
293 if(t) t.innerText = title;
294 return this;
295 };
296
 
297 /**
298 Returns a function, that, as long as it continues to be invoked,
299 will not be triggered. The function will be called after it stops
300 being called for N milliseconds. If `immediate` is passed, call
301 the callback immediately and hinder future invocations until at
302
--- src/fossil.bootstrap.js
+++ src/fossil.bootstrap.js
@@ -283,19 +283,21 @@
283 }
284 return this;
285 };
286
287 /**
288 Sets the innerText of the page's TITLE tag to the given text and
289 returns this object. If passed a falsy value then the title is
290 reverted to its page-load-time value.
291 */
292 F.page.setPageTitle = function f(title){
293 const t = document.querySelector('title');
294 if(t) t.innerText = title || f.$orig;
295 return this;
296 };
297 F.onPageLoad(()=>F.page.setPageTitle.$orig
298 = document.querySelector('title').innerText);
299 /**
300 Returns a function, that, as long as it continues to be invoked,
301 will not be triggered. The function will be called after it stops
302 being called for N milliseconds. If `immediate` is passed, call
303 the callback immediately and hinder future invocations until at
304
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -90,15 +90,14 @@
9090
While we're here, we also use this to cap the max-height
9191
of the input field so that pasting huge text does not scroll
9292
the upper area of the input widget off-screen. */
9393
const elemsToCount = GetFramingElements();
9494
const contentArea = E1('div.content');
95
- const bcl = document.body.classList;
9695
const resized = function f(){
9796
if(f.$disabled) return;
9897
const wh = window.innerHeight,
99
- com = bcl.contains('chat-only-mode');
98
+ com = document.body.classList.contains('chat-only-mode');
10099
var ht;
101100
var extra = 0;
102101
if(com){
103102
ht = wh;
104103
}else{
@@ -161,10 +160,11 @@
161160
searchContent: E1('#chat-search-content'),
162161
btnPreview: E1('#chat-button-preview'),
163162
views: document.querySelectorAll('.chat-view'),
164163
activeUserListWrapper: E1('#chat-user-list-wrapper'),
165164
activeUserList: E1('#chat-user-list'),
165
+ btnClearFilter: E1('#chat-clear-filter'),
166166
eMsgPollError: undefined /* current connection error MessageMidget */,
167167
pollErrorMarker: document.body /* element to toggle 'connection-error' CSS class on */
168168
},
169169
me: F.user.name,
170170
mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
@@ -181,15 +181,31 @@
181181
(JS Date object). Only messages received by the chat client
182182
are considered. */
183183
/* Reminder: to convert a Julian time J to JS:
184184
new Date((J - 2440587.5) * 86400000) */
185185
},
186
- filterState:{
187
- activeUser: undefined,
188
- match: function(uname){
189
- return this.activeUser===uname || !this.activeUser;
190
- }
186
+ filter: {
187
+ user:{
188
+ activeTag: undefined,
189
+ match: function(uname){
190
+ return !this.activeTag || this.activeTag===uname;
191
+ },
192
+ matchElem: function(e){
193
+ return !this.activeTag || this.activeTag===e.dataset.xfrom;
194
+ }
195
+ },
196
+ hashtag:{
197
+ activeTag: undefined,
198
+ match: function(tag){
199
+ return !this.activeTag || tag?.toLowerCase?.()===this.activeTag;
200
+ },
201
+ matchElem: function(e){
202
+ return !this.activeTag
203
+ || !!e.querySelector('[data-hashtag="'+this.activeTag+'"]');
204
+ }
205
+ },
206
+ current: undefined/*gets set to current active filter*/
191207
},
192208
/**
193209
The timer object is used to control connection throttling
194210
when connection errors arrise. It starts off with a polling
195211
delay of $initialDelay ms. If there's a connection error,
@@ -376,11 +392,12 @@
376392
the list. */
377393
injectMessageElem: function f(e, atEnd){
378394
const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
379395
holder = this.e.viewMessages,
380396
prevMessage = this.e.newestMessage;
381
- if(!this.filterState.match(e.dataset.xfrom)){
397
+ if(this.filter.current
398
+ && !this.filter.current.matchElem(e)){
382399
e.classList.add('hidden');
383400
}
384401
if(atEnd){
385402
const fe = mip.nextElementSibling;
386403
if(fe) mip.parentNode.insertBefore(e, fe);
@@ -622,11 +639,10 @@
622639
}
623640
this.e.views.forEach(function(E){
624641
if(e!==E) D.addClass(E,'hidden');
625642
});
626643
this.e.currentView = e;
627
- if(this.e.currentView.$beforeShow) this.e.currentView.$beforeShow();
628644
D.removeClass(e,'hidden');
629645
this.animate(this.e.currentView, 'anim-fade-in-fast');
630646
return this.e.currentView;
631647
},
632648
@@ -674,11 +690,11 @@
674690
else return 0;
675691
};
676692
callee.addUserElem = function(u){
677693
const uSpan = D.addClass(D.span(), 'chat-user');
678694
const uDate = self.usersLastSeen[u];
679
- if(self.filterState.activeUser===u){
695
+ if(self.filter.user.activeTag===u){
680696
uSpan.classList.add('selected');
681697
}
682698
uSpan.dataset.uname = u;
683699
D.append(uSpan, u, "\n",
684700
D.append(
@@ -696,11 +712,82 @@
696712
Object.keys(this.usersLastSeen).sort(
697713
callee.sortUsersSeen
698714
).forEach(callee.addUserElem);
699715
return this;
700716
},
701
- /** Show or hide the active user list. Returns this object. */
717
+ /**
718
+ For each Chat.MessageWidget element (X.message-widget) for
719
+ which predicate(elem) returns true, the 'hidden' class is
720
+ removed from that message. For all others, 'hidden' is
721
+ added. If predicate is falsy, 'hidden' is removed from all
722
+ elements. After filtering, it will try to scroll the last
723
+ not-filtered-out message into view, but exactly where it
724
+ scrolls into view (top, middle, button) is
725
+ unpredictable. Returns this object.
726
+
727
+ The argument may optionally be an object from this.filter,
728
+ in which case its matchElem() method becomes the predicate.
729
+
730
+ Note that this does not encapsulate certain filter-specific
731
+ logic which applies changes to elements other than the
732
+ main message list or this.e.btnClearFilter.
733
+ */
734
+ applyMessageFilter: function(predicate){
735
+ const self = this;
736
+ let eLast;
737
+ console.debug("applyMessageFilter(",predicate,")");
738
+ if(!predicate){
739
+ D.removeClass(this.e.viewMessages.querySelectorAll('.message-widget.hidden'),
740
+ 'hidden');
741
+ D.addClass(this.e.btnClearFilter, 'hidden');
742
+ }else if('function'!==typeof predicate
743
+ && predicate.matchElem){
744
+ /* assume Chat.filter object */
745
+ const p = predicate;
746
+ predicate = (e)=>p.matchElem(e);
747
+ }
748
+ if(predicate){
749
+ this.e.viewMessages.querySelectorAll('.message-widget').forEach(function(e){
750
+ if(predicate(e)){
751
+ e.classList.remove('hidden');
752
+ eLast = e;
753
+ }else{
754
+ e.classList.add('hidden');
755
+ }
756
+ });
757
+ D.removeClass(this.e.btnClearFilter, 'hidden');
758
+ }
759
+ this.setCurrentView(this.e.viewMessages);
760
+ if(eLast) eLast.scrollIntoView(false);
761
+ else this.scrollMessagesTo(1);
762
+ return this;
763
+ },
764
+ /**
765
+ Clears the current message filter, if any, and clears the
766
+ activeTag property of all members of this.filter. Returns
767
+ this object. This also unfortunately performs some
768
+ filter-type-specific logic which we have not yet managed to
769
+ encapsulate more cleanly.
770
+ */
771
+ clearFilters: function(){
772
+ if(!this.filter.current) return this;
773
+ this.filter.current = undefined;
774
+ this.applyMessageFilter(false);
775
+ const self = this;
776
+ Object.keys(this.filter).forEach(function(k){
777
+ const f = self.filter[k];
778
+ if(f) f.activeTag = undefined;
779
+ });
780
+ this.e.activeUserList.querySelectorAll('.chat-user').forEach(
781
+ /*Unfortante filter-specific logic*/
782
+ (e)=>e.classList.remove('selected')
783
+ );
784
+ return this;
785
+ },
786
+ /**
787
+ Show or hide the active user list. Returns this object.
788
+ */
702789
showActiveUserList: function(yes){
703790
if(0===arguments.length) yes = true;
704791
this.e.activeUserListWrapper.classList[
705792
yes ? 'remove' : 'add'
706793
]('hidden');
@@ -726,32 +813,38 @@
726813
/**
727814
Applies user name filter to all current messages, or clears
728815
the filter if uname is falsy.
729816
*/
730817
setUserFilter: function(uname){
731
- this.filterState.activeUser = uname;
732
- const mw = this.e.viewMessages.querySelectorAll('.message-widget');
818
+ if(!uname || (this.filter.current
819
+ && this.filter.current!==this.filter.user)){
820
+ this.clearFilters();
821
+ }
822
+ this.filter.user.activeTag = uname;
823
+ if(uname) this.applyMessageFilter(this.filter.user);
824
+ this.filter.current = uname ? this.filter.user : undefined;
733825
const self = this;
734
- let eLast;
735
- if(!uname){
736
- D.removeClass(Chat.e.viewMessages.querySelectorAll('.message-widget.hidden'),
737
- 'hidden');
738
- }else{
739
- mw.forEach(function(w){
740
- if(self.filterState.match(w.dataset.xfrom)){
741
- w.classList.remove('hidden');
742
- eLast = w;
743
- }else{
744
- w.classList.add('hidden');
745
- }
746
- });
747
- }
748
- if(eLast) eLast.scrollIntoView(false);
749
- else this.scrollMessagesTo(1);
750
- cs.e.activeUserList.querySelectorAll('.chat-user').forEach(function(e){
751
- e.classList[uname===e.dataset.uname ? 'add' : 'remove']('selected');
752
- });
826
+ this.e.activeUserList.querySelectorAll('.chat-user').forEach(function(e){
827
+ e.classList[
828
+ self.filter.user.activeTag===e.dataset.uname
829
+ ? 'add' : 'remove'
830
+ ]('selected');
831
+ });
832
+ return this;
833
+ },
834
+ /**
835
+ Applies a hashtag filter to all current messages, or clears
836
+ the filter if tag is falsy.
837
+ */
838
+ setHashtagFilter: function(tag){
839
+ if(!tag || (this.filter.current
840
+ && this.filter.current!==this.filter.hashtag)){
841
+ this.clearFilters();
842
+ }
843
+ this.filter.hashtag.activeTag = tag.toLowerCase();
844
+ if(tag) this.applyMessageFilter(this.filter.hashtag);
845
+ this.filter.current = tag ? this.filter.hashtag : undefined;
753846
return this;
754847
},
755848
756849
/**
757850
If animations are enabled, passes its arguments
@@ -1073,22 +1166,106 @@
10731166
cs.setCurrentView(cs.e.viewMessages);
10741167
if(eUser.classList.contains('selected')){
10751168
/* If currently selected, toggle filter off */
10761169
eUser.classList.remove('selected');
10771170
cs.setUserFilter(false);
1078
- delete f.$eSelected;
10791171
}else{
1080
- if(f.$eSelected) f.$eSelected.classList.remove('selected');
1081
- f.$eSelected = eUser;
10821172
eUser.classList.add('selected');
10831173
cs.setUserFilter(uname);
10841174
}
10851175
return false;
10861176
}, false);
1177
+
1178
+ cs.e.btnClearFilter.addEventListener('click',function(){
1179
+ D.addClass(this,'hidden');
1180
+ cs.clearFilters();
1181
+ }, false);
10871182
return cs;
10881183
})()/*Chat initialization*/;
10891184
1185
+ /**
1186
+ An experiment in history navigation: when a message numtag is
1187
+ clicked, we push the origin message onto the history and
1188
+ set up the back button to return to that message.
1189
+ */
1190
+ window.onpopstate = function(event){
1191
+ const msgid = Chat.numtagHistoryStack.pop();
1192
+ if(msgid){
1193
+ const e = Chat.setCurrentView(Chat.e.viewMessages).
1194
+ querySelector('.message-widget[data-msgid="'+msgid+'"]');
1195
+ //console.debug("Popping history back to",msgid, e);
1196
+ if(e){
1197
+ Chat.MessageWidget.scrollToMessageElem(e);
1198
+ return;
1199
+ }
1200
+ }
1201
+ Chat.scrollMessagesTo(1);
1202
+ };
1203
+ Chat.numtagHistoryStack = [
1204
+ /* Relying on the pushHistory() state object for holding
1205
+ the message ID is completely misbehaving, not giving
1206
+ us the expected state object when window.onpopstate
1207
+ is triggered (plus, the browser persists it, which
1208
+ introduces its own problems). Thus we use our own
1209
+ stack of message IDs for history navigation purposes. */];
1210
+
1211
+ /** If e or one of its parents has the given CSS class, that element
1212
+ is returned, else falsy is returned. */
1213
+ const findParentWithClass = function(e, className){
1214
+ while(e && !e.classList.contains(className)){
1215
+ e = e.parentNode;
1216
+ }
1217
+ return e;
1218
+ };
1219
+
1220
+ /** To be passed each MessageWidget's top-level DOM element
1221
+ after initial processing of the message, to set up
1222
+ hashtag and numtag references. */
1223
+ const setupHashtags = function f(elem){
1224
+ if(!f.$clickTag){
1225
+ f.$clickTag = function(ev){
1226
+ /* Click handler for hashtags */
1227
+ const tag = ev.target.dataset.hashtag;
1228
+ if(tag){
1229
+ Chat.setHashtagFilter(
1230
+ tag===Chat.filter.hashtag.activeTag
1231
+ ? false : tag
1232
+ );
1233
+ }
1234
+ };
1235
+ f.$clickNum = function(ev){
1236
+ /* Click handler for #NNN references */
1237
+ const tag = ev.target.dataset.numtag;
1238
+ if(tag){
1239
+ const e = Chat.e.viewMessages.querySelector(
1240
+ '.message-widget[data-msgid="'+tag+'"]'
1241
+ );
1242
+ if(e){
1243
+ Chat.MessageWidget.scrollToMessageElem(e);
1244
+ //Set up window.history() state...
1245
+ const p = 0 ? false : findParentWithClass(ev.target, 'message-widget');
1246
+ if(p){
1247
+ const state = {msgId: p.dataset.msgid};
1248
+ Chat.numtagHistoryStack.push(p.dataset.msgid);
1249
+ const rc = window.history.pushState(state, "");
1250
+ //console.debug("Pushing history for msgid", state);
1251
+ //console.debug("Chat.numtagHistoryStack =",Chat.numtagHistoryStack);
1252
+ }
1253
+ }else{
1254
+ Chat.submitSearch('#'+tag);
1255
+ }
1256
+ }
1257
+ };
1258
+ }
1259
+ elem.querySelectorAll('[data-hashtag]').forEach(function(e){
1260
+ e.dataset.hashtag = e.dataset.hashtag.toLowerCase();
1261
+ e.addEventListener('click', f.$clickTag, false);
1262
+ })
1263
+ elem.querySelectorAll('[data-numtag]').forEach(
1264
+ (e)=>e.addEventListener('click', f.$clickNum, false)
1265
+ )
1266
+ }/*setupHashtags()*/;
10901267
10911268
/** Returns the first .message-widget element in DOM element
10921269
e's lineage. */
10931270
const findMessageWidgetParent = function(e){
10941271
while( e && !e.classList.contains('message-widget')){
@@ -1354,10 +1531,11 @@
13541531
// Used by Chat.reportErrorAsMessage()
13551532
D.append(contentTarget, m.xmsg);
13561533
}else{
13571534
contentTarget.innerHTML = m.xmsg;
13581535
contentTarget.querySelectorAll('a').forEach(addAnchorTargetBlank);
1536
+ setupHashtags(contentTarget);
13591537
if(F.pikchr){
13601538
F.pikchr.addSrcView(contentTarget.querySelectorAll('svg.pikchr'));
13611539
}
13621540
}
13631541
}
@@ -1470,31 +1648,23 @@
14701648
y: 'a'
14711649
}), "User's Timeline"),
14721650
'target', '_blank'
14731651
);
14741652
D.append(toolbar2, timelineLink);
1475
- if(Chat.filterState.activeUser &&
1476
- Chat.filterState.match(eMsg.dataset.xfrom)){
1477
- /* Add a button to clear user filter and jump to
1653
+ if(Chat.filter.current){
1654
+ /* Add a button to clear filter and jump to
14781655
this message in its original context. */
14791656
D.append(
14801657
this.e,
14811658
D.append(
14821659
D.addClass(D.div(), 'toolbar'),
14831660
D.button(
14841661
"Message in context",
14851662
function(){
14861663
self.hide();
1487
- Chat.setUserFilter(false);
1488
- eMsg.scrollIntoView(false);
1489
- Chat.animate(
1490
- eMsg.firstElementChild, 'anim-flip-h'
1491
- //eMsg.firstElementChild, 'anim-flip-v'
1492
- //eMsg.childNodes, 'anim-rotate-360'
1493
- //eMsg.childNodes, 'anim-flip-v'
1494
- //eMsg, 'anim-flip-v'
1495
- );
1664
+ Chat.clearFilters();
1665
+ Chat.MessageWidget.scrollToMessageElem(eMsg);
14961666
})
14971667
)
14981668
);
14991669
}/*jump-to button*/
15001670
}
@@ -1524,10 +1694,20 @@
15241694
}/*f.popup*/;
15251695
}/*end static init*/
15261696
const theMsg = findMessageWidgetParent(ev.target);
15271697
if(theMsg) f.popup.show(theMsg);
15281698
}/*_handleLegendClicked()*/
1699
+ }/*MessageWidget.prototype*/;
1700
+ /** Assumes that e is a MessageWidget element, ensures that
1701
+ Chat.e.viewMessages is visible, scrolls the message,
1702
+ and animates it a bit to make it more visible. */
1703
+ ctor.scrollToMessageElem = function(e){
1704
+ if(e.firstElementChild){
1705
+ Chat.setCurrentView(Chat.e.viewMessages);
1706
+ e.scrollIntoView(false);
1707
+ Chat.animate(e, 'anim-fade-out-in');
1708
+ }
15291709
};
15301710
return ctor;
15311711
})()/*MessageWidget*/;
15321712
15331713
/**
@@ -2067,11 +2247,12 @@
20672247
}/*namedOptions.activeUsers additional setup*/
20682248
/* Settings menu entries... they are presented in the order listed
20692249
here, so the most frequently-needed ones "should" (arguably) be
20702250
closer to the start of this list. */
20712251
/**
2072
- Settings ops structure:
2252
+ Settings options structure: an array of Objects with the
2253
+ following properties:
20732254
20742255
label: string for the UI
20752256
20762257
boolValue: string (name of Chat.settings setting) or a function
20772258
which returns true or false. If it is a string, it gets
@@ -2080,10 +2261,11 @@
20802261
to the persistentSetting property of this object.
20812262
20822263
select: SELECT element (instead of boolValue)
20832264
20842265
callback: optional handler to call after setting is modified.
2266
+ It gets passed the setting object: {key:string, value:something}.
20852267
Its "this" is the options object. If this object has a
20862268
boolValue string or a persistentSetting property, the argument
20872269
passed to the callback is a settings object in the form {key:K,
20882270
value:V}. If this object does not have boolValue string or
20892271
persistentSetting then the callback is passed an event object
@@ -2410,10 +2592,11 @@
24102592
const btnPreview = Chat.e.btnPreview;
24112593
Chat.setPreviewText = function(t){
24122594
this.setCurrentView(this.e.viewPreview);
24132595
this.e.previewContent.innerHTML = t;
24142596
this.e.viewPreview.querySelectorAll('a').forEach(addAnchorTargetBlank);
2597
+ setupHashtags(this.e.previewContent)/*arguable, for usability reasons*/;
24152598
this.inputFocus();
24162599
};
24172600
Chat.e.viewPreview.querySelector('button.action-close').
24182601
addEventListener('click', ()=>Chat.setCurrentView(Chat.e.viewMessages), false);
24192602
let previewPending = false;
@@ -2652,15 +2835,16 @@
26522835
}
26532836
return e;
26542837
};
26552838
Chat.clearSearch(true);
26562839
/**
2657
- Submits a history search using the main input field's current
2658
- text. It is assumed that Chat.e.viewSearch===Chat.e.currentView.
2840
+ Submits a history search using either its argument or the the
2841
+ main input field's current text.
26592842
*/
2660
- Chat.submitSearch = function(){
2661
- const term = this.inputValue(true);
2843
+ Chat.submitSearch = function(term){
2844
+ Chat.setCurrentView(Chat.e.viewSearch);
2845
+ if(!arguments.length) term = this.inputValue(true);
26622846
const eMsgTgt = this.clearSearch(true);
26632847
if( !term ) return;
26642848
D.append( eMsgTgt, "Searching for ",term," ...");
26652849
const fd = new FormData();
26662850
fd.set('q', term);
26672851
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -90,15 +90,14 @@
90 While we're here, we also use this to cap the max-height
91 of the input field so that pasting huge text does not scroll
92 the upper area of the input widget off-screen. */
93 const elemsToCount = GetFramingElements();
94 const contentArea = E1('div.content');
95 const bcl = document.body.classList;
96 const resized = function f(){
97 if(f.$disabled) return;
98 const wh = window.innerHeight,
99 com = bcl.contains('chat-only-mode');
100 var ht;
101 var extra = 0;
102 if(com){
103 ht = wh;
104 }else{
@@ -161,10 +160,11 @@
161 searchContent: E1('#chat-search-content'),
162 btnPreview: E1('#chat-button-preview'),
163 views: document.querySelectorAll('.chat-view'),
164 activeUserListWrapper: E1('#chat-user-list-wrapper'),
165 activeUserList: E1('#chat-user-list'),
 
166 eMsgPollError: undefined /* current connection error MessageMidget */,
167 pollErrorMarker: document.body /* element to toggle 'connection-error' CSS class on */
168 },
169 me: F.user.name,
170 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
@@ -181,15 +181,31 @@
181 (JS Date object). Only messages received by the chat client
182 are considered. */
183 /* Reminder: to convert a Julian time J to JS:
184 new Date((J - 2440587.5) * 86400000) */
185 },
186 filterState:{
187 activeUser: undefined,
188 match: function(uname){
189 return this.activeUser===uname || !this.activeUser;
190 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191 },
192 /**
193 The timer object is used to control connection throttling
194 when connection errors arrise. It starts off with a polling
195 delay of $initialDelay ms. If there's a connection error,
@@ -376,11 +392,12 @@
376 the list. */
377 injectMessageElem: function f(e, atEnd){
378 const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
379 holder = this.e.viewMessages,
380 prevMessage = this.e.newestMessage;
381 if(!this.filterState.match(e.dataset.xfrom)){
 
382 e.classList.add('hidden');
383 }
384 if(atEnd){
385 const fe = mip.nextElementSibling;
386 if(fe) mip.parentNode.insertBefore(e, fe);
@@ -622,11 +639,10 @@
622 }
623 this.e.views.forEach(function(E){
624 if(e!==E) D.addClass(E,'hidden');
625 });
626 this.e.currentView = e;
627 if(this.e.currentView.$beforeShow) this.e.currentView.$beforeShow();
628 D.removeClass(e,'hidden');
629 this.animate(this.e.currentView, 'anim-fade-in-fast');
630 return this.e.currentView;
631 },
632
@@ -674,11 +690,11 @@
674 else return 0;
675 };
676 callee.addUserElem = function(u){
677 const uSpan = D.addClass(D.span(), 'chat-user');
678 const uDate = self.usersLastSeen[u];
679 if(self.filterState.activeUser===u){
680 uSpan.classList.add('selected');
681 }
682 uSpan.dataset.uname = u;
683 D.append(uSpan, u, "\n",
684 D.append(
@@ -696,11 +712,82 @@
696 Object.keys(this.usersLastSeen).sort(
697 callee.sortUsersSeen
698 ).forEach(callee.addUserElem);
699 return this;
700 },
701 /** Show or hide the active user list. Returns this object. */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
702 showActiveUserList: function(yes){
703 if(0===arguments.length) yes = true;
704 this.e.activeUserListWrapper.classList[
705 yes ? 'remove' : 'add'
706 ]('hidden');
@@ -726,32 +813,38 @@
726 /**
727 Applies user name filter to all current messages, or clears
728 the filter if uname is falsy.
729 */
730 setUserFilter: function(uname){
731 this.filterState.activeUser = uname;
732 const mw = this.e.viewMessages.querySelectorAll('.message-widget');
 
 
 
 
 
733 const self = this;
734 let eLast;
735 if(!uname){
736 D.removeClass(Chat.e.viewMessages.querySelectorAll('.message-widget.hidden'),
737 'hidden');
738 }else{
739 mw.forEach(function(w){
740 if(self.filterState.match(w.dataset.xfrom)){
741 w.classList.remove('hidden');
742 eLast = w;
743 }else{
744 w.classList.add('hidden');
745 }
746 });
747 }
748 if(eLast) eLast.scrollIntoView(false);
749 else this.scrollMessagesTo(1);
750 cs.e.activeUserList.querySelectorAll('.chat-user').forEach(function(e){
751 e.classList[uname===e.dataset.uname ? 'add' : 'remove']('selected');
752 });
 
753 return this;
754 },
755
756 /**
757 If animations are enabled, passes its arguments
@@ -1073,22 +1166,106 @@
1073 cs.setCurrentView(cs.e.viewMessages);
1074 if(eUser.classList.contains('selected')){
1075 /* If currently selected, toggle filter off */
1076 eUser.classList.remove('selected');
1077 cs.setUserFilter(false);
1078 delete f.$eSelected;
1079 }else{
1080 if(f.$eSelected) f.$eSelected.classList.remove('selected');
1081 f.$eSelected = eUser;
1082 eUser.classList.add('selected');
1083 cs.setUserFilter(uname);
1084 }
1085 return false;
1086 }, false);
 
 
 
 
 
1087 return cs;
1088 })()/*Chat initialization*/;
1089
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1090
1091 /** Returns the first .message-widget element in DOM element
1092 e's lineage. */
1093 const findMessageWidgetParent = function(e){
1094 while( e && !e.classList.contains('message-widget')){
@@ -1354,10 +1531,11 @@
1354 // Used by Chat.reportErrorAsMessage()
1355 D.append(contentTarget, m.xmsg);
1356 }else{
1357 contentTarget.innerHTML = m.xmsg;
1358 contentTarget.querySelectorAll('a').forEach(addAnchorTargetBlank);
 
1359 if(F.pikchr){
1360 F.pikchr.addSrcView(contentTarget.querySelectorAll('svg.pikchr'));
1361 }
1362 }
1363 }
@@ -1470,31 +1648,23 @@
1470 y: 'a'
1471 }), "User's Timeline"),
1472 'target', '_blank'
1473 );
1474 D.append(toolbar2, timelineLink);
1475 if(Chat.filterState.activeUser &&
1476 Chat.filterState.match(eMsg.dataset.xfrom)){
1477 /* Add a button to clear user filter and jump to
1478 this message in its original context. */
1479 D.append(
1480 this.e,
1481 D.append(
1482 D.addClass(D.div(), 'toolbar'),
1483 D.button(
1484 "Message in context",
1485 function(){
1486 self.hide();
1487 Chat.setUserFilter(false);
1488 eMsg.scrollIntoView(false);
1489 Chat.animate(
1490 eMsg.firstElementChild, 'anim-flip-h'
1491 //eMsg.firstElementChild, 'anim-flip-v'
1492 //eMsg.childNodes, 'anim-rotate-360'
1493 //eMsg.childNodes, 'anim-flip-v'
1494 //eMsg, 'anim-flip-v'
1495 );
1496 })
1497 )
1498 );
1499 }/*jump-to button*/
1500 }
@@ -1524,10 +1694,20 @@
1524 }/*f.popup*/;
1525 }/*end static init*/
1526 const theMsg = findMessageWidgetParent(ev.target);
1527 if(theMsg) f.popup.show(theMsg);
1528 }/*_handleLegendClicked()*/
 
 
 
 
 
 
 
 
 
 
1529 };
1530 return ctor;
1531 })()/*MessageWidget*/;
1532
1533 /**
@@ -2067,11 +2247,12 @@
2067 }/*namedOptions.activeUsers additional setup*/
2068 /* Settings menu entries... they are presented in the order listed
2069 here, so the most frequently-needed ones "should" (arguably) be
2070 closer to the start of this list. */
2071 /**
2072 Settings ops structure:
 
2073
2074 label: string for the UI
2075
2076 boolValue: string (name of Chat.settings setting) or a function
2077 which returns true or false. If it is a string, it gets
@@ -2080,10 +2261,11 @@
2080 to the persistentSetting property of this object.
2081
2082 select: SELECT element (instead of boolValue)
2083
2084 callback: optional handler to call after setting is modified.
 
2085 Its "this" is the options object. If this object has a
2086 boolValue string or a persistentSetting property, the argument
2087 passed to the callback is a settings object in the form {key:K,
2088 value:V}. If this object does not have boolValue string or
2089 persistentSetting then the callback is passed an event object
@@ -2410,10 +2592,11 @@
2410 const btnPreview = Chat.e.btnPreview;
2411 Chat.setPreviewText = function(t){
2412 this.setCurrentView(this.e.viewPreview);
2413 this.e.previewContent.innerHTML = t;
2414 this.e.viewPreview.querySelectorAll('a').forEach(addAnchorTargetBlank);
 
2415 this.inputFocus();
2416 };
2417 Chat.e.viewPreview.querySelector('button.action-close').
2418 addEventListener('click', ()=>Chat.setCurrentView(Chat.e.viewMessages), false);
2419 let previewPending = false;
@@ -2652,15 +2835,16 @@
2652 }
2653 return e;
2654 };
2655 Chat.clearSearch(true);
2656 /**
2657 Submits a history search using the main input field's current
2658 text. It is assumed that Chat.e.viewSearch===Chat.e.currentView.
2659 */
2660 Chat.submitSearch = function(){
2661 const term = this.inputValue(true);
 
2662 const eMsgTgt = this.clearSearch(true);
2663 if( !term ) return;
2664 D.append( eMsgTgt, "Searching for ",term," ...");
2665 const fd = new FormData();
2666 fd.set('q', term);
2667
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -90,15 +90,14 @@
90 While we're here, we also use this to cap the max-height
91 of the input field so that pasting huge text does not scroll
92 the upper area of the input widget off-screen. */
93 const elemsToCount = GetFramingElements();
94 const contentArea = E1('div.content');
 
95 const resized = function f(){
96 if(f.$disabled) return;
97 const wh = window.innerHeight,
98 com = document.body.classList.contains('chat-only-mode');
99 var ht;
100 var extra = 0;
101 if(com){
102 ht = wh;
103 }else{
@@ -161,10 +160,11 @@
160 searchContent: E1('#chat-search-content'),
161 btnPreview: E1('#chat-button-preview'),
162 views: document.querySelectorAll('.chat-view'),
163 activeUserListWrapper: E1('#chat-user-list-wrapper'),
164 activeUserList: E1('#chat-user-list'),
165 btnClearFilter: E1('#chat-clear-filter'),
166 eMsgPollError: undefined /* current connection error MessageMidget */,
167 pollErrorMarker: document.body /* element to toggle 'connection-error' CSS class on */
168 },
169 me: F.user.name,
170 mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
@@ -181,15 +181,31 @@
181 (JS Date object). Only messages received by the chat client
182 are considered. */
183 /* Reminder: to convert a Julian time J to JS:
184 new Date((J - 2440587.5) * 86400000) */
185 },
186 filter: {
187 user:{
188 activeTag: undefined,
189 match: function(uname){
190 return !this.activeTag || this.activeTag===uname;
191 },
192 matchElem: function(e){
193 return !this.activeTag || this.activeTag===e.dataset.xfrom;
194 }
195 },
196 hashtag:{
197 activeTag: undefined,
198 match: function(tag){
199 return !this.activeTag || tag?.toLowerCase?.()===this.activeTag;
200 },
201 matchElem: function(e){
202 return !this.activeTag
203 || !!e.querySelector('[data-hashtag="'+this.activeTag+'"]');
204 }
205 },
206 current: undefined/*gets set to current active filter*/
207 },
208 /**
209 The timer object is used to control connection throttling
210 when connection errors arrise. It starts off with a polling
211 delay of $initialDelay ms. If there's a connection error,
@@ -376,11 +392,12 @@
392 the list. */
393 injectMessageElem: function f(e, atEnd){
394 const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint,
395 holder = this.e.viewMessages,
396 prevMessage = this.e.newestMessage;
397 if(this.filter.current
398 && !this.filter.current.matchElem(e)){
399 e.classList.add('hidden');
400 }
401 if(atEnd){
402 const fe = mip.nextElementSibling;
403 if(fe) mip.parentNode.insertBefore(e, fe);
@@ -622,11 +639,10 @@
639 }
640 this.e.views.forEach(function(E){
641 if(e!==E) D.addClass(E,'hidden');
642 });
643 this.e.currentView = e;
 
644 D.removeClass(e,'hidden');
645 this.animate(this.e.currentView, 'anim-fade-in-fast');
646 return this.e.currentView;
647 },
648
@@ -674,11 +690,11 @@
690 else return 0;
691 };
692 callee.addUserElem = function(u){
693 const uSpan = D.addClass(D.span(), 'chat-user');
694 const uDate = self.usersLastSeen[u];
695 if(self.filter.user.activeTag===u){
696 uSpan.classList.add('selected');
697 }
698 uSpan.dataset.uname = u;
699 D.append(uSpan, u, "\n",
700 D.append(
@@ -696,11 +712,82 @@
712 Object.keys(this.usersLastSeen).sort(
713 callee.sortUsersSeen
714 ).forEach(callee.addUserElem);
715 return this;
716 },
717 /**
718 For each Chat.MessageWidget element (X.message-widget) for
719 which predicate(elem) returns true, the 'hidden' class is
720 removed from that message. For all others, 'hidden' is
721 added. If predicate is falsy, 'hidden' is removed from all
722 elements. After filtering, it will try to scroll the last
723 not-filtered-out message into view, but exactly where it
724 scrolls into view (top, middle, button) is
725 unpredictable. Returns this object.
726
727 The argument may optionally be an object from this.filter,
728 in which case its matchElem() method becomes the predicate.
729
730 Note that this does not encapsulate certain filter-specific
731 logic which applies changes to elements other than the
732 main message list or this.e.btnClearFilter.
733 */
734 applyMessageFilter: function(predicate){
735 const self = this;
736 let eLast;
737 console.debug("applyMessageFilter(",predicate,")");
738 if(!predicate){
739 D.removeClass(this.e.viewMessages.querySelectorAll('.message-widget.hidden'),
740 'hidden');
741 D.addClass(this.e.btnClearFilter, 'hidden');
742 }else if('function'!==typeof predicate
743 && predicate.matchElem){
744 /* assume Chat.filter object */
745 const p = predicate;
746 predicate = (e)=>p.matchElem(e);
747 }
748 if(predicate){
749 this.e.viewMessages.querySelectorAll('.message-widget').forEach(function(e){
750 if(predicate(e)){
751 e.classList.remove('hidden');
752 eLast = e;
753 }else{
754 e.classList.add('hidden');
755 }
756 });
757 D.removeClass(this.e.btnClearFilter, 'hidden');
758 }
759 this.setCurrentView(this.e.viewMessages);
760 if(eLast) eLast.scrollIntoView(false);
761 else this.scrollMessagesTo(1);
762 return this;
763 },
764 /**
765 Clears the current message filter, if any, and clears the
766 activeTag property of all members of this.filter. Returns
767 this object. This also unfortunately performs some
768 filter-type-specific logic which we have not yet managed to
769 encapsulate more cleanly.
770 */
771 clearFilters: function(){
772 if(!this.filter.current) return this;
773 this.filter.current = undefined;
774 this.applyMessageFilter(false);
775 const self = this;
776 Object.keys(this.filter).forEach(function(k){
777 const f = self.filter[k];
778 if(f) f.activeTag = undefined;
779 });
780 this.e.activeUserList.querySelectorAll('.chat-user').forEach(
781 /*Unfortante filter-specific logic*/
782 (e)=>e.classList.remove('selected')
783 );
784 return this;
785 },
786 /**
787 Show or hide the active user list. Returns this object.
788 */
789 showActiveUserList: function(yes){
790 if(0===arguments.length) yes = true;
791 this.e.activeUserListWrapper.classList[
792 yes ? 'remove' : 'add'
793 ]('hidden');
@@ -726,32 +813,38 @@
813 /**
814 Applies user name filter to all current messages, or clears
815 the filter if uname is falsy.
816 */
817 setUserFilter: function(uname){
818 if(!uname || (this.filter.current
819 && this.filter.current!==this.filter.user)){
820 this.clearFilters();
821 }
822 this.filter.user.activeTag = uname;
823 if(uname) this.applyMessageFilter(this.filter.user);
824 this.filter.current = uname ? this.filter.user : undefined;
825 const self = this;
826 this.e.activeUserList.querySelectorAll('.chat-user').forEach(function(e){
827 e.classList[
828 self.filter.user.activeTag===e.dataset.uname
829 ? 'add' : 'remove'
830 ]('selected');
831 });
832 return this;
833 },
834 /**
835 Applies a hashtag filter to all current messages, or clears
836 the filter if tag is falsy.
837 */
838 setHashtagFilter: function(tag){
839 if(!tag || (this.filter.current
840 && this.filter.current!==this.filter.hashtag)){
841 this.clearFilters();
842 }
843 this.filter.hashtag.activeTag = tag.toLowerCase();
844 if(tag) this.applyMessageFilter(this.filter.hashtag);
845 this.filter.current = tag ? this.filter.hashtag : undefined;
846 return this;
847 },
848
849 /**
850 If animations are enabled, passes its arguments
@@ -1073,22 +1166,106 @@
1166 cs.setCurrentView(cs.e.viewMessages);
1167 if(eUser.classList.contains('selected')){
1168 /* If currently selected, toggle filter off */
1169 eUser.classList.remove('selected');
1170 cs.setUserFilter(false);
 
1171 }else{
 
 
1172 eUser.classList.add('selected');
1173 cs.setUserFilter(uname);
1174 }
1175 return false;
1176 }, false);
1177
1178 cs.e.btnClearFilter.addEventListener('click',function(){
1179 D.addClass(this,'hidden');
1180 cs.clearFilters();
1181 }, false);
1182 return cs;
1183 })()/*Chat initialization*/;
1184
1185 /**
1186 An experiment in history navigation: when a message numtag is
1187 clicked, we push the origin message onto the history and
1188 set up the back button to return to that message.
1189 */
1190 window.onpopstate = function(event){
1191 const msgid = Chat.numtagHistoryStack.pop();
1192 if(msgid){
1193 const e = Chat.setCurrentView(Chat.e.viewMessages).
1194 querySelector('.message-widget[data-msgid="'+msgid+'"]');
1195 //console.debug("Popping history back to",msgid, e);
1196 if(e){
1197 Chat.MessageWidget.scrollToMessageElem(e);
1198 return;
1199 }
1200 }
1201 Chat.scrollMessagesTo(1);
1202 };
1203 Chat.numtagHistoryStack = [
1204 /* Relying on the pushHistory() state object for holding
1205 the message ID is completely misbehaving, not giving
1206 us the expected state object when window.onpopstate
1207 is triggered (plus, the browser persists it, which
1208 introduces its own problems). Thus we use our own
1209 stack of message IDs for history navigation purposes. */];
1210
1211 /** If e or one of its parents has the given CSS class, that element
1212 is returned, else falsy is returned. */
1213 const findParentWithClass = function(e, className){
1214 while(e && !e.classList.contains(className)){
1215 e = e.parentNode;
1216 }
1217 return e;
1218 };
1219
1220 /** To be passed each MessageWidget's top-level DOM element
1221 after initial processing of the message, to set up
1222 hashtag and numtag references. */
1223 const setupHashtags = function f(elem){
1224 if(!f.$clickTag){
1225 f.$clickTag = function(ev){
1226 /* Click handler for hashtags */
1227 const tag = ev.target.dataset.hashtag;
1228 if(tag){
1229 Chat.setHashtagFilter(
1230 tag===Chat.filter.hashtag.activeTag
1231 ? false : tag
1232 );
1233 }
1234 };
1235 f.$clickNum = function(ev){
1236 /* Click handler for #NNN references */
1237 const tag = ev.target.dataset.numtag;
1238 if(tag){
1239 const e = Chat.e.viewMessages.querySelector(
1240 '.message-widget[data-msgid="'+tag+'"]'
1241 );
1242 if(e){
1243 Chat.MessageWidget.scrollToMessageElem(e);
1244 //Set up window.history() state...
1245 const p = 0 ? false : findParentWithClass(ev.target, 'message-widget');
1246 if(p){
1247 const state = {msgId: p.dataset.msgid};
1248 Chat.numtagHistoryStack.push(p.dataset.msgid);
1249 const rc = window.history.pushState(state, "");
1250 //console.debug("Pushing history for msgid", state);
1251 //console.debug("Chat.numtagHistoryStack =",Chat.numtagHistoryStack);
1252 }
1253 }else{
1254 Chat.submitSearch('#'+tag);
1255 }
1256 }
1257 };
1258 }
1259 elem.querySelectorAll('[data-hashtag]').forEach(function(e){
1260 e.dataset.hashtag = e.dataset.hashtag.toLowerCase();
1261 e.addEventListener('click', f.$clickTag, false);
1262 })
1263 elem.querySelectorAll('[data-numtag]').forEach(
1264 (e)=>e.addEventListener('click', f.$clickNum, false)
1265 )
1266 }/*setupHashtags()*/;
1267
1268 /** Returns the first .message-widget element in DOM element
1269 e's lineage. */
1270 const findMessageWidgetParent = function(e){
1271 while( e && !e.classList.contains('message-widget')){
@@ -1354,10 +1531,11 @@
1531 // Used by Chat.reportErrorAsMessage()
1532 D.append(contentTarget, m.xmsg);
1533 }else{
1534 contentTarget.innerHTML = m.xmsg;
1535 contentTarget.querySelectorAll('a').forEach(addAnchorTargetBlank);
1536 setupHashtags(contentTarget);
1537 if(F.pikchr){
1538 F.pikchr.addSrcView(contentTarget.querySelectorAll('svg.pikchr'));
1539 }
1540 }
1541 }
@@ -1470,31 +1648,23 @@
1648 y: 'a'
1649 }), "User's Timeline"),
1650 'target', '_blank'
1651 );
1652 D.append(toolbar2, timelineLink);
1653 if(Chat.filter.current){
1654 /* Add a button to clear filter and jump to
 
1655 this message in its original context. */
1656 D.append(
1657 this.e,
1658 D.append(
1659 D.addClass(D.div(), 'toolbar'),
1660 D.button(
1661 "Message in context",
1662 function(){
1663 self.hide();
1664 Chat.clearFilters();
1665 Chat.MessageWidget.scrollToMessageElem(eMsg);
 
 
 
 
 
 
 
1666 })
1667 )
1668 );
1669 }/*jump-to button*/
1670 }
@@ -1524,10 +1694,20 @@
1694 }/*f.popup*/;
1695 }/*end static init*/
1696 const theMsg = findMessageWidgetParent(ev.target);
1697 if(theMsg) f.popup.show(theMsg);
1698 }/*_handleLegendClicked()*/
1699 }/*MessageWidget.prototype*/;
1700 /** Assumes that e is a MessageWidget element, ensures that
1701 Chat.e.viewMessages is visible, scrolls the message,
1702 and animates it a bit to make it more visible. */
1703 ctor.scrollToMessageElem = function(e){
1704 if(e.firstElementChild){
1705 Chat.setCurrentView(Chat.e.viewMessages);
1706 e.scrollIntoView(false);
1707 Chat.animate(e, 'anim-fade-out-in');
1708 }
1709 };
1710 return ctor;
1711 })()/*MessageWidget*/;
1712
1713 /**
@@ -2067,11 +2247,12 @@
2247 }/*namedOptions.activeUsers additional setup*/
2248 /* Settings menu entries... they are presented in the order listed
2249 here, so the most frequently-needed ones "should" (arguably) be
2250 closer to the start of this list. */
2251 /**
2252 Settings options structure: an array of Objects with the
2253 following properties:
2254
2255 label: string for the UI
2256
2257 boolValue: string (name of Chat.settings setting) or a function
2258 which returns true or false. If it is a string, it gets
@@ -2080,10 +2261,11 @@
2261 to the persistentSetting property of this object.
2262
2263 select: SELECT element (instead of boolValue)
2264
2265 callback: optional handler to call after setting is modified.
2266 It gets passed the setting object: {key:string, value:something}.
2267 Its "this" is the options object. If this object has a
2268 boolValue string or a persistentSetting property, the argument
2269 passed to the callback is a settings object in the form {key:K,
2270 value:V}. If this object does not have boolValue string or
2271 persistentSetting then the callback is passed an event object
@@ -2410,10 +2592,11 @@
2592 const btnPreview = Chat.e.btnPreview;
2593 Chat.setPreviewText = function(t){
2594 this.setCurrentView(this.e.viewPreview);
2595 this.e.previewContent.innerHTML = t;
2596 this.e.viewPreview.querySelectorAll('a').forEach(addAnchorTargetBlank);
2597 setupHashtags(this.e.previewContent)/*arguable, for usability reasons*/;
2598 this.inputFocus();
2599 };
2600 Chat.e.viewPreview.querySelector('button.action-close').
2601 addEventListener('click', ()=>Chat.setCurrentView(Chat.e.viewMessages), false);
2602 let previewPending = false;
@@ -2652,15 +2835,16 @@
2835 }
2836 return e;
2837 };
2838 Chat.clearSearch(true);
2839 /**
2840 Submits a history search using either its argument or the the
2841 main input field's current text.
2842 */
2843 Chat.submitSearch = function(term){
2844 Chat.setCurrentView(Chat.e.viewSearch);
2845 if(!arguments.length) term = this.inputValue(true);
2846 const eMsgTgt = this.clearSearch(true);
2847 if( !term ) return;
2848 D.append( eMsgTgt, "Searching for ",term," ...");
2849 const fd = new FormData();
2850 fd.set('q', term);
2851
+11 -9
--- src/info.c
+++ src/info.c
@@ -984,19 +984,21 @@
984984
*/
985985
986986
/*
987987
** WEBPAGE: vinfo
988988
** WEBPAGE: ci
989
-** URL: /ci/ARTIFACTID
990
-** OR: /ci?name=ARTIFACTID
991
-**
992
-** Display information about a particular check-in. The exact
993
-** same information is shown on the /info page if the name query
994
-** parameter to /info describes a check-in.
995
-**
996
-** The ARTIFACTID can be a unique prefix for the HASH of the check-in,
997
-** or a tag or branch name that identifies the check-in.
989
+**
990
+** Display information about a single check-in identified by
991
+** the "name" query parameter.
992
+**
993
+** The exact same information is shown on the /info page if the name query
994
+** parameter to /info describes a check-in. The /info page is preferred.
995
+** The /ci and /vinfo pages are retained for historical compatibility.
996
+**
997
+** The value of the "name" query parameter can be a unique prefix for
998
+** the HASH of the check-in, or a tag or branch name that identifies
999
+** the check-in.
9981000
*/
9991001
void ci_page(void){
10001002
Stmt q1, q2, q3;
10011003
int rid;
10021004
int isLeaf;
10031005
--- src/info.c
+++ src/info.c
@@ -984,19 +984,21 @@
984 */
985
986 /*
987 ** WEBPAGE: vinfo
988 ** WEBPAGE: ci
989 ** URL: /ci/ARTIFACTID
990 ** OR: /ci?name=ARTIFACTID
991 **
992 ** Display information about a particular check-in. The exact
993 ** same information is shown on the /info page if the name query
994 ** parameter to /info describes a check-in.
995 **
996 ** The ARTIFACTID can be a unique prefix for the HASH of the check-in,
997 ** or a tag or branch name that identifies the check-in.
 
 
998 */
999 void ci_page(void){
1000 Stmt q1, q2, q3;
1001 int rid;
1002 int isLeaf;
1003
--- src/info.c
+++ src/info.c
@@ -984,19 +984,21 @@
984 */
985
986 /*
987 ** WEBPAGE: vinfo
988 ** WEBPAGE: ci
989 **
990 ** Display information about a single check-in identified by
991 ** the "name" query parameter.
992 **
993 ** The exact same information is shown on the /info page if the name query
994 ** parameter to /info describes a check-in. The /info page is preferred.
995 ** The /ci and /vinfo pages are retained for historical compatibility.
996 **
997 ** The value of the "name" query parameter can be a unique prefix for
998 ** the HASH of the check-in, or a tag or branch name that identifies
999 ** the check-in.
1000 */
1001 void ci_page(void){
1002 Stmt q1, q2, q3;
1003 int rid;
1004 int isLeaf;
1005
+12 -6
--- src/leaf.c
+++ src/leaf.c
@@ -35,13 +35,15 @@
3535
int rc;
3636
static const char zSql[] =
3737
@ SELECT 1 FROM plink
3838
@ WHERE pid=%d
3939
@ AND coalesce((SELECT value FROM tagxref
40
- @ WHERE tagid=%d AND rid=plink.pid), 'trunk')
40
+ @ WHERE tagid=%d AND rid=plink.pid
41
+ @ AND tagtype>0), 'trunk')
4142
@ =coalesce((SELECT value FROM tagxref
42
- @ WHERE tagid=%d AND rid=plink.cid), 'trunk')
43
+ @ WHERE tagid=%d AND rid=plink.cid
44
+ @ AND tagtype>0), 'trunk')
4345
;
4446
rc = db_int(0, zSql /*works-like:"%d,%d,%d"*/,
4547
rid, TAG_BRANCH, TAG_BRANCH);
4648
return rc==0;
4749
}
@@ -60,13 +62,15 @@
6062
static Stmt q;
6163
static const char zSql[] =
6264
@ SELECT count(*) FROM plink
6365
@ WHERE pid=:pid AND isprim
6466
@ AND coalesce((SELECT value FROM tagxref
65
- @ WHERE tagid=%d AND rid=plink.pid), 'trunk')
67
+ @ WHERE tagid=%d AND rid=plink.pid
68
+ @ AND tagtype>0), 'trunk')
6669
@ =coalesce((SELECT value FROM tagxref
67
- @ WHERE tagid=%d AND rid=plink.cid), 'trunk')
70
+ @ WHERE tagid=%d AND rid=plink.cid
71
+ @ AND tagtype>0), 'trunk')
6872
;
6973
db_static_prepare(&q, zSql /*works-like: "%d,%d"*/, TAG_BRANCH, TAG_BRANCH);
7074
db_bind_int(&q, ":pid", pid);
7175
if( db_step(&q)==SQLITE_ROW ){
7276
nNonBranch = db_column_int(&q, 0);
@@ -88,13 +92,15 @@
8892
"INSERT OR IGNORE INTO leaf"
8993
" SELECT cid FROM plink"
9094
" EXCEPT"
9195
" SELECT pid FROM plink"
9296
" WHERE coalesce((SELECT value FROM tagxref"
93
- " WHERE tagid=%d AND rid=plink.pid),'trunk')"
97
+ " WHERE tagid=%d AND rid=plink.pid"
98
+ " AND tagtype>0),'trunk')"
9499
" == coalesce((SELECT value FROM tagxref"
95
- " WHERE tagid=%d AND rid=plink.cid),'trunk')",
100
+ " WHERE tagid=%d AND rid=plink.cid"
101
+ " AND tagtype>0),'trunk')",
96102
TAG_BRANCH, TAG_BRANCH
97103
);
98104
}
99105
100106
/*
101107
--- src/leaf.c
+++ src/leaf.c
@@ -35,13 +35,15 @@
35 int rc;
36 static const char zSql[] =
37 @ SELECT 1 FROM plink
38 @ WHERE pid=%d
39 @ AND coalesce((SELECT value FROM tagxref
40 @ WHERE tagid=%d AND rid=plink.pid), 'trunk')
 
41 @ =coalesce((SELECT value FROM tagxref
42 @ WHERE tagid=%d AND rid=plink.cid), 'trunk')
 
43 ;
44 rc = db_int(0, zSql /*works-like:"%d,%d,%d"*/,
45 rid, TAG_BRANCH, TAG_BRANCH);
46 return rc==0;
47 }
@@ -60,13 +62,15 @@
60 static Stmt q;
61 static const char zSql[] =
62 @ SELECT count(*) FROM plink
63 @ WHERE pid=:pid AND isprim
64 @ AND coalesce((SELECT value FROM tagxref
65 @ WHERE tagid=%d AND rid=plink.pid), 'trunk')
 
66 @ =coalesce((SELECT value FROM tagxref
67 @ WHERE tagid=%d AND rid=plink.cid), 'trunk')
 
68 ;
69 db_static_prepare(&q, zSql /*works-like: "%d,%d"*/, TAG_BRANCH, TAG_BRANCH);
70 db_bind_int(&q, ":pid", pid);
71 if( db_step(&q)==SQLITE_ROW ){
72 nNonBranch = db_column_int(&q, 0);
@@ -88,13 +92,15 @@
88 "INSERT OR IGNORE INTO leaf"
89 " SELECT cid FROM plink"
90 " EXCEPT"
91 " SELECT pid FROM plink"
92 " WHERE coalesce((SELECT value FROM tagxref"
93 " WHERE tagid=%d AND rid=plink.pid),'trunk')"
 
94 " == coalesce((SELECT value FROM tagxref"
95 " WHERE tagid=%d AND rid=plink.cid),'trunk')",
 
96 TAG_BRANCH, TAG_BRANCH
97 );
98 }
99
100 /*
101
--- src/leaf.c
+++ src/leaf.c
@@ -35,13 +35,15 @@
35 int rc;
36 static const char zSql[] =
37 @ SELECT 1 FROM plink
38 @ WHERE pid=%d
39 @ AND coalesce((SELECT value FROM tagxref
40 @ WHERE tagid=%d AND rid=plink.pid
41 @ AND tagtype>0), 'trunk')
42 @ =coalesce((SELECT value FROM tagxref
43 @ WHERE tagid=%d AND rid=plink.cid
44 @ AND tagtype>0), 'trunk')
45 ;
46 rc = db_int(0, zSql /*works-like:"%d,%d,%d"*/,
47 rid, TAG_BRANCH, TAG_BRANCH);
48 return rc==0;
49 }
@@ -60,13 +62,15 @@
62 static Stmt q;
63 static const char zSql[] =
64 @ SELECT count(*) FROM plink
65 @ WHERE pid=:pid AND isprim
66 @ AND coalesce((SELECT value FROM tagxref
67 @ WHERE tagid=%d AND rid=plink.pid
68 @ AND tagtype>0), 'trunk')
69 @ =coalesce((SELECT value FROM tagxref
70 @ WHERE tagid=%d AND rid=plink.cid
71 @ AND tagtype>0), 'trunk')
72 ;
73 db_static_prepare(&q, zSql /*works-like: "%d,%d"*/, TAG_BRANCH, TAG_BRANCH);
74 db_bind_int(&q, ":pid", pid);
75 if( db_step(&q)==SQLITE_ROW ){
76 nNonBranch = db_column_int(&q, 0);
@@ -88,13 +92,15 @@
92 "INSERT OR IGNORE INTO leaf"
93 " SELECT cid FROM plink"
94 " EXCEPT"
95 " SELECT pid FROM plink"
96 " WHERE coalesce((SELECT value FROM tagxref"
97 " WHERE tagid=%d AND rid=plink.pid"
98 " AND tagtype>0),'trunk')"
99 " == coalesce((SELECT value FROM tagxref"
100 " WHERE tagid=%d AND rid=plink.cid"
101 " AND tagtype>0),'trunk')",
102 TAG_BRANCH, TAG_BRANCH
103 );
104 }
105
106 /*
107
+10 -2
--- src/main.c
+++ src/main.c
@@ -692,12 +692,20 @@
692692
** Check to see if the Fossil binary contains an appended repository
693693
** file using the appendvfs extension. If so, change command-line arguments
694694
** to cause Fossil to launch with "fossil ui" on that repo.
695695
*/
696696
static int fossilExeHasAppendedRepo(void){
697
- extern int deduceDatabaseType(const char*,int);
698
- if( 2==deduceDatabaseType(g.nameOfExe,0) ){
697
+ int rc;
698
+ /* Warning: dedeuceDatabaseType is an internal function of the SQLite
699
+ ** CLI and is subject to change. Verify the following function template
700
+ ** when updating to a newer SQLite */
701
+ extern int deduceDatabaseType(const char*,int,int);
702
+
703
+ g.dbIgnoreErrors++;
704
+ rc = deduceDatabaseType(g.nameOfExe,0,SQLITE_OPEN_READWRITE);
705
+ g.dbIgnoreErrors--;
706
+ if( rc ){
699707
static char *azAltArgv[] = { 0, "ui", 0, 0 };
700708
azAltArgv[0] = g.nameOfExe;
701709
azAltArgv[2] = g.nameOfExe;
702710
g.argv = azAltArgv;
703711
g.argc = 3;
704712
--- src/main.c
+++ src/main.c
@@ -692,12 +692,20 @@
692 ** Check to see if the Fossil binary contains an appended repository
693 ** file using the appendvfs extension. If so, change command-line arguments
694 ** to cause Fossil to launch with "fossil ui" on that repo.
695 */
696 static int fossilExeHasAppendedRepo(void){
697 extern int deduceDatabaseType(const char*,int);
698 if( 2==deduceDatabaseType(g.nameOfExe,0) ){
 
 
 
 
 
 
 
 
699 static char *azAltArgv[] = { 0, "ui", 0, 0 };
700 azAltArgv[0] = g.nameOfExe;
701 azAltArgv[2] = g.nameOfExe;
702 g.argv = azAltArgv;
703 g.argc = 3;
704
--- src/main.c
+++ src/main.c
@@ -692,12 +692,20 @@
692 ** Check to see if the Fossil binary contains an appended repository
693 ** file using the appendvfs extension. If so, change command-line arguments
694 ** to cause Fossil to launch with "fossil ui" on that repo.
695 */
696 static int fossilExeHasAppendedRepo(void){
697 int rc;
698 /* Warning: dedeuceDatabaseType is an internal function of the SQLite
699 ** CLI and is subject to change. Verify the following function template
700 ** when updating to a newer SQLite */
701 extern int deduceDatabaseType(const char*,int,int);
702
703 g.dbIgnoreErrors++;
704 rc = deduceDatabaseType(g.nameOfExe,0,SQLITE_OPEN_READWRITE);
705 g.dbIgnoreErrors--;
706 if( rc ){
707 static char *azAltArgv[] = { 0, "ui", 0, 0 };
708 azAltArgv[0] = g.nameOfExe;
709 azAltArgv[2] = g.nameOfExe;
710 g.argv = azAltArgv;
711 g.argc = 3;
712
--- src/markdown.c
+++ src/markdown.c
@@ -39,10 +39,16 @@
3939
MKDA_NOT_AUTOLINK, /* used internally when it is not an autolink*/
4040
MKDA_NORMAL, /* normal http/http/ftp link */
4141
MKDA_EXPLICIT_EMAIL, /* e-mail link with explicit mailto: */
4242
MKDA_IMPLICIT_EMAIL /* e-mail link without mailto: */
4343
};
44
+
45
+/* mkd_tagspan -- type of tagged <span> */
46
+enum mkd_tagspan {
47
+ MKDT_HASHTAG, /* #hashtags */
48
+ MKDT_NUMTAG /* #123[.456] /chat or /forum message IDs. */
49
+};
4450
4551
/* mkd_renderer -- functions for rendering parsed data */
4652
struct mkd_renderer {
4753
/* document level callbacks */
4854
void (*prolog)(struct Blob *ob, void *opaque);
@@ -80,10 +86,12 @@
8086
struct Blob *alt, void *opaque);
8187
int (*linebreak)(struct Blob *ob, void *opaque);
8288
int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
8389
struct Blob *content, void *opaque);
8490
int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
91
+ int (*tagspan)(struct Blob *ob, struct Blob *ref, enum mkd_tagspan type,
92
+ void *opaque);
8593
int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
8694
char c, void *opaque);
8795
int (*footnote_ref)(struct Blob *ob, const struct Blob *span,
8896
const struct Blob *upc, int index, int locus, void *opaque);
8997
@@ -950,10 +958,163 @@
950958
}else{
951959
blob_append(ob, data, end);
952960
}
953961
return end;
954962
}
963
+
964
+/* char_hashref_tag -- '#' followed by "word" characters to tag
965
+** post numbers, hashtags, etc.
966
+**
967
+** Basic syntax:
968
+**
969
+** ^[a-zA-Z]X*
970
+**
971
+** Where X is:
972
+**
973
+** - Any number of alphanumeric characters.
974
+**
975
+** - Single underscores. Adjacent underscores are not recognized
976
+** as valid hashtags. That decision is somewhat arbitrary
977
+** and up for debate.
978
+**
979
+** Hashtags must end at the end of input or be followed by whitespace
980
+** or what appears to be the end or separator of a logical
981
+** natural-language construct, e.g. period, colon, etc.
982
+**
983
+** Current limitations of this implementation:
984
+**
985
+** - Currently requires starting alpha and trailing
986
+** alphanumeric or underscores. "Should" be extended to
987
+** handle #X[.Y], where X and optional Y are integer
988
+** values, for forum post references.
989
+*/
990
+static size_t char_hashref_tag(
991
+ struct Blob *ob,
992
+ struct render *rndr,
993
+ char *data,
994
+ size_t offset,
995
+ size_t size
996
+){
997
+ size_t end;
998
+ struct Blob work = BLOB_INITIALIZER;
999
+ int nUscore = 0; /* Consecutive underscore counter */
1000
+ int numberMode = 0 /* 0 for normal, 1 for #NNN numeric,
1001
+ and 2 for #NNN.NNN. */;
1002
+ if(offset>0 && !fossil_isspace(data[-1])){
1003
+ /* Only ever match if the *previous* character is whitespace or
1004
+ ** we're at the start of the input. Note that we rely on fossil
1005
+ ** processing emphasis markup before reaching this function, so
1006
+ ** *#Hash* will Do The Right Thing. Not that this means that
1007
+ ** "#Hash." will match while ".#Hash" won't. That's okay. */
1008
+ return 0;
1009
+ }
1010
+ assert( '#' == data[0] );
1011
+ if(size < 2) return 0;
1012
+ end = 2;
1013
+ if(fossil_isdigit(data[1])){
1014
+ numberMode = 1;
1015
+ }else if(!fossil_isalpha(data[1])){
1016
+ switch(data[1] & 0xF0){
1017
+ /* Reminder: UTF8 char lengths can be determined by
1018
+ ** masking against 0xF0: 0xf0==4, 0xe0==3, 0xc0==2,
1019
+ ** else 1. */
1020
+ case 0xF0: end+=3; break;
1021
+ case 0xE0: end+=2; break;
1022
+ case 0xC0: end+=1; break;
1023
+ default: return 0;
1024
+ }
1025
+ }
1026
+#if 0
1027
+ fprintf(stderr,"HASHREF offset=%d size=%d: %.*s\n",
1028
+ (int)offset, (int)size, (int)size, data);
1029
+#endif
1030
+#define HASHTAG_LEGAL_END \
1031
+ case ' ': case '\t': case '\r': case '\n': \
1032
+ case ':': case ';': case '!': case '?': case ','
1033
+ /* ^^^^ '.' is handled separately */
1034
+ for(; end < size; ++end){
1035
+ char ch = data[end];
1036
+ switch(ch & 0xF0){
1037
+ case 0xF0: end+=3; continue;
1038
+ case 0xE0: end+=2; continue;
1039
+ case 0xC0: end+=1; continue;
1040
+ case 0x80: goto hashref_bailout /*invalid UTF8*/;
1041
+ default: break;
1042
+ }
1043
+#if 0
1044
+ fprintf(stderr,"hashtag? checking... length=%d: %.*s\n",
1045
+ (int)end, (int)end, data);
1046
+#endif
1047
+ switch(ch){
1048
+ case '_':
1049
+ /* Multiple adjacent underscores not permitted. */
1050
+ if(++nUscore>1) goto hashref_bailout;
1051
+ numberMode = 0;
1052
+ break;
1053
+ case '.':
1054
+ if(1==numberMode) ++numberMode;
1055
+ ch = 0;
1056
+ break;
1057
+ HASHTAG_LEGAL_END:
1058
+ ch = 0;
1059
+ break;
1060
+ case '0': case '1': case '2': case '3': case '4':
1061
+ case '5': case '6': case '7': case '8': case '9':
1062
+ nUscore = 0;
1063
+ break;
1064
+ default:
1065
+ if(numberMode || !fossil_isalpha(ch)){
1066
+ goto hashref_bailout;
1067
+ }
1068
+ nUscore = 0;
1069
+ break;
1070
+ }
1071
+ if(ch) continue;
1072
+ break;
1073
+ }
1074
+ if((end<3/* #. or some such */ && !numberMode)
1075
+ || end>size/*from truncated multi-byte char*/){
1076
+ return 0;
1077
+ }
1078
+ if(numberMode>1){
1079
+ /* Check for trailing part of #NNN.nnn... */
1080
+ assert('.'==data[end]);
1081
+ if(end<size-1 && fossil_isdigit(data[end+1])){
1082
+ for(++end; end<size; ++end){
1083
+ if(!fossil_isdigit(data[end])) break;
1084
+ }
1085
+ }
1086
+ }
1087
+#if 0
1088
+ fprintf(stderr,"???HASHREF length=%d: %.*s\n",
1089
+ (int)end, (int)end, data);
1090
+#endif
1091
+ if(end<size){
1092
+ /* Only match if we end at end of input or what "might" be the end
1093
+ of a natural language grammar construct, e.g. period or
1094
+ [semi]colon. */
1095
+ switch(data[end]){
1096
+ case '.':
1097
+ HASHTAG_LEGAL_END:
1098
+ break;
1099
+ default:
1100
+ goto hashref_bailout;
1101
+ }
1102
+ }
1103
+ blob_init(&work, data + 1, end - 1);
1104
+ rndr->make.tagspan(ob, &work,
1105
+ numberMode ? MKDT_NUMTAG : MKDT_HASHTAG,
1106
+ rndr->make.opaque);
1107
+ return end;
1108
+ hashref_bailout:
1109
+#if 0
1110
+ fprintf(stderr,"BAILING HASHREF examined=%d:\n[%.*s] of\n[%.*s]\n",
1111
+ (int)end, (int)end, data, (int)size, data);
1112
+#endif
1113
+#undef HASHTAG_LEGAL_END
1114
+ return 0;
1115
+}
9551116
9561117
/*
9571118
** char_langle_tag -- '<' when tags or autolinks are allowed.
9581119
*/
9591120
static size_t char_langle_tag(
@@ -2686,10 +2847,11 @@
26862847
}
26872848
}
26882849
if( rndr.make.codespan ) rndr.active_char['`'] = char_codespan;
26892850
if( rndr.make.linebreak ) rndr.active_char['\n'] = char_linebreak;
26902851
if( rndr.make.image || rndr.make.link ) rndr.active_char['['] = char_link;
2852
+ rndr.active_char['#'] = char_hashref_tag;
26912853
if( rndr.make.footnote_ref ) rndr.active_char['('] = char_footnote;
26922854
rndr.active_char['<'] = char_langle_tag;
26932855
rndr.active_char['\\'] = char_escape;
26942856
rndr.active_char['&'] = char_entity;
26952857
26962858
--- src/markdown.c
+++ src/markdown.c
@@ -39,10 +39,16 @@
39 MKDA_NOT_AUTOLINK, /* used internally when it is not an autolink*/
40 MKDA_NORMAL, /* normal http/http/ftp link */
41 MKDA_EXPLICIT_EMAIL, /* e-mail link with explicit mailto: */
42 MKDA_IMPLICIT_EMAIL /* e-mail link without mailto: */
43 };
 
 
 
 
 
 
44
45 /* mkd_renderer -- functions for rendering parsed data */
46 struct mkd_renderer {
47 /* document level callbacks */
48 void (*prolog)(struct Blob *ob, void *opaque);
@@ -80,10 +86,12 @@
80 struct Blob *alt, void *opaque);
81 int (*linebreak)(struct Blob *ob, void *opaque);
82 int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
83 struct Blob *content, void *opaque);
84 int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
 
 
85 int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
86 char c, void *opaque);
87 int (*footnote_ref)(struct Blob *ob, const struct Blob *span,
88 const struct Blob *upc, int index, int locus, void *opaque);
89
@@ -950,10 +958,163 @@
950 }else{
951 blob_append(ob, data, end);
952 }
953 return end;
954 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
955
956 /*
957 ** char_langle_tag -- '<' when tags or autolinks are allowed.
958 */
959 static size_t char_langle_tag(
@@ -2686,10 +2847,11 @@
2686 }
2687 }
2688 if( rndr.make.codespan ) rndr.active_char['`'] = char_codespan;
2689 if( rndr.make.linebreak ) rndr.active_char['\n'] = char_linebreak;
2690 if( rndr.make.image || rndr.make.link ) rndr.active_char['['] = char_link;
 
2691 if( rndr.make.footnote_ref ) rndr.active_char['('] = char_footnote;
2692 rndr.active_char['<'] = char_langle_tag;
2693 rndr.active_char['\\'] = char_escape;
2694 rndr.active_char['&'] = char_entity;
2695
2696
--- src/markdown.c
+++ src/markdown.c
@@ -39,10 +39,16 @@
39 MKDA_NOT_AUTOLINK, /* used internally when it is not an autolink*/
40 MKDA_NORMAL, /* normal http/http/ftp link */
41 MKDA_EXPLICIT_EMAIL, /* e-mail link with explicit mailto: */
42 MKDA_IMPLICIT_EMAIL /* e-mail link without mailto: */
43 };
44
45 /* mkd_tagspan -- type of tagged <span> */
46 enum mkd_tagspan {
47 MKDT_HASHTAG, /* #hashtags */
48 MKDT_NUMTAG /* #123[.456] /chat or /forum message IDs. */
49 };
50
51 /* mkd_renderer -- functions for rendering parsed data */
52 struct mkd_renderer {
53 /* document level callbacks */
54 void (*prolog)(struct Blob *ob, void *opaque);
@@ -80,10 +86,12 @@
86 struct Blob *alt, void *opaque);
87 int (*linebreak)(struct Blob *ob, void *opaque);
88 int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title,
89 struct Blob *content, void *opaque);
90 int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque);
91 int (*tagspan)(struct Blob *ob, struct Blob *ref, enum mkd_tagspan type,
92 void *opaque);
93 int (*triple_emphasis)(struct Blob *ob, struct Blob *text,
94 char c, void *opaque);
95 int (*footnote_ref)(struct Blob *ob, const struct Blob *span,
96 const struct Blob *upc, int index, int locus, void *opaque);
97
@@ -950,10 +958,163 @@
958 }else{
959 blob_append(ob, data, end);
960 }
961 return end;
962 }
963
964 /* char_hashref_tag -- '#' followed by "word" characters to tag
965 ** post numbers, hashtags, etc.
966 **
967 ** Basic syntax:
968 **
969 ** ^[a-zA-Z]X*
970 **
971 ** Where X is:
972 **
973 ** - Any number of alphanumeric characters.
974 **
975 ** - Single underscores. Adjacent underscores are not recognized
976 ** as valid hashtags. That decision is somewhat arbitrary
977 ** and up for debate.
978 **
979 ** Hashtags must end at the end of input or be followed by whitespace
980 ** or what appears to be the end or separator of a logical
981 ** natural-language construct, e.g. period, colon, etc.
982 **
983 ** Current limitations of this implementation:
984 **
985 ** - Currently requires starting alpha and trailing
986 ** alphanumeric or underscores. "Should" be extended to
987 ** handle #X[.Y], where X and optional Y are integer
988 ** values, for forum post references.
989 */
990 static size_t char_hashref_tag(
991 struct Blob *ob,
992 struct render *rndr,
993 char *data,
994 size_t offset,
995 size_t size
996 ){
997 size_t end;
998 struct Blob work = BLOB_INITIALIZER;
999 int nUscore = 0; /* Consecutive underscore counter */
1000 int numberMode = 0 /* 0 for normal, 1 for #NNN numeric,
1001 and 2 for #NNN.NNN. */;
1002 if(offset>0 && !fossil_isspace(data[-1])){
1003 /* Only ever match if the *previous* character is whitespace or
1004 ** we're at the start of the input. Note that we rely on fossil
1005 ** processing emphasis markup before reaching this function, so
1006 ** *#Hash* will Do The Right Thing. Not that this means that
1007 ** "#Hash." will match while ".#Hash" won't. That's okay. */
1008 return 0;
1009 }
1010 assert( '#' == data[0] );
1011 if(size < 2) return 0;
1012 end = 2;
1013 if(fossil_isdigit(data[1])){
1014 numberMode = 1;
1015 }else if(!fossil_isalpha(data[1])){
1016 switch(data[1] & 0xF0){
1017 /* Reminder: UTF8 char lengths can be determined by
1018 ** masking against 0xF0: 0xf0==4, 0xe0==3, 0xc0==2,
1019 ** else 1. */
1020 case 0xF0: end+=3; break;
1021 case 0xE0: end+=2; break;
1022 case 0xC0: end+=1; break;
1023 default: return 0;
1024 }
1025 }
1026 #if 0
1027 fprintf(stderr,"HASHREF offset=%d size=%d: %.*s\n",
1028 (int)offset, (int)size, (int)size, data);
1029 #endif
1030 #define HASHTAG_LEGAL_END \
1031 case ' ': case '\t': case '\r': case '\n': \
1032 case ':': case ';': case '!': case '?': case ','
1033 /* ^^^^ '.' is handled separately */
1034 for(; end < size; ++end){
1035 char ch = data[end];
1036 switch(ch & 0xF0){
1037 case 0xF0: end+=3; continue;
1038 case 0xE0: end+=2; continue;
1039 case 0xC0: end+=1; continue;
1040 case 0x80: goto hashref_bailout /*invalid UTF8*/;
1041 default: break;
1042 }
1043 #if 0
1044 fprintf(stderr,"hashtag? checking... length=%d: %.*s\n",
1045 (int)end, (int)end, data);
1046 #endif
1047 switch(ch){
1048 case '_':
1049 /* Multiple adjacent underscores not permitted. */
1050 if(++nUscore>1) goto hashref_bailout;
1051 numberMode = 0;
1052 break;
1053 case '.':
1054 if(1==numberMode) ++numberMode;
1055 ch = 0;
1056 break;
1057 HASHTAG_LEGAL_END:
1058 ch = 0;
1059 break;
1060 case '0': case '1': case '2': case '3': case '4':
1061 case '5': case '6': case '7': case '8': case '9':
1062 nUscore = 0;
1063 break;
1064 default:
1065 if(numberMode || !fossil_isalpha(ch)){
1066 goto hashref_bailout;
1067 }
1068 nUscore = 0;
1069 break;
1070 }
1071 if(ch) continue;
1072 break;
1073 }
1074 if((end<3/* #. or some such */ && !numberMode)
1075 || end>size/*from truncated multi-byte char*/){
1076 return 0;
1077 }
1078 if(numberMode>1){
1079 /* Check for trailing part of #NNN.nnn... */
1080 assert('.'==data[end]);
1081 if(end<size-1 && fossil_isdigit(data[end+1])){
1082 for(++end; end<size; ++end){
1083 if(!fossil_isdigit(data[end])) break;
1084 }
1085 }
1086 }
1087 #if 0
1088 fprintf(stderr,"???HASHREF length=%d: %.*s\n",
1089 (int)end, (int)end, data);
1090 #endif
1091 if(end<size){
1092 /* Only match if we end at end of input or what "might" be the end
1093 of a natural language grammar construct, e.g. period or
1094 [semi]colon. */
1095 switch(data[end]){
1096 case '.':
1097 HASHTAG_LEGAL_END:
1098 break;
1099 default:
1100 goto hashref_bailout;
1101 }
1102 }
1103 blob_init(&work, data + 1, end - 1);
1104 rndr->make.tagspan(ob, &work,
1105 numberMode ? MKDT_NUMTAG : MKDT_HASHTAG,
1106 rndr->make.opaque);
1107 return end;
1108 hashref_bailout:
1109 #if 0
1110 fprintf(stderr,"BAILING HASHREF examined=%d:\n[%.*s] of\n[%.*s]\n",
1111 (int)end, (int)end, data, (int)size, data);
1112 #endif
1113 #undef HASHTAG_LEGAL_END
1114 return 0;
1115 }
1116
1117 /*
1118 ** char_langle_tag -- '<' when tags or autolinks are allowed.
1119 */
1120 static size_t char_langle_tag(
@@ -2686,10 +2847,11 @@
2847 }
2848 }
2849 if( rndr.make.codespan ) rndr.active_char['`'] = char_codespan;
2850 if( rndr.make.linebreak ) rndr.active_char['\n'] = char_linebreak;
2851 if( rndr.make.image || rndr.make.link ) rndr.active_char['['] = char_link;
2852 rndr.active_char['#'] = char_hashref_tag;
2853 if( rndr.make.footnote_ref ) rndr.active_char['('] = char_footnote;
2854 rndr.active_char['<'] = char_langle_tag;
2855 rndr.active_char['\\'] = char_escape;
2856 rndr.active_char['&'] = char_entity;
2857
2858
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -868,10 +868,42 @@
868868
blob_appendb(ob, content);
869869
}
870870
blob_append(ob, zClose, -1);
871871
return 1;
872872
}
873
+
874
+/* Invoked for @name and #tag tagged words, marked up in the
875
+** output text in a way that JS and CSS can do something
876
+** interesting with them. This isn't standard Markdown, so
877
+** it's implementation-specific what occurs here. More, each
878
+** Fossil feature using Markdown is free to apply styling and
879
+** behavior to these in feature-specific ways.
880
+*/
881
+static int html_tagspan(
882
+ struct Blob *ob, /* Write the output here */
883
+ struct Blob *text, /* The word after the tag character */
884
+ enum mkd_tagspan type, /* Which type of tagspan we're creating */
885
+ void *opaque
886
+){
887
+ if( text==0 ){
888
+ /* no-op */
889
+ }else{
890
+ char cPrefix = '!';
891
+ blob_append_literal(ob, "<span data-");
892
+ switch (type) {
893
+ case MKDT_HASHTAG:
894
+ cPrefix = '#'; blob_append_literal(ob, "hashtag"); break;
895
+ case MKDT_NUMTAG:
896
+ cPrefix = '#'; blob_append_literal(ob, "numtag"); break;
897
+ }
898
+ blob_append_literal(ob, "=\"");
899
+ html_quote(ob, blob_buffer(text), blob_size(text));
900
+ blob_append_literal(ob, "\"");
901
+ blob_appendf(ob, ">%c%b</span>", cPrefix,text);
902
+ }
903
+ return 1;
904
+}
873905
874906
static int html_triple_emphasis(
875907
struct Blob *ob,
876908
struct Blob *text,
877909
char c,
@@ -926,10 +958,11 @@
926958
html_emphasis,
927959
html_image,
928960
html_linebreak,
929961
html_link,
930962
html_raw_html_tag,
963
+ html_tagspan,
931964
html_triple_emphasis,
932965
html_footnote_ref,
933966
934967
/* low level elements */
935968
0, /* entity */
936969
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -868,10 +868,42 @@
868 blob_appendb(ob, content);
869 }
870 blob_append(ob, zClose, -1);
871 return 1;
872 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
873
874 static int html_triple_emphasis(
875 struct Blob *ob,
876 struct Blob *text,
877 char c,
@@ -926,10 +958,11 @@
926 html_emphasis,
927 html_image,
928 html_linebreak,
929 html_link,
930 html_raw_html_tag,
 
931 html_triple_emphasis,
932 html_footnote_ref,
933
934 /* low level elements */
935 0, /* entity */
936
--- src/markdown_html.c
+++ src/markdown_html.c
@@ -868,10 +868,42 @@
868 blob_appendb(ob, content);
869 }
870 blob_append(ob, zClose, -1);
871 return 1;
872 }
873
874 /* Invoked for @name and #tag tagged words, marked up in the
875 ** output text in a way that JS and CSS can do something
876 ** interesting with them. This isn't standard Markdown, so
877 ** it's implementation-specific what occurs here. More, each
878 ** Fossil feature using Markdown is free to apply styling and
879 ** behavior to these in feature-specific ways.
880 */
881 static int html_tagspan(
882 struct Blob *ob, /* Write the output here */
883 struct Blob *text, /* The word after the tag character */
884 enum mkd_tagspan type, /* Which type of tagspan we're creating */
885 void *opaque
886 ){
887 if( text==0 ){
888 /* no-op */
889 }else{
890 char cPrefix = '!';
891 blob_append_literal(ob, "<span data-");
892 switch (type) {
893 case MKDT_HASHTAG:
894 cPrefix = '#'; blob_append_literal(ob, "hashtag"); break;
895 case MKDT_NUMTAG:
896 cPrefix = '#'; blob_append_literal(ob, "numtag"); break;
897 }
898 blob_append_literal(ob, "=\"");
899 html_quote(ob, blob_buffer(text), blob_size(text));
900 blob_append_literal(ob, "\"");
901 blob_appendf(ob, ">%c%b</span>", cPrefix,text);
902 }
903 return 1;
904 }
905
906 static int html_triple_emphasis(
907 struct Blob *ob,
908 struct Blob *text,
909 char c,
@@ -926,10 +958,11 @@
958 html_emphasis,
959 html_image,
960 html_linebreak,
961 html_link,
962 html_raw_html_tag,
963 html_tagspan,
964 html_triple_emphasis,
965 html_footnote_ref,
966
967 /* low level elements */
968 0, /* entity */
969
+1 -1
--- src/merge.c
+++ src/merge.c
@@ -1050,11 +1050,11 @@
10501050
integrateFlag ? "integrate:" : "merge-from:");
10511051
print_checkin_description(pid, 12, "baseline:");
10521052
}
10531053
vfile_check_signature(vid, CKSIG_ENOTFILE);
10541054
if( nMerge==0 ) db_begin_transaction();
1055
- if( useUndo ) undo_begin();
1055
+ if( useUndo && nMerge==0 ) undo_begin();
10561056
if( load_vfile_from_rid(mid) && !forceMissingFlag ){
10571057
fossil_fatal("missing content, unable to merge");
10581058
}
10591059
if( load_vfile_from_rid(pid) && !forceMissingFlag ){
10601060
fossil_fatal("missing content, unable to merge");
10611061
--- src/merge.c
+++ src/merge.c
@@ -1050,11 +1050,11 @@
1050 integrateFlag ? "integrate:" : "merge-from:");
1051 print_checkin_description(pid, 12, "baseline:");
1052 }
1053 vfile_check_signature(vid, CKSIG_ENOTFILE);
1054 if( nMerge==0 ) db_begin_transaction();
1055 if( useUndo ) undo_begin();
1056 if( load_vfile_from_rid(mid) && !forceMissingFlag ){
1057 fossil_fatal("missing content, unable to merge");
1058 }
1059 if( load_vfile_from_rid(pid) && !forceMissingFlag ){
1060 fossil_fatal("missing content, unable to merge");
1061
--- src/merge.c
+++ src/merge.c
@@ -1050,11 +1050,11 @@
1050 integrateFlag ? "integrate:" : "merge-from:");
1051 print_checkin_description(pid, 12, "baseline:");
1052 }
1053 vfile_check_signature(vid, CKSIG_ENOTFILE);
1054 if( nMerge==0 ) db_begin_transaction();
1055 if( useUndo && nMerge==0 ) undo_begin();
1056 if( load_vfile_from_rid(mid) && !forceMissingFlag ){
1057 fossil_fatal("missing content, unable to merge");
1058 }
1059 if( load_vfile_from_rid(pid) && !forceMissingFlag ){
1060 fossil_fatal("missing content, unable to merge");
1061
--- src/style.chat.css
+++ src/style.chat.css
@@ -655,10 +655,21 @@
655655
}
656656
body.chat #chat-user-list .chat-user.selected {
657657
font-weight: bold;
658658
text-decoration: underline;
659659
}
660
+
661
+body.chat span[data-hashtag],
662
+body.chat span[data-numtag]{
663
+ font-family: monospace;
664
+ text-decoration: underline;
665
+ cursor: pointer;
666
+}
667
+
668
+body.chat #chat-clear-filter {
669
+ margin: 0.25em 0.5em;
670
+}
660671
661672
body.chat .searchForm {
662673
margin-top: 1em;
663674
}
664675
body.chat .spacer-widget button {
@@ -692,19 +703,26 @@
692703
}
693704
body.chat .anim-fade-in {
694705
animation: fade-in 750ms linear;
695706
}
696707
body.chat .anim-fade-in-fast {
697
- animation: fade-in 350ms linear;
708
+ animation: fade-in 300ms linear;
698709
}
699710
@keyframes fade-in {
700711
from { opacity: 0; }
701712
to { opacity: 1; }
702713
}
703714
body.chat .anim-fade-out-fast {
704
- animation: fade-out 250ms linear;
715
+ animation: fade-out 300ms linear;
705716
}
706717
@keyframes fade-out {
707718
from { opacity: 1; }
708719
to { opacity: 0; }
709720
}
710721
722
+body.chat .anim-fade-out-in {
723
+ animation: fade-out-in 1000ms linear;
724
+}
725
+@keyframes fade-out-in {
726
+ 0%,100% { opacity: 0 }
727
+ 50% { opacity: 1 }
728
+}
711729
--- src/style.chat.css
+++ src/style.chat.css
@@ -655,10 +655,21 @@
655 }
656 body.chat #chat-user-list .chat-user.selected {
657 font-weight: bold;
658 text-decoration: underline;
659 }
 
 
 
 
 
 
 
 
 
 
 
660
661 body.chat .searchForm {
662 margin-top: 1em;
663 }
664 body.chat .spacer-widget button {
@@ -692,19 +703,26 @@
692 }
693 body.chat .anim-fade-in {
694 animation: fade-in 750ms linear;
695 }
696 body.chat .anim-fade-in-fast {
697 animation: fade-in 350ms linear;
698 }
699 @keyframes fade-in {
700 from { opacity: 0; }
701 to { opacity: 1; }
702 }
703 body.chat .anim-fade-out-fast {
704 animation: fade-out 250ms linear;
705 }
706 @keyframes fade-out {
707 from { opacity: 1; }
708 to { opacity: 0; }
709 }
710
 
 
 
 
 
 
 
711
--- src/style.chat.css
+++ src/style.chat.css
@@ -655,10 +655,21 @@
655 }
656 body.chat #chat-user-list .chat-user.selected {
657 font-weight: bold;
658 text-decoration: underline;
659 }
660
661 body.chat span[data-hashtag],
662 body.chat span[data-numtag]{
663 font-family: monospace;
664 text-decoration: underline;
665 cursor: pointer;
666 }
667
668 body.chat #chat-clear-filter {
669 margin: 0.25em 0.5em;
670 }
671
672 body.chat .searchForm {
673 margin-top: 1em;
674 }
675 body.chat .spacer-widget button {
@@ -692,19 +703,26 @@
703 }
704 body.chat .anim-fade-in {
705 animation: fade-in 750ms linear;
706 }
707 body.chat .anim-fade-in-fast {
708 animation: fade-in 300ms linear;
709 }
710 @keyframes fade-in {
711 from { opacity: 0; }
712 to { opacity: 1; }
713 }
714 body.chat .anim-fade-out-fast {
715 animation: fade-out 300ms linear;
716 }
717 @keyframes fade-out {
718 from { opacity: 1; }
719 to { opacity: 0; }
720 }
721
722 body.chat .anim-fade-out-in {
723 animation: fade-out-in 1000ms linear;
724 }
725 @keyframes fade-out-in {
726 0%,100% { opacity: 0 }
727 50% { opacity: 1 }
728 }
729
+14 -5
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -2148,13 +2148,15 @@
21482148
}
21492149
21502150
/*
21512151
** COMMAND: test-markdown-render
21522152
**
2153
-** Usage: %fossil test-markdown-render FILE ...
2153
+** Usage: %fossil test-markdown-render TEXT ...
21542154
**
2155
-** Render markdown in FILE as HTML on stdout.
2155
+** Render markdown in TEXT as HTML on stdout, where TEXT
2156
+** may be a file name or a markdown-formatted string.
2157
+**
21562158
** Options:
21572159
**
21582160
** --dark-pikchr Render pikchrs in dark mode
21592161
** --lint-footnotes Print stats for footnotes-related issues
21602162
** --safe Restrict the output to use only "safe" HTML
@@ -2172,13 +2174,20 @@
21722174
}
21732175
bText = find_option("text",0,0)!=0;
21742176
verify_all_options();
21752177
for(i=2; i<g.argc; i++){
21762178
blob_zero(&out);
2177
- blob_read_from_file(&in, g.argv[i], ExtFILE);
2178
- if( g.argc>3 ){
2179
- fossil_print("<!------ %h ------->\n", g.argv[i]);
2179
+ if(file_isfile(g.argv[i], ExtFILE)){
2180
+ blob_read_from_file(&in, g.argv[i], ExtFILE);
2181
+ if( g.argc>3 ){
2182
+ fossil_print("<!------ %h ------->\n", g.argv[i]);
2183
+ }
2184
+ }else{
2185
+ blob_init(&in, g.argv[i], -1);
2186
+ if( g.argc>3 ){
2187
+ fossil_print("<!------ script #%d ------->\n", i-1);
2188
+ }
21802189
}
21812190
markdown_to_html(&in, 0, &out);
21822191
safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED );
21832192
safe_html(&out);
21842193
if( bText ){
21852194
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -2148,13 +2148,15 @@
2148 }
2149
2150 /*
2151 ** COMMAND: test-markdown-render
2152 **
2153 ** Usage: %fossil test-markdown-render FILE ...
2154 **
2155 ** Render markdown in FILE as HTML on stdout.
 
 
2156 ** Options:
2157 **
2158 ** --dark-pikchr Render pikchrs in dark mode
2159 ** --lint-footnotes Print stats for footnotes-related issues
2160 ** --safe Restrict the output to use only "safe" HTML
@@ -2172,13 +2174,20 @@
2172 }
2173 bText = find_option("text",0,0)!=0;
2174 verify_all_options();
2175 for(i=2; i<g.argc; i++){
2176 blob_zero(&out);
2177 blob_read_from_file(&in, g.argv[i], ExtFILE);
2178 if( g.argc>3 ){
2179 fossil_print("<!------ %h ------->\n", g.argv[i]);
 
 
 
 
 
 
 
2180 }
2181 markdown_to_html(&in, 0, &out);
2182 safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED );
2183 safe_html(&out);
2184 if( bText ){
2185
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -2148,13 +2148,15 @@
2148 }
2149
2150 /*
2151 ** COMMAND: test-markdown-render
2152 **
2153 ** Usage: %fossil test-markdown-render TEXT ...
2154 **
2155 ** Render markdown in TEXT as HTML on stdout, where TEXT
2156 ** may be a file name or a markdown-formatted string.
2157 **
2158 ** Options:
2159 **
2160 ** --dark-pikchr Render pikchrs in dark mode
2161 ** --lint-footnotes Print stats for footnotes-related issues
2162 ** --safe Restrict the output to use only "safe" HTML
@@ -2172,13 +2174,20 @@
2174 }
2175 bText = find_option("text",0,0)!=0;
2176 verify_all_options();
2177 for(i=2; i<g.argc; i++){
2178 blob_zero(&out);
2179 if(file_isfile(g.argv[i], ExtFILE)){
2180 blob_read_from_file(&in, g.argv[i], ExtFILE);
2181 if( g.argc>3 ){
2182 fossil_print("<!------ %h ------->\n", g.argv[i]);
2183 }
2184 }else{
2185 blob_init(&in, g.argv[i], -1);
2186 if( g.argc>3 ){
2187 fossil_print("<!------ script #%d ------->\n", i-1);
2188 }
2189 }
2190 markdown_to_html(&in, 0, &out);
2191 safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED );
2192 safe_html(&out);
2193 if( bText ){
2194
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -229,11 +229,11 @@
229229
!elseif $(OPTIMIZATIONS)>1
230230
RELOPTS = /O2
231231
!elseif $(OPTIMIZATIONS)>0
232232
RELOPTS = /O1
233233
!else
234
-RELOPTS =
234
+RELOPTS = /Od
235235
!endif
236236
237237
!if $(DEBUG)!=0
238238
CFLAGS = $(CFLAGS) /Zi $(CRTFLAGS) $(DBGOPTS) /DFOSSIL_DEBUG /DTH_MEMDEBUG
239239
LDFLAGS = $(LDFLAGS) /DEBUG
240240
241241
ADDED win/build64-debug.bat
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -229,11 +229,11 @@
229 !elseif $(OPTIMIZATIONS)>1
230 RELOPTS = /O2
231 !elseif $(OPTIMIZATIONS)>0
232 RELOPTS = /O1
233 !else
234 RELOPTS =
235 !endif
236
237 !if $(DEBUG)!=0
238 CFLAGS = $(CFLAGS) /Zi $(CRTFLAGS) $(DBGOPTS) /DFOSSIL_DEBUG /DTH_MEMDEBUG
239 LDFLAGS = $(LDFLAGS) /DEBUG
240
241 DDED win/build64-debug.bat
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -229,11 +229,11 @@
229 !elseif $(OPTIMIZATIONS)>1
230 RELOPTS = /O2
231 !elseif $(OPTIMIZATIONS)>0
232 RELOPTS = /O1
233 !else
234 RELOPTS = /Od
235 !endif
236
237 !if $(DEBUG)!=0
238 CFLAGS = $(CFLAGS) /Zi $(CRTFLAGS) $(DBGOPTS) /DFOSSIL_DEBUG /DTH_MEMDEBUG
239 LDFLAGS = $(LDFLAGS) /DEBUG
240
241 DDED win/build64-debug.bat
--- a/win/build64-debug.bat
+++ b/win/build64-debug.bat
@@ -0,0 +1,10 @@
1
+REM Based on /wiki/Release%20Build%20How-To
2
+nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 OPTIMIZATIONS=0 DEBUG=1 clean fossil.exe
3
+@echo off
4
+echo:
5
+echo ********************************************************
6
+echo:
7
+echo Now run: "set FOSSIL_BREAK=1"
8
+echo Then run: "fossil"
9
+echo Then attach VisualStudio to the process
10
+echo Then press ENTER
--- a/win/build64-debug.bat
+++ b/win/build64-debug.bat
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
--- a/win/build64-debug.bat
+++ b/win/build64-debug.bat
@@ -0,0 +1,10 @@
1 REM Based on /wiki/Release%20Build%20How-To
2 nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 OPTIMIZATIONS=0 DEBUG=1 clean fossil.exe
3 @echo off
4 echo:
5 echo ********************************************************
6 echo:
7 echo Now run: "set FOSSIL_BREAK=1"
8 echo Then run: "fossil"
9 echo Then attach VisualStudio to the process
10 echo Then press ENTER
--- www/changes.wiki
+++ www/changes.wiki
@@ -35,10 +35,16 @@
3535
<li> The "diff" command (in all its variations) when invoked with --tk
3636
now accepts an extra argument of the form "HOST:DIRECTORY" or
3737
"USER@HOST:DIRECTORY" to specify a directory on a remote host in which
3838
the diff is to be taken. The diff is run using SSH, the results are
3939
captured, then displayed locally using the Tcl/Tk diff GUI.
40
+ <li> Markdown-to-HTML now recognizes hashtag-like constructs. This
41
+ change is usually invisible but it allows specific pages to
42
+ plug in styling and/or JavaScript functionality. In
43
+ [/help/www/chat|/chat], tapping on a reference in the form
44
+ "#1234" will jump to that message if it's loaded or search for
45
+ it if it's not.
4046
</ol>
4147
4248
<h2 id='v2_28'>Changes for version 2.28 (2026-03-11)</h2><ol>
4349
<li> Improvements to [./antibot.wiki|anti-robot defenses]:<ol type="a">
4450
<li> The default configuration now allows robots to download any tarball
4551
--- www/changes.wiki
+++ www/changes.wiki
@@ -35,10 +35,16 @@
35 <li> The "diff" command (in all its variations) when invoked with --tk
36 now accepts an extra argument of the form "HOST:DIRECTORY" or
37 "USER@HOST:DIRECTORY" to specify a directory on a remote host in which
38 the diff is to be taken. The diff is run using SSH, the results are
39 captured, then displayed locally using the Tcl/Tk diff GUI.
 
 
 
 
 
 
40 </ol>
41
42 <h2 id='v2_28'>Changes for version 2.28 (2026-03-11)</h2><ol>
43 <li> Improvements to [./antibot.wiki|anti-robot defenses]:<ol type="a">
44 <li> The default configuration now allows robots to download any tarball
45
--- www/changes.wiki
+++ www/changes.wiki
@@ -35,10 +35,16 @@
35 <li> The "diff" command (in all its variations) when invoked with --tk
36 now accepts an extra argument of the form "HOST:DIRECTORY" or
37 "USER@HOST:DIRECTORY" to specify a directory on a remote host in which
38 the diff is to be taken. The diff is run using SSH, the results are
39 captured, then displayed locally using the Tcl/Tk diff GUI.
40 <li> Markdown-to-HTML now recognizes hashtag-like constructs. This
41 change is usually invisible but it allows specific pages to
42 plug in styling and/or JavaScript functionality. In
43 [/help/www/chat|/chat], tapping on a reference in the form
44 "#1234" will jump to that message if it's loaded or search for
45 it if it's not.
46 </ol>
47
48 <h2 id='v2_28'>Changes for version 2.28 (2026-03-11)</h2><ol>
49 <li> Improvements to [./antibot.wiki|anti-robot defenses]:<ol type="a">
50 <li> The default configuration now allows robots to download any tarball
51
+61 -61
--- www/ssl.wiki
+++ www/ssl.wiki
@@ -1,25 +1,21 @@
11
<title>Securing a Repository with TLS</title>
22
3
-<h2>Using TLS-Encrypted Communications with Fossil</h2>
3
+<h2>Motivation</h2>
44
55
If you are storing sensitive information in a repository accessible over
66
a network whose integrity you do not fully trust, you should use TLS to
77
encrypt all communications with it. This is most true for repositories
88
accessed over the Internet, especially if they will be accessed from
9
-edge networks you do not control, since that admits of various forms of
9
+edge networks you do not control, since that allows various forms of
1010
[https://en.wikipedia.org/wiki/Man-in-the-middle_attack|man in the
1111
middle attack].
1212
1313
TLS protects the credentials used to access the server, prevents
1414
eavesdropping, prevents in-flight data modification, prevents server
1515
identify spoofing, and more.
1616
17
-There are two major aspects to this, both of which have to be addressed
18
-in different ways. Those are the subjects of the next two major
19
-sections.
20
-
2117
2218
<h2 id="client">Client-Side Configuration</h2>
2319
2420
You can build Fossil against [https://www.openssl.org/ |
2521
OpenSSL] to allow it to clone and sync with a remote
@@ -38,44 +34,33 @@
3834
package manager. Examples:
3935
4036
* <b>RHEL & Fedora</b>: <tt>sudo dnf install openssl-devel</tt>
4137
* <b>Debian & Ubuntu</b>: <tt>sudo apt install libssl-dev</tt>
4238
* <b>FreeBSD</b>: <tt>su -c 'pkg install openssl'</tt>
43
- * <b>macOS</b>: <tt>sudo brew install openssl</tt>
39
+ * <b>macOS</b>: <tt>brew install openssl</tt>
4440
* <b>Cygwin</b>: Install <tt>openssl-devel</tt> via Cygwin's
4541
<tt>setup-*.exe</tt> program
4642
47
-The macOS case requires explanation. Apple last shipped OpenSSL
48
-develpoment files in OS X 10.6 (Snow Leopard), choosing to deprecate it
49
-from that point forward. (Apple wants you to use their proprietary
50
-platform-specific encryption methods instead.) Since macOS has no
51
-built-in package manager, a number have sprung up out of the FOSS world.
52
-It is not known to this author whether Fossil's current build system can
53
-find OpenSSL as installed with any of these other package managers, so
54
-unless you have a particular reason to avoid it, we recomend that you
55
-use [https://brew.sh|Homebrew] on macOS to install OpenSSL as above.
56
-Fossil's build system will seek it out and use it automatically.
43
+The macOS case requires explanation. Apple deprecated OpenSSL in favor
44
+of the nominally API-compatible LibreSSL long ago, but the conformance
45
+often lags in ways that affect whether Fossil will build against it.
46
+The [https://brew.sh | Homebrew] method given above is not the only way
47
+to install the OpenSSL development libraries on macOS, but it *is* the
48
+only one the <tt>configure</tt> script includes automatic checks for.
5749
5850
5951
<h3 id="openssl-src">Building Against a Non-Platform Version of
6052
OpenSSL</h3>
6153
62
-The Fossil build system has a few other methods for finding OpenSSL when
63
-the automatic methods fail or when you'd prefer that Fossil use a
64
-different version of OpenSSL than the one Fossil's build system picks on
65
-its own.
66
-
67
-A good reason to do this is when the Fossil build system finds a
68
-functioning version of OpenSSL which is nevertheless unsuitable. One
69
-common case is that your OS is sufficiently outdated that the platform
54
+The Fossil build system offers manual overrides for finding OpenSSL.
55
+These are useful either when the automatic methods fail or when you'd
56
+prefer that Fossil use a different version of OpenSSL than the one
57
+Fossil's build system picks on its own. Perhaps the most common reason
58
+to do this is when your OS is sufficiently outdated that the platform
7059
version of OpenSSL can no longer communicate with remote systems
71
-adhering to the latest advice on secure communications. An old OpenSSL
72
-might not support any of the
73
-[https://en.wikipedia.org/wiki/Cipher_suite|cipher suites] the remote
74
-Fossil repository's HTTPS proxy is willing to offer, for example, so
75
-that even though both sides are speaking a variant of TLS/SSL, the peers
76
-cannot come to an agreement on the cryptography.
60
+adhering to the latest advice on secure communications for lack of
61
+compatible [https://en.wikipedia.org/wiki/Cipher_suite | cipher suites].
7762
7863
If you've installed the OpenSSL development files somewhere that
7964
Fossil's build system cannot find on its own, you can clue it in by
8065
passing the <tt>--with-openssl</tt> option to the <tt>configure</tt>
8166
script. Type <tt>./configure --help</tt> for details.
@@ -210,53 +195,68 @@
210195
When you build Fossil on Linux platforms against the binary OpenSSL
211196
package provided with the OS, you typically get a root cert store along
212197
with the platform OpenSSL package, either built-in or as a hard
213198
dependency.
214199
215
-
216
-<h4>Client-Side Certificates</h4>
217
-
218
-You can also use client side certificates to add an extra layer of
219
-authentication, over and above Fossil's built in user management. If you
220
-are particularly paranoid, you'll want to use this to remove the ability
221
-of anyone on the internet from making any request to Fossil. Without
222
-presenting a valid client side certificate, the web server won't invoke
223
-the Fossil CGI handler.
224
-
225
-Configure your server to request a client side certificate, and set up a
226
-certificate authority to sign your client certificates. For each person
227
-who needs to access the repository, create a private key and certificate
228
-signed with that CA.
229
-
230
-The PEM encoded private key and certificate should be stored in a single
231
-file, simply by concatenating the key and certificate files. Specify the
232
-location of this file with the <tt>ssl-identity</tt> setting, or the
233
-<tt>--ssl-identity</tt> option to the <tt>clone</tt> command.
234
-
235
-If you've password protected the private key, the password will be
236
-requested every time you connect to the server. This password is not
237
-stored by fossil, as doing so would defeat the purpose of having a
238
-password.
239
-
240
-If you attempt to connect to a server which requests a client
241
-certificate, but don't provide one, fossil will show an error message
242
-which explains what to do to authenticate with the server.
243
-
244200
245201
<h2 id="server">Server-Side Configuration</h2>
246202
247203
Before Fossil's built-in HTTP server gained [./ssl-server.md | TLS support],
248204
system administrators that wanted to add this
249
-had to put it behind a reverse proxy that would do the translation.
205
+had to put it behind a reverse proxy (RP) that would do the translation.
250206
Since advantages remain for delegating TLS to another layer in the
251207
stack, instructions for doing so continue to be included in our
252208
documentation, such as:
253209
254210
* <a id="stunnel" href="./server/any/stunnel.md">Serving via stunnel</a>
255211
* <a id="althttpd" href="./server/any/althttpd.md">Serving via stunnel + althttpd</a>
256212
* <a id="nginx" href="./server/debian/nginx.md#tls">Serving via SCGI with nginx on Debian</a>
257213
214
+One such case is…
215
+
216
+
217
+<h3>Client-Side Certificates</h3>
218
+
219
+One way to harden a Fossil server is to require the <i>clients</i> to
220
+provide their own certificates. This builds on top of the normal TLS
221
+scheme of having the <i>server</i> identify itself to the clients,
222
+producing an [https://en.wikipedia.org/wiki/Mutual_authentication#mTLS |
223
+mTLS] scheme. This prevents random Internet hosts from establishing a
224
+connection to the RP fronting your Fossil server.
225
+
226
+Because Fossil's own built-in TLS serving features do not include mTLS
227
+support, this is one of the solid reasons for continuing to run Fossil
228
+behind a third-party TLS-aware RP.
229
+
230
+The method varies by the RP you choose to delegate this to, and because
231
+there is no tie-in with the configuration of the proxied Fossil server,
232
+we do not include duplicate documentation of it here. The basic idea is
233
+simple enough to sketch here, however: set the RP up to require client
234
+certificates, generate one per client using a mutually-recognized
235
+certificate authority, and register the public half with the RP. Only
236
+those clients possessing a matching private half of one of those
237
+registered key-pairs will then be able to establish an mTLS connection
238
+to the server.
239
+
240
+The Fossil client side does require special configuration to make this
241
+work. First, ensure that the PEM private key file also contains the
242
+public certificate; this is common, though not universal. (If not,
243
+simply concatenate the two in cert-then-key order.) Second, tell your
244
+Fossil client where to find that combined PEM file, either via the
245
+<tt>ssl-identity</tt> setting or by giving the <tt>--ssl-identity</tt>
246
+global option to each <tt>fossil</tt> command using the sync protocol
247
+with a server configured to require mTLS.
248
+
249
+If you've passphrase-protected that PEM file, Fossil will prompt you for
250
+it as part of setting up each of these mTLS-protected sync sessions.
251
+Unlike the regular sync password, Fossil will not offer to store this
252
+PEM passphrase.
253
+
254
+If you attempt to connect to a server which requests a client
255
+certificate but fail to provide one, Fossil will issue a warning
256
+which explains what to do to authenticate with the server.
257
+
258258
259259
<h2 id="enforcing">Enforcing TLS Access</h2>
260260
261261
To use TLS encryption in cloning and syncing to a remote Fossil
262262
repository, be sure to use the <tt>https:</tt> URI scheme in
263263
--- www/ssl.wiki
+++ www/ssl.wiki
@@ -1,25 +1,21 @@
1 <title>Securing a Repository with TLS</title>
2
3 <h2>Using TLS-Encrypted Communications with Fossil</h2>
4
5 If you are storing sensitive information in a repository accessible over
6 a network whose integrity you do not fully trust, you should use TLS to
7 encrypt all communications with it. This is most true for repositories
8 accessed over the Internet, especially if they will be accessed from
9 edge networks you do not control, since that admits of various forms of
10 [https://en.wikipedia.org/wiki/Man-in-the-middle_attack|man in the
11 middle attack].
12
13 TLS protects the credentials used to access the server, prevents
14 eavesdropping, prevents in-flight data modification, prevents server
15 identify spoofing, and more.
16
17 There are two major aspects to this, both of which have to be addressed
18 in different ways. Those are the subjects of the next two major
19 sections.
20
21
22 <h2 id="client">Client-Side Configuration</h2>
23
24 You can build Fossil against [https://www.openssl.org/ |
25 OpenSSL] to allow it to clone and sync with a remote
@@ -38,44 +34,33 @@
38 package manager. Examples:
39
40 * <b>RHEL & Fedora</b>: <tt>sudo dnf install openssl-devel</tt>
41 * <b>Debian & Ubuntu</b>: <tt>sudo apt install libssl-dev</tt>
42 * <b>FreeBSD</b>: <tt>su -c 'pkg install openssl'</tt>
43 * <b>macOS</b>: <tt>sudo brew install openssl</tt>
44 * <b>Cygwin</b>: Install <tt>openssl-devel</tt> via Cygwin's
45 <tt>setup-*.exe</tt> program
46
47 The macOS case requires explanation. Apple last shipped OpenSSL
48 develpoment files in OS X 10.6 (Snow Leopard), choosing to deprecate it
49 from that point forward. (Apple wants you to use their proprietary
50 platform-specific encryption methods instead.) Since macOS has no
51 built-in package manager, a number have sprung up out of the FOSS world.
52 It is not known to this author whether Fossil's current build system can
53 find OpenSSL as installed with any of these other package managers, so
54 unless you have a particular reason to avoid it, we recomend that you
55 use [https://brew.sh|Homebrew] on macOS to install OpenSSL as above.
56 Fossil's build system will seek it out and use it automatically.
57
58
59 <h3 id="openssl-src">Building Against a Non-Platform Version of
60 OpenSSL</h3>
61
62 The Fossil build system has a few other methods for finding OpenSSL when
63 the automatic methods fail or when you'd prefer that Fossil use a
64 different version of OpenSSL than the one Fossil's build system picks on
65 its own.
66
67 A good reason to do this is when the Fossil build system finds a
68 functioning version of OpenSSL which is nevertheless unsuitable. One
69 common case is that your OS is sufficiently outdated that the platform
70 version of OpenSSL can no longer communicate with remote systems
71 adhering to the latest advice on secure communications. An old OpenSSL
72 might not support any of the
73 [https://en.wikipedia.org/wiki/Cipher_suite|cipher suites] the remote
74 Fossil repository's HTTPS proxy is willing to offer, for example, so
75 that even though both sides are speaking a variant of TLS/SSL, the peers
76 cannot come to an agreement on the cryptography.
77
78 If you've installed the OpenSSL development files somewhere that
79 Fossil's build system cannot find on its own, you can clue it in by
80 passing the <tt>--with-openssl</tt> option to the <tt>configure</tt>
81 script. Type <tt>./configure --help</tt> for details.
@@ -210,53 +195,68 @@
210 When you build Fossil on Linux platforms against the binary OpenSSL
211 package provided with the OS, you typically get a root cert store along
212 with the platform OpenSSL package, either built-in or as a hard
213 dependency.
214
215
216 <h4>Client-Side Certificates</h4>
217
218 You can also use client side certificates to add an extra layer of
219 authentication, over and above Fossil's built in user management. If you
220 are particularly paranoid, you'll want to use this to remove the ability
221 of anyone on the internet from making any request to Fossil. Without
222 presenting a valid client side certificate, the web server won't invoke
223 the Fossil CGI handler.
224
225 Configure your server to request a client side certificate, and set up a
226 certificate authority to sign your client certificates. For each person
227 who needs to access the repository, create a private key and certificate
228 signed with that CA.
229
230 The PEM encoded private key and certificate should be stored in a single
231 file, simply by concatenating the key and certificate files. Specify the
232 location of this file with the <tt>ssl-identity</tt> setting, or the
233 <tt>--ssl-identity</tt> option to the <tt>clone</tt> command.
234
235 If you've password protected the private key, the password will be
236 requested every time you connect to the server. This password is not
237 stored by fossil, as doing so would defeat the purpose of having a
238 password.
239
240 If you attempt to connect to a server which requests a client
241 certificate, but don't provide one, fossil will show an error message
242 which explains what to do to authenticate with the server.
243
244
245 <h2 id="server">Server-Side Configuration</h2>
246
247 Before Fossil's built-in HTTP server gained [./ssl-server.md | TLS support],
248 system administrators that wanted to add this
249 had to put it behind a reverse proxy that would do the translation.
250 Since advantages remain for delegating TLS to another layer in the
251 stack, instructions for doing so continue to be included in our
252 documentation, such as:
253
254 * <a id="stunnel" href="./server/any/stunnel.md">Serving via stunnel</a>
255 * <a id="althttpd" href="./server/any/althttpd.md">Serving via stunnel + althttpd</a>
256 * <a id="nginx" href="./server/debian/nginx.md#tls">Serving via SCGI with nginx on Debian</a>
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
259 <h2 id="enforcing">Enforcing TLS Access</h2>
260
261 To use TLS encryption in cloning and syncing to a remote Fossil
262 repository, be sure to use the <tt>https:</tt> URI scheme in
263
--- www/ssl.wiki
+++ www/ssl.wiki
@@ -1,25 +1,21 @@
1 <title>Securing a Repository with TLS</title>
2
3 <h2>Motivation</h2>
4
5 If you are storing sensitive information in a repository accessible over
6 a network whose integrity you do not fully trust, you should use TLS to
7 encrypt all communications with it. This is most true for repositories
8 accessed over the Internet, especially if they will be accessed from
9 edge networks you do not control, since that allows various forms of
10 [https://en.wikipedia.org/wiki/Man-in-the-middle_attack|man in the
11 middle attack].
12
13 TLS protects the credentials used to access the server, prevents
14 eavesdropping, prevents in-flight data modification, prevents server
15 identify spoofing, and more.
16
 
 
 
 
17
18 <h2 id="client">Client-Side Configuration</h2>
19
20 You can build Fossil against [https://www.openssl.org/ |
21 OpenSSL] to allow it to clone and sync with a remote
@@ -38,44 +34,33 @@
34 package manager. Examples:
35
36 * <b>RHEL & Fedora</b>: <tt>sudo dnf install openssl-devel</tt>
37 * <b>Debian & Ubuntu</b>: <tt>sudo apt install libssl-dev</tt>
38 * <b>FreeBSD</b>: <tt>su -c 'pkg install openssl'</tt>
39 * <b>macOS</b>: <tt>brew install openssl</tt>
40 * <b>Cygwin</b>: Install <tt>openssl-devel</tt> via Cygwin's
41 <tt>setup-*.exe</tt> program
42
43 The macOS case requires explanation. Apple deprecated OpenSSL in favor
44 of the nominally API-compatible LibreSSL long ago, but the conformance
45 often lags in ways that affect whether Fossil will build against it.
46 The [https://brew.sh | Homebrew] method given above is not the only way
47 to install the OpenSSL development libraries on macOS, but it *is* the
48 only one the <tt>configure</tt> script includes automatic checks for.
 
 
 
 
49
50
51 <h3 id="openssl-src">Building Against a Non-Platform Version of
52 OpenSSL</h3>
53
54 The Fossil build system offers manual overrides for finding OpenSSL.
55 These are useful either when the automatic methods fail or when you'd
56 prefer that Fossil use a different version of OpenSSL than the one
57 Fossil's build system picks on its own. Perhaps the most common reason
58 to do this is when your OS is sufficiently outdated that the platform
 
 
 
59 version of OpenSSL can no longer communicate with remote systems
60 adhering to the latest advice on secure communications for lack of
61 compatible [https://en.wikipedia.org/wiki/Cipher_suite | cipher suites].
 
 
 
 
62
63 If you've installed the OpenSSL development files somewhere that
64 Fossil's build system cannot find on its own, you can clue it in by
65 passing the <tt>--with-openssl</tt> option to the <tt>configure</tt>
66 script. Type <tt>./configure --help</tt> for details.
@@ -210,53 +195,68 @@
195 When you build Fossil on Linux platforms against the binary OpenSSL
196 package provided with the OS, you typically get a root cert store along
197 with the platform OpenSSL package, either built-in or as a hard
198 dependency.
199
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
201 <h2 id="server">Server-Side Configuration</h2>
202
203 Before Fossil's built-in HTTP server gained [./ssl-server.md | TLS support],
204 system administrators that wanted to add this
205 had to put it behind a reverse proxy (RP) that would do the translation.
206 Since advantages remain for delegating TLS to another layer in the
207 stack, instructions for doing so continue to be included in our
208 documentation, such as:
209
210 * <a id="stunnel" href="./server/any/stunnel.md">Serving via stunnel</a>
211 * <a id="althttpd" href="./server/any/althttpd.md">Serving via stunnel + althttpd</a>
212 * <a id="nginx" href="./server/debian/nginx.md#tls">Serving via SCGI with nginx on Debian</a>
213
214 One such case is…
215
216
217 <h3>Client-Side Certificates</h3>
218
219 One way to harden a Fossil server is to require the <i>clients</i> to
220 provide their own certificates. This builds on top of the normal TLS
221 scheme of having the <i>server</i> identify itself to the clients,
222 producing an [https://en.wikipedia.org/wiki/Mutual_authentication#mTLS |
223 mTLS] scheme. This prevents random Internet hosts from establishing a
224 connection to the RP fronting your Fossil server.
225
226 Because Fossil's own built-in TLS serving features do not include mTLS
227 support, this is one of the solid reasons for continuing to run Fossil
228 behind a third-party TLS-aware RP.
229
230 The method varies by the RP you choose to delegate this to, and because
231 there is no tie-in with the configuration of the proxied Fossil server,
232 we do not include duplicate documentation of it here. The basic idea is
233 simple enough to sketch here, however: set the RP up to require client
234 certificates, generate one per client using a mutually-recognized
235 certificate authority, and register the public half with the RP. Only
236 those clients possessing a matching private half of one of those
237 registered key-pairs will then be able to establish an mTLS connection
238 to the server.
239
240 The Fossil client side does require special configuration to make this
241 work. First, ensure that the PEM private key file also contains the
242 public certificate; this is common, though not universal. (If not,
243 simply concatenate the two in cert-then-key order.) Second, tell your
244 Fossil client where to find that combined PEM file, either via the
245 <tt>ssl-identity</tt> setting or by giving the <tt>--ssl-identity</tt>
246 global option to each <tt>fossil</tt> command using the sync protocol
247 with a server configured to require mTLS.
248
249 If you've passphrase-protected that PEM file, Fossil will prompt you for
250 it as part of setting up each of these mTLS-protected sync sessions.
251 Unlike the regular sync password, Fossil will not offer to store this
252 PEM passphrase.
253
254 If you attempt to connect to a server which requests a client
255 certificate but fail to provide one, Fossil will issue a warning
256 which explains what to do to authenticate with the server.
257
258
259 <h2 id="enforcing">Enforcing TLS Access</h2>
260
261 To use TLS encryption in cloning and syncing to a remote Fossil
262 repository, be sure to use the <tt>https:</tt> URI scheme in
263

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button