Merge the latest trunk enhancements into the wal2 branch.

FossilOrigin-Name: c8ad869938b06378f49c02655c00ee4f3315e1275d15e69d4ff61d6f60230fe8
This commit is contained in:
drh 2022-06-16 13:44:49 +00:00
commit 029eb1bf15
50 changed files with 37172 additions and 2316 deletions

View File

@ -631,7 +631,7 @@ FUZZCHECK_OPT += -DSQLITE_ENABLE_RTREE
FUZZCHECK_OPT += -DSQLITE_ENABLE_GEOPOLY
FUZZCHECK_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB
FUZZCHECK_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB
FUZZCHECK_SRC = $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c
FUZZCHECK_SRC = $(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c $(TOP)/test/fuzzinvariants.c
DBFUZZ_OPT =
# This is the default Makefile target. The objects listed here
@ -1523,48 +1523,66 @@ fiddle_dir_abs = $(TOP)/$(fiddle_dir)
# ^^^ some emcc opts require absolute paths
fiddle_html = $(fiddle_dir)/fiddle.html
fiddle_module_js = $(fiddle_dir)/fiddle-module.js
fiddle_generated = $(fiddle_module_js) \
$(fiddle_dir)/fiddle-module.wasm
sqlite3_wasm_js = $(fiddle_dir)/sqlite3.js
sqlite3_wasm = $(fiddle_dir)/sqlite3.wasm
sqlite3_wasm_generated = $(sqlite3_wasm) $(sqlite3_wasm_js)
clean-wasm:
rm -f $(fiddle_generated) $(sqlite3_wasm_generated)
clean: clean-wasm
#emcc_opt = -O0
#emcc_opt = -O1
#emcc_opt = -O2
#emcc_opt = -O3
emcc_opt = -Oz
emcc_flags = $(emcc_opt) -sALLOW_TABLE_GROWTH -I. $(SHELL_OPT)
emcc_flags = $(emcc_opt) -sALLOW_TABLE_GROWTH -sSTRICT_JS \
-sENVIRONMENT=web -sMODULARIZE \
-sEXPORTED_RUNTIME_METHODS=@$(fiddle_dir_abs)/EXPORTED_RUNTIME_METHODS \
-sDYNAMIC_EXECUTION=0 \
-I. $(SHELL_OPT)
$(fiddle_module_js): Makefile sqlite3.c shell.c \
$(fiddle_dir)/EXPORTED_RUNTIME_METHODS \
$(fiddle_dir)/EXPORTED_FUNCTIONS.fiddle
emcc -o $@ $(emcc_flags) \
-sENVIRONMENT=web \
-sMODULARIZE \
-sEXPORT_NAME=initFiddleModule \
-sEXPORTED_RUNTIME_METHODS=@$(fiddle_dir_abs)/EXPORTED_RUNTIME_METHODS \
-sEXPORTED_FUNCTIONS=@$(fiddle_dir_abs)/EXPORTED_FUNCTIONS.fiddle \
sqlite3.c shell.c
gzip < $@ > $@.gz
gzip < $(fiddle_dir)/fiddle-module.wasm > $(fiddle_dir)/fiddle-module.wasm.gz
$(sqlite3_wasm_js): Makefile sqlite3.c \
$(fiddle_dir)/sqlite3-api.js \
$(fiddle_dir)/EXPORTED_RUNTIME_METHODS \
$(fiddle_dir)/EXPORTED_FUNCTIONS.sqlite3-api
emcc -o $@ $(emcc_flags) \
-sENVIRONMENT=web \
-sMODULARIZE \
-sEXPORT_NAME=initSqlite3Module \
-sEXPORTED_RUNTIME_METHODS=@$(fiddle_dir_abs)/EXPORTED_RUNTIME_METHODS \
-sEXPORTED_FUNCTIONS=@$(fiddle_dir_abs)/EXPORTED_FUNCTIONS.sqlite3-api \
--post-js=$(fiddle_dir)/sqlite3-api.js \
--no-entry \
sqlite3.c
fiddle: $(fiddle_module_js)
gzip < $@ > $@.gz
gzip < $(sqlite3_wasm) > $(sqlite3_wasm).gz
gzip < $(fiddle_dir)/sqlite3-api.js > $(fiddle_dir)/sqlite3-api.js.gz
$(fiddle_dir)/fiddle.js.gz: $(fiddle_dir)/fiddle.js
gzip < $< > $@
$(fiddle_dir)/sqlite3-api.js.gz: $(fiddle_dir)/sqlite3-api.js
gzip < $< > $@
fiddle_generated = $(fiddle_module_js) $(fiddle_module_js).gz \
$(fiddle_dir)/fiddle-module.wasm \
$(fiddle_dir)/fiddle-module.wasm.gz \
$(fiddle_dir)/fiddle.js.gz
sqlite3_wasm_generated = \
$(sqlite3_wasm) $(sqlite3_wasm).gz \
$(sqlite3_wasm_js) $(sqlite3_wasm_js).gz \
$(fiddle_dir)/sqlite3.js.gz \
$(fiddle_dir)/sqlite3-api.js.gz
clean-wasm:
rm -f $(fiddle_generated) $(sqlite3_wasm_generated)
clean: clean-wasm
fiddle: $(fiddle_module_js) $(fiddle_dir)/fiddle.js.gz
sqlite3-wasm: $(sqlite3_wasm_js)
wasm: fiddle sqlite3-wasm
########################################################################
# Explanation of the emcc build flags:
# Explanation of the emcc build flags follows. Full docs for these can
# be found at:
#
# https://github.com/emscripten-core/emscripten/blob/main/src/settings.js
#
# -sENVIRONMENT=web: elides bootstrap code related to non-web JS
# environments like node.js. Removing this makes the output a tiny
@ -1588,6 +1606,23 @@ wasm: fiddle sqlite3-wasm
# developers. e.g., _sqlite3_open_v2 and _sqlite3_finalize. Must be
# an absolute path!
#
# -sSTRICT_JS ensures that the emitted JS code includes the 'use
# strict' option. Note that -sSTRICT is more broadly-scoped and
# results in build errors.
#
# -sALLOW_TABLE_GROWTH is required for (at a minimum) the UDF-binding
# feature.
#
# -sDYNAMIC_EXECUTION=0 disables eval() and the Function constructor.
# If the build runs without these, it's preferable to use this flag
# because certain execution environments disallow those constructs.
# This flag is not strictly necessary, however.
#
# -sWASM_BIGINT is UNTESTED but "should" allow the int64-using C APIs
# to work with JS/wasm, insofar as the JS environment supports the
# BigInt type. That support requires an extremely recent browser:
# Safari didn't get that support until late 2020.
#
# --no-entry: for compiling library code with no main(). If this is
# not supplied and the code has a main(), it is called as part of the
# module init process. Note that main() is #if'd out of shell.c

View File

@ -1706,7 +1706,7 @@ FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_GEOPOLY
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_DBSTAT_VTAB
FUZZCHECK_OPTS = $(FUZZCHECK_OPTS) -DSQLITE_ENABLE_BYTECODE_VTAB
FUZZCHECK_SRC = $(TOP)\test\fuzzcheck.c $(TOP)\test\ossfuzz.c
FUZZCHECK_SRC = $(TOP)\test\fuzzcheck.c $(TOP)\test\ossfuzz.c $(TOP)\test\fuzzinvariants.c
OSSSHELL_SRC = $(TOP)\test\ossshell.c $(TOP)\test\ossfuzz.c
DBFUZZ_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION
KV_COMPILE_OPTS = -DSQLITE_THREADSAFE=0 -DSQLITE_DIRECT_OVERFLOW_READ

View File

@ -8,7 +8,8 @@ clean:
fiddle_files = emscripten.css fiddle.html \
fiddle.js fiddle-module.js \
fiddle-module.wasm fiddle-worker.js
fiddle-module.wasm fiddle-worker.js \
$(wildcard *.wasm.gz) $(wildcard *.js.gz)
# fiddle_remote is the remote destination for the fiddle app. It
# must be a [user@]HOST:/path for rsync.

View File

@ -234,8 +234,9 @@
fiddleModule.FS.createDataFile("/", fn, buffer, true, true);
const oldName = Sqlite3Shell.dbFilename();
Sqlite3Shell.exec('.open "/'+fn+'"');
if(oldName !== fn){
fiddleModule.FS.unlink(oldName);
if(oldName && oldName !== fn){
try{fiddleModule.FS.unlink(oldName);}
catch(e){/*ignored*/}
}
stdout("Replaced DB with",fn+".");
return;

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>sqlite3 fiddle</title>
<title>SQLite3 Fiddle</title>
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
<!-- to add a togglable terminal-style view, uncomment the following
two lines and ensure that these files are on the web server. -->
@ -12,50 +12,97 @@
<link rel="stylesheet" href="emscripten.css"/>
<style>
/* The following styles are for app-level use. */
:root {
--sqlite-blue: #044a64;
--textarea-color1: #044a64;
--textarea-color2: white;
}
textarea {
font-family: monospace;
flex: 1 1 auto;
background-color: var(--textarea-color1);
color: var(--textarea-color2);
}
textarea#input {
color: var(--textarea-color1);
background-color: var(--textarea-color2);
}
header {
font-size: 130%;
display: flex;
justify-content: space-between;
align-items: center;
background-color: var(--sqlite-blue);
color: white;
font-size: 120%;
font-weight: bold;
border-radius: 0.25em;
padding: 0.2em 0.5em;
}
header > .powered-by {
font-size: 80%;
}
header a, header a:visited, header a:hover {
color: inherit;
}
#main-wrapper {
display: flex;
flex-direction: column-reverse;
flex: 20 1 auto;
flex: 1 1 auto;
margin: 0.5em 0;
overflow: hidden;
}
#main-wrapper.side-by-side {
flex-direction: row-reverse;
flex-direction: row;
}
#main-wrapper.side-by-side > fieldset {
margin-left: 0.25em;
margin-right: 0.25em;
}
#main-wrapper:not(.side-by-side) > fieldset {
margin-bottom: 0.25em;
}
#main-wrapper.swapio {
flex-direction: column;
}
#main-wrapper.side-by-side.swapio {
flex-direction: row;
flex-direction: row-reverse;
}
.ta-wrapper{
.zone-wrapper{
display: flex;
flex-direction: column;
align-items: stretch;
margin: 0 0.25em;
margin: 0;
flex: 1 1 0%;
border-radius: 0.5em;
min-width: inherit/*important: resolves inability to scroll fieldset child element!*/;
padding: 0.35em 0 0 0;
}
.zone-wrapper textarea {
border-radius: 0.5em;
flex: 1 1 auto;
/*min/max width resolve an inexplicable margin on the RHS. The -1em
is for the padding, else we overlap the parent boundaries.*/
/*min-width: calc(100% - 1em);
max-width: calc(100% - 1em);
padding: 0 0.5em;*/
}
.ta-wrapper.input { flex: 10 1 auto; }
.ta-wrapper.output { flex: 20 1 auto; }
.ta-wrapper textarea {
font-size: 110%;
filter: invert(100%);
flex: 10 1 auto;
.zone-wrapper.input { flex: 10 1 auto; }
.zone-wrapper.output { flex: 20 1 auto; }
.zone-wrapper > div {
display:flex;
flex: 1 1 0%;
}
.zone-wrapper.output {}
.button-bar {
display: flex;
justify-content: center;
flex: 0 1 auto;
flex-wrap: wrap;
align-items: center;
align-content: space-between;
justify-content: flex-start;
}
.button-bar button {
margin: 0.25em 1em;
.button-bar > * {
margin: 0.05em 0.5em 0.05em 0;
flex: 0 1 auto;
align-self: auto;
}
label[for] {
cursor: pointer;
@ -70,97 +117,68 @@
pointer-events: none !important;
display: none !important;
}
/* Safari supports neither styling of nor event handling on a
fieldset legend, so we emulate a fieldset-like widget. */
.fieldset {
fieldset {
border-radius: 0.5em;
border: 1px inset;
display: flex;
flex-direction: column;
padding: 0.25em;
}
.fieldset > .legend {
position: relative;
top: -1.5ex;
padding: 0 0.5em;
font-size: 85%;
margin-left: 0.5em;
flex: 0 1 auto;
align-self: self-start;
cursor: pointer;
fieldset.options {
font-size: 80%;
margin-top: 0.5em;
}
.fieldset.options > div {
fieldset:not(.options) > legend {
font-size: 80%;
}
fieldset.options > div {
display: flex;
flex-wrap: wrap;
font-size: 70%;
margin: 0 0.5em 0.5em 0.5em;
}
.fieldset > .legend > span {
position: relative;
fieldset button {
font-size: inherit;
}
.fieldset > .legend::before {
/* Hide the parent element's top border where this
element intersects it. */
content: ' ';
width: 100%;
height: 100%;
background-color: white
/* REALLY want to 'inherit' the color from the fieldset's
parent, but inherit leads to a transparent bg, which is
exactly what we're trying to avoid here. */;
opacity: 1;
position: absolute;
top: 0;
left: 0;
}
.fieldset > .legend::after {
fieldset.collapsible > legend > .fieldset-toggle::after {
content: " [hide]";
position: relative;
}
.fieldset.collapsed > .legend::after {
fieldset.collapsible.collapsed > legend > .fieldset-toggle::after {
content: " [show]";
position: relative;
}
span.labeled-input {
padding: 0.25em;
margin: 0.25em 0.5em;
margin: 0.05em 0.25em;
border-radius: 0.25em;
white-space: nowrap;
background: #0002;
display: flex;
align-items: center;
}
#notes-caveats {
border-top: 1px dotted;
padding-top: 0.25em;
margin-top: 0.5em;
span.labeled-input > *:nth-child(2) {
margin-left: 0.3em;
}
.center { text-align: center; }
body.terminal-mode {
max-height: calc(100% - 2em);
display: flex;
flex-direction: column;
align-items: stretch;
}
#view-terminal {
}
#view-terminal {}
.app-view {
flex: 20 1 auto;
}
#titlebar {
display: flex;
justify-content: space-between;
margin-bottom: 0.5em;
}
#view-split {
display: flex;
flex-direction: column-reverse;
}
#view-split > .fieldset.options {
margin-top: 0.5em;
}
</style>
</head>
<body>
<header id='titlebar'><span>sqlite3 fiddle</span></header>
<header id='titlebar'>
<span>SQLite3 Fiddle</span>
<span class='powered-by'>Powered by
<a href='https://sqlite.org'>SQLite3</a></span>
</header>
<!-- emscripten bits -->
<figure id="module-spinner">
<div class="spinner"></div>
@ -177,12 +195,13 @@
</div><!-- /emscripten bits -->
<div id='view-terminal' class='app-view hidden initially-hidden'>
This is a placeholder for a terminal-like view.
This is a placeholder for a terminal-like view which is not in
the default build.
</div>
<div id='view-split' class='app-view initially-hidden'>
<div class='fieldset options collapsible'>
<span class='legend'><span>Options</span></span>
<fieldset class='options collapsible'>
<legend><button class='fieldset-toggle'>Options</button></legend>
<div class=''>
<span class='labeled-input'>
<input type='checkbox' id='opt-cb-sbs'
@ -209,8 +228,8 @@
<label for='opt-cb-autoclear'>Auto-clear output</label>
</span>
<span class='labeled-input'>
<input type='file' id='load-db'/>
<label>Load DB</label>
<input type='file' id='load-db' class='hidden'/>
<button id='btn-load-db'>Load DB...</button>
</span>
<span class='labeled-input'>
<button id='btn-export'>Download DB</button>
@ -218,40 +237,40 @@
<span class='labeled-input'>
<button id='btn-reset'>Reset DB</button>
</span>
<span class='labeled-input'>
<select id='select-examples'></select>
</span>
</div>
</div><!-- .fieldset -->
</fieldset><!-- .options -->
<div id='main-wrapper' class=''>
<div class='ta-wrapper input'>
<textarea id="input"
placeholder="Shell input. Ctrl-enter/shift-enter runs it.">
-- Use ctrl-enter or shift-enter to execute SQL. If only a subset
-- is currently selected, only that part is executed.
.nullvalue NULL
.mode box
CREATE TABLE t(a,b);
INSERT INTO t(a,b) VALUES('abc',123),('def',456),(NULL,789),('ghi',012);
SELECT * FROM t;</textarea>
<div class='button-bar'>
<fieldset class='zone-wrapper input'>
<legend><div class='button-bar'>
<button id='btn-shell-exec'>Run</button>
<button id='btn-clear'>Clear Input</button>
<button data-cmd='.help'>Help</button>
</div>
</div>
<div class='ta-wrapper output'>
<textarea id="output" readonly
placeholder="Shell output."></textarea>
<div class='button-bar'>
<!--button data-cmd='.help'>Help</button-->
<select id='select-examples'></select>
</div></legend>
<div><textarea id="input"
placeholder="Shell input. Ctrl-enter/shift-enter runs it.">
-- ==================================================
-- Use ctrl-enter or shift-enter to execute sqlite3
-- shell commands and SQL.
-- If a subset of the text is currently selected,
-- only that part is executed.
-- ==================================================
.nullvalue NULL
.headers on
</textarea></div>
</fieldset>
<fieldset class='zone-wrapper output'>
<legend><div class='button-bar'>
<button id='btn-clear-output'>Clear Output</button>
<button id='btn-interrupt' class='hidden' disabled>Interrupt</button>
<!-- interruption cannot work in the current configuration
because we cannot send an interrupt message when work
is currently underway. At that point the Worker is
tied up and will not receive the message. -->
</div>
</div>
</div></legend>
<div><textarea id="output" readonly
placeholder="Shell output."></textarea></div>
</fieldset>
</div>
</div> <!-- #view-split -->
<!-- Maintenance notes:

View File

@ -216,7 +216,7 @@
That slows it down but is useful for testing. */
echoToConsole: false,
/* If true, display input/output areas side-by-side. */
sideBySide: false,
sideBySide: true,
/* If true, swap positions of the input/output areas. */
swapInOut: false
},
@ -525,19 +525,35 @@
/** Initiate a download of the db. */
const btnExport = E('#btn-export');
const eDisableDuringExport = [
/* UI elements to disable while export is running. Normally
the export is fast enough that this won't matter, but we
really don't want to be reading (from outside of sqlite)
the db when the user taps btnShellExec. */
btnShellExec, btnExport
];
const eLoadDb = E('#load-db');
const btnLoadDb = E('#btn-load-db');
btnLoadDb.addEventListener('click', ()=>eLoadDb.click());
/**
Enables (if passed true) or disables all UI elements which
"might," if timed "just right," interfere with an
in-progress db import/export/exec operation.
*/
const enableMutatingElements = function f(enable){
if(!f._elems){
f._elems = [
/* UI elements to disable while import/export are
running. Normally the export is fast enough
that this won't matter, but we really don't
want to be reading (from outside of sqlite) the
db when the user taps btnShellExec. */
btnShellExec, btnExport, eLoadDb
];
}
f._elems.forEach( enable
? (e)=>e.removeAttribute('disabled')
: (e)=>e.setAttribute('disabled','disabled') );
};
btnExport.addEventListener('click',function(){
eDisableDuringExport.forEach(e=>e.setAttribute('disabled','disabled'));
enableMutatingElements(false);
SF.wMsg('db-export');
});
SF.addMsgHandler('db-export', function(ev){
eDisableDuringExport.forEach(e=>e.removeAttribute('disabled'));
enableMutatingElements(true);
ev = ev.data;
if(ev.error){
SF.echo("Export failed:",ev.error);
@ -560,11 +576,11 @@
/**
Handle load/import of an external db file.
*/
E('#load-db').addEventListener('change',function(){
eLoadDb.addEventListener('change',function(){
const f = this.files[0];
const r = new FileReader();
const status = {loaded: 0, total: 0};
this.setAttribute('disabled','disabled');
enableMutatingElements(false);
r.addEventListener('loadstart', function(){
SF.echo("Loading",f.name,"...");
});
@ -573,7 +589,7 @@
});
const that = this;
r.addEventListener('load', function(){
that.removeAttribute('disabled');
enableMutatingElements(true);
SF.echo("Loaded",f.name+". Opening db...");
SF.wMsg('open',{
filename: f.name,
@ -581,25 +597,25 @@
});
});
r.addEventListener('error',function(){
that.removeAttribute('disabled');
enableMutatingElements(true);
SF.echo("Loading",f.name,"failed for unknown reasons.");
});
r.addEventListener('abort',function(){
that.removeAttribute('disabled');
enableMutatingElements(true);
SF.echo("Cancelled loading of",f.name+".");
});
r.readAsArrayBuffer(f);
});
EAll('.fieldset.collapsible').forEach(function(fs){
const legend = E(fs,'span.legend'),
EAll('fieldset.collapsible').forEach(function(fs){
const btnToggle = E(fs,'legend > .fieldset-toggle'),
content = EAll(fs,':scope > div');
legend.addEventListener('click', function(){
btnToggle.addEventListener('click', function(){
fs.classList.toggle('collapsed');
content.forEach((d)=>d.classList.toggle('hidden'));
}, false);
});
/**
Given a DOM element, this routine measures its "effective
height", which is the bounding top/bottom range of this element
@ -706,6 +722,14 @@
(function(){
const xElem = E('#select-examples');
const examples = [
{name: "Help", sql:
`-- ================================================
-- Use ctrl-enter or shift-enter to execute sqlite3
-- shell commands and SQL.
-- If a subset of the text is currently selected,
-- only that part is executed.
-- ================================================
.help`},
{name: "Timer on", sql: ".timer on"},
{name: "Setup table T", sql:`.nullvalue NULL
CREATE TABLE t(a,b);
@ -779,5 +803,7 @@ SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;`}
'any number of changes or outright removal at any time.\n');
delete ForceResizeKludge.$disabled;
ForceResizeKludge();
btnShellExec.click();
}/*onSFLoaded()*/;
})();

View File

@ -85,7 +85,8 @@
if(!Module.postRun) Module.postRun = [];
/* ^^^^ the name Module is, in this setup, scope-local in the generated
file sqlite3.js, with which this file gets combined at build-time. */
Module.postRun.push(function(namespace){
Module.postRun.push(function(namespace/*the module object, the target for
installed features*/){
'use strict';
/* For reference: sql.js does essentially everything we want and
it solves much of the wasm-related voodoo, but we'll need a
@ -221,11 +222,10 @@ Module.postRun.push(function(namespace){
["sqlite3_prepare_v2", "number", ["number", "string", "number", "number", "number"]],
["sqlite3_prepare_v2_sqlptr", "sqlite3_prepare_v2",
/* Impl which requires that the 2nd argument be a pointer to
the SQL, instead of a string. This is used for cases where
we require a non-NULL value for the final argument. We may
or may not need this, depending on how our higher-level
API shapes up, but this code's spiritual guide (sql.js)
uses it we we'll include it. */
the SQL string, instead of being converted to a
string. This is used for cases where we require a non-NULL
value for the final argument (exec()'ing multiple
statements from one input string). */
"number", ["number", "number", "number", "number", "number"]],
["sqlite3_reset", "number", ["number"]],
["sqlite3_result_blob",null,["number", "number", "number", "number"]],
@ -270,33 +270,56 @@ Module.postRun.push(function(namespace){
- ()
- (undefined) (same effect as ())
- (Uint8Array holding an sqlite3 db image)
- (filename[,buffer])
- (buffer)
It always generates a random filename and sets is to
the `filename` property of this object.
Where a buffer indicates a Uint8Array holding an sqlite3 db
image.
Developer's note: the reason it does not (any longer) support
":memory:" as a name is because we can apparently only export
images of DBs which are stored in the pseudo-filesystem
provided by the JS APIs. Since exporting and importing images
is an important usability feature for this class, ":memory:"
DBs are not supported (until/unless we can find a way to export
those as well). The naming semantics will certainly evolve as
this API does.
If the filename is provided, only the last component of the
path is used - any path prefix is stripped and certain
"special" characters are replaced with `_`. If no name is
provided, a random name is generated. The resulting filename is
the one used for accessing the db file within root directory of
the emscripten-supplied virtual filesystem, and is set (with no
path part) as the DB object's `filename` property.
Note that the special sqlite3 db names ":memory:" and ""
(temporary db) have no special meanings here. We can apparently
only export images of DBs which are stored in the
pseudo-filesystem provided by the JS APIs. Since exporting and
importing images is an important usability feature for this
class, ":memory:" DBs are not supported (until/unless we can
find a way to export those as well). The naming semantics will
certainly evolve as this API does.
*/
const DB = function(arg){
const fn = "db-"+((Math.random() * 10000000) | 0)+
"-"+((Math.random() * 10000000) | 0)+".sqlite3";
let buffer;
if(name instanceof Uint8Array){
let buffer, fn;
if(arg instanceof Uint8Array){
buffer = arg;
arg = undefined;
}else if(arguments.length && undefined!==arg){
toss("Invalid arguments to DB constructor.",
"Expecting no args, undefined, or a",
"sqlite3 file as a Uint8Array.");
}else if(arguments.length){ /*(filename[,buffer])*/
if('string'===typeof arg){
const p = arg.split('/').pop().replace(':','_');
if(p) fn = p;
if(arguments.length>1){
buffer = arguments[1];
}
}else if(undefined!==arg){
toss("Invalid arguments to DB constructor.",
"Expecting (), (undefined), (name,buffer),",
"or (buffer), where buffer an sqlite3 db ",
"as a Uint8Array.");
}
}
if(!fn){
fn = "db-"+((Math.random() * 10000000) | 0)+
"-"+((Math.random() * 10000000) | 0)+".sqlite3";
}
if(buffer){
if(!(buffer instanceof Uint8Array)){
toss("Expecting Uint8Array image of db contents.");
}
FS.createDataFile("/", fn, buffer, true, true);
}
setValue(pPtrArg, 0, "i32");
@ -381,9 +404,9 @@ Module.postRun.push(function(namespace){
default: toss("Invalid argument count for exec().");
};
if('string'!==typeof out.sql) toss("Missing SQL argument.");
if(out.opt.callback){
if(out.opt.callback || out.opt.resultRows){
switch((undefined===out.opt.rowMode)
? 'stmt' : out.opt.rowMode) {
? 'stmt' : out.opt.rowMode) {
case 'object': out.cbArg = (stmt)=>stmt.get({}); break;
case 'array': out.cbArg = (stmt)=>stmt.get([]); break;
case 'stmt': out.cbArg = (stmt)=>stmt; break;
@ -417,9 +440,12 @@ Module.postRun.push(function(namespace){
/**
Finalizes all open statements and closes this database
connection. This is a no-op if the db has already been
closed.
closed. If the db is open and alsoUnlink is truthy then the
this.filename entry in the pseudo-filesystem will also be
removed (and any error in that attempt is silently
ignored).
*/
close: function(){
close: function(alsoUnlink){
if(this._pDb){
let s;
const that = this;
@ -430,9 +456,15 @@ Module.postRun.push(function(namespace){
Object.values(this._udfs).forEach(SQM.removeFunction);
delete this._udfs;
delete this._statements;
delete this.filename;
api.sqlite3_close_v2(this._pDb);
delete this._pDb;
if(this.filename){
if(alsoUnlink){
try{SQM.FS.unlink('/'+this.filename);}
catch(e){/*ignored*/}
}
delete this.filename;
}
}
},
/**
@ -461,16 +493,38 @@ Module.postRun.push(function(namespace){
return stmt;
},
/**
This function works like execMulti(), and takes the same
arguments, but is more efficient (performs much less work)
when the input SQL is only a single statement. If passed a
multi-statement SQL, it only processes the first one.
This function works like execMulti(), and takes most of the
same arguments, but is more efficient (performs much less
work) when the input SQL is only a single statement. If
passed a multi-statement SQL, it only processes the first
one.
This function supports one additional option not used by
execMulti():
This function supports the following additional options not
supported by execMulti():
- .multi: if true, this function acts as a proxy for
execMulti().
execMulti() and behaves identically to that function.
- .resultRows: if this is an array, each row of the result
set (if any) is appended to it in the format specified
for the `rowMode` property, with the exception that the
only legal values for `rowMode` in this case are 'array'
or 'object', neither of which is the default. It is legal
to use both `resultRows` and `callback`, but `resultRows`
is likely much simpler to use for small data sets and can
be used over a WebWorker-style message interface.
- .columnNames: if this is an array and the query has
result columns, the array is passed to
Stmt.getColumnNames() to append the column names to it
(regardless of whether the query produces any result
rows). If the query has no result columns, this value is
unchanged.
The following options to execMulti() are _not_ supported by
this method (they are simply ignored):
- .saveSql
*/
exec: function(/*(sql [,optionsObj]) or (optionsObj)*/){
affirmDbOpen(this);
@ -480,15 +534,29 @@ Module.postRun.push(function(namespace){
return this.execMulti(arg, undefined, BindTypes);
}
const opt = arg.opt;
let stmt;
let stmt, rowTarget;
try {
if(Array.isArray(opt.resultRows)){
if(opt.rowMode!=='array' && opt.rowMode!=='object'){
toss("Invalid rowMode for resultRows array: must",
"be one of 'array' or 'object'.");
}
rowTarget = opt.resultRows;
}
stmt = this.prepare(arg.sql);
if(stmt.columnCount && Array.isArray(opt.columnNames)){
stmt.getColumnNames(opt.columnNames);
}
if(opt.bind) stmt.bind(opt.bind);
if(opt.callback){
if(opt.callback || rowTarget){
while(stmt.step()){
stmt._isLocked = true;
opt.callback(arg.cbArg(stmt), stmt);
stmt._isLocked = false;
const row = arg.cbArg(stmt);
if(rowTarget) rowTarget.push(row);
if(opt.callback){
stmt._isLocked = true;
opt.callback(row, stmt);
stmt._isLocked = false;
}
}
}else{
stmt.step();
@ -503,10 +571,11 @@ Module.postRun.push(function(namespace){
}/*exec()*/,
/**
Executes one or more SQL statements. Its arguments
must be either (sql,optionsObject) or (optionsObject).
In the latter case, optionsObject.sql must contain the
SQL to execute. Returns this object. Throws on error.
Executes one or more SQL statements in the form of a single
string. Its arguments must be either (sql,optionsObject) or
(optionsObject). In the latter case, optionsObject.sql
must contain the SQL to execute. Returns this
object. Throws on error.
If no SQL is provided, or a non-string is provided, an
exception is triggered. Empty SQL, on the other hand, is
@ -547,15 +616,20 @@ Module.postRun.push(function(namespace){
don't have the string until after that). Empty SQL
statements are elided.
See also the exec() method, which is a close cousin of this
one.
ACHTUNG #1: The callback MUST NOT modify the Stmt
object. Calling any of the Stmt.get() variants,
Stmt.getColumnName(), or simililar, is legal, but calling
Stmt.getColumnName(), or similar, is legal, but calling
step() or finalize() is not. Routines which are illegal
in this context will trigger an exception.
ACHTUNG #2: The semantics of the `bind` and `callback`
options may well change or those options may be removed
altogether for this function (but retained for exec()).
Generally speaking, neither bind parameters nor a callback
are generically useful when executing multi-statement SQL.
*/
execMulti: function(/*(sql [,obj]) || (obj)*/){
affirmDbOpen(this);
@ -803,8 +877,13 @@ Module.postRun.push(function(namespace){
/**
Exports a copy of this db's file as a Uint8Array and
returns it. It is technically not legal to call this while
any prepared statement are currently active. Throws if this
db is not open.
any prepared statement are currently active because,
depending on the platform, it might not be legal to read
the db while a statement is locking it. Throws if this db
is not open or has any opened statements.
The resulting buffer can be passed to this class's
constructor to restore the DB.
Maintenance reminder: the corresponding sql.js impl of this
feature closes the current db, finalizing any active
@ -822,8 +901,7 @@ Module.postRun.push(function(namespace){
toss("Cannot export with prepared statements active!",
"finalize() all statements and try again.");
}
const img = FS.readFile(this.filename, {encoding:"binary"});
return img;
return FS.readFile(this.filename, {encoding:"binary"});
}
}/*DB.prototype*/;
@ -1248,6 +1326,13 @@ Module.postRun.push(function(namespace){
const ptr = api.sqlite3_column_blob(this._pStmt, ndx);
const rc = new Uint8Array(n);
for(let i = 0; i < n; ++i) rc[i] = HEAP8[ptr + i];
if(n && this.db._blobXfer instanceof Array){
/* This is an optimization soley for the
Worker-based API. These values will be
transfered to the main thread directly
instead of being copied. */
this.db._blobXfer.push(rc.buffer);
}
return rc;
}
default: toss("Don't know how to translate",
@ -1327,8 +1412,8 @@ Module.postRun.push(function(namespace){
DB,
Stmt,
/**
Reports whether a given compile-time option, named by the
given argument. It has several distinct uses:
Reports info about compile-time options. It has several
distinct uses:
If optName is an array then it is expected to be a list of
compilation options and this function returns an object
@ -1387,10 +1472,310 @@ Module.postRun.push(function(namespace){
'string'===typeof optName
) ? !!api.sqlite3_compileoption_used(optName) : false;
}
};
}/*SQLite3 object*/;
namespace.sqlite3 = {
api: api,
SQLite3
};
if(self === self.window){
/* This is running in the main window thread, so we're done. */
setTimeout(()=>postMessage({type:'sqlite3-api',data:'loaded'}), 0);
return;
}
/******************************************************************
End of main window thread. What follows is only intended for use
in Worker threads.
******************************************************************/
/*
UNDER CONSTRUCTION
We need an API which can proxy the DB API via a Worker message
interface. The primary quirky factor in such an API is that we
cannot pass callback functions between the window thread and a
worker thread, so we have to receive all db results via
asynchronous message-passing.
Certain important considerations here include:
- Support only one db connection or multiple? The former is far
easier, but there's always going to be a user out there who
wants to juggle six database handles at once. Do we add that
complexity or tell such users to write their own code using
the provided lower-level APIs?
- Fetching multiple results: do we pass them on as a series of
messages, with start/end messages on either end, or do we
collect all results and bundle them back in a single message?
The former is, generically speaking, more memory-efficient but
the latter far easier to implement in this environment. The
latter is untennable for large data sets. Despite a web page
hypothetically being a relatively limited environment, there
will always be those users who feel that they should/need to
be able to work with multi-hundred-meg (or larger) blobs, and
passing around arrays of those may quickly exhaust the JS
engine's memory.
TODOs include, but are not limited to:
- The ability to manage multiple DB handles. This can
potentially be done via a simple mapping of DB.filename or
DB._pDb (`sqlite3*` handle) to DB objects. The open()
interface would need to provide an ID (probably DB._pDb) back
to the user which can optionally be passed as an argument to
the other APIs (they'd default to the first-opened DB, for
ease of use). Client-side usability of this feature would
benefit from making another wrapper class (or a singleton)
available to the main thread, with that object proxying all(?)
communication with the worker.
- Revisit how virtual files are managed. We currently delete DBs
from the virtual filesystem when we close them, for the sake
of saving memory (the VFS lives in RAM). Supporting multiple
DBs may require that we give up that habit. Similarly, fully
supporting ATTACH, where a user can upload multiple DBs and
ATTACH them, also requires the that we manage the VFS entries
better. As of this writing, ATTACH will fail fatally in the
fiddle app (but not the lower-level APIs) because it runs in
safe mode, where ATTACH is disabled.
*/
/**
Helper for managing Worker-level state.
*/
const wState = {
db: undefined,
open: function(arg){
if(!arg && this.db) return this.db;
else if(this.db) this.db.close();
return this.db = (Array.isArray(arg) ? new DB(...arg) : new DB(arg));
},
close: function(alsoUnlink){
if(this.db){
this.db.close(alsoUnlink);
this.db = undefined;
}
},
affirmOpen: function(){
return this.db || toss("DB is not opened.");
},
post: function(type,data,xferList){
if(xferList){
self.postMessage({type, data},xferList);
xferList.length = 0;
}else{
self.postMessage({type, data});
}
}
};
/**
A level of "organizational abstraction" for the Worker
API. Each method in this object must map directly to a Worker
message type key. The onmessage() dispatcher attempts to
dispatch all inbound messages to a method of this object,
passing it the event.data part of the inbound event object. All
methods must return a plain Object containing any response
state, which the dispatcher may amend. All methods must throw
on error.
*/
const wMsgHandler = {
xfer: [/*Temp holder for "transferable" postMessage() state.*/],
/**
Proxy for DB.exec() which expects a single argument of type
string (SQL to execute) or an options object in the form
expected by exec(). The notable differences from exec()
include:
- The default value for options.rowMode is 'array' because
the normal default cannot cross the window/Worker boundary.
- A function-type options.callback property cannot cross
the window/Worker boundary, so is not useful here. If
options.callback is a string then it is assumed to be a
message type key, in which case a callback function will be
applied which posts each row result via:
postMessage({type: thatKeyType, data: theRow})
And, at the end of the result set (whether or not any
result rows were produced), it will post an identical
message with data:null to alert the caller than the result
set is completed.
The callback proxy must not recurse into this interface, or
results are undefined. (It hypothetically cannot recurse
because an exec() call will be tying up the Worker thread,
causing any recursion attempt to wait until the first
exec() is completed.)
The response is the input options object (or a synthesized
one if passed only a string), noting that
options.resultRows and options.columnNames may be populated
by the call to exec().
This opens/creates the Worker's db if needed.
*/
exec: function(ev){
const opt = (
'string'===typeof ev.data
) ? {sql: ev.data} : (ev.data || {});
if(!opt.rowMode){
/* Since the default rowMode of 'stmt' is not useful
for the Worker interface, we'll default to
something else. */
opt.rowMode = 'array';
}else if('stmt'===opt.rowMode){
toss("Invalid rowMode for exec(): stmt mode",
"does not work in the Worker API.");
}
const db = wState.open();
if(opt.callback || opt.resultRows instanceof Array){
// Part of a copy-avoidance optimization for blobs
db._blobXfer = this.xfer;
}
const callbackMsgType = opt.callback;
if('string' === typeof callbackMsgType){
const that = this;
opt.callback =
(row)=>wState.post(callbackMsgType,row,this.xfer);
}
try {
db.exec(opt);
if(opt.callback instanceof Function){
opt.callback = callbackMsgType;
wState.post(callbackMsgType, null);
}
}finally{
delete db._blobXfer;
if('string'===typeof callbackMsgType){
opt.callback = callbackMsgType;
}
}
return opt;
}/*exec()*/,
/**
Proxy for DB.exportBinaryImage(). Throws if the db has not
been opened. Response is an object:
{
buffer: Uint8Array (db file contents),
filename: the current db filename,
mimetype: string
}
*/
export: function(ev){
const db = wState.affirmOpen();
const response = {
buffer: db.exportBinaryImage(),
filename: db.filename,
mimetype: 'application/x-sqlite3'
};
this.xfer.push(response.buffer.buffer);
return response;
}/*export()*/,
/**
Proxy for the DB constructor. Expects to be passed a single
object or a falsy value to use defaults. The object may
have a filename property to name the db file (see the DB
constructor for peculiarities and transformations) and/or a
buffer property (a Uint8Array holding a complete database
file's contents). The response is an object:
{
filename: db filename (possibly differing from the input)
}
If the Worker's db is currently opened, this call closes it
before proceeding.
*/
open: function(ev){
wState.close(/*true???*/);
const args = [], data = (ev.data || {});
if(data.filename) args.push(data.filename);
if(data.buffer){
args.push(data.buffer);
this.xfer.push(data.buffer.buffer);
}
const db = wState.open(args);
return {filename: db.filename};
},
/**
Proxy for DB.close(). If ev.data may either be a boolean or
an object with an `unlink` property. If that value is
truthy then the db file (if the db is currently open) will
be unlinked from the virtual filesystem, else it will be
kept intact. The response object is:
{filename: db filename _if_ the db is is opened when this
is called, else the undefined value
}
*/
close: function(ev){
const response = {
filename: wState.db && wState.db.filename
};
if(wState.db){
wState.close(!!(ev.data && 'object'===typeof ev.data)
? ev.data.unlink : ev.data);
}
return response;
}
}/*wMsgHandler*/;
/**
UNDER CONSTRUCTION!
A subset of the DB API is accessible via Worker messages in the form:
{ type: apiCommand,
data: apiArguments }
As a rule, these commands respond with a postMessage() of their
own in the same form, but will, if needed, transform the `data`
member to an object and may add state to it. The responses
always have an object-format `data` part. If the inbound `data`
is an object which has a `messageId` property, that property is
always mirrored in the result object, for use in client-side
dispatching of these asynchronous results. Exceptions thrown
during processing result in an `error`-type event with a
payload in the form:
{
message: error string,
errorClass: class name of the error type,
input: ev.data,
[messageId: if set in the inbound message]
}
The individual APIs are documented in the wMsgHandler object.
*/
self.onmessage = function(ev){
ev = ev.data;
let response, evType = ev.type;
try {
if(wMsgHandler.hasOwnProperty(evType) &&
wMsgHandler[evType] instanceof Function){
response = wMsgHandler[evType](ev);
}else{
toss("Unknown db worker message type:",ev.type);
}
}catch(err){
evType = 'error';
response = {
message: err.message,
errorClass: err.name,
input: ev
};
}
if(!response.messageId && ev.data
&& 'object'===typeof ev.data && ev.data.messageId){
response.messageId = ev.data.messageId;
}
wState.post(evType, response, wMsgHandler.xfer);
};
setTimeout(()=>postMessage({type:'sqlite3-api',data:'loaded'}), 0);
});

View File

@ -0,0 +1,44 @@
/*
2022-05-23
The author disclaims copyright to this source code. In place of a
legal notice, here is a blessing:
* May you do good and not evil.
* May you find forgiveness for yourself and forgive others.
* May you share freely, never taking more than you give.
***********************************************************************
This is a JS Worker file for the main sqlite3 api. It loads
sqlite3.js, initializes the module, and postMessage()'s a message
after the module is initialized:
{type: 'sqlite3-api', data: 'ready'}
This seemingly superfluous level of indirection is necessary when
loading sqlite3.js via a Worker. Loading sqlite3.js from the main
window thread elides the Worker-specific API. Instantiating a worker
with new Worker("sqlite.js") will not (cannot) call
initSqlite3Module() to initialize the module due to a
timing/order-of-operations conflict (and that symbol is not exported
in a way that a Worker loading it that way can see it). Thus JS
code wanting to load the sqlite3 Worker-specific API needs to pass
_this_ file (or equivalent) to the Worker constructor and then
listen for an event in the form shown above in order to know when
the module has completed initialization. sqlite3.js will fire a
similar event, with data:'loaded' as the final step in its loading
process. Whether or not we _really_ need both 'loaded' and 'ready'
events is unclear, but they are currently separate events primarily
for the sake of clarity in the timing of when it's okay to use the
loaded module. At the time the 'loaded' event is fired, it's
possible (but unknown and unknowable) that the emscripten-generated
module-setup infrastructure still has work to do. Thus it is
hypothesized that client code is better off waiting for the 'ready'
even before using the API.
*/
"use strict";
importScripts('sqlite3.js');
initSqlite3Module().then(function(){
setTimeout(()=>self.postMessage({type:'sqlite3-api',data:'ready'}), 0);
});

View File

@ -10,7 +10,8 @@
***********************************************************************
A basic test script for sqlite3-api.js.
A basic test script for sqlite3-api.js. This file must be run in
main JS thread and sqlite3.js must have been loaded before it.
*/
(function(){
const T = self.SqliteTestUtil;
@ -70,23 +71,27 @@ INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`,
T.assert(2 === list.length);
//log("Exec'd SQL:", list);
let counter = 0, colNames = [];
list.length = 0;
db.exec("SELECT a a, b b FROM t",{
rowMode: 'object',
resultRows: list,
columnNames: colNames,
callback: function(row,stmt){
if(!counter) stmt.getColumnNames(colNames);
++counter;
T.assert(row.a%2 && row.a<6);
}
});
assert(2 === colNames.length);
assert('a' === colNames[0]);
T.assert(3 === counter);
T.assert(2 === colNames.length)
.assert('a' === colNames[0])
.assert(3 === counter)
.assert(3 === list.length);
list.length = 0;
db.exec("SELECT a a, b b FROM t",{
rowMode: 'array',
callback: function(row,stmt){
++counter;
assert(Array.isArray(row));
T.assert(0===row[1]%2 && row[1]<7);
T.assert(Array.isArray(row))
.assert(0===row[1]%2 && row[1]<7);
}
});
T.assert(6 === counter);
@ -114,11 +119,32 @@ INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`,
assert(3===db.selectValue("select bar(1,2)")).
assert(-1===db.selectValue("select bar(1,2,-4)"));
const eqApprox = function(v1,v2,factor=0.05){
return v1>=(v2-factor) && v1<=(v2+factor);
};
T.assert('hi' === db.selectValue("select ?",'hi')).
assert(null===db.selectValue("select null")).
assert(null === db.selectValue("select ?",null)).
assert(null === db.selectValue("select ?",[null])).
assert(null === db.selectValue("select $a",{$a:null}));
assert(null === db.selectValue("select $a",{$a:null})).
assert(eqApprox(3.1,db.selectValue("select 3.0 + 0.1")))
;
};
const testAttach = function(db){
log("Testing ATTACH...");
db.exec({
sql:[
"attach 'foo.db' as foo",
"create table foo.bar(a)",
"insert into foo.bar(a) values(1),(2),(3)"
].join(';'),
multi: true
});
T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a'));
db.exec("detach foo");
T.mustThrow(()=>db.exec("select * from foo.bar"));
};
const runTests = function(Module){
@ -136,8 +162,12 @@ INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`,
try {
log("DB:",db.filename);
[
test1, testUDF
].forEach((f)=>f(db, sqlite3));
test1, testUDF, testAttach
].forEach((f)=>{
const t = T.counter;
f(db, sqlite3);
log("Test count:",T.counter - t);
});
}finally{
db.close();
}

32
ext/fiddle/testing2.html Normal file
View File

@ -0,0 +1,32 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
<link rel="stylesheet" href="emscripten.css"/>
<link rel="stylesheet" href="testing.css"/>
<title>sqlite3-worker.js tests</title>
<style></style>
</head>
<body>
<header id='titlebar'><span>sqlite3-worker.js tests</span></header>
<!-- emscripten bits -->
<figure id="module-spinner">
<div class="spinner"></div>
<div class='center'><strong>Initializing app...</strong></div>
<div class='center'>
On a slow internet connection this may take a moment. If this
message displays for "a long time", intialization may have
failed and the JavaScript console may contain clues as to why.
</div>
</figure>
<div class="emscripten" id="module-status">Downloading...</div>
<div class="emscripten">
<progress value="0" max="100" id="module-progress" hidden='1'></progress>
</div><!-- /emscripten bits -->
<div>Everything on this page happens in the dev console.</div>
<script src="testing-common.js"></script>
<script src="testing2.js"></script>
</body>
</html>

235
ext/fiddle/testing2.js Normal file
View File

@ -0,0 +1,235 @@
/*
2022-05-22
The author disclaims copyright to this source code. In place of a
legal notice, here is a blessing:
* May you do good and not evil.
* May you find forgiveness for yourself and forgive others.
* May you share freely, never taking more than you give.
***********************************************************************
A basic test script for sqlite3-worker.js.
*/
(function(){
const T = self.SqliteTestUtil;
const SW = new Worker("sqlite3-worker.js");
/** Posts a worker message as {type:type, data:data}. */
const wMsg = function(type,data){
SW.postMessage({type, data});
return SW;
};
const log = console.log.bind(console);
const warn = console.warn.bind(console);
const error = console.error.bind(console);
SW.onerror = function(event){
error("onerror",event);
};
/**
A queue for callbacks which are to be run in response to async
DB commands. See the notes in runTests() for why we need
this. The event-handling plumbing of this file requires that
any DB command which includes a `messageId` property also have
a queued callback entry, as the existence of that property in
response payloads is how it knows whether or not to shift an
entry off of the queue.
*/
const MsgHandlerQueue = {
queue: [],
id: 0,
push: function(type,callback){
this.queue.push(callback);
return type + '-' + (++this.id);
},
shift: function(){
return this.queue.shift();
}
};
const testCount = ()=>log("Total test count:",T.counter);
const runOneTest = function(eventType, eventData, callback){
T.assert(eventData && 'object'===typeof eventData);
/* ^^^ that is for the testing and messageId-related code, not
a hard requirement of all of the Worker-exposed APIs. */
eventData.messageId = MsgHandlerQueue.push(eventType,function(ev){
log("runOneTest",eventType,"result",ev.data);
if(callback instanceof Function){
callback(ev);
testCount();
}
});
wMsg(eventType, eventData);
};
/** Methods which map directly to onmessage() event.type keys.
They get passed the inbound event.data. */
const dbMsgHandler = {
open: function(ev){
log("open result",ev.data);
},
exec: function(ev){
log("exec result",ev.data);
},
export: function(ev){
log("exec result",ev.data);
},
error: function(ev){
error("ERROR from the worker:",ev.data);
},
resultRowTest1: function f(ev){
if(undefined === f.counter) f.counter = 0;
if(ev.data) ++f.counter;
//log("exec() result row:",ev.data);
T.assert(null===ev.data || 'number' === typeof ev.data.b);
}
};
const runTests = function(){
const mustNotReach = ()=>{
throw new Error("This is not supposed to be reached.");
};
/**
"The problem" now is that the test results are async. We
know, however, that the messages posted to the worker will
be processed in the order they are passed to it, so we can
create a queue of callbacks to handle them. The problem
with that approach is that it's not error-handling
friendly, in that an error can cause us to bypass a result
handler queue entry. We have to perform some extra
acrobatics to account for that.
*/
runOneTest('open', {filename:'testing2.sqlite3'}, function(ev){
//log("open result",ev);
T.assert('testing2.sqlite3'===ev.data.filename)
.assert(ev.data.messageId);
});
runOneTest('exec',{
sql: ["create table t(a,b)",
"insert into t(a,b) values(1,2),(3,4),(5,6)"
].join(';'),
multi: true,
resultRows: [], columnNames: []
}, function(ev){
ev = ev.data;
T.assert(0===ev.resultRows.length)
.assert(0===ev.columnNames.length);
});
runOneTest('exec',{
sql: 'select a a, b b from t order by a',
resultRows: [], columnNames: [],
}, function(ev){
ev = ev.data;
T.assert(3===ev.resultRows.length)
.assert(1===ev.resultRows[0][0])
.assert(6===ev.resultRows[2][1])
.assert(2===ev.columnNames.length)
.assert('b'===ev.columnNames[1]);
});
runOneTest('exec',{
sql: 'select a a, b b from t order by a',
resultRows: [], columnNames: [],
rowMode: 'object'
}, function(ev){
ev = ev.data;
T.assert(3===ev.resultRows.length)
.assert(1===ev.resultRows[0].a)
.assert(6===ev.resultRows[2].b)
});
runOneTest('exec',{sql:'intentional_error'}, mustNotReach);
// Ensure that the message-handler queue survives ^^^ that error...
runOneTest('exec',{
sql:'select 1',
resultRows: [],
//rowMode: 'array', // array is the default in the Worker interface
}, function(ev){
ev = ev.data;
T.assert(1 === ev.resultRows.length)
.assert(1 === ev.resultRows[0][0]);
});
runOneTest('exec',{
sql: 'select a a, b b from t order by a',
callback: 'resultRowTest1',
rowMode: 'object'
}, function(ev){
T.assert(3===dbMsgHandler.resultRowTest1.counter);
dbMsgHandler.resultRowTest1.counter = 0;
});
runOneTest('exec',{sql: 'delete from t where a>3'});
runOneTest('exec',{
sql: 'select count(a) from t',
resultRows: []
},function(ev){
ev = ev.data;
T.assert(1===ev.resultRows.length)
.assert(2===ev.resultRows[0][0]);
});
runOneTest('export',{}, function(ev){
ev = ev.data;
T.assert('string' === typeof ev.filename)
.assert(ev.buffer instanceof Uint8Array)
.assert(ev.buffer.length > 1024)
.assert('application/x-sqlite3' === ev.mimetype);
});
/***** close() tests must come last. *****/
runOneTest('close',{unlink:true},function(ev){
ev = ev.data;
T.assert('string' === typeof ev.filename);
});
runOneTest('close',{unlink:true},function(ev){
ev = ev.data;
T.assert(undefined === ev.filename);
});
};
SW.onmessage = function(ev){
if(!ev.data || 'object'!==typeof ev.data){
warn("Unknown sqlite3-worker message type:",ev);
return;
}
ev = ev.data/*expecting a nested object*/;
//log("main window onmessage:",ev);
if(ev.data && ev.data.messageId){
/* We're expecting a queued-up callback handler. */
const f = MsgHandlerQueue.shift();
if('error'===ev.type){
dbMsgHandler.error(ev);
return;
}
T.assert(f instanceof Function);
f(ev);
return;
}
switch(ev.type){
case 'sqlite3-api':
switch(ev.data){
case 'loaded':
log("Message:",ev); return;
case 'ready':
log("Message:",ev);
self.sqlite3TestModule.setStatus(null);
setTimeout(runTests, 0);
return;
default:
warn("Unknown sqlite3-api message type:",ev);
return;
}
default:
if(dbMsgHandler.hasOwnProperty(ev.type)){
try{dbMsgHandler[ev.type](ev);}
catch(err){
error("Exception while handling db result message",
ev,":",err);
}
return;
}
warn("Unknown sqlite3-api message type:",ev);
}
};
log("Init complete, but async init bits may still be running.");
})();

View File

@ -3899,6 +3899,8 @@ static int fts3IncrmergePush(
pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nPrefix);
}
pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nSuffix);
assert( nPrefix+nSuffix<=nTerm );
assert( nPrefix>=0 );
memcpy(&pBlk->a[pBlk->n], &zTerm[nPrefix], nSuffix);
pBlk->n += nSuffix;
@ -4021,6 +4023,7 @@ static int fts3IncrmergeAppend(
pLeaf = &pWriter->aNodeWriter[0];
nPrefix = fts3PrefixCompress(pLeaf->key.a, pLeaf->key.n, zTerm, nTerm);
nSuffix = nTerm - nPrefix;
if(nSuffix<=0 ) return FTS_CORRUPT_VTAB;
nSpace = sqlite3Fts3VarintLen(nPrefix);
nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix;

View File

@ -547,6 +547,9 @@ FUZZCHECK_OPT += -DSQLITE_ENABLE_RTREE
FUZZCHECK_OPT += -DSQLITE_ENABLE_GEOPOLY
FUZZCHECK_OPT += -DSQLITE_ENABLE_DBSTAT_VTAB
FUZZCHECK_OPT += -DSQLITE_ENABLE_BYTECODE_VTAB
FUZZSRC += $(TOP)/test/fuzzcheck.c
FUZZSRC += $(TOP)/test/ossfuzz.c
FUZZSRC += $(TOP)/test/fuzzinvariants.c
DBFUZZ_OPT =
KV_OPT = -DSQLITE_THREADSAFE=0 -DSQLITE_DIRECT_OVERFLOW_READ
ST_OPT = -DSQLITE_THREADSAFE=0
@ -605,10 +608,10 @@ dbfuzz2$(EXE): $(TOP)/test/dbfuzz2.c sqlite3.c sqlite3.h
$(TCCX) -I. -g -O0 -DSTANDALONE -o dbfuzz2$(EXE) \
$(DBFUZZ2_OPTS) $(TOP)/test/dbfuzz2.c sqlite3.c $(TLIBS) $(THREADLIB)
fuzzcheck$(EXE): $(TOP)/test/fuzzcheck.c sqlite3.c sqlite3.h $(TOP)/test/ossfuzz.c
fuzzcheck$(EXE): $(FUZZSRC) sqlite3.c sqlite3.h
$(TCCX) -o fuzzcheck$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
-DSQLITE_ENABLE_MEMSYS5 $(FUZZCHECK_OPT) -DSQLITE_OSS_FUZZ \
$(TOP)/test/fuzzcheck.c $(TOP)/test/ossfuzz.c sqlite3.c $(TLIBS) $(THREADLIB)
$(FUZZSRC) sqlite3.c $(TLIBS) $(THREADLIB)
ossshell$(EXE): $(TOP)/test/ossfuzz.c $(TOP)/test/ossshell.c sqlite3.c sqlite3.h
$(TCCX) -o ossshell$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \

View File

@ -1,11 +1,11 @@
C Merge\sthe\slatest\strunk\senhancements\sinto\sthe\swal2\sbranch.
D 2022-05-28T14:44:19.777
D 2022-06-16T13:44:49.125
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F Makefile.in 6e2350cc3bed7a467fbdaa3ce42e665627aa91c9344c464cd21e750b75280181
F Makefile.in 915ed9176f23f867575c1bd4e2d4b8a60e4e61965538b52418508f1c226558c4
F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
F Makefile.msc f969c28231012e50ee07c5062ee1dd5bbfc00883d52573af44b4fd477ec63843
F Makefile.msc 028810319c9b921358303610bc64b0b7689b23a5810435a56bc268d45bdbee3f
F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e
F VERSION fa8e7d2d1cc962f9e14c6d410387cf75860ee139462763fda887c1be4261f824
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
@ -59,17 +59,20 @@ F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be
F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 7fb73f7150ab79d83bb45a67d257553c905c78cd3d693101699243f36c5ae6c3
F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api 540b9dec63a3a62a256e2f030827848a92e9b9d9b6fa5c0188295a4a1c5382cd
F ext/fiddle/EXPORTED_RUNTIME_METHODS b831017ba67ba993b34a27400cef2f6095bd6789c0fc4eba7e7a251c207be31c
F ext/fiddle/Makefile de65d04bfb312e94dbd7a0e7d99fb126f0abc1db62f920159c4124b5a42347d8
F ext/fiddle/Makefile e25d34a0e1324f771d64c09c592601b97219282011587e6ce410fa8acdedb913
F ext/fiddle/SqliteTestUtil.js 559731c3e8e0de330ec7d292e6c1846566408caee6637acc8a119ac338a8781c
F ext/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
F ext/fiddle/fiddle-worker.js 3a19253dc026d1ad9064ee853f3c4da3385223ce4434dab1838837525d817371
F ext/fiddle/fiddle.html 724f1cd4126616bc87f5871f78d3f7aaaf41e45c9728724627baab87e6af35f0
F ext/fiddle/fiddle.js 5b456ed7085830cda2fc75a0801476174a978521949335f24bc4154d076dcd4d
F ext/fiddle/fiddle-worker.js 88bc2193a6cb6a3f04d8911bed50a4401fe6f277de7a71ba833865ab64a1b4ae
F ext/fiddle/fiddle.html 550c5aafce40bd218de9bf26192749f69f9b10bc379423ecd2e162bcef885c08
F ext/fiddle/fiddle.js 812f9954cc7c4b191884ad171f36fcf2d0112d0a7ecfdf6087896833a0c079a8
F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf
F ext/fiddle/sqlite3-api.js 8500698d2163f4a25f8e5e6810ad826487342579d6a321d82b244dbc8e6f6db6
F ext/fiddle/sqlite3-api.js ccf4bd0c1c5bbb3be3469573423d6c53991941bec497eac63e9f17ea13bf8952
F ext/fiddle/sqlite3-worker.js a9c2b614beca187dbdd8c053ec2770cc61ec1ac9c0ec6398ceb49a79f705a421
F ext/fiddle/testing.css 750572dded671d2cf142bbcb27af5542522ac08db128245d0b9fe410aa1d7f2a
F ext/fiddle/testing1.html ea1f3be727f78e420007f823912c1a03b337ecbb8e79449abc2244ad4fe15d9a
F ext/fiddle/testing1.js 94a7597955c8fdbd15839a70d9b8279bc690205dda65ff175f688f13bf315745
F ext/fiddle/testing1.js e2fa02ac8adbd21c69bc50cfcb79bfc26af0d30a8d6b95ac473a17e0dc9de733
F ext/fiddle/testing2.html 9063b2430ade2fe9da4e711addd1b51a2741cf0c7ebf6926472a5e5dd63c0bc4
F ext/fiddle/testing2.js 7b45b4e7fddbd51dbaf89b6722c02758051b34bac5a98c11b569a7e7572f88ee
F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e
F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b
F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea
@ -118,7 +121,7 @@ F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3
F ext/fts3/fts3_tokenizer1.c 5c98225a53705e5ee34824087478cf477bdb7004
F ext/fts3/fts3_unicode.c de426ff05c1c2e7bce161cf6b706638419c3a1d9c2667de9cb9dc0458c18e226
F ext/fts3/fts3_unicode2.c 416eb7e1e81142703520d284b768ca2751d40e31fa912cae24ba74860532bf0f
F ext/fts3/fts3_write.c 3109c1a232da86474e196cc7db754445a354409f141e08cb11c846cdb17bdf31
F ext/fts3/fts3_write.c 85279b980f99253c296006503a13f92957ec49b716123083f021acc74545ecfc
F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9
F ext/fts3/tool/fts3cov.sh c331d006359456cf6f8f953e37f2b9c7d568f3863f00bb5f7eb87fea4ac01b73
F ext/fts3/tool/fts3view.c 413c346399159df81f86c4928b7c4a455caab73bfbc8cd68f950f632e5751674
@ -490,7 +493,7 @@ F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
F main.mk 50dac6249cce84aca4a365127a771b0a32247b151d64752997cc679660e0c3c4
F main.mk d486b4a787c6836371d1f769e7f29074c519d7ec998bc09c036018a0adae6207
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
@ -509,7 +512,7 @@ F src/auth.c f4fa91b6a90bbc8e0d0f738aa284551739c9543a367071f55574681e0f24f8cf
F src/backup.c a2891172438e385fdbe97c11c9745676bec54f518d4447090af97189fd8e52d7
F src/bitvec.c 7c849aac407230278445cb069bebc5f89bf2ddd87c5ed9459b070a9175707b3d
F src/btmutex.c 8acc2f464ee76324bf13310df5692a262b801808984c1b79defb2503bbafadb6
F src/btree.c 01a6bd849a63b1ae8ac01aa21e58637a3c7db29bdc687269e476f3d20f39ef2e
F src/btree.c db537493a3aaa2ee44631bb9ddad305b457561531114b5b084d7286ad0ea5a12
F src/btree.h 74d64b8f28cfa4a894d14d4ed64fa432cd697b98b61708d4351482ae15913e22
F src/btreeInt.h 8ce1332edd89dfd2461d561ac10a0ab5601c8e06200cb5230596c3caaf54482e
F src/build.c 23f874642825d7eaaeeb7a3281b2b1a75e1d4c4dd9ae4dceddcd908266634214
@ -520,10 +523,10 @@ F src/date.c 15082566229d4b1e5f24fdb490bf9bcc68824b911d70e3573ef075a1b9e2d26f
F src/dbpage.c 90661a87e1db8bfbc8d2ebbdcd3749651ddb287c555c07a28fb17c7c591ffb68
F src/dbstat.c 861e08690fcb0f2ee1165eff0060ea8d4f3e2ea10f80dab7d32ad70443a6ff2d
F src/delete.c a8e844af211a48b13b5b358be77a12c860c6a557c21990ad51a548e2536500ce
F src/expr.c 19507ae3244402860cac2944be3b92bf9a8b50212fbfabaf7e9817127fec7c00
F src/expr.c 4907afcb86d72b5525d8767515ce425ec53c7a2d3664441b46cef5b376ee0cba
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
F src/fkey.c d965ede15d8360c09ed59348940649ee647b192e784466837d7aefa836d1d91e
F src/func.c 41bf487f04d54e694baf84baacff7de621847fd7e89a35b776d5fb9ade772ff7
F src/func.c 8f72e88cccdee22185133c10f96ccd61dc34c5ea4b1fa9a73c237ef59b2e64f1
F src/global.c e83ee571b79ee3adc32e380cf554cf1254bc43763d23786c71721fbcdfbbb965
F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19
F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51
@ -556,7 +559,7 @@ F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586
F src/os_unix.c 2df2b33db88f00af13805d4573ee126bc5973f9e3b91d03c575fa7ba64e7dc41
F src/os_win.c a8ea80037e81127ca01959daa87387cc135f325c88dc745376c4f760de852a10
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
F src/pager.c edd204a77e24583e4be80f8e0c596a5a2d941cafc618ac4c17f724893cfc704f
F src/pager.c adb8600c3ff85ad95973379f7132c01d6f2cc5265df959ac3d589f075d004dc3
F src/pager.h c49ff262186a78bc5f27e3891edefb900afa769b9e2eaeca0322c7f3553536d4
F src/parse.y 8e67d820030d2655b9942ffe61c1e7e6b96cea2f2f72183533299393907d0564
F src/pcache.c 084e638432c610f95aea72b8509f0845d2791293f39d1b82f0c0a7e089c3bb6b
@ -569,16 +572,16 @@ F src/printf.c 6166a30417b05c5b2f82e1f183f75faa2926ad60531c0b688a57dbc951441a20
F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
F src/resolve.c a4eb3c617027fd049b07432f3b942ea7151fa793a332a11a7d0f58c9539e104f
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
F src/select.c 7a4c5023d6c3bcd243546dbe9bbf5b280a60ca565658d037b8c0ec8dd77e1136
F src/shell.c.in b76e681f9e441928d574f21f9473ef615158bbeab1ae49f05ecab9d81730a51d
F src/select.c ee3113de67330163a35307eacb4188b6778fcae1e2d2f738a9dda2daa0346e24
F src/shell.c.in 08e59f1cb9d9b1180aba52861aaada0c95f6ddd210488719684e160a0724c806
F src/sqlite.h.in 172528c287399a34f188154017b7268bf82c6d5b780902e361958d2318c4e37c
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h a988810c9b21c0dc36dc7a62735012339dc76fc7ab448fb0792721d30eacb69d
F src/sqliteInt.h 3064533677f135771e71843b5221482df18d6589afe65e6a7ef828ccb8879a5f
F src/sqliteInt.h 8353e96646372efdb0795a13cd9949831b4992c928de8f5c43b2524e8a4c6e7b
F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
F src/status.c 4a3da6d77eeb3531cb0dbdf7047772a2a1b99f98c69e90ce009c75fe6328b2c0
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
F src/tclsqlite.c 1f6673991147bc2cecc08a40d22f9803b84c805b24b499fe727f392256f73474
F src/tclsqlite.c 4e64ba300a5a26e0f1170e09032429faeb65e45e8f3d1a7833e8edb69fc2979e
F src/test1.c 1356984e97bff07e4a8cc3863e892f05b3348678a74783bb6f350b76316736f1
F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5
F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644
@ -634,20 +637,20 @@ F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394
F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
F src/tokenize.c a38f52058b517929e264094abd0b5fd1e8e145a1aa43bc6f6a72ae5218f96c98
F src/treeview.c 73facf395c8841653b9a54e789d8c80e15bc3d0d1cb9d16104c2d889c15e33cd
F src/treeview.c c48bbb4b04a951dcecf95b464d0fe94930339af56688a77f18ee50a526bc1706
F src/trigger.c 61bea163b1fa3039bc572ed8312461b978e5c527e5301f302b078f4c1ccdec6a
F src/update.c 2cfaded82ca80ff56afb8c3ae5e88284e0824bfd86119827cc22481959f96f92
F src/update.c c52a7991bece0453d22c77c08469512ee2f1391c12503fd347d1c939220c5877
F src/upsert.c 8789047a8f0a601ea42fa0256d1ba3190c13746b6ba940fe2d25643a7e991937
F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0
F src/util.c 602fe229f32a96ceccae4f40824129669582096f7c355f53dbac156c9fecef23
F src/vacuum.c bb346170b0b54c6683bba4a5983aea40485597fdf605c87ec8bc2e199fe88cd8
F src/vdbe.c e2ce1c8c86bba823698b1c176283c8b06fbe19c2a1e7238822cefb4f6bce7e3e
F src/vdbe.c 4842b98a3043d8fbb0e06de81492caac096a0625301e0f7a14270ad67b28df60
F src/vdbe.h 07641758ca8b4f4c6d81ea667ea167c541e6ece21f5574da11e3d21ec37e2662
F src/vdbeInt.h ef43f7fdc5fde29fc3fd29c506c12830f366178fdb4edbbf0cbc3dfbd1278b5f
F src/vdbeInt.h 2cad0aeeb106371ed0e0946bab89f60627087068847afc2451c05056961c18da
F src/vdbeapi.c 354c893f1500cf524cc45c32879b9c68893a28b77e3442c24668d6afe4236217
F src/vdbeaux.c 75c4f75ed7e1d12eb3d80093a160ec998c839f3008a1c3c967fc5acf522d0e3c
F src/vdbeblob.c 5e61ce31aca17db8fb60395407457a8c1c7fb471dde405e0cd675974611dcfcd
F src/vdbemem.c 7189090b72baa025f945a1ac8c61ee420c645254476e8a191d555db76dfea5d4
F src/vdbemem.c 3db315458f8dc158aff58719795441437dd6c0fd302e9d9379a8f2a61e185ad6
F src/vdbesort.c 43756031ca7430f7aec3ef904824a7883c4ede783e51f280d99b9b65c0796e35
F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823
F src/vdbevtab.c f99b275366c5fc5e2d99f734729880994ab9500bdafde7fae3b02d562b9d323c
@ -656,10 +659,10 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
F src/wal.c a9cc35881efbb97e568b654dffa2716cf4c5d3fd15489980ab96e48fc5bc60a7
F src/wal.h d01234e828943e002040c22a7e017642962f9fd9b2dc142fa599769ae4e459e9
F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b
F src/where.c c4b64c6fa224e5b89ed547ec0ebdfd243c081509b195e71581164a2fbb8d4a80
F src/whereInt.h 8da918f392bf202ccc0ee61291455b33ad171d209445f1ff3eaf62e0b6f6b363
F src/wherecode.c 2a8a73bcf1886632f2b2247c79395f94852a4b74484d8aa70a005892ce73d339
F src/whereexpr.c 7c5ee52e1df81d6a43f39e6b6f35d540fd37254e2b6e953a4e2715c3abf26f46
F src/where.c 267caa227dd38ede46959468118ef4067316dae589d889c200911ff77df53ef1
F src/whereInt.h b48ca529ffe293c18cbfa8326af18a09e39910de66fb3e96ef788c7cbf8ef3a7
F src/wherecode.c 0b09abfcb88c61c6a6984a3e065786631ff35495e9bdf865e6b74ab0a1299c5b
F src/whereexpr.c 20255cf03e0b765b742301197d165511ff99e95da0d7ee9c8a2ebc1e888dd049
F src/window.c fff1b51757438c664e471d5184634e48dcdf8ea34b640f3b1b0810b1e06de18c
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627
@ -788,6 +791,7 @@ F test/carray01.test d55d57bf66b1af1c7ac55fae66ff4910884a8f5d21a90a18797ce386212
F test/cast.test 336fa21989b5170ebcaf90c24266be22dd97b3e23d1fad5ecf6ad4efb04c4423
F test/cffault.test 9d6b20606afe712374952eec4f8fd74b1a8097ef
F test/changes.test 9dd8e597d84072122fc8a4fcdea837f4a54a461e6e536053ea984303e8ca937b
F test/changes2.test d222c0cbf5ab0ac4d7c180594e486c1bf20b2098d33e56ce33b8e12eba6823b9
F test/check.test 56e4ed457e9f8683b9fc56f5b964f461f6e8a8dd5a13f3d495408215d66419ed
F test/checkfault.test da6cb3d50247169efcb20bdf57863a3ccfa1d27d9e55cd324f0680096970f014
F test/chunksize.test 427d87791743486cbf0c3b8c625002f3255cb3a89c6eba655a98923b1387b760
@ -1002,12 +1006,12 @@ F test/fts3b.test c15c4a9d04e210d0be67e54ce6a87b927168fbf9c1e3faec8c1a732c366fd4
F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958
F test/fts3comp1.test a0f5b16a2df44dd0b15751787130af2183167c0c
F test/fts3conf.test c84bbaec81281c1788aa545ac6e78a6bd6cde2bdbbce2da261690e3659f5a76b
F test/fts3corrupt.test 43c6c89b994e90997590ece4dfa9c9325c9b61cddd7c97e158498da8b1de79f8
F test/fts3corrupt.test 6732477c5ace050c5758a40a8b5706c8c0cccd416b9c558e0e15224805a40e57
F test/fts3corrupt2.test e318f0676e5e78d5a4b702637e2bb25265954c08a1b1e4aaf93c7880bb0c67d0
F test/fts3corrupt3.test 0d5b69a0998b4adf868cc301fc78f3d0707745f1d984ce044c205cdb764b491f
F test/fts3corrupt4.test 799ff994b964fed7201be6b6b62c7ff2ef7bb3da6c02b9eaf0d96a5a4d9b6ca3
F test/fts3corrupt5.test 0549f85ec4bd22e992f645f13c59b99d652f2f5e643dac75568bfd23a6db7ed5
F test/fts3corrupt6.test 657b4b8e5791d8d4adc93c90588fb25f1c7346544dd877c6c298a0746749146d
F test/fts3corrupt6.test f417c910254f32c0bc9ead7affa991a1d5aec35b3b32a183ffb05eea78289525
F test/fts3cov.test 7eacdbefd756cfa4dc2241974e3db2834e9b372ca215880e00032222f32194cf
F test/fts3d.test 2bd8c97bcb9975f2334147173b4872505b6a41359a4f9068960a36afe07a679f
F test/fts3defer.test f4c20e4c7153d20a98ee49ee5f3faef624fefc9a067f8d8d629db380c4d9f1de
@ -1083,8 +1087,8 @@ F test/fuzz3.test 9c813e6613b837cb7a277b0383cd66bfa07042b4cf0317157c35852f30043c
F test/fuzz4.test c229bcdb45518a89e1d208a21343e061503460ac69fae1539320a89f572eb634
F test/fuzz_common.tcl b7197de6ed1ee8250a4f82d67876f4561b42ee8cbbfc6160dcb66331bad3f830
F test/fuzz_malloc.test f348276e732e814802e39f042b1f6da6362a610af73a528d8f76898fde6b22f2
F test/fuzzcheck.c e34696a5db46738118b2efd14fb71f8458ecf0f482df8bbae18fa1d64db9ab7b
F test/fuzzdata1.db d36e88741b4f23bcbaaf55b006290669d03c6c891cf13c7b3a53bc1b097b693f
F test/fuzzcheck.c 609152902fb51e718554719f44d13677f68c53d98d15fb359fbefdd134be153b
F test/fuzzdata1.db 3e86d9cf5aea68ddb8e27c02d7dfdaa226347426c7eb814918e4d95475bf8517
F test/fuzzdata2.db 128b3feeb78918d075c9b14b48610145a0dd4c8d6f1ca7c2870c7e425f5bf31f
F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba
F test/fuzzdata4.db b502c7d5498261715812dd8b3c2005bad08b3a26e6489414bd13926cd3e42ed2
@ -1095,6 +1099,7 @@ F test/fuzzdata8.db ca9a97f401b06b0d5376139ec7e1f9e773e13345a9a2d9ccc0032cdbfede
F test/fuzzer1.test 3d4c4b7e547aba5e5511a2991e3e3d07166cfbb8
F test/fuzzer2.test a85ef814ce071293bce1ad8dffa217cbbaad4c14
F test/fuzzerfault.test f64c4aef4c9e9edf1d6dc0d3f1e65dcc81e67c996403c88d14f09b74807a42bc
F test/fuzzinvariants.c cd5a4c9b891a5c843e0f97a513c095490e974f1ea51392b13c0a7d77fff8e46d
F test/gcfault.test dd28c228a38976d6336a3fc42d7e5f1ad060cb8c
F test/gencol1.test cc0dbb0ee116e5602e18ea7d47f2a0f76b26e09a823b7c36ef254370c2b0f3c1
F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98
@ -1114,7 +1119,7 @@ F test/in5.test b32ce7f4a93f44c5dee94af16886d922cc16ebe33c8e1765c73d4049d0f4b40f
F test/in6.test f5f40d6816a8bb7c784424b58a10ac38efb76ab29127a2c17399e0cbeeda0e4b
F test/incrblob.test c9b96afc292aeff43d6687bcb09b0280aa599822
F test/incrblob2.test a494c9e848560039a23974b9119cfc2cf3ad3bd15cc2694ee6367ae537ef8f1f
F test/incrblob3.test d47be78a46da142dd60b93772db6e03936a8a36a3b6129dff8d11923dfdc6d63
F test/incrblob3.test 67621a04b3084113bf38ce03797d70eca012d9d8f948193b8f655df577b0da6f
F test/incrblob4.test 21a52a6843a56cdcce968c6a86b72a7066d0e6ba
F test/incrblob_err.test 89372a28f1d98254f03fed705f9efcd34ef61a674df16d2dbb4726944a2de5e9
F test/incrblobfault.test de274b1e329169c2c3438f9528994807ea8201ebf38ae9f157d34bf3ec0cc549
@ -1128,10 +1133,10 @@ F test/index2.test f835d5e13ca163bd78c4459ca15fd2e4ed487407
F test/index3.test 51685f39345462b84fcf77eb8537af847fdf438cc96b05c45d6aaca4e473ade0
F test/index4.test ab92e736d5946840236cd61ac3191f91a7856bf6
F test/index5.test 8621491915800ec274609e42e02a97d67e9b13e7
F test/index6.test 6e5b6943f6a97a34898e48c4d0d4990caf55c12c00465a43a9c33d2fd9a3a820
F test/index6.test b376a648e85aa71c50074382784e6cb0c126ec46e43d1ad15af9a4d234c52e65
F test/index7.test b238344318e0b4e42126717f6554f0e7dfd0b39cecad4b736039b43e1e3b6eb3
F test/index8.test caa097735c91dbc23d8a402f5e63a2a03c83840ba3928733ed7f9a03f8a912a3
F test/index9.test 0aa3e509dddf81f93380396e40e9bb386904c1054924ba8fa9bcdfe85a8e7721
F test/index9.test 2ac891806a4136ef3e91280477e23114e67575207dc331e6797fa0ed9379f997
F test/indexedby.test f21eca4f7a6ffe14c8500a7ad6cd53166666c99e5ccd311842a28bc94a195fe0
F test/indexexpr1.test 3360c2a29a8844e7c4b13293567025281257f9e13a31854cfff6959cede11502
F test/indexexpr2.test 2c7abe3c48f8aaa5a448615ab4d13df3662185d28419c00999670834a3f0b484
@ -1158,20 +1163,21 @@ F test/ioerr4.test f130fe9e71008577b342b8874d52984bd04ede2c
F test/ioerr5.test 2edfa4fb0f896f733071303b42224df8bedd9da4
F test/ioerr6.test a395a6ab144b26a9e3e21059a1ab6a7149cca65b
F test/istrue.test e7f285bb70282625c258e866ce6337d4c762922f5a300e1b50f958aef6e7d9c9
F test/join.test edeaff6edc1c1a2bcfebee343744e04d000f861c3d67cb653114f88565f8c955
F test/join.test 5c7f917aa219a125d1df517d3df384c79f74860fdfb2e48c0a599f1e2b3e9ed8
F test/join2.test 466b07233820f5deee66a6c3bf6e4500c8bbf7b83649e67606f5f649c07928c0
F test/join3.test 6f0c774ff1ba0489e6c88a3e77b9d3528fb4fda0
F test/join4.test 1a352e4e267114444c29266ce79e941af5885916
F test/join5.test d22b6cba8fb59ab3f1c82701434c360705eb12d4ce200c449f37b018fc47681a
F test/join6.test f809c025fa253f9e150c0e9afd4cef8813257bceeb6f46e04041228c9403cc2c
F test/join7.test 8e72de4b45e5e930d18c305c7efe86015fb2552731e4e03ea226353036b0dab0
F test/join8.test 616eb7c2e4f2a54f2d730b914884d2205c8ada4757e89d08089255964a28e78e
F test/join7.test 2268dcbb54b724391dda3748ea95c60d960607ffeed67885675998e7117697f6
F test/join8.test e3c8ca1419e3519596a7ef129246ed118d2508b1ebb69ea83b51ea0eda32037c
F test/join9.test 9056ddd3b0c0f4f9d658f4521038d9a37dc23ead8ca9a505d0b0db2b6a471e05
F test/joinA.test 7eab225dc1c1ab258a5e62513a4ed7cabbd3db971d59d5d92f4fb6fa14c12f6a
F test/joinB.test 1b2ba3fc8568b49411787fccbf540570c148e9b6a53a30f80691cb6268098ded
F test/joinC.test 1f1a602c2127f55f136e2cbd3bf2d26546614bf8cffe5902ec1ac9c07f87f207
F test/joinD.test 7f0f4dd1f2767330bf1fda5c9cc8a437015a54bcd2355036b4d04ddfc1519d76
F test/joinD.test 1a430af8dac5b68663f13df534ffe98775e582bac2305b80f1e8eb4ab778672a
F test/joinE.test d5d182f3812771e2c0d97c9dcf5dbe4c41c8e21c82560e59358731c4a3981d6b
F test/joinF.test 53dd66158806823ea680dd7543b5406af151b5aafa5cd06a7f3231cd94938127
F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497
F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4
F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ffa140e
@ -1352,9 +1358,9 @@ F test/round1.test 768018b04522ca420b1aba8a24bd76091d269f3bce3902af3ec6ebcee41ab
F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc
F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81
F test/rowid.test e29025be95baf6b32f0d5edef59a7633028325896a98f1caa8019559ca910350
F test/rowvalue.test 228b312f8526ed000ecda559a6a9adf30aa2d79dcb12afa5c04eebaafcf55eae
F test/rowvalue.test ff1ffa31cebe12feb6f989e09263f3b1e8c560db94b40fe006126a8435fd6832
F test/rowvalue2.test 060d238b7e5639a7c5630cb5e63e311b44efef2b
F test/rowvalue3.test 3068f508753af69884b12125995f023da0dbb256
F test/rowvalue3.test 1347e25ca11c547c5a6ff0cc5626f95aa9740e9275bfaec096029f57cb2130ce
F test/rowvalue4.test 441e7e366ac6d939a3a95a574031c56ec2a854077a91d66eee5ff1d86cb5be58
F test/rowvalue5.test 00740304ea6a53a8704640c7405690f0045d5d2a6b4b04dde7bccc14c3068ea7
F test/rowvalue6.test d19b54feb604d5601f8614b15e214e0774c01087
@ -1476,7 +1482,7 @@ F test/subquery.test 3a1a5b600b8d4f504d2a2c61f33db820983dba94a0ef3e4aedca8f0165e
F test/subquery2.test 90cf944b9de8204569cf656028391e4af1ccc8c0cc02d4ef38ee3be8de1ffb12
F test/subselect.test 0966aa8e720224dbd6a5e769a3ec2a723e332303
F test/substr.test a673e3763e247e9b5e497a6cacbaf3da2bd8ec8921c0677145c109f2e633f36b
F test/subtype1.test 7fe09496352f97053af1437150751be2d0a0cae8
F test/subtype1.test 45c85632abd38f7ea9b33f17448d966d67550f552e0822bab74576814d0d1718
F test/superlock.test ec94f0556b6488d97f71c79f9061ae08d9ab8f12
F test/swarmvtab.test 250231404fcac88f61a6c147bb0e3a118ed879278cd3ccb0ae2d3a729e1e8e26
F test/swarmvtab2.test c948cb2fdfc5b01d85e8f6d6504854202dc1a0782ab2a0ed61538f27cbd0aa5c
@ -1854,6 +1860,7 @@ F test/window9.test 349c71eab4288a1ffc19e2f65872ec2c37e6cf8a1dda2ad300364b7450ae
F test/windowA.test 6d63dc1260daa17141a55007600581778523a8b420629f1282d2acfc36af23be
F test/windowB.test f2fb42b864b0cf431c956407583e9478a74c3642bdf8737fdcb6ff4a40298b07
F test/windowC.test 6fd75f5bb2f1343d34e470e36e68f0ff638d8a42f6aa7d99471261b31a0d42f2
F test/windowD.test 65cf5a765fb8072450e8a0de2979ce7f09a38d87724fe1280c6444073e3da49b
F test/windowerr.tcl f5acd6fbc210d7b5546c0e879d157888455cd4a17a1d3f28f07c1c8a387019e0
F test/windowerr.test a8b752402109c15aa1c5efe1b93ccb0ce1ef84fa964ae1cd6684dd0b3cc1819b
F test/windowfault.test 15094c1529424e62f798bc679e3fe9dfab6e8ba2f7dfe8c923b6248c31660a7c
@ -1983,8 +1990,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 33d77fea4084c5aba9203dfeddb820424f102dcb8347dc59e32b922bdb241382 7e87892c249f023ee9ed1d5f75a9ad8db10fb38f14dd9e6954b12b9b28400b07
R c9081974dfbb7d140ac326c1bbce54a8
P 934656f13dabc41ccf307b10dca7377c758b8a3b93eca57c072745c2786d6b3c 3a461f61b47e6ba6d5dcc2b7470ebde512b57bc68086f65050e07b06f42b7351
R 25e193ae4a494f5ff48654bafa28631f
U drh
Z bbe2f38f083ae616992549cc7e11561e
Z b958844303a6732e4928e10fcec459d3
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
934656f13dabc41ccf307b10dca7377c758b8a3b93eca57c072745c2786d6b3c
c8ad869938b06378f49c02655c00ee4f3315e1275d15e69d4ff61d6f60230fe8

View File

@ -3933,12 +3933,17 @@ static int incrVacuumStep(BtShared *pBt, Pgno nFin, Pgno iLastPg, int bCommit){
}
do {
MemPage *pFreePg;
Pgno dbSize = btreePagecount(pBt);
rc = allocateBtreePage(pBt, &pFreePg, &iFreePg, iNear, eMode);
if( rc!=SQLITE_OK ){
releasePage(pLastPg);
return rc;
}
releasePage(pFreePg);
if( iFreePg>dbSize ){
releasePage(pLastPg);
return SQLITE_CORRUPT_BKPT;
}
}while( bCommit && iFreePg>nFin );
assert( iFreePg<iLastPg );
@ -9165,7 +9170,7 @@ int sqlite3BtreeInsert(
TRACE(("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n",
pCur->pgnoRoot, pX->nKey, pX->nData, pPage->pgno,
loc==0 ? "overwrite" : "new entry"));
assert( pPage->isInit );
assert( pPage->isInit || CORRUPT_DB );
newCell = pBt->pTmpSpace;
assert( newCell!=0 );
if( flags & BTREE_PREFORMAT ){

View File

@ -3955,7 +3955,17 @@ static int exprCodeInlineFunction(
caseExpr.x.pList = pFarg;
return sqlite3ExprCodeTarget(pParse, &caseExpr, target);
}
#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
case INLINEFUNC_sqlite_offset: {
Expr *pArg = pFarg->a[0].pExpr;
if( pArg->op==TK_COLUMN && pArg->iTable>=0 ){
sqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target);
}else{
sqlite3VdbeAddOp2(v, OP_Null, 0, target);
}
break;
}
#endif
default: {
/* The UNLIKELY() function is a no-op. The result is the value
** of the first argument.
@ -4494,20 +4504,8 @@ expr_code_doover:
if( !pColl ) pColl = db->pDfltColl;
sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ);
}
#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
if( (pDef->funcFlags & SQLITE_FUNC_OFFSET)!=0 && ALWAYS(pFarg!=0) ){
Expr *pArg = pFarg->a[0].pExpr;
if( pArg->op==TK_COLUMN ){
sqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target);
}else{
sqlite3VdbeAddOp2(v, OP_Null, 0, target);
}
}else
#endif
{
sqlite3VdbeAddFunctionCall(pParse, constMask, r1, target, nFarg,
pDef, pExpr->op2);
}
sqlite3VdbeAddFunctionCall(pParse, constMask, r1, target, nFarg,
pDef, pExpr->op2);
if( nFarg ){
if( constMask==0 ){
sqlite3ReleaseTempRange(pParse, r1, nFarg);
@ -4579,8 +4577,24 @@ expr_code_doover:
exprCodeBetween(pParse, pExpr, target, 0, 0);
return target;
}
case TK_COLLATE: {
if( !ExprHasProperty(pExpr, EP_Collate)
&& ALWAYS(pExpr->pLeft)
&& pExpr->pLeft->op==TK_FUNCTION
){
inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target);
if( inReg!=target ){
sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target);
inReg = target;
}
sqlite3VdbeAddOp1(v, OP_ClrSubtype, inReg);
return inReg;
}else{
pExpr = pExpr->pLeft;
goto expr_code_doover; /* 2018-04-28: Prevent deep recursion. */
}
}
case TK_SPAN:
case TK_COLLATE:
case TK_UPLUS: {
pExpr = pExpr->pLeft;
goto expr_code_doover; /* 2018-04-28: Prevent deep recursion. OSSFuzz. */

View File

@ -2241,8 +2241,7 @@ void sqlite3RegisterBuiltinFunctions(void){
INLINE_FUNC(likelihood, 2, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY),
INLINE_FUNC(likely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY),
#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC
{1, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_OFFSET|SQLITE_FUNC_TYPEOF,
0, 0, noopFunc, 0, 0, 0, "sqlite_offset", {0} },
INLINE_FUNC(sqlite_offset, 1, INLINEFUNC_sqlite_offset, 0 ),
#endif
FUNCTION(ltrim, 1, 1, 0, trimFunc ),
FUNCTION(ltrim, 2, 1, 0, trimFunc ),

View File

@ -2613,6 +2613,7 @@ static int pager_truncate(Pager *pPager, Pgno nPage){
memset(pTmp, 0, szPage);
testcase( (newSize-szPage) == currentSize );
testcase( (newSize-szPage) > currentSize );
sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_SIZE_HINT, &newSize);
rc = sqlite3OsWrite(pPager->fd, pTmp, szPage, newSize-szPage);
}
if( rc==SQLITE_OK ){

View File

@ -429,15 +429,21 @@ void sqlite3SetJoinExpr(Expr *p, int iTable, u32 joinFlag){
** an ordinary term that omits the EP_OuterON mark.
**
** This happens when a LEFT JOIN is simplified into an ordinary JOIN.
**
** If nullable is true, that means that Expr p might evaluate to NULL even
** if it is a reference to a NOT NULL column. This can happen, for example,
** if the table that p references is on the left side of a RIGHT JOIN.
** If nullable is true, then take care to not remove the EP_CanBeNull bit.
** See forum thread https://sqlite.org/forum/forumpost/b40696f50145d21c
*/
static void unsetJoinExpr(Expr *p, int iTable){
static void unsetJoinExpr(Expr *p, int iTable, int nullable){
while( p ){
if( ExprHasProperty(p, EP_OuterON)
&& (iTable<0 || p->w.iJoin==iTable) ){
ExprClearProperty(p, EP_OuterON);
ExprSetProperty(p, EP_InnerON);
}
if( p->op==TK_COLUMN && p->iTable==iTable ){
if( p->op==TK_COLUMN && p->iTable==iTable && !nullable ){
ExprClearProperty(p, EP_CanBeNull);
}
if( p->op==TK_FUNCTION ){
@ -445,11 +451,11 @@ static void unsetJoinExpr(Expr *p, int iTable){
if( p->x.pList ){
int i;
for(i=0; i<p->x.pList->nExpr; i++){
unsetJoinExpr(p->x.pList->a[i].pExpr, iTable);
unsetJoinExpr(p->x.pList->a[i].pExpr, iTable, nullable);
}
}
}
unsetJoinExpr(p->pLeft, iTable);
unsetJoinExpr(p->pLeft, iTable, nullable);
p = p->pRight;
}
}
@ -610,6 +616,7 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){
sqlite3SetJoinExpr(pRight->u3.pOn, pRight->iCursor, joinType);
p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pRight->u3.pOn);
pRight->u3.pOn = 0;
pRight->fg.isOn = 1;
}
}
return 0;
@ -3763,9 +3770,10 @@ static Expr *substExpr(
Expr *pExpr /* Expr in which substitution occurs */
){
if( pExpr==0 ) return 0;
if( ExprHasProperty(pExpr, EP_OuterON)
if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON)
&& pExpr->w.iJoin==pSubst->iTable
){
testcase( ExprHasProperty(pExpr, EP_InnerON) );
pExpr->w.iJoin = pSubst->iNewTable;
}
if( pExpr->op==TK_COLUMN
@ -3810,6 +3818,11 @@ static Expr *substExpr(
}
sqlite3ExprDelete(db, pExpr);
pExpr = pNew;
if( pExpr->op==TK_TRUEFALSE ){
pExpr->u.iValue = sqlite3ExprTruthValue(pExpr);
pExpr->op = TK_INTEGER;
ExprSetProperty(pExpr, EP_IntValue);
}
/* Ensure that the expression now has an implicit collation sequence,
** just as it did when it was a column of a view or sub-query. */
@ -4163,6 +4176,11 @@ static void renumberCursors(
**
** (28) The subquery is not a MATERIALIZED CTE.
**
** (29) Either the subquery is not the right-hand operand of a join with an
** ON or USING clause nor the right-hand operand of a NATURAL JOIN, or
** the right-most table within the FROM clause of the subquery
** is not part of an outer join.
**
**
** In this routine, the "p" parameter is a pointer to the outer query.
** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query
@ -4290,6 +4308,35 @@ static int flattenSubquery(
return 0; /* (28) */
}
/* Restriction (29):
**
** We do not want two constraints on the same term of the flattened
** query where one constraint has EP_InnerON and the other is EP_OuterON.
** To prevent this, one or the other of the following conditions must be
** false:
**
** (29a) The right-most entry in the FROM clause of the subquery
** must not be part of an outer join.
**
** (29b) The subquery itself must not be the right operand of a
** NATURAL join or a join that as an ON or USING clause.
**
** These conditions are sufficient to keep an EP_OuterON from being
** flattened into an EP_InnerON. Restrictions (3a) and (27) prevent
** an EP_InnerON from being flattened into an EP_OuterON.
*/
if( pSubSrc->nSrc>=2
&& (pSubSrc->a[pSubSrc->nSrc-1].fg.jointype & JT_OUTER)!=0
){
if( (pSubitem->fg.jointype & JT_NATURAL)!=0
|| pSubitem->fg.isUsing
|| NEVER(pSubitem->u3.pOn!=0) /* ON clause already shifted into WHERE */
|| pSubitem->fg.isOn
){
return 0;
}
}
/* Restriction (17): If the sub-query is a compound SELECT, then it must
** use only the UNION ALL operator. And none of the simple select queries
** that make up the compound SELECT are allowed to be aggregate or distinct
@ -4681,7 +4728,11 @@ static void constInsert(
static void findConstInWhere(WhereConst *pConst, Expr *pExpr){
Expr *pRight, *pLeft;
if( NEVER(pExpr==0) ) return;
if( ExprHasProperty(pExpr, EP_OuterON) ) return;
if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON) ){
testcase( ExprHasProperty(pExpr, EP_OuterON) );
testcase( ExprHasProperty(pExpr, EP_InnerON) );
return;
}
if( pExpr->op==TK_AND ){
findConstInWhere(pConst, pExpr->pRight);
findConstInWhere(pConst, pExpr->pLeft);
@ -5022,7 +5073,7 @@ static int pushDownWhereTerms(
while( pSubq ){
SubstContext x;
pNew = sqlite3ExprDup(pParse->db, pWhere, 0);
unsetJoinExpr(pNew, -1);
unsetJoinExpr(pNew, -1, 1);
x.pParse = pParse;
x.iTable = pSrc->iCursor;
x.iNewTable = pSrc->iCursor;
@ -6706,7 +6757,8 @@ int sqlite3Select(
SELECTTRACE(0x100,pParse,p,
("LEFT-JOIN simplifies to JOIN on term %d\n",i));
pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER);
unsetJoinExpr(p->pWhere, pItem->iCursor);
unsetJoinExpr(p->pWhere, pItem->iCursor,
pTabList->a[0].fg.jointype & JT_LTORJ);
}
/* No futher action if this term of the FROM clause is no a subquery */

View File

@ -1838,7 +1838,11 @@ static int safeModeAuth(
UNUSED_PARAMETER(zA4);
switch( op ){
case SQLITE_ATTACH: {
#ifndef SQLITE_SHELL_WASM_MODE
/* In WASM builds the filesystem is a virtual sandbox, so
** there's no harm in using ATTACH. */
failIfSafeMode(p, "cannot run ATTACH in safe mode");
#endif
break;
}
case SQLITE_FUNCTION: {
@ -4293,7 +4297,9 @@ static const char *(azHelp[]) = {
".connection [close] [#] Open or close an auxiliary database connection",
".databases List names and files of attached databases",
".dbconfig ?op? ?val? List or change sqlite3_db_config() options",
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
".dbinfo ?DB? Show status information about the database",
#endif
".dump ?OBJECTS? Render database content as SQL",
" Options:",
" --data-only Output only INSERT statements",
@ -5831,6 +5837,7 @@ static int db_int(sqlite3 *db, const char *zSql){
return res;
}
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
/*
** Convert a 2-byte or 4-byte big-endian integer into a native integer
*/
@ -5937,6 +5944,8 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){
utf8_printf(p->out, "%-20s %u\n", "data version", iDataVersion);
return 0;
}
#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE)
&& defined(SQLITE_ENABLE_DBPAGE_VTAB) */
/*
** Print the current sqlite3_errmsg() value to stderr and return 1.
@ -8479,11 +8488,11 @@ static int do_meta_command(char *zLine, ShellState *p){
}
}else
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){
rc = shell_dbinfo_command(p, nArg, azArg);
}else
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){
open_db(p, 0);
rc = recoverDatabaseCmd(p, nArg, azArg);

View File

@ -1884,7 +1884,7 @@ struct FuncDestructor {
#define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a
** single query - might change over time */
#define SQLITE_FUNC_TEST 0x4000 /* Built-in testing functions */
#define SQLITE_FUNC_OFFSET 0x8000 /* Built-in sqlite_offset() function */
/* 0x8000 -- available for reuse */
#define SQLITE_FUNC_WINDOW 0x00010000 /* Built-in window-only function */
#define SQLITE_FUNC_INTERNAL 0x00040000 /* For use by NestedParse() only */
#define SQLITE_FUNC_DIRECT 0x00080000 /* Not for use in TRIGGERs or VIEWs */
@ -1901,6 +1901,7 @@ struct FuncDestructor {
#define INLINEFUNC_expr_compare 3
#define INLINEFUNC_affinity 4
#define INLINEFUNC_iif 5
#define INLINEFUNC_sqlite_offset 6
#define INLINEFUNC_unlikely 99 /* Default case */
/*
@ -3100,6 +3101,7 @@ struct SrcItem {
unsigned isCte :1; /* This is a CTE */
unsigned notCte :1; /* This item may not match a CTE */
unsigned isUsing :1; /* u3.pUsing is valid */
unsigned isOn :1; /* u3.pOn was once valid and non-NULL */
unsigned isSynthUsing :1; /* u3.pUsing is synthensized from NATURAL */
unsigned isNestedFrom :1; /* pSelect is a SF_NestedFrom subquery */
} fg;

View File

@ -2965,7 +2965,7 @@ deserialize_error:
}
if( objc==(6+isReadonly) ){
zDb = Tcl_GetString(objv[2]);
zDb = Tcl_GetString(objv[2+isReadonly]);
}
zTable = Tcl_GetString(objv[objc-3]);
zColumn = Tcl_GetString(objv[objc-2]);

View File

@ -215,6 +215,9 @@ void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){
if( pItem->fg.isCte ){
sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse);
}
if( pItem->fg.isOn || (pItem->fg.isUsing==0 && pItem->u3.pOn!=0) ){
sqlite3_str_appendf(&x, " ON");
}
sqlite3StrAccumFinish(&x);
sqlite3TreeViewItem(pView, zLine, i<pSrc->nSrc-1);
n = 0;

View File

@ -1028,7 +1028,7 @@ void sqlite3Update(
}else{
sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue,regOldRowid);
}
VdbeCoverageNeverTaken(v);
VdbeCoverage(v);
}
/* Do FK constraint checks. */

View File

@ -1479,11 +1479,16 @@ case OP_Move: {
break;
}
/* Opcode: Copy P1 P2 P3 * *
/* Opcode: Copy P1 P2 P3 * P5
** Synopsis: r[P2@P3+1]=r[P1@P3+1]
**
** Make a copy of registers P1..P1+P3 into registers P2..P2+P3.
**
** If the 0x0002 bit of P5 is set then also clear the MEM_Subtype flag in the
** destination. The 0x0001 bit of P5 indicates that this Copy opcode cannot
** be merged. The 0x0001 bit is used by the query planner and does not
** come into play during query execution.
**
** This instruction makes a deep copy of the value. A duplicate
** is made of any string or blob constant. See also OP_SCopy.
*/
@ -1498,6 +1503,9 @@ case OP_Copy: {
memAboutToChange(p, pOut);
sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem);
Deephemeralize(pOut);
if( (pOut->flags & MEM_Subtype)!=0 && (pOp->p5 & 0x0002)!=0 ){
pOut->flags &= ~MEM_Subtype;
}
#ifdef SQLITE_DEBUG
pOut->pScopyFrom = 0;
#endif
@ -3880,6 +3888,11 @@ case OP_Transaction: {
}
p->expired = 1;
rc = SQLITE_SCHEMA;
/* Set changeCntOn to 0 to prevent the value returned by sqlite3_changes()
** from being modified in sqlite3VdbeHalt(). If this statement is
** reprepared, changeCntOn will be set again. */
p->changeCntOn = 0;
}
if( rc ) goto abort_due_to_error;
break;
@ -4179,8 +4192,8 @@ case OP_OpenDup: {
pCx->pgnoRoot = pOrig->pgnoRoot;
pCx->isOrdered = pOrig->isOrdered;
pCx->ub.pBtx = pOrig->ub.pBtx;
pCx->hasBeenDuped = 1;
pOrig->hasBeenDuped = 1;
pCx->noReuse = 1;
pOrig->noReuse = 1;
rc = sqlite3BtreeCursor(pCx->ub.pBtx, pCx->pgnoRoot, BTREE_WRCSR,
pCx->pKeyInfo, pCx->uc.pCursor);
/* The sqlite3BtreeCursor() routine can only fail for the first cursor
@ -4247,7 +4260,7 @@ case OP_OpenEphemeral: {
aMem[pOp->p3].z = "";
}
pCx = p->apCsr[pOp->p1];
if( pCx && !pCx->hasBeenDuped && ALWAYS(pOp->p2<=pCx->nField) ){
if( pCx && !pCx->noReuse && ALWAYS(pOp->p2<=pCx->nField) ){
/* If the ephermeral table is already open and has no duplicates from
** OP_OpenDup, then erase all existing content so that the table is
** empty again, rather than creating a new table. */
@ -5834,6 +5847,7 @@ case OP_NullRow: {
if( pC==0 ) goto no_mem;
pC->seekResult = 0;
pC->isTable = 1;
pC->noReuse = 1;
pC->uc.pCursor = sqlite3BtreeFakeValidCursor();
}
pC->nullRow = 1;
@ -7978,7 +7992,6 @@ case OP_VColumn: {
VdbeCursor *pCur = p->apCsr[pOp->p1];
assert( pCur!=0 );
assert( pCur->eCurType==CURTYPE_VTAB );
assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) );
pDest = &aMem[pOp->p3];
memAboutToChange(p, pDest);
@ -7986,6 +7999,7 @@ case OP_VColumn: {
sqlite3VdbeMemSetNull(pDest);
break;
}
assert( pCur->eCurType==CURTYPE_VTAB );
pVtab = pCur->uc.pVCur->pVtab;
pModule = pVtab->pModule;
assert( pModule->xColumn );
@ -8313,6 +8327,17 @@ case OP_Function: { /* group */
break;
}
/* Opcode: ClrSubtype P1 * * * *
** Synopsis: r[P1].subtype = 0
**
** Clear the subtype from register P1.
*/
case OP_ClrSubtype: { /* in1 */
pIn1 = &aMem[pOp->p1];
pIn1->flags &= ~MEM_Subtype;
break;
}
/* Opcode: FilterAdd P1 * P3 P4 *
** Synopsis: filter(P1) += key(P3@P4)
**

View File

@ -86,7 +86,7 @@ struct VdbeCursor {
Bool isEphemeral:1; /* True for an ephemeral table */
Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */
Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */
Bool hasBeenDuped:1; /* This cursor was source or target of OP_OpenDup */
Bool noReuse:1; /* OpenEphemeral may not reuse this cursor */
u16 seekHit; /* See the OP_SeekHit and OP_IfNoHope opcodes */
union { /* pBtx for isEphermeral. pAltMap otherwise */
Btree *pBtx; /* Separate file holding temporary table */

View File

@ -1571,8 +1571,8 @@ static int valueFromExpr(
rc = valueFromExpr(db, pExpr->pLeft, enc, aff, ppVal, pCtx);
testcase( rc!=SQLITE_OK );
if( *ppVal ){
sqlite3VdbeMemCast(*ppVal, aff, SQLITE_UTF8);
sqlite3ValueApplyAffinity(*ppVal, affinity, SQLITE_UTF8);
sqlite3VdbeMemCast(*ppVal, aff, enc);
sqlite3ValueApplyAffinity(*ppVal, affinity, enc);
}
return rc;
}

View File

@ -682,6 +682,7 @@ static void translateColumnToCopy(
pOp->p1 = pOp->p2 + iRegister;
pOp->p2 = pOp->p3;
pOp->p3 = 0;
pOp->p5 = 2; /* Cause the MEM_Subtype flag to be cleared */
}else if( pOp->opcode==OP_Rowid ){
pOp->opcode = OP_Sequence;
pOp->p1 = iAutoidxCur;
@ -756,14 +757,17 @@ static int termCanDriveIndex(
char aff;
if( pTerm->leftCursor!=pSrc->iCursor ) return 0;
if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) return 0;
if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ))!=0
&& !ExprHasProperty(pTerm->pExpr, EP_OuterON)
&& (pTerm->eOperator & WO_IS)
){
/* Cannot use an IS term from the WHERE clause as an index driver for
** the RHS of a LEFT JOIN or for the LHS of a RIGHT JOIN. Such a term
** can only be used if it is from the ON clause. */
return 0;
assert( (pSrc->fg.jointype & JT_RIGHT)==0 );
if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ){
testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT );
testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ );
testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) )
testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) );
if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON)
|| pTerm->pExpr->w.iJoin != pSrc->iCursor
){
return 0; /* See tag-20191211-001 */
}
}
if( (pTerm->prereqRight & notReady)!=0 ) return 0;
assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 );
@ -1177,13 +1181,20 @@ static sqlite3_index_info *allocateIndexInfo(
assert( pTerm->u.x.leftColumn<pTab->nCol );
/* tag-20191211-002: WHERE-clause constraints are not useful to the
** right-hand table of a LEFT JOIN nor to the left-hand table of a
** right-hand table of a LEFT JOIN nor to the either table of a
** RIGHT JOIN. See tag-20191211-001 for the
** equivalent restriction for ordinary tables. */
if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ))!=0
&& !ExprHasProperty(pTerm->pExpr, EP_OuterON)
){
continue;
if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ){
testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT );
testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_RIGHT );
testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ );
testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) );
testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) );
if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON)
|| pTerm->pExpr->w.iJoin != pSrc->iCursor
){
continue;
}
}
nTerm++;
pTerm->wtFlags |= TERM_OK;
@ -2833,12 +2844,28 @@ static int whereLoopAddBtreeIndex(
/* tag-20191211-001: Do not allow constraints from the WHERE clause to
** be used by the right table of a LEFT JOIN nor by the left table of a
** RIGHT JOIN. Only constraints in the
** ON clause are allowed. See tag-20191211-002 for the vtab equivalent. */
if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ))!=0
&& !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON)
){
continue;
** RIGHT JOIN. Only constraints in the ON clause are allowed.
** See tag-20191211-002 for the vtab equivalent.
**
** 2022-06-06: See https://sqlite.org/forum/forumpost/206d99a16dd9212f
** for an example of a WHERE clause constraints that may not be used on
** the right table of a RIGHT JOIN because the constraint implies a
** not-NULL condition on the left table of the RIGHT JOIN.
**
** 2022-06-10: The same condition applies to termCanDriveIndex() above.
** https://sqlite.org/forum/forumpost/51e6959f61
*/
if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ){
testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT );
testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_RIGHT );
testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ );
testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) )
testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) );
if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON)
|| pTerm->pExpr->w.iJoin != pSrc->iCursor
){
continue;
}
}
if( IsUniqueIndex(pProbe) && saved_nEq==pProbe->nKeyCol-1 ){
@ -3190,15 +3217,18 @@ static int indexMightHelpWithOrderBy(
*/
static int whereUsablePartialIndex(
int iTab, /* The table for which we want an index */
int isLeft, /* True if iTab is the right table of a LEFT JOIN */
u8 jointype, /* The JT_* flags on the join */
WhereClause *pWC, /* The WHERE clause of the query */
Expr *pWhere /* The WHERE clause from the partial index */
){
int i;
WhereTerm *pTerm;
Parse *pParse = pWC->pWInfo->pParse;
Parse *pParse;
if( jointype & JT_LTORJ ) return 0;
pParse = pWC->pWInfo->pParse;
while( pWhere->op==TK_AND ){
if( !whereUsablePartialIndex(iTab,isLeft,pWC,pWhere->pLeft) ) return 0;
if( !whereUsablePartialIndex(iTab,jointype,pWC,pWhere->pLeft) ) return 0;
pWhere = pWhere->pRight;
}
if( pParse->db->flags & SQLITE_EnableQPSG ) pParse = 0;
@ -3206,7 +3236,7 @@ static int whereUsablePartialIndex(
Expr *pExpr;
pExpr = pTerm->pExpr;
if( (!ExprHasProperty(pExpr, EP_OuterON) || pExpr->w.iJoin==iTab)
&& (isLeft==0 || ExprHasProperty(pExpr, EP_OuterON))
&& ((jointype & JT_OUTER)==0 || ExprHasProperty(pExpr, EP_OuterON))
&& sqlite3ExprImpliesExpr(pParse, pExpr, pWhere, iTab)
&& (pTerm->wtFlags & TERM_VNULL)==0
){
@ -3372,9 +3402,8 @@ static int whereLoopAddBtree(
for(; rc==SQLITE_OK && pProbe;
pProbe=(pSrc->fg.isIndexedBy ? 0 : pProbe->pNext), iSortIdx++
){
int isLeft = (pSrc->fg.jointype & JT_OUTER)!=0;
if( pProbe->pPartIdxWhere!=0
&& !whereUsablePartialIndex(pSrc->iCursor, isLeft, pWC,
&& !whereUsablePartialIndex(pSrc->iCursor, pSrc->fg.jointype, pWC,
pProbe->pPartIdxWhere)
){
testcase( pNew->iTab!=pSrc->iCursor ); /* See ticket [98d973b8f5] */
@ -6034,6 +6063,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
SrcList *pTabList = pWInfo->pTabList;
sqlite3 *db = pParse->db;
int iEnd = sqlite3VdbeCurrentAddr(v);
int nRJ = 0;
/* Generate loop termination code.
*/
@ -6050,8 +6080,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
pRJ->endSubrtn = sqlite3VdbeCurrentAddr(v);
sqlite3VdbeAddOp3(v, OP_Return, pRJ->regReturn, pRJ->addrSubrtn, 1);
VdbeCoverage(v);
assert( pParse->withinRJSubrtn>0 );
pParse->withinRJSubrtn--;
nRJ++;
}
pLoop = pLevel->pWLoop;
if( pLevel->op!=OP_Noop ){
@ -6332,5 +6361,6 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
*/
pParse->nQueryLoop = pWInfo->savedNQueryLoop;
whereInfoFree(db, pWInfo);
pParse->withinRJSubrtn -= nRJ;
return;
}

View File

@ -612,8 +612,9 @@ void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*);
#define WO_AND 0x0400 /* Two or more AND-connected terms */
#define WO_EQUIV 0x0800 /* Of the form A==B, both columns */
#define WO_NOOP 0x1000 /* This term does not restrict search space */
#define WO_ROWVAL 0x2000 /* A row-value term */
#define WO_ALL 0x1fff /* Mask of all possible WO_* values */
#define WO_ALL 0x3fff /* Mask of all possible WO_* values */
#define WO_SINGLE 0x01ff /* Mask of all non-compound WO_* values */
/*

View File

@ -2624,6 +2624,9 @@ Bitmask sqlite3WhereCodeOneLoopStart(
/* Defer processing WHERE clause constraints until after outer
** join processing. tag-20220513a */
continue;
}else if( (pTabItem->fg.jointype & JT_LEFT)==JT_LEFT
&& !ExprHasProperty(pE,EP_OuterON) ){
continue;
}else{
Bitmask m = sqlite3WhereGetMask(&pWInfo->sMaskSet, pE->w.iJoin);
if( m & pLevel->notReady ){
@ -2859,7 +2862,11 @@ SQLITE_NOINLINE void sqlite3WhereRightJoinLoop(
mAll |= pLoop->maskSelf;
for(k=0; k<pWC->nTerm; k++){
WhereTerm *pTerm = &pWC->a[k];
if( pTerm->wtFlags & TERM_VIRTUAL ) break;
if( (pTerm->wtFlags & (TERM_VIRTUAL|TERM_SLICE))!=0
&& pTerm->eOperator!=WO_ROWVAL
){
break;
}
if( pTerm->prereqAll & ~mAll ) continue;
if( ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) ) continue;
pSubWhere = sqlite3ExprAnd(pParse, pSubWhere,

View File

@ -1105,7 +1105,7 @@ static void exprAnalyze(
if( prereqAll!=sqlite3WhereExprUsageNN(pMaskSet, pExpr) ){
printf("\n*** Incorrect prereqAll computed for:\n");
sqlite3TreeViewExpr(0,pExpr,0);
abort();
assert( 0 );
}
#endif
@ -1411,7 +1411,7 @@ static void exprAnalyze(
}
pTerm = &pWC->a[idxTerm];
pTerm->wtFlags |= TERM_CODED|TERM_VIRTUAL; /* Disable the original */
pTerm->eOperator = 0;
pTerm->eOperator = WO_ROWVAL;
}
/* If there is a vector IN term - e.g. "(a, b) IN (SELECT ...)" - create
@ -1612,7 +1612,7 @@ void sqlite3WhereAddLimit(WhereClause *pWC, Select *p){
/* This term is a vector operation that has been decomposed into
** other, subsequent terms. It can be ignored. See tag-20220128a */
assert( pWC->a[ii].wtFlags & TERM_VIRTUAL );
assert( pWC->a[ii].eOperator==0 );
assert( pWC->a[ii].eOperator==WO_ROWVAL );
continue;
}
if( pWC->a[ii].leftCursor!=iCsr ) return;

95
test/changes2.test Normal file
View File

@ -0,0 +1,95 @@
# 2022 June 6
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix changes2
do_execsql_test 1.0 {
CREATE TABLE some_table (
id INTEGER NOT NULL, value VARCHAR(40) NOT NULL, PRIMARY KEY (id)
);
INSERT INTO some_table (id, value) VALUES (1, 'v1');
} {}
set ::stmt [sqlite3_prepare_v2 db {
UPDATE some_table SET value='v2' WHERE id=1 RETURNING id
} -1 dummy]
do_test 1.1 {
list [sqlite3_step $::stmt] [db changes]
} {SQLITE_ROW 1}
do_test 1.2 {
list [sqlite3_step $::stmt] [db changes]
} {SQLITE_DONE 1}
sqlite3_reset $::stmt
do_execsql_test 1.2 {
DROP TABLE some_table;
CREATE TABLE some_table (
id INTEGER NOT NULL, value VARCHAR(40) NOT NULL, PRIMARY KEY (id)
);
INSERT INTO some_table (id, value) VALUES (1, 'v1');
} {}
do_test 1.3 {
list [sqlite3_step $::stmt] [db changes]
} {SQLITE_ROW 1}
do_test 1.4 {
list [sqlite3_step $::stmt] [db changes]
} {SQLITE_DONE 1}
sqlite3_finalize $::stmt
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {
CREATE TABLE t1(a, b);
CREATE TABLE log(t);
} {}
set ::stmt [sqlite3_prepare_v2 db {
INSERT INTO log VALUES(changes() || ' changes')
} -1 dummy]
do_execsql_test 2.1 {
INSERT INTO t1 VALUES (1, 'v1'), (2, 'v2');
} {}
do_test 2.2 {
list [sqlite3_step $::stmt] [sqlite3_reset $::stmt]
} {SQLITE_DONE SQLITE_OK}
do_execsql_test 2.3 {
CREATE TABLE t3(x);
}
do_execsql_test 2.2 {
INSERT INTO t1 VALUES (3, 'v1'), (4, 'v2');
} {}
do_test 2.3 {
list [sqlite3_step $::stmt] [sqlite3_reset $::stmt]
} {SQLITE_DONE SQLITE_OK}
sqlite3_finalize $::stmt
do_execsql_test 2.4 {
SELECT * FROM log;
} {{2 changes} {2 changes}}
finish_test

View File

@ -193,4 +193,42 @@ do_catchsql_test 7.10 {
SELECT matchinfo( f , 'pcx') FROM f WHERE b MATCH x'c533';
} {1 {database disk image is malformed}}
reset_db
sqlite3_fts3_may_be_corrupt 1
do_execsql_test 8.1 {
CREATE VIRTUAL TABLE f USING fts3(a);
INSERT INTO f(f) VALUES('nodesize=24');
BEGIN;
INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz0123456789');
INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz0123456789');
INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz0123456789');
INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz012345678X');
INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz012345678X');
INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz012345678X');
COMMIT;
BEGIN;
INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz0123456789');
INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz0123456789');
INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz0123456789');
INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz012345678X');
INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz012345678X');
INSERT INTO f VALUES('abcdefghijklmnopqrstuvwxyz012345678X');
COMMIT;
SELECT count(*) FROM f_segments;
} {4}
do_execsql_test 8.2 {
UPDATE f_segments SET block = (
SELECT block FROM f_segments WHERE blockid=1
) WHERE blockid=2
}
do_catchsql_test 8.3 {
INSERT INTO f(f) VALUES('merge=2,2');
} {1 {database disk image is malformed}}
sqlite3_fts3_may_be_corrupt 0
finish_test

View File

@ -64,7 +64,8 @@ do_execsql_test 2.1 {
#-------------------------------------------------------------------------
reset_db
do_execsql_test 3.0 {
breakpoint
do_catchsql_test 3.0 {
CREATE VIRTUAL TABLE main.Table0 USING fts3();
INSERT INTO Table0 VALUES (1), (printf('%8.1280000X') ), (1), (printf('%8.1280000X') ), (1) ;
INSERT INTO Table0 VALUES (0), (printf('%8.1280000X%8.1280000X') ), (1), (printf('%1280000.1280000X%#1280000.1280000E%8.1280000X') ), (1) ;
@ -72,7 +73,7 @@ do_execsql_test 3.0 {
UPDATE Table0_segdir SET start_block = 1;
INSERT INTO Table0 VALUES (1) ;
INSERT INTO Table0(Table0) VALUES('merge=6,8');
}
} {1 {database disk image is malformed}}
set sqlite_fts3_enable_parentheses $saved_sqlite_fts3_enable_parentheses
finish_test

View File

@ -153,6 +153,8 @@ static struct GlobalVars {
int nSql; /* Number of SQL scripts */
Blob *pFirstSql; /* First SQL script */
unsigned int uRandom; /* Seed for the SQLite PRNG */
unsigned char doInvariantChecks; /* True to run query invariant checks */
unsigned int nInvariant; /* Number of invariant checks run */
char zTestName[100]; /* Name of current test */
} g;
@ -830,6 +832,13 @@ static int progress_handler(void *pClientData) {
return rc;
}
/*
** Flag bits set by block_troublesome_sql()
*/
#define BTS_SELECT 0x000001
#define BTS_NONSELECT 0x000002
#define BTS_BADFUNC 0x000004
/*
** Disallow debugging pragmas such as "PRAGMA vdbe_debug" and
** "PRAGMA parser_trace" since they can dramatically increase the
@ -838,63 +847,126 @@ static int progress_handler(void *pClientData) {
** Also block ATTACH if attaching a file from the filesystem.
*/
static int block_troublesome_sql(
void *Notused,
void *pClientData,
int eCode,
const char *zArg1,
const char *zArg2,
const char *zArg3,
const char *zArg4
){
(void)Notused;
(void)zArg2;
unsigned int *pFlags = (unsigned int*)pClientData;
(void)zArg3;
(void)zArg4;
if( eCode==SQLITE_PRAGMA ){
if( sqlite3_stricmp("busy_timeout",zArg1)==0
&& (zArg2==0 || strtoll(zArg2,0,0)>100 || strtoll(zArg2,0,10)>100)
){
return SQLITE_DENY;
}else if( eVerbosity==0 ){
if( sqlite3_strnicmp("vdbe_", zArg1, 5)==0
|| sqlite3_stricmp("parser_trace", zArg1)==0
|| sqlite3_stricmp("temp_store_directory", zArg1)==0
switch( eCode ){
case SQLITE_PRAGMA: {
if( sqlite3_stricmp("busy_timeout",zArg1)==0
&& (zArg2==0 || strtoll(zArg2,0,0)>100 || strtoll(zArg2,0,10)>100)
){
return SQLITE_DENY;
}else if( eVerbosity==0 ){
if( sqlite3_strnicmp("vdbe_", zArg1, 5)==0
|| sqlite3_stricmp("parser_trace", zArg1)==0
|| sqlite3_stricmp("temp_store_directory", zArg1)==0
){
return SQLITE_DENY;
}
}else if( sqlite3_stricmp("oom",zArg1)==0
&& zArg2!=0 && zArg2[0]!=0 ){
oomCounter = atoi(zArg2);
}
}else if( sqlite3_stricmp("oom",zArg1)==0
&& zArg2!=0 && zArg2[0]!=0 ){
oomCounter = atoi(zArg2);
*pFlags |= BTS_NONSELECT;
break;
}
}else if( eCode==SQLITE_ATTACH ){
/* Deny the ATTACH if it is attaching anything other than an in-memory
** database. */
if( zArg1==0 ) return SQLITE_DENY;
if( strcmp(zArg1,":memory:")==0 ) return SQLITE_OK;
if( sqlite3_strglob("file:*[?]vfs=memdb", zArg1)==0
&& sqlite3_strglob("file:*[^/a-zA-Z0-9_.]*[?]vfs=memdb", zArg1)!=0
){
return SQLITE_OK;
case SQLITE_ATTACH: {
/* Deny the ATTACH if it is attaching anything other than an in-memory
** database. */
*pFlags |= BTS_NONSELECT;
if( zArg1==0 ) return SQLITE_DENY;
if( strcmp(zArg1,":memory:")==0 ) return SQLITE_OK;
if( sqlite3_strglob("file:*[?]vfs=memdb", zArg1)==0
&& sqlite3_strglob("file:*[^/a-zA-Z0-9_.]*[?]vfs=memdb", zArg1)!=0
){
return SQLITE_OK;
}
return SQLITE_DENY;
}
case SQLITE_SELECT: {
*pFlags |= BTS_SELECT;
break;
}
case SQLITE_FUNCTION: {
static const char *azBadFuncs[] = {
"random",
"randomblob",
"rtreedepth",
};
int i;
for(i=0; i<sizeof(azBadFuncs)/sizeof(azBadFuncs[0]); i++){
if( sqlite3_stricmp(azBadFuncs[i], zArg2)==0 ){
*pFlags |= BTS_BADFUNC;
break;
}
}
break;
}
case SQLITE_READ: {
/* Benign */
break;
}
default: {
*pFlags |= BTS_NONSELECT;
}
return SQLITE_DENY;
}
return SQLITE_OK;
}
/* Implementation found in fuzzinvariant.c */
int fuzz_invariant(
sqlite3 *db, /* The database connection */
sqlite3_stmt *pStmt, /* Test statement stopped on an SQLITE_ROW */
int iCnt, /* Invariant sequence number, starting at 0 */
int iRow, /* The row number for pStmt */
int *pbCorrupt, /* IN/OUT: Flag indicating a corrupt database file */
int eVerbosity /* How much debugging output */
);
/*
** Run the SQL text
*/
static int runDbSql(sqlite3 *db, const char *zSql){
static int runDbSql(sqlite3 *db, const char *zSql, unsigned int *pBtsFlags){
int rc;
sqlite3_stmt *pStmt;
int bCorrupt = 0;
while( isspace(zSql[0]&0x7f) ) zSql++;
if( zSql[0]==0 ) return SQLITE_OK;
if( eVerbosity>=4 ){
printf("RUNNING-SQL: [%s]\n", zSql);
fflush(stdout);
}
(*pBtsFlags) = 0;
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
if( rc==SQLITE_OK ){
int nRow = 0;
while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
nRow++;
if( (*pBtsFlags)==BTS_SELECT
&& g.doInvariantChecks
&& !sqlite3_stmt_isexplain(pStmt)
){
int iCnt = 0;
for(iCnt=0; iCnt<99999; iCnt++){
rc = fuzz_invariant(db, pStmt, iCnt, nRow, &bCorrupt, eVerbosity);
if( rc==SQLITE_DONE ) break;
if( rc!=SQLITE_ERROR ) g.nInvariant++;
if( eVerbosity>0 ){
if( rc==SQLITE_OK ){
printf("invariant-check: ok\n");
}else if( rc==SQLITE_CORRUPT ){
printf("invariant-check: failed due to database corruption\n");
}
}
}
}
if( eVerbosity>=5 ){
int j;
for(j=0; j<sqlite3_column_count(pStmt); j++){
@ -971,6 +1043,7 @@ int runCombinedDbSqlInput(
char *zSql = 0; /* SQL text to run */
int nSql; /* Bytes of SQL text */
FuzzCtx cx; /* Fuzzing context */
unsigned int btsFlags = 0; /* Parsing flags */
if( nByte<10 ) return 0;
if( sqlite3_initialize() ) return 0;
@ -1058,7 +1131,7 @@ int runCombinedDbSqlInput(
/* Block debug pragmas and ATTACH/DETACH. But wait until after
** deserialize to do this because deserialize depends on ATTACH */
sqlite3_set_authorizer(cx.db, block_troublesome_sql, 0);
sqlite3_set_authorizer(cx.db, block_troublesome_sql, &btsFlags);
#ifdef VT02_SOURCES
sqlite3_vt02_init(cx.db, 0, 0);
@ -1083,7 +1156,7 @@ int runCombinedDbSqlInput(
char cSaved = zSql[i+1];
zSql[i+1] = 0;
if( sqlite3_complete(zSql+j) ){
rc = runDbSql(cx.db, zSql+j);
rc = runDbSql(cx.db, zSql+j, &btsFlags);
j = i+1;
}
zSql[i+1] = cSaved;
@ -1093,7 +1166,7 @@ int runCombinedDbSqlInput(
}
}
if( j<i ){
runDbSql(cx.db, zSql+j);
runDbSql(cx.db, zSql+j, &btsFlags);
}
}
testrun_finished:
@ -1574,6 +1647,7 @@ static void showHelp(void){
" --oss-fuzz Enable OSS-FUZZ testing\n"
" --prng-seed N Seed value for the PRGN inside of SQLite\n"
" -q|--quiet Reduced output\n"
" --query-invariants Run query invariant checks\n"
" --rebuild Rebuild and vacuum the database file\n"
" --result-trace Show the results of each SQL command\n"
" --script Output CLI script instead of running tests\n"
@ -1734,6 +1808,9 @@ int main(int argc, char **argv){
verboseFlag = 0;
eVerbosity = 0;
}else
if( strcmp(z,"query-invariants")==0 ){
g.doInvariantChecks = 1;
}else
if( strcmp(z,"rebuild")==0 ){
rebuildFlag = 1;
openFlags4Data = SQLITE_OPEN_READWRITE;
@ -2269,6 +2346,9 @@ int main(int argc, char **argv){
if( !quietFlag && !bScript ){
sqlite3_int64 iElapse = timeOfDay() - iBegin;
if( g.nInvariant ){
printf("fuzzcheck: %u query invariants checked\n", g.nInvariant);
}
printf("fuzzcheck: 0 errors out of %d tests in %d.%03d seconds\n"
"SQLite %s %s\n",
nTest, (int)(iElapse/1000), (int)(iElapse%1000),

Binary file not shown.

316
test/fuzzinvariants.c Normal file
View File

@ -0,0 +1,316 @@
/*
** 2022-06-14
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
**
** This library is used by fuzzcheck to test query invariants.
**
** An sqlite3_stmt is passed in that has just returned SQLITE_ROW. This
** routine does:
**
** * Record the output of the current row
** * Construct an alternative query that should return the same row
** * Run the alternative query and verify that it does in fact return
** the same row
**
*/
#include "sqlite3.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
/* Forward references */
static char *fuzz_invariant_sql(sqlite3_stmt*, int);
static int sameValue(sqlite3_value*,sqlite3_value*);
static void reportInvariantFailed(sqlite3_stmt*,sqlite3_stmt*,int);
/*
** Do an invariant check on pStmt. iCnt determines which invariant check to
** perform. The first check is iCnt==0.
**
** *pbCorrupt is a flag that, if true, indicates that the database file
** is known to be corrupt. A value of non-zero means "yes, the database
** is corrupt". A zero value means "we do not know whether or not the
** database is corrupt". The value might be set prior to entry, or this
** routine might set the value.
**
** Return values:
**
** SQLITE_OK This check was successful.
**
** SQLITE_DONE iCnt is out of range.
**
** SQLITE_CORRUPT The invariant failed, but the underlying database
** file is indicating that it is corrupt, which might
** be the cause of the malfunction.
**
** SQLITE_INTERNAL The invariant failed, and the database file is not
** corrupt. (This never happens because this function
** will call abort() following an invariant failure.)
**
** (other) Some other kind of error occurred.
*/
int fuzz_invariant(
sqlite3 *db, /* The database connection */
sqlite3_stmt *pStmt, /* Test statement stopped on an SQLITE_ROW */
int iCnt, /* Invariant sequence number, starting at 0 */
int iRow, /* The row number for pStmt */
int *pbCorrupt, /* IN/OUT: Flag indicating a corrupt database file */
int eVerbosity /* How much debugging output */
){
char *zTest;
sqlite3_stmt *pTestStmt = 0;
int rc;
int i;
int nCol;
if( *pbCorrupt ) return SQLITE_DONE;
zTest = fuzz_invariant_sql(pStmt, iCnt);
if( zTest==0 ) return SQLITE_DONE;
rc = sqlite3_prepare_v2(db, zTest, -1, &pTestStmt, 0);
if( rc ){
if( eVerbosity ){
printf("invariant compile failed: %s\n%s\n",
sqlite3_errmsg(db), zTest);
}
sqlite3_free(zTest);
sqlite3_finalize(pTestStmt);
return rc;
}
sqlite3_free(zTest);
nCol = sqlite3_column_count(pStmt);
for(i=0; i<nCol; i++){
sqlite3_bind_value(pTestStmt, i+1, sqlite3_column_value(pStmt,i));
}
if( eVerbosity>=2 ){
char *zSql = sqlite3_expanded_sql(pTestStmt);
printf("invariant-sql #%d:\n%s\n", iCnt, zSql);
sqlite3_free(zSql);
}
while( (rc = sqlite3_step(pTestStmt))==SQLITE_ROW ){
for(i=0; i<nCol; i++){
if( !sameValue(sqlite3_column_value(pStmt,i),
sqlite3_column_value(pTestStmt,i)) ) break;
}
if( i>=nCol ) break;
}
if( rc!=SQLITE_ROW ){
/* No matching output row found */
sqlite3_stmt *pCk = 0;
rc = sqlite3_prepare_v2(db, "PRAGMA integrity_check", -1, &pCk, 0);
if( rc ){
sqlite3_finalize(pCk);
sqlite3_finalize(pTestStmt);
return rc;
}
rc = sqlite3_step(pCk);
if( rc!=SQLITE_ROW
|| sqlite3_column_text(pCk, 0)==0
|| strcmp((const char*)sqlite3_column_text(pCk,0),"ok")!=0
){
*pbCorrupt = 1;
sqlite3_finalize(pCk);
sqlite3_finalize(pTestStmt);
return SQLITE_CORRUPT;
}
sqlite3_finalize(pCk);
rc = sqlite3_prepare_v2(db,
"SELECT 1 FROM bytecode(?1) WHERE opcode='VOpen'", -1, &pCk, 0);
if( rc==SQLITE_OK ) rc = sqlite3_step(pCk);
sqlite3_finalize(pCk);
if( rc==SQLITE_DONE ){
reportInvariantFailed(pStmt, pTestStmt, iRow);
return SQLITE_INTERNAL;
}else if( eVerbosity>0 ){
printf("invariant-error ignored due to the use of virtual tables\n");
}
}
sqlite3_finalize(pTestStmt);
return SQLITE_OK;
}
/*
** Generate SQL used to test a statement invariant.
**
** Return 0 if the iCnt is out of range.
*/
static char *fuzz_invariant_sql(sqlite3_stmt *pStmt, int iCnt){
const char *zIn;
size_t nIn;
const char *zAnd = "WHERE";
int i;
sqlite3_str *pTest;
sqlite3_stmt *pBase = 0;
sqlite3 *db = sqlite3_db_handle(pStmt);
int rc;
int nCol = sqlite3_column_count(pStmt);
int mxCnt;
int bDistinct = 0;
int bOrderBy = 0;
switch( iCnt % 4 ){
case 1: bDistinct = 1; break;
case 2: bOrderBy = 1; break;
case 3: bDistinct = bOrderBy = 1; break;
}
iCnt /= 4;
if( nCol==1 ){
mxCnt = 0;
}else{
mxCnt = nCol;
}
if( iCnt<0 || iCnt>mxCnt ) return 0;
zIn = sqlite3_sql(pStmt);
if( zIn==0 ) return 0;
nIn = strlen(zIn);
while( nIn>0 && (isspace(zIn[nIn-1]) || zIn[nIn-1]==';') ) nIn--;
if( strchr(zIn, '?') ) return 0;
pTest = sqlite3_str_new(0);
sqlite3_str_appendf(pTest, "SELECT %s* FROM (%.*s)",
bDistinct ? "DISTINCT " : "", (int)nIn, zIn);
rc = sqlite3_prepare_v2(db, sqlite3_str_value(pTest), -1, &pBase, 0);
if( rc ){
sqlite3_finalize(pBase);
pBase = pStmt;
}
for(i=0; i<sqlite3_column_count(pStmt); i++){
const char *zColName = sqlite3_column_name(pBase,i);
const char *zSuffix = strchr(zColName, ':');
if( zSuffix
&& isdigit(zSuffix[1])
&& (zSuffix[1]>'3' || isdigit(zSuffix[2]))
){
/* This is a randomized column name and so cannot be used in the
** WHERE clause. */
continue;
}
if( iCnt>0 && i+1!=iCnt ) continue;
if( sqlite3_column_type(pStmt, i)==SQLITE_NULL ){
sqlite3_str_appendf(pTest, " %s \"%w\" ISNULL", zAnd, zColName);
}else{
sqlite3_str_appendf(pTest, " %s \"%w\"=?%d", zAnd, zColName, i+1);
}
zAnd = "AND";
}
if( pBase!=pStmt ) sqlite3_finalize(pBase);
if( bOrderBy ){
sqlite3_str_appendf(pTest, " ORDER BY 1");
}
return sqlite3_str_finish(pTest);
}
/*
** Return true if and only if v1 and is the same as v2.
*/
static int sameValue(sqlite3_value *v1, sqlite3_value *v2){
int x = 1;
if( sqlite3_value_type(v1)!=sqlite3_value_type(v2) ) return 0;
switch( sqlite3_value_type(v1) ){
case SQLITE_INTEGER: {
x = sqlite3_value_int64(v1)==sqlite3_value_int64(v2);
break;
}
case SQLITE_FLOAT: {
x = sqlite3_value_double(v1)==sqlite3_value_double(v2);
break;
}
case SQLITE_TEXT: {
const char *z1 = (const char*)sqlite3_value_text(v1);
const char *z2 = (const char*)sqlite3_value_text(v2);
x = ((z1==0 && z2==0) || (z1!=0 && z2!=0 && strcmp(z1,z1)==0));
break;
}
case SQLITE_BLOB: {
int len1 = sqlite3_value_bytes(v1);
const unsigned char *b1 = sqlite3_value_blob(v1);
int len2 = sqlite3_value_bytes(v2);
const unsigned char *b2 = sqlite3_value_blob(v2);
if( len1!=len2 ){
x = 0;
}else if( len1==0 ){
x = 1;
}else{
x = (b1!=0 && b2!=0 && memcmp(b1,b2,len1)==0);
}
break;
}
}
return x;
}
/*
** Print a single row from the prepared statement
*/
static void printRow(sqlite3_stmt *pStmt, int iRow){
int i, nCol;
nCol = sqlite3_column_count(pStmt);
for(i=0; i<nCol; i++){
printf("row%d.col%d] = ", iRow, i);
switch( sqlite3_column_type(pStmt, i) ){
case SQLITE_NULL: {
printf("NULL\n");
break;
}
case SQLITE_INTEGER: {
printf("(integer) %lld\n", sqlite3_column_int64(pStmt, i));
break;
}
case SQLITE_FLOAT: {
printf("(float) %f\n", sqlite3_column_double(pStmt, i));
break;
}
case SQLITE_TEXT: {
printf("(text) \"%s\"\n", sqlite3_column_text(pStmt, i));
break;
}
case SQLITE_BLOB: {
int n = sqlite3_column_bytes(pStmt, i);
int j;
unsigned const char *data = sqlite3_column_blob(pStmt, i);
printf("(blob %d bytes) x'", n);
for(j=0; j<20 && j<n; j++){
printf("%02x", data[j]);
}
if( j<n ) printf("...");
printf("'\n");
break;
}
}
}
}
/*
** Report a failure of the invariant: The current output row of pOrig
** does not appear in any row of the output from pTest.
*/
static void reportInvariantFailed(
sqlite3_stmt *pOrig, /* The original query */
sqlite3_stmt *pTest, /* The alternative test query with a missing row */
int iRow /* Row number in pOrig */
){
int iTestRow = 0;
printf("Invariant check failed on row %d.\n", iRow);
printf("Original query --------------------------------------------------\n");
printf("%s\n", sqlite3_expanded_sql(pOrig));
printf("Alternative query -----------------------------------------------\n");
printf("%s\n", sqlite3_expanded_sql(pTest));
printf("Result row that is missing from the alternative -----------------\n");
printRow(pOrig, iRow);
printf("Complete results from the alternative query ---------------------\n");
sqlite3_reset(pTest);
while( sqlite3_step(pTest)==SQLITE_ROW ){
iTestRow++;
printRow(pTest, iTestRow);
}
sqlite3_finalize(pTest);
abort();
}

View File

@ -13,6 +13,7 @@
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix incrblob3
ifcapable !incrblob {
finish_test
@ -273,4 +274,41 @@ do_test incrblob3-7.2 {
db close
tvfs delete
#-------------------------------------------------------------------------
#
reset_db
forcedelete test.db2
do_execsql_test 8.1 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
ATTACH 'test.db2' AS aux;
CREATE TABLE aux.t1(a INTEGER PRIMARY KEY, b);
INSERT INTO t1 VALUES(4, 'hello');
INSERT INTO aux.t1 VALUES(4, 'world');
}
do_test 8.2 {
set ::blob [db incrblob -readonly main t1 b 4]
read $::blob
} {hello}
close $::blob
do_test 8.3 {
set ::blob [db incrblob -readonly aux t1 b 4]
read $::blob
} {world}
close $::blob
do_test 8.4 {
set ::blob [db incrblob -readonly t1 b 4]
read $::blob
} {hello}
close $::blob
do_test 8.5 {
list [catch { db incrblob -readonly nosuchdb t1 b 4 } msg] $msg
} {1 {no such table: nosuchdb.t1}}
db close
finish_test

View File

@ -508,4 +508,24 @@ do_execsql_test index6-18.1 {
SELECT * FROM t1 WHERE a IS NOT NULL;
} {10 10}
# 2022-06-09
# https://sqlite.org/forum/forumpost/c4676c4956
# Cannot do a scan of a partial index on the left table of a RIGHT JOIN
# since that will cause extra rows to appear in the output during the
# right-join no-match loop. The following testcase is verify using
# PostgreSQL 14.
#
reset_db
do_execsql_test index6-19.1 {
CREATE TABLE t1(a INT, b INT);
INSERT INTO t1(a) VALUES(2);
CREATE TABLE t2(c INT);
CREATE INDEX i0 ON t2(c) WHERE c=3;
CREATE TABLE t3(d INT);
INSERT INTO t3 VALUES(1);
}
do_execsql_test index6-19.2 {
SELECT * FROM t2 RIGHT JOIN t3 ON d<>0 LEFT JOIN t1 ON c=3 WHERE t1.a<>0;
} {}
finish_test

View File

@ -34,6 +34,7 @@ do_execsql_test 1.0 {
CREATE TABLE t1(x, y);
CREATE INDEX t1x ON t1(x) WHERE y=45;
}
unset -nocomplain a
set y [expr 45]
do_sqluses_test 1.1 { SELECT * FROM t1 WHERE x=? AND y=$y } {t1 t1x}
set y [expr 45.1]

View File

@ -1067,4 +1067,38 @@ do_catchsql_test join-26.1 {
SELECT * FROM t5 JOIN ((t4 JOIN (t5 JOIN t6)) t7);
} {/1 {.*}/}
# 2022-06-09 Invalid subquery flattening caused by
# check-in 3f45007d544e5f78 and detected by dbsqlfuzz
#
reset_db
do_execsql_test join-27.1 {
CREATE TABLE t1(a INT,b INT,c INT); INSERT INTO t1 VALUES(NULL,NULL,NULL);
CREATE TABLE t2(d INT,e INT); INSERT INTO t2 VALUES(NULL,NULL);
CREATE INDEX x2 ON t1(c,b);
CREATE TABLE t3(x INT); INSERT INTO t3 VALUES(NULL);
}
do_execsql_test join-27.2 {
WITH t99(b) AS MATERIALIZED (
SELECT b FROM t2 LEFT JOIN t1 ON c IN (SELECT x FROM t3)
)
SELECT 5 FROM t2 JOIN t99 ON b IN (1,2,3);
} {}
do_execsql_test join-27.3 {
WITH t99(b) AS NOT MATERIALIZED (
SELECT b FROM t2 LEFT JOIN t1 ON c IN (SELECT x FROM t3)
)
SELECT 5 FROM t2 JOIN t99 ON b IN (1,2,3);
} {}
do_execsql_test join-27.4 {
WITH t99(b) AS (SELECT b FROM t2 LEFT JOIN t1 ON c IN (SELECT x FROM t3))
SELECT 5 FROM t2 JOIN t99 ON b IN (1,2,3);
} {}
do_execsql_test join-27.5 {
SELECT 5
FROM t2 JOIN (
SELECT b FROM t2 LEFT JOIN t1 ON c IN (SELECT x FROM t3)
) AS t99 ON b IN (1,2,3);
} {}
finish_test

View File

@ -139,6 +139,72 @@ foreach {id schema} {
1 3 3 33
1 4 4 44
}
do_execsql_test join7-$id.32 {
SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c
WHERE b=c
ORDER BY +b;
} {
1 3 3 33
1 4 4 44
}
do_execsql_test join7-$id.33 {
SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c
WHERE b>0
ORDER BY +b;
} {
1 2 NULL NULL
1 3 3 33
1 4 4 44
}
do_execsql_test join7-$id.34 {
SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c
WHERE b>0 OR b IS NULL
ORDER BY +b;
} {
NULL NULL 5 55
1 2 NULL NULL
1 3 3 33
1 4 4 44
}
do_execsql_test join7-$id.35 {
SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c AND b>3 AND c>4
ORDER BY coalesce(b,c,0);
} {
1 2 NULL NULL
NULL NULL 3 33
1 3 NULL NULL
NULL NULL 4 44
1 4 NULL NULL
NULL NULL 5 55
}
do_execsql_test join7-$id.36 {
SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c AND b>3 WHERE c>4
ORDER BY coalesce(b,c,0);
} {
NULL NULL 5 55
}
do_execsql_test join7-$id.37 {
SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c WHERE b>3 AND c>4
ORDER BY coalesce(b,c,0);
} {
}
do_execsql_test join7-$id.38 {
SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c WHERE b>3 OR c>4
ORDER BY coalesce(b,c,0);
} {
1 4 4 44
NULL NULL 5 55
}
do_execsql_test join7-$id.39 {
SELECT t1.*, t2.* FROM t2 FULL OUTER JOIN t1 ON b=c AND (b>3 OR c>4)
ORDER BY coalesce(b,c,0);
} {
1 2 NULL NULL
NULL NULL 3 33
1 3 NULL NULL
1 4 4 44
NULL NULL 5 55
}
do_execsql_test join7-$id.40 {
SELECT * FROM t1 RIGHT OUTER JOIN t2 ON b=c ORDER BY +b;
} {

View File

@ -445,4 +445,306 @@ do_execsql_test join8-14020 {
- 2 4
}
# 2022-05-30
# https://sqlite.org/forum/forumpost/3902c7b833
#
reset_db
do_execsql_test join8-15000 {
CREATE TABLE t1(x INT);
CREATE TABLE t2(y INT);
CREATE TABLE t3(z INT);
INSERT INTO t1 VALUES(10);
INSERT INTO t3 VALUES(20),(30);
}
do_execsql_test join8-15010 {
SELECT * FROM t1 LEFT JOIN t2 ON true JOIN t3 ON t2.y IS NOT NULL;
} {}
do_execsql_test join8-15020 {
SELECT * FROM t1 LEFT JOIN t2 ON true JOIN t3 ON t2.y IS NOT NULL
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600);
} {}
do_execsql_test join8-15100 {
PRAGMA automatic_index = 0;
CREATE TABLE t4(x TEXT);
CREATE TABLE t5(y TEXT);
CREATE TABLE t6(z TEXT);
INSERT INTO t4 VALUES('a'), ('b');
INSERT INTO t5 VALUES('b'), ('c');
INSERT INTO t6 VALUES('a'), ('d');
} {}
db null -
do_execsql_test join8-15110 {
SELECT * FROM t4 LEFT JOIN t5 ON x=y LEFT JOIN t6 ON (x=z) ORDER BY +x;
} {a - a b b -}
do_execsql_test join8-15120 {
SELECT * FROM t4 LEFT JOIN t5 ON x=y LEFT JOIN t6 ON (x=z)
WHERE t5.y!='x' AND t4.x!='x';
} {b b -}
# 2022-05-31
# https://sqlite.org/forum/forumpost/c2554d560b
reset_db
do_execsql_test join8-16000 {
CREATE TABLE t1(a TEXT);
CREATE TABLE t2(b TEXT);
CREATE TABLE t3(c TEXT);
INSERT INTO t2(b) VALUES ('x');
INSERT INTO t3(c) VALUES ('y'), ('z');
} {}
db null -
do_execsql_test join8-16010 {
SELECT * FROM t1 RIGHT JOIN t2 ON true LEFT JOIN t3 ON a<>'';
} {- x -}
do_execsql_test join8-16020 {
SELECT * FROM t1 RIGHT JOIN t2 ON true LEFT JOIN t3 ON a<>'' WHERE c IS NULL;
} {- x -}
do_execsql_test join8-16020 {
SELECT * FROM t1 RIGHT JOIN t2 ON true JOIN t3 ON a<>'' WHERE c IS NULL;
} {}
do_execsql_test join8-16030 {
SELECT * FROM t1 RIGHT JOIN t2 ON true JOIN t3 ON a<>'';
} {}
do_execsql_test join8-16040 {
SELECT * FROM t1 RIGHT JOIN t2 ON true LEFT JOIN t3 ON a<>'' WHERE c<>'';
} {}
do_execsql_test join8-16050 {
SELECT * FROM t1 RIGHT JOIN t2 ON true LEFT JOIN t3 ON a<>'' WHERE c IS NOT NULL;
} {}
do_execsql_test join8-16060 {
SELECT * FROM t1 RIGHT JOIN t2 ON true JOIN t3 ON a<>'' WHERE c<>'';
} {}
do_execsql_test join8-16070 {
SELECT * FROM t1 RIGHT JOIN t2 ON true JOIN t3 ON a<>'' WHERE c IS NOT NULL;
} {}
# 2022-06-01
# https://sqlite.org/forum/forumpost/087de2d9ec
#
reset_db
do_execsql_test join8-17000 {
CREATE TABLE t1(id INTEGER PRIMARY KEY, x INT, y INT);
CREATE TABLE t2(z INT);
INSERT INTO t1(id,x,y) VALUES(1, 0, 0);
} {}
db null NULL
do_execsql_test join8-17010 {
SELECT * FROM t2 RIGHT JOIN t1 ON true;
} {NULL 1 0 0}
do_execsql_test join8-17020 {
SELECT 99=id AND 0=y AS "truth" FROM t2 RIGHT JOIN t1 ON true;
} {0}
do_execsql_test join8-17030 {
SELECT (99, 0)==(id, y) AS "truth" FROM t2 RIGHT JOIN t1;
} {0}
do_execsql_test join8-17040 {
SELECT * FROM t2 RIGHT JOIN t1 WHERE 99=id AND 0=y;
} {}
do_execsql_test join8-17041 {
SELECT * FROM t2 RIGHT JOIN t1 WHERE 99=+id AND 0=y;
} {}
do_execsql_test join8-17050 {
SELECT * FROM t2 RIGHT JOIN t1 WHERE (99, 0)==(id,y);
} {}
do_execsql_test join8-17051 {
SELECT * FROM t2 RIGHT JOIN t1 WHERE (99, 0)==(+id,y);
} {}
do_execsql_test join8-17060 {
SELECT * FROM t2 RIGHT JOIN t1 WHERE 1=id AND 0=y;
} {NULL 1 0 0}
do_execsql_test join8-17061 {
SELECT * FROM t2 RIGHT JOIN t1 WHERE 1=+id AND 0=y;
} {NULL 1 0 0}
do_execsql_test join8-17070 {
SELECT * FROM t2 RIGHT JOIN t1 WHERE (1, 0)==(id,y);
} {NULL 1 0 0}
do_execsql_test join8-17071 {
SELECT * FROM t2 RIGHT JOIN t1 WHERE (1, 0)==(+id,y);
} {NULL 1 0 0}
do_execsql_test join8-17080 {
CREATE TABLE t3(a INTEGER PRIMARY KEY, b INT);
CREATE TABLE t4(x INT, y INT);
INSERT INTO t3(a,b) VALUES(1, 3);
} {}
do_execsql_test join8-17090 {
SELECT t3.a FROM t4 RIGHT JOIN t3 ON (x=a) WHERE (b, 4)=(SELECT 3, 4);
} {1}
do_execsql_test join8-17091 {
SELECT t3.a FROM t4 RIGHT JOIN t3 ON (x=a) WHERE (b, 4) IS (SELECT 3, 4);
} {1}
# 2022-06-06
# https://sqlite.org/forum/forumpost/206d99a16dd9212f
# tag-20191211-001
#
reset_db
do_execsql_test join8-18000 {
CREATE TABLE t1(a BOOLEAN); INSERT INTO t1 VALUES (false);
CREATE TABLE t2(x INT); INSERT INTO t2 VALUES (0);
SELECT *, x NOTNULL, (x NOTNULL)=a FROM t2 RIGHT JOIN t1 ON true WHERE (x NOTNULL)=a;
} {}
do_execsql_test join8-18010 {
CREATE INDEX t1a ON t1(a);
SELECT *, x NOTNULL, (x NOTNULL)=a FROM t2 RIGHT JOIN t1 ON true WHERE (x NOTNULL)=a;
} {}
do_execsql_test join8-18020 {
CREATE TABLE t3(z);
INSERT INTO t3 VALUES('t3value');
SELECT *, x NOTNULL, (x NOTNULL)=a FROM t2 RIGHT JOIN t1 ON true INNER JOIN t3 ON (x NOTNULL)=a;
} {}
ifcapable rtree {
do_execsql_test join8-18030 {
CREATE VIRTUAL TABLE rtree1 USING rtree(a, x1, x2);
INSERT INTO rtree1 VALUES(0, 0, 0);
}
do_execsql_test join8-18040 {
SELECT *, x NOTNULL, (x NOTNULL)=a FROM t2
RIGHT JOIN rtree1 ON true INNER JOIN t3 ON (x NOTNULL)=+a;
} {}
do_execsql_test join8-18050 {
SELECT *, x NOTNULL, (x NOTNULL)=a FROM t2
RIGHT JOIN rtree1 ON true INNER JOIN t3 ON (x NOTNULL)=a;
} {}
}
reset_db
do_execsql_test join8-19000 {
CREATE TABLE t1(a INT);
CREATE TABLE t2(b INT, c INT);
CREATE TABLE t3(d INT);
INSERT INTO t1 VALUES(10);
INSERT INTO t2 VALUES(50,51);
INSERT INTO t3 VALUES(299);
CREATE INDEX t2b ON t2( (b IS NOT NULL) );
}
do_execsql_test join8-19010 {
SELECT * FROM t1 LEFT JOIN t2 ON true INNER JOIN t3 ON (b IS NOT NULL)=0;
}
# 2022-06-07
# https://sqlite.org/forum/forumpost/323f86cc30
reset_db
do_execsql_test join8-20000 {
CREATE TABLE t1(x TEXT);
INSERT INTO t1(x) VALUES('aaa');
CREATE VIEW v0(y) AS SELECT x FROM t1;
CREATE TABLE t2(z TEXT);
} {}
db null -
do_execsql_test join8-20010 {
SELECT * FROM t2 JOIN v0 ON z<>'bbb' RIGHT JOIN t1 ON z<>'ccc';
} {- - aaa}
do_execsql_test join8-20020 {
SELECT * FROM t2 JOIN v0 ON z<>'bbb' RIGHT JOIN t1 ON z<>'ccc' ORDER BY z;
} {- - aaa}
do_execsql_test join8-20030 {
SELECT 99 as "m" FROM t2 JOIN v0 ON z<>'bbb' RIGHT JOIN t1 ON z<>'ccc';
} {99}
do_execsql_test join8-20040 {
SELECT 99 as "m" FROM t2 JOIN v0 ON z<>'bbb' RIGHT JOIN t1 ON z<>'ccc' ORDER BY z;
} {99}
do_execsql_test join8-20050 {
SELECT count(*)
FROM (SELECT 99 as "m" FROM t2 JOIN v0 ON z<>'' RIGHT JOIN t1 ON z<>'') AS "t3";
} {1}
do_execsql_test join8-20060 {
SELECT count(*)
FROM (SELECT 99 as "m" FROM t2 JOIN v0 ON z<>'' RIGHT JOIN t1 ON z<>'' ORDER BY z) AS "t3";
} {1}
# 2022-06-10
# https://sqlite.org/forum/forumpost/8e4c352937e82929
#
# Do not allow constant propagation between ON and WHERE clause terms.
#
reset_db
do_execsql_test join8-21000 {
CREATE TABLE t1(a INT,b BOOLEAN);
CREATE TABLE t2(c INT); INSERT INTO t2 VALUES(NULL);
CREATE TABLE t3(d INT);
}
do_execsql_test join8-21010 {
SELECT (b IS TRUE) FROM t1 JOIN t3 ON (b=TRUE) RIGHT JOIN t2 ON TRUE;
} {0}
do_execsql_test join8-22020 {
SELECT * FROM t1 JOIN t3 ON (b=TRUE) RIGHT JOIN t2 ON TRUE WHERE (b IS TRUE);
} {}
# 2022-06-10
# https://sqlite.org/forum/forumpost/51e6959f61
#
# Restrictions on the usage of WHERE clause constraints by joins that are
# involved with a RIGHT JOIN must also be applied to automatic indexes.
#
reset_db
do_execsql_test join8-22000 {
CREATE TABLE t1(a INT);
CREATE TABLE t2(b INT);
CREATE TABLE t3(c TEXT); INSERT INTO t3 VALUES('x');
CREATE TABLE t4(d TEXT); INSERT INTO t4 VALUES('y');
SELECT 99
FROM t1
LEFT JOIN t2 ON true
RIGHT JOIN t3 ON true
RIGHT JOIN t4 ON true
WHERE a=b;
} {}
# 2022-06-13
# https://sqlite.org/forum/forumpost/b40696f501
#
# This optimization that converts "x ISNULL" into "FALSE" when column "x" has a
# NOT NULL constraint is too aggresive if the query contains RIGHT JOIN.
#
reset_db
db null -
do_execsql_test join8-23000 {
CREATE TABLE t1(a TEXT);
INSERT INTO t1 VALUES('c');
CREATE TABLE t2(b TEXT, c TEXT NOT NULL);
INSERT INTO t2 VALUES('a', 'b');
CREATE TABLE t3(d TEXT);
INSERT INTO t3 VALUES('x');
CREATE TABLE t4(e TEXT);
INSERT INTO t4 VALUES('y');
}
do_execsql_test join8-23010 {
SELECT *
FROM t1
LEFT JOIN t2 ON TRUE
JOIN t3 ON c=''
RIGHT JOIN t4 ON b='';
} {- - - - y}
do_execsql_test join8-23020 {
SELECT *
FROM t1
LEFT JOIN t2 ON TRUE
JOIN t3 ON c=''
RIGHT JOIN t4 ON b=''
WHERE d ISNULL
} {- - - - y}
# 2022-06-14
# dbsqlfuzz 2f3101834d14325a976f601b9267a0fd323d6bbd
#
# When the OP_NullRow opcode creates a new cursor, it must
# set the cursor to no-reuse so that an OP_OpenEphemeral in
# a subroutine does not try to reuse it.
#
reset_db
db null -
do_execsql_test join8-24000 {
CREATE TABLE t4(b INT, c INT);
CREATE TABLE t5(a INT, f INT);
INSERT INTO t5 VALUES(1,2);
WITH t7(x, y) AS (SELECT 100, 200 FROM t5)
SELECT * FROM t4 JOIN t7 ON true RIGHT JOIN (SELECT y AS z FROM t7) AS t6 ON (x=z);
} {- - - - 200}
finish_test

File diff suppressed because it is too large Load Diff

613
test/joinF.test Normal file
View File

@ -0,0 +1,613 @@
# 2022-05-31
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
# This file implements tests for JOINs
#
# The test case output is (mostly) all generated by PostgreSQL 14. This
# test module was created as follows:
#
# 1. Run a TCL script (included at the bottom of this file) that
# generates an input script for "psql" that will run man
# diverse tests on joins.
#
# 2. Run the script from step (1) through psql and collect the
# output.
#
# 3. Make a few minor global search-and-replace operations to convert
# the psql output into a form suitable for this test module.
#
# 4. Add this header, and the script content at the footer.
#
# A few extra tests that were not generated from postgresql output are
# added at the end.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
db nullvalue -
db eval {
CREATE TABLE t1(x INT);
CREATE TABLE t2(y INT);
CREATE TABLE t3(z INT);
CREATE TABLE t4(w INT);
INSERT INTO t1 VALUES(10);
INSERT INTO t3 VALUES(20),(30);
INSERT INTO t4 VALUES(50);
}
do_execsql_test joinF-1 {
SELECT *
FROM t1 INNER JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-2 {
SELECT *
FROM t1 INNER JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-3 {
SELECT *
FROM t1 INNER JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-4 {
SELECT *
FROM t1 INNER JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-5 {
SELECT *
FROM t1 INNER JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - - 50
}
do_execsql_test joinF-6 {
SELECT *
FROM t1 INNER JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-7 {
SELECT *
FROM t1 INNER JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-8 {
SELECT *
FROM t1 INNER JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-9 {
SELECT *
FROM t1 INNER JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-10 {
SELECT *
FROM t1 INNER JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-11 {
SELECT *
FROM t1 INNER JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - - 50
}
do_execsql_test joinF-12 {
SELECT *
FROM t1 INNER JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-13 {
SELECT *
FROM t1 INNER JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-14 {
SELECT *
FROM t1 INNER JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-15 {
SELECT *
FROM t1 INNER JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-16 {
SELECT *
FROM t1 INNER JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-17 {
SELECT *
FROM t1 INNER JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-18 {
SELECT *
FROM t1 INNER JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-19 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-20 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-21 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-22 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-23 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - - 50
}
do_execsql_test joinF-24 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-25 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
10 - - 50
}
do_execsql_test joinF-26 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-27 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
10 - - 50
}
do_execsql_test joinF-28 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-29 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
10 - - 50
}
do_execsql_test joinF-30 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-31 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-32 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-33 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-34 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-35 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-36 {
SELECT *
FROM t1 LEFT JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-37 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-38 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-39 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-40 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-41 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - - 50
}
do_execsql_test joinF-42 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
INNER JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-43 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-44 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-45 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-46 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-47 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - - 50
}
do_execsql_test joinF-48 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
LEFT JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
}
do_execsql_test joinF-49 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-50 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
INNER JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-51 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-52 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
LEFT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-53 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
do_execsql_test joinF-54 {
SELECT *
FROM t1 RIGHT JOIN t2 ON true
RIGHT JOIN t3 ON t2.y IS NOT NULL
RIGHT JOIN t4 ON true
WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)
ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);
} {
- - 20 50
- - 30 50
}
finish_test
############################################################################
# This is the TCL script used to generate the psql script that generated
# the data above.
#
# puts "
# \\pset border off
# \\pset tuples_only on
# \\pset null -
#
# DROP TABLE IF EXISTS t1;
# DROP TABLE IF EXISTS t2;
# DROP TABLE IF EXISTS t3;
# DROP TABLE IF EXISTS t4;
# CREATE TABLE t1(x INT);
# CREATE TABLE t2(y INT);
# CREATE TABLE t3(z INT);
# CREATE TABLE t4(w INT);
# INSERT INTO t1 VALUES(10);
# INSERT INTO t3 VALUES(20),(30);
# INSERT INTO t4 VALUES(50);
# "
#
# proc echo {prefix txt} {
# regsub -all {\n} $txt \n$prefix txt
# puts "$prefix$txt"
# }
#
# set n 0
# foreach j1 {INNER LEFT RIGHT} {
# foreach j2 {INNER LEFT RIGHT} {
# foreach j3 {INNER LEFT RIGHT} {
#
# incr n
# set q1 ""
# append q1 "SELECT *\n"
# append q1 " FROM t1 $j1 JOIN t2 ON true\n"
# append q1 " $j2 JOIN t3 ON t2.y IS NOT NULL\n"
# append q1 " $j3 JOIN t4 ON true\n"
# append q1 " ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);"
#
# echo "\\qecho " "do_execsql_test joinF-$n \{"
# echo "\\qecho X " $q1
# echo "\\qecho " "\} \{"
# puts $q1
# echo "\\qecho " "\}"
#
# incr n
# set q1 ""
# append q1 "SELECT *\n"
# append q1 " FROM t1 $j1 JOIN t2 ON true\n"
# append q1 " $j2 JOIN t3 ON t2.y IS NOT NULL\n"
# append q1 " $j3 JOIN t4 ON true\n"
# append q1 " WHERE (t3.z!=400 AND t3.z!=500 AND t3.z!=600)\n"
# append q1 " ORDER BY coalesce(t1.x,t2.y,t3.z,t4.w,0);"
#
# echo "\\qecho " "do_execsql_test joinF-$n \{"
# echo "\\qecho X " $q1
# echo "\\qecho " "\} \{"
# puts $q1
# echo "\\qecho " "\}"
#
# }
# }
# }
#

View File

@ -260,11 +260,23 @@ do_catchsql_test 11.8 {
#
do_execsql_test 12.1 {
DROP TABLE IF EXISTS t1;
CREATE TABLE t1(a,b); INSERT INTO t1 VALUES(1,2);
CREATE TABLE t1(a INT,b INT); INSERT INTO t1 VALUES(1,2);
DROP TABLE IF EXISTS t2;
CREATE TABLE t2(x,y); INSERT INTO t2 VALUES(3,4);
CREATE TABLE t2(x INT,y INT); INSERT INTO t2 VALUES(3,4);
SELECT *,'x' FROM t1 LEFT JOIN t2 ON (a,b)=(x,y);
} {1 2 {} {} x}
db null -
do_execsql_test 12.2 {
SELECT t1.*, t2.* FROM t2 RIGHT JOIN t1 ON (a,b)=(x,y);
} {1 2 - -}
do_execsql_test 12.3 {
SELECT t1.*, t2.* FROM t1 FULL JOIN t2 ON (a,b)=(x,y)
ORDER BY coalesce(a,x);
} {
1 2 - -
- - 3 4
}
db null {}
foreach {tn sql} {
@ -540,7 +552,7 @@ do_execsql_test 19.36 {
SELECT * FROM t1 WHERE (3,32)>=(a,b) ORDER BY a DESC;
} {2 22 1 11}
# 2018-02-18: Memory leak nexted row-value. Detected by OSSFuzz.
# 2018-02-18: Memory leak nested row-value. Detected by OSSFuzz.
#
do_catchsql_test 20.1 {
SELECT 1 WHERE (2,(2,0)) IS (2,(2,0));
@ -632,9 +644,15 @@ do_execsql_test 26.10 {
do_execsql_test 26.20 {
SELECT 2 FROM t1 LEFT JOIN t0 ON (c0, x'') != (NULL, 0);
} {2}
do_execsql_test 26.21 {
SELECT 21 FROM t0 RIGHT JOIN t1 ON (c0, x'') != (NULL, 0);
} {21}
do_execsql_test 26.30 {
SELECT 3 FROM t1 LEFT JOIN t0 WHERE (c0, x'') != (NULL, 0);
} {3}
do_execsql_test 26.31 {
SELECT 31 FROM t0 RIGHT JOIN t1 WHERE (c0, x'') != (NULL, 0);
} {31}
# 2019-12-30 ticket 892575cdba4e1e36
#
@ -700,6 +718,9 @@ do_execsql_test 31.1 {
CREATE TABLE b(b1 UNIQUE,b2);
SELECT * FROM a LEFT JOIN b ON b2=NULL AND b2=5 WHERE (b1,substr(b.b1,1,1))==(SELECT 1024,'b');
} {}
do_execsql_test 31.1b {
SELECT * FROM b RIGHT JOIN a ON b2=NULL AND b2=5 WHERE (b1,substr(b.b1,1,1))==(SELECT 1024,'b');
} {}
do_execsql_test 31.2 {
CREATE TABLE t1(a);
INSERT INTO t1 VALUES(0);
@ -707,6 +728,9 @@ do_execsql_test 31.2 {
INSERT INTO t2 VALUES(NULL,123,456);
SELECT * FROM t1 LEFT JOIN t2 ON b=NULL WHERE (c,d)==(SELECT 123, 456+a);
} {}
do_execsql_test 31.2b {
SELECT * FROM t2 RIGHT JOIN t1 ON b=NULL WHERE (c,d)==(SELECT 123, 456+a);
} {}
# 2022-02-03 dbsqlfuzz 80a9fade844b4fb43564efc972bcb2c68270f5d1
reset_db

View File

@ -213,7 +213,8 @@ do_execsql_test 5.0 {
CREATE TABLE T2(a TEXT PRIMARY KEY,n INT);
INSERT INTO T2(a, n) VALUES('aaa',0);
SELECT * FROM T2
WHERE (a,n) IN (SELECT T1.a, V.n FROM T1, (SELECT * FROM (SELECT 0 n)) V);
WHERE (a,n) IN (SELECT T1.a, V.n
FROM T1, (SELECT * FROM (SELECT 0 n) T3) V);
} {aaa 0}

View File

@ -28,4 +28,32 @@ do_execsql_test subtype1-130 {
SELECT test_setsubtype('hello',123);
} {hello}
# 2022-06-09
# https://sqlite.org/forum/forumpost/3d9caa45cbe38c78
#
# Avoid carrying subtypes through into a subquery that has been flattened
# or to which the outer WHERE clause has been pushed down.
#
reset_db
do_execsql_test subtype1-200 {
CREATE TABLE t1(a); INSERT INTO t1 VALUES ('x');
CREATE VIEW t2(b) AS SELECT json(TRUE);
CREATE TABLE t3(b); INSERT INTO t3 VALUES(json(TRUE));
}
do_execsql_test subtype1-210 {
SELECT * FROM t3, t1 WHERE NOT json_quote(b);
} {1 x}
do_execsql_test subtype1-220 {
SELECT * FROM t2, t1 WHERE NOT json_quote(b);
} {1 x}
do_execsql_test subtype1-230 {
WITH t4(a) AS MATERIALIZED (SELECT json(1)) SELECT subtype(a) FROM t4;
} {0}
do_execsql_test subtype1-231 {
WITH t4(a) AS NOT MATERIALIZED (SELECT json(1)) SELECT subtype(a) FROM t4;
} {0}
finish_test

94
test/windowD.test Normal file
View File

@ -0,0 +1,94 @@
# 2022 June 2
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix windowD
do_execsql_test 1.0 {
CREATE TABLE t0(c0 TEXT);
CREATE VIEW v0(c0, c1)
AS SELECT CUME_DIST() OVER (PARTITION BY t0.c0), TRUE FROM t0;
INSERT INTO t0 VALUES ('x');
}
do_execsql_test 1.1 {
SELECT ('500') IS (v0.c1) FROM v0;
} {
0
}
do_execsql_test 1.2 {
SELECT (('500') IS (v0.c1)) FROM v0, t0;
} {
0
}
do_execsql_test 1.2 {
SELECT (('500') IS (v0.c1)) IS FALSE FROM v0;
} {
1
}
do_execsql_test 1.3 {
SELECT * FROM v0;
} {
1.0 1
}
do_execsql_test 1.4 {
SELECT * FROM v0 WHERE ('500' IS v0.c1) IS FALSE;
} {
1.0 1
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 2.0 {
CREATE TABLE t1(x);
INSERT INTO t1 VALUES('value');
CREATE VIEW v1(a, b, c, d) AS SELECT 1, 2, TRUE, FALSE FROM t1;
}
do_execsql_test 2.1 {
SELECT 500 IS a, 500 IS b, 500 IS c, 500 IS d FROM v1
} {0 0 0 0}
do_execsql_test 2.2 {
SELECT * FROM v1 WHERE 500 IS c;
} {}
do_execsql_test 2.3 {
SELECT * FROM v1 WHERE 500 IS d;
} {}
do_execsql_test 2.4 {
CREATE VIEW v2 AS SELECT max(x) OVER () AS a, TRUE AS c FROM t1;
}
do_execsql_test 2.5 {
SELECT 500 IS c FROM v2;
} 0
do_execsql_test 2.6 {
SELECT * FROM v2 WHERE 500 IS c;
} {}
finish_test