# BST-TIMEOUT: 1800 # 215 cases, 600s measured on a quiet host; the heaviest suite in the image . "$BST_LIB" # # sqlite3 3.45.3 — the embedded SQL engine: the /usr/bin/sqlite3 shell, the # libsqlite3.a archive and the sqlite3.h / sqlite3ext.h headers. This is the # ENGINE half of the port's coverage and it runs entirely against `:memory:` # databases, so nothing here touches the filesystem. The file-backed half lives # in 77-sqlite3-file.test and the admin/utility half in 77-sqlite3-admin.test. # # WHY THE SPLIT, AND WHY THE SWEEPS ARE BATCHED. Every other suite on this image # can afford one process per case; this one cannot. /usr/bin/sqlite3 is a ~16 MB # wasm module and each invocation instantiates the whole of it, while the runner # gives each test FILE a 60 s wall-clock budget (BST_SUITE_TIMEOUT). So the # engine cases are not one-command-per-case: three sweeps run ~110 SQL # statements in THREE processes, each statement printing a `tag|value` line, and # the assertions below are check_eq against values awk'd out of that capture. # Only the cases where the PROCESS BOUNDARY IS THE THING UNDER TEST — an exit # code, a shell flag — get their own invocation. Splitting the coverage across # three files gives each layer its own budget as well. # # FIVE SHAPE FACTS decided what is asserted here, and every one of them was # measured against a native x86_64 build of the identical 3.45.3 source before # anything ran on target. Four of the five were WRONG EXPECTATIONS OF MINE that # the control caught; had they shipped they would have reported a correct port # as broken. # # 1. SQLITE_DQS=0 IS NOT IN EFFECT, even though the recipe passes it. # sqlite3.inc puts -DSQLITE_DQS=0 in sqlite3_CFLAGS — the SHELL's flags — # while the parser lives in sqlite3.c, which is compiled into libsqlite3 # under AM_CFLAGS alone. With --disable-static-shell (also the recipe's) the # shell links that library rather than recompiling the amalgamation, so the # define reaches nothing that parses SQL: `SELECT "abc"` yields the STRING # abc here, not a no-such-column error. Asserted in that direction below. # # 2. THE SAME SPLIT REMOVES THE dbstat / dbpage / stmt VTABS. # -DSQLITE_ENABLE_DBSTAT_VTAB, -DSQLITE_ENABLE_DBPAGE_VTAB and # -DSQLITE_ENABLE_STMTVTAB are shell-only flags for the same reason, so the # modules they gate are never compiled: `SELECT * FROM dbstat` is # "no such table" and `.dbinfo` fails on sqlite_dbpage. That is a property # of poky's recipe on ANY target, not of wasm32 — it reproduces on the # native control — so it is asserted as the shipped contract rather than # filed as a defect. A future recipe that compiles the amalgamation into # the shell would flip these two cases, which is exactly when a reader # wants to be told. # # 3. A CONSTRAINT VIOLATION EXITS 19, NOT 1. The shell returns the SQLite # result code, so a UNIQUE failure is rc=19 (SQLITE_CONSTRAINT) and a # readonly database is rc=8. Only prepare-time errors (syntax, unknown # function) are rc=1. A check_rc 1 on the constraint case would have gone # red against a perfectly good engine. # # 4. THERE IS NO ZLIB IN THIS BUILD, so the shell has no .archive command and # no zipfile/sqlar support. PACKAGECONFIG does not name zlib, and the # recipe's CACHED_CONFIGUREVARS then pins ac_cv_search_deflate=no so the # configure probe cannot pick the sysroot's libz up by accident. Asserted # as an absence, so a later build that adds zlib is noticed here. # # 5. FTS4's `NOT` IS NOT AN OPERATOR IN THIS BUILD. The enhanced query syntax # (AND/OR/NOT infix, parentheses) needs SQLITE_ENABLE_FTS3_PARENTHESIS, # which nothing defines, so `alpha NOT delta` searches for the three TOKENS # alpha, not, delta and matches nothing. The standard syntax `alpha -delta` # is the working spelling and is what is asserted; `OR` happens to work # because it is standard-syntax too. # # THE ARM ITSELF NEEDED NOTHING. sqlite3.c and shell.c contain no VLA and no # alloca (alloca appears only under SQLITE_USE_ALLOCA, which nothing defines), no # setjmp/longjmp, and DEFAULT_MMAP_SIZE is 0 so the engine never maps the # database — so the dynamic-alloca-address-escape class that cost acl, # sharutils, mbuffer, onig, mtd-utils, squashfs-tools and dpkg a patch each has # no site here, and the read-write-mmap gap that shapes 77-gdbm.test does not # arise either. The port carries no bbappend and no patch. # require sqlite3 sqlite3/present # The tagged-sweep readers. Column 1 is the tag, 2..n the values. sq_v() { printf '%s\n' "$2" | awk -F'|' -v k="$1" '$1==k{print $2; exit}'; } sq_v2() { printf '%s\n' "$2" | awk -F'|' -v k="$1" '$1==k{print $3; exit}'; } sq_v3() { printf '%s\n' "$2" | awk -F'|' -v k="$1" '$1==k{print $4; exit}'; } # A tool that signals by exit status cannot be asserted with check_out (which # also requires rc=0), so keep the output and swallow the status; the status # itself is asserted separately by check_rc. Same wrapper 77-gdbm.test uses. out_of() { "$@" 2>&1 || true; } # ------------------------------------------------------------- 1. the shell is real check sqlite3/bin-present test -x /usr/bin/sqlite3 # No busybox applet is named sqlite3 in this defconfig, so a bare `sqlite3` is # this port and nothing else. check_out sqlite3/version "3.45.3" sqlite3 -version check_out sqlite3/source-id "2024-04-15" sqlite3 -version # ---------------------------------------------------- 2. engine sweep (one process) # Scalar functions, type system, arithmetic, collation, JSON and date/time. One # invocation, ~45 statements; every expected value below was read off the native # control of the same release. E=$(sqlite3 :memory: 2>&1 <<'SQL' .mode list .separator | .headers off SELECT 'ver', sqlite_version(); SELECT 'int64', 9223372036854775807; SELECT 'int64neg', -9223372036854775808; SELECT 'arith', 2147483647 + 1; SELECT 'real', 1.0/8; SELECT 'realfmt', 0.1 + 0.2; SELECT 'cast', CAST('42abc' AS INTEGER); SELECT 'hex', hex(zeroblob(3)); SELECT 'blobrt', hex(x'DEADBEEF'); SELECT 'lengthblob', length(x'0102030405'); SELECT 'printf', printf('%s-%05.2f-%d', 'x', 3.14159, 7); SELECT 'char', char(72,73); SELECT 'unicode', unicode('A'); SELECT 'replace', replace('a.b.c', '.', '/'); SELECT 'substr', substr('abcdef', 2, 3); SELECT 'instr', instr('hello','ll'); SELECT 'trim', '['||trim(' pad ')||']'; SELECT 'upperlower', upper('mIx'), lower('mIx'); SELECT 'likepat', 'abc' LIKE 'a_c', 'abc' LIKE 'A%'; SELECT 'globpat', 'abc' GLOB 'a[bc]c', 'abc' GLOB 'A*'; SELECT 'nocase', 'ABC' = 'abc' COLLATE NOCASE; SELECT 'coalesce', coalesce(NULL, NULL, 'z'); SELECT 'nullcmp', (NULL = NULL) IS NULL; SELECT 'iif', iif(1>0,'y','n'); SELECT 'abs', abs(-5), max(1,9,3), min(1,9,3); SELECT 'round', round(2.567, 2); SELECT 'dqs', "abc"; SELECT 'mathpow', cast(pow(2,20) as int); SELECT 'mathsqrt', cast(sqrt(144) as int); SELECT 'mathlog', cast(round(log(10,1000)) as int); SELECT 'mathtrig', cast(round(degrees(acos(-1))) as int); SELECT 'json', json_extract('{"a":{"b":[1,2,3]}}','$.a.b[2]'); SELECT 'jsonarr', json_array_length('[1,2,3,4]'); SELECT 'jsonvalid', json_valid('{"a":1}'), json_valid('{oops'); SELECT 'jsontype', json_type('{"a":1}','$.a'); SELECT 'jsonobj', json_object('k', 'v'); SELECT 'jsoneach', (SELECT count(*) FROM json_each('[10,20,30]')); SELECT 'date', date('2024-02-29'); SELECT 'datemod', date('2024-01-31','+1 month'); SELECT 'julian', cast(julianday('2000-01-01 12:00:00') as int); SELECT 'strftime', strftime('%Y-%m-%dT%H:%M:%S','2024-06-01 07:08:09'); SELECT 'unixepoch', unixepoch('1970-01-02 00:00:00'); SELECT 'daydiff', cast((julianday('2024-01-02')-julianday('2024-01-01')) as int); SELECT 'randomblob', length(randomblob(16)); SELECT 'zeroblob64k', length(zeroblob(65536)); SELECT 'bigstring', length(replace(hex(zeroblob(20000)),'0','ab')); SQL ) # The gate: if the engine could not run at all, say so once here rather than # leaving 45 empty-value reds to be read as 45 separate defects. check_eq sqlite3/engine-sweep-ran 3.45.3 "$(sq_v ver "$E")" check_eq sqlite3/int64-max 9223372036854775807 "$(sq_v int64 "$E")" check_eq sqlite3/int64-min -9223372036854775808 "$(sq_v int64neg "$E")" # 32-bit arm, 64-bit SQLite integers: this addition overflows int32 and must not # wrap. It is the single most direct witness that i64 arithmetic is real here. check_eq sqlite3/int32-overflow 2147483648 "$(sq_v arith "$E")" check_eq sqlite3/real-exact 0.125 "$(sq_v real "$E")" check_eq sqlite3/real-rounding 0.3 "$(sq_v realfmt "$E")" check_eq sqlite3/cast-prefix 42 "$(sq_v cast "$E")" check_eq sqlite3/zeroblob-hex 000000 "$(sq_v hex "$E")" check_eq sqlite3/blob-literal DEADBEEF "$(sq_v blobrt "$E")" check_eq sqlite3/blob-length 5 "$(sq_v lengthblob "$E")" check_eq sqlite3/printf x-03.14-7 "$(sq_v printf "$E")" check_eq sqlite3/char HI "$(sq_v char "$E")" check_eq sqlite3/unicode 65 "$(sq_v unicode "$E")" check_eq sqlite3/replace a/b/c "$(sq_v replace "$E")" check_eq sqlite3/substr bcd "$(sq_v substr "$E")" check_eq sqlite3/instr 3 "$(sq_v instr "$E")" check_eq sqlite3/trim "[pad]" "$(sq_v trim "$E")" check_eq sqlite3/upper MIX "$(sq_v upperlower "$E")" check_eq sqlite3/lower mix "$(sq_v2 upperlower "$E")" check_eq sqlite3/like-underscore 1 "$(sq_v likepat "$E")" # LIKE is case-insensitive for ASCII by default; GLOB never is. The pair is what # makes each meaningful — either alone passes on a broken matcher. check_eq sqlite3/like-nocase 1 "$(sq_v2 likepat "$E")" check_eq sqlite3/glob-class 1 "$(sq_v globpat "$E")" check_eq sqlite3/glob-case 0 "$(sq_v2 globpat "$E")" check_eq sqlite3/collate-nocase 1 "$(sq_v nocase "$E")" check_eq sqlite3/coalesce z "$(sq_v coalesce "$E")" check_eq sqlite3/null-is-null 1 "$(sq_v nullcmp "$E")" check_eq sqlite3/iif y "$(sq_v iif "$E")" check_eq sqlite3/abs 5 "$(sq_v abs "$E")" check_eq sqlite3/max-scalar 9 "$(sq_v2 abs "$E")" check_eq sqlite3/min-scalar 1 "$(sq_v3 abs "$E")" check_eq sqlite3/round-2dp 2.57 "$(sq_v round "$E")" # Shape fact 1: the shell's -DSQLITE_DQS=0 does not reach the parser. check_eq sqlite3/dqs-is-string abc "$(sq_v dqs "$E")" # SQLITE_ENABLE_MATH_FUNCTIONS: --enable-math is configure's default and these # four are the library's, not the shell's. check_eq sqlite3/math-pow 1048576 "$(sq_v mathpow "$E")" check_eq sqlite3/math-sqrt 12 "$(sq_v mathsqrt "$E")" check_eq sqlite3/math-log 3 "$(sq_v mathlog "$E")" check_eq sqlite3/math-degrees 180 "$(sq_v mathtrig "$E")" check_eq sqlite3/json-extract 3 "$(sq_v json "$E")" check_eq sqlite3/json-arrlen 4 "$(sq_v jsonarr "$E")" check_eq sqlite3/json-valid 1 "$(sq_v jsonvalid "$E")" check_eq sqlite3/json-invalid 0 "$(sq_v2 jsonvalid "$E")" check_eq sqlite3/json-type integer "$(sq_v jsontype "$E")" check_eq sqlite3/json-object '{"k":"v"}' "$(sq_v jsonobj "$E")" check_eq sqlite3/json-each 3 "$(sq_v jsoneach "$E")" check_eq sqlite3/date-leap 2024-02-29 "$(sq_v date "$E")" # Jan 31 + 1 month is March 2, not Feb 29: SQLite normalises the out-of-range # day rather than clamping it. Asserting Feb 29 here is the natural mistake. check_eq sqlite3/date-normalise 2024-03-02 "$(sq_v datemod "$E")" check_eq sqlite3/julianday 2451545 "$(sq_v julian "$E")" check_eq sqlite3/strftime 2024-06-01T07:08:09 "$(sq_v strftime "$E")" check_eq sqlite3/unixepoch 86400 "$(sq_v unixepoch "$E")" check_eq sqlite3/day-diff 1 "$(sq_v daydiff "$E")" check_eq sqlite3/randomblob 16 "$(sq_v randomblob "$E")" check_eq sqlite3/zeroblob-64k 65536 "$(sq_v zeroblob64k "$E")" # 20 kB blob -> 40 kB hex -> 80 kB after the expansion: a real allocation on a # 32-bit heap, not a constant fold. check_eq sqlite3/string-80k 80000 "$(sq_v bigstring "$E")" # ---------------------------------------------- 3. relational sweep (one process) # DDL/DML, transactions, savepoints, constraints, triggers, views, indexes, # joins, set operations, CTEs, window functions, UPSERT, RETURNING, generated # columns and STRICT tables. Still :memory: — durability is the file suite's job. R=$(sqlite3 :memory: 2>&1 <<'SQL' .mode list .separator | .headers off PRAGMA foreign_keys=ON; CREATE TABLE emp(id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, dept TEXT, sal REAL CHECK(sal>0)); CREATE TABLE log(ev TEXT); CREATE TRIGGER emp_ins AFTER INSERT ON emp BEGIN INSERT INTO log VALUES('ins:'||new.name); END; INSERT INTO emp(name,dept,sal) VALUES('ann','eng',100.5),('bob','eng',90),('cid','ops',80); SELECT 'rows', count(*) FROM emp; SELECT 'trig', (SELECT count(*) FROM log), (SELECT ev FROM log ORDER BY rowid LIMIT 1); SELECT 'agg', sum(sal), cast(avg(sal)*100 as int), count(DISTINCT dept) FROM emp; SELECT 'grp', group_concat(g,'/') FROM (SELECT dept||':'||count(*) g FROM emp GROUP BY dept ORDER BY dept); SELECT 'having', dept FROM emp GROUP BY dept HAVING count(*)>1; SELECT 'orderby', group_concat(name,',') FROM (SELECT name FROM emp ORDER BY sal DESC); CREATE VIEW eng AS SELECT name FROM emp WHERE dept='eng'; SELECT 'view', count(*) FROM eng; CREATE INDEX i_dept ON emp(dept); SELECT 'idxcol', (SELECT name FROM pragma_index_info('i_dept')); SELECT 'idxused', (SELECT count(*) FROM pragma_index_list('emp') WHERE name='i_dept'); BEGIN; UPDATE emp SET sal=1 WHERE name='ann'; ROLLBACK; SELECT 'rollback', sal FROM emp WHERE name='ann'; BEGIN; UPDATE emp SET sal=200 WHERE name='ann'; COMMIT; SELECT 'commit', sal FROM emp WHERE name='ann'; SAVEPOINT sp1; UPDATE emp SET sal=5 WHERE name='bob'; ROLLBACK TO sp1; RELEASE sp1; SELECT 'savepoint', sal FROM emp WHERE name='bob'; INSERT INTO emp(id,name,dept,sal) VALUES(1,'ann2','x',5) ON CONFLICT(id) DO UPDATE SET dept='upserted'; SELECT 'upsert', dept FROM emp WHERE id=1; CREATE TABLE ret(a INTEGER PRIMARY KEY, b TEXT); -- RETURNING cannot be a subquery source, so the tag is a literal IN the -- RETURNING list; the row it emits is the tagged line itself. INSERT INTO ret VALUES(7,'seven') RETURNING 'returning', a||':'||b; CREATE TABLE gen(a INT, b INT GENERATED ALWAYS AS (a*3) VIRTUAL, c INT GENERATED ALWAYS AS (a+1) STORED); INSERT INTO gen(a) VALUES(4); SELECT 'generated', b, c FROM gen; CREATE TABLE st(i INTEGER, t TEXT) STRICT; INSERT INTO st VALUES(1,'x'); SELECT 'strict', i||t FROM st; CREATE TABLE par(id INTEGER PRIMARY KEY); CREATE TABLE kid(id INTEGER PRIMARY KEY, p INT REFERENCES par(id)); INSERT INTO par VALUES(1); INSERT INTO kid VALUES(1,1); SELECT 'fkok', (SELECT count(*) FROM kid); SELECT 'fkcheck', (SELECT count(*) FROM pragma_foreign_key_check); WITH RECURSIVE cnt(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM cnt WHERE x<10) SELECT 'cte', sum(x) FROM cnt; SELECT 'winrow', name||'='||rn FROM (SELECT name, row_number() OVER (ORDER BY sal DESC) rn FROM emp) WHERE rn=1; SELECT 'winsum', (SELECT sum(s) FROM (SELECT sum(sal) OVER () s FROM emp LIMIT 1)); SELECT 'winntile', (SELECT count(DISTINCT nt) FROM (SELECT ntile(2) OVER (ORDER BY id) nt FROM emp)); SELECT 'winlag', (SELECT count(*) FROM (SELECT lag(name) OVER (ORDER BY id) FROM emp)); SELECT 'subq', name FROM emp WHERE sal=(SELECT max(sal) FROM emp); SELECT 'exists', EXISTS(SELECT 1 FROM emp WHERE dept='ops'); SELECT 'join', (SELECT count(*) FROM emp a JOIN emp b ON a.dept=b.dept); SELECT 'leftjoin', (SELECT count(*) FROM emp a LEFT JOIN ret b ON a.id=b.a); SELECT 'union', (SELECT count(*) FROM (SELECT name FROM emp UNION SELECT 'zz')); SELECT 'except', (SELECT count(*) FROM (SELECT name FROM emp EXCEPT SELECT 'ann')); SELECT 'intersect', (SELECT count(*) FROM (SELECT dept FROM emp INTERSECT SELECT 'eng')); SELECT 'integrity', (SELECT * FROM pragma_integrity_check()); SELECT 'quick', (SELECT * FROM pragma_quick_check()); SELECT 'colcount', (SELECT count(*) FROM pragma_table_info('emp')); SELECT 'usertables', (SELECT count(*) FROM sqlite_schema WHERE type='table' AND name NOT LIKE 'sqlite_%'); SELECT 'encoding', (SELECT * FROM pragma_encoding()); SELECT 'threadsafe', (SELECT count(*) FROM pragma_compile_options WHERE compile_options='THREADSAFE=1'); SELECT 'optfts3', (SELECT count(*) FROM pragma_compile_options WHERE compile_options='ENABLE_FTS3'); SELECT 'optfts4', (SELECT count(*) FROM pragma_compile_options WHERE compile_options='ENABLE_FTS4'); SELECT 'optfts5', (SELECT count(*) FROM pragma_compile_options WHERE compile_options='ENABLE_FTS5'); SELECT 'optrtree', (SELECT count(*) FROM pragma_compile_options WHERE compile_options='ENABLE_RTREE'); SELECT 'optgeo', (SELECT count(*) FROM pragma_compile_options WHERE compile_options='ENABLE_GEOPOLY'); SELECT 'optmath', (SELECT count(*) FROM pragma_compile_options WHERE compile_options='ENABLE_MATH_FUNCTIONS'); SELECT 'optcolmeta', (SELECT count(*) FROM pragma_compile_options WHERE compile_options='ENABLE_COLUMN_METADATA'); SELECT 'optzlib', (SELECT count(*) FROM pragma_compile_options WHERE compile_options LIKE '%ZLIB%'); SELECT 'optmmap', (SELECT * FROM pragma_compile_options WHERE compile_options LIKE 'DEFAULT_MMAP_SIZE%'); SELECT 'optmutex', (SELECT count(*) FROM pragma_compile_options WHERE compile_options='MUTEX_PTHREADS'); SQL ) check_eq sqlite3/rel-sweep-ran 3 "$(sq_v rows "$R")" check_eq sqlite3/trigger-count 3 "$(sq_v trig "$R")" check_eq sqlite3/trigger-value ins:ann "$(sq_v2 trig "$R")" check_eq sqlite3/agg-sum 270.5 "$(sq_v agg "$R")" check_eq sqlite3/agg-avg 9016 "$(sq_v2 agg "$R")" check_eq sqlite3/agg-distinct 2 "$(sq_v3 agg "$R")" check_eq sqlite3/group-by "eng:2/ops:1" "$(sq_v grp "$R")" check_eq sqlite3/having eng "$(sq_v having "$R")" check_eq sqlite3/order-by "ann,bob,cid" "$(sq_v orderby "$R")" check_eq sqlite3/view 2 "$(sq_v view "$R")" check_eq sqlite3/index-column dept "$(sq_v idxcol "$R")" check_eq sqlite3/index-listed 1 "$(sq_v idxused "$R")" # The transaction layer, three ways. REAL values render with a trailing .0 here, # which is the shell's formatting and not a rounding error. check_eq sqlite3/rollback 100.5 "$(sq_v rollback "$R")" check_eq sqlite3/commit 200.0 "$(sq_v commit "$R")" check_eq sqlite3/savepoint 90.0 "$(sq_v savepoint "$R")" check_eq sqlite3/upsert upserted "$(sq_v upsert "$R")" check_eq sqlite3/returning 7:seven "$(sq_v returning "$R")" check_eq sqlite3/generated-virt 12 "$(sq_v generated "$R")" check_eq sqlite3/generated-stor 5 "$(sq_v2 generated "$R")" check_eq sqlite3/strict-table 1x "$(sq_v strict "$R")" check_eq sqlite3/foreign-key 1 "$(sq_v fkok "$R")" check_eq sqlite3/fk-check-clean 0 "$(sq_v fkcheck "$R")" check_eq sqlite3/recursive-cte 55 "$(sq_v cte "$R")" check_eq sqlite3/window-rownum ann=1 "$(sq_v winrow "$R")" check_eq sqlite3/window-sum 370.0 "$(sq_v winsum "$R")" check_eq sqlite3/window-ntile 2 "$(sq_v winntile "$R")" check_eq sqlite3/window-lag 3 "$(sq_v winlag "$R")" check_eq sqlite3/subquery ann "$(sq_v subq "$R")" check_eq sqlite3/exists 1 "$(sq_v exists "$R")" check_eq sqlite3/inner-join 3 "$(sq_v join "$R")" check_eq sqlite3/left-join 3 "$(sq_v leftjoin "$R")" check_eq sqlite3/union 4 "$(sq_v union "$R")" check_eq sqlite3/except 2 "$(sq_v except "$R")" check_eq sqlite3/intersect 1 "$(sq_v intersect "$R")" check_eq sqlite3/integrity ok "$(sq_v integrity "$R")" check_eq sqlite3/quick-check ok "$(sq_v quick "$R")" check_eq sqlite3/table-info 4 "$(sq_v colcount "$R")" check_eq sqlite3/user-tables 7 "$(sq_v usertables "$R")" check_eq sqlite3/encoding UTF-8 "$(sq_v encoding "$R")" # The compile-time feature set, read out of the engine that shipped rather than # out of the recipe. This is also the cross-configure witness: these options are # set by ./configure on this arm, so their presence says the target configure ran # to completion instead of falling back to defaults. check_eq sqlite3/opt-threadsafe 1 "$(sq_v threadsafe "$R")" check_eq sqlite3/opt-mutex-pth 1 "$(sq_v optmutex "$R")" check_eq sqlite3/opt-fts3 1 "$(sq_v optfts3 "$R")" check_eq sqlite3/opt-fts4 1 "$(sq_v optfts4 "$R")" check_eq sqlite3/opt-fts5 1 "$(sq_v optfts5 "$R")" check_eq sqlite3/opt-rtree 1 "$(sq_v optrtree "$R")" check_eq sqlite3/opt-geopoly 1 "$(sq_v optgeo "$R")" check_eq sqlite3/opt-math 1 "$(sq_v optmath "$R")" check_eq sqlite3/opt-colmeta 1 "$(sq_v optcolmeta "$R")" # Shape fact 4, from inside the engine: no zlib anywhere in the build. check_eq sqlite3/opt-no-zlib 0 "$(sq_v optzlib "$R")" # mmap is off by default, so the database is read with pread() and the arm's # file-mmap behaviour never enters the picture. 77-gdbm.test is where that gap # does show, on a package that maps unconditionally. check_eq sqlite3/opt-mmap-off DEFAULT_MMAP_SIZE=0 "$(sq_v optmmap "$R")" # --------------------------------------- 4. compiled-in extensions (one process) X=$(sqlite3 :memory: 2>&1 <<'SQL' .mode list .separator | .headers off CREATE VIRTUAL TABLE ft3 USING fts3(title, body); INSERT INTO ft3 VALUES('wasm port','sqlite runs in the browser sandbox'),('other','nothing to see'); SELECT 'f3match', count(*) FROM ft3 WHERE ft3 MATCH 'browser'; SELECT 'f3phrase', count(*) FROM ft3 WHERE ft3 MATCH '"in the browser"'; SELECT 'f3offsets', offsets(ft3) FROM ft3 WHERE ft3 MATCH 'sandbox'; SELECT 'f3snip', snippet(ft3, '<', '>') FROM ft3 WHERE ft3 MATCH 'browser'; CREATE VIRTUAL TABLE ft4 USING fts4(c); INSERT INTO ft4 VALUES('alpha beta gamma'),('delta epsilon'),('alpha delta'); SELECT 'f4match', count(*) FROM ft4 WHERE c MATCH 'beta'; SELECT 'f4prefix', count(*) FROM ft4 WHERE c MATCH 'gam*'; SELECT 'f4minus', count(*) FROM ft4 WHERE c MATCH 'alpha -delta'; SELECT 'f4or', count(*) FROM ft4 WHERE c MATCH 'beta OR epsilon'; SELECT 'f4not', count(*) FROM ft4 WHERE c MATCH 'alpha NOT delta'; CREATE VIRTUAL TABLE ft5 USING fts5(body, tokenize='porter unicode61'); INSERT INTO ft5 VALUES('running quickly through databases'),('static archives only'); SELECT 'f5match', count(*) FROM ft5 WHERE ft5 MATCH 'database'; SELECT 'f5stem', count(*) FROM ft5 WHERE ft5 MATCH 'run'; SELECT 'f5hl', highlight(ft5,0,'[',']') FROM ft5 WHERE ft5 MATCH 'static'; SELECT 'f5near', count(*) FROM ft5 WHERE ft5 MATCH 'NEAR(running databases, 5)'; SELECT 'f5bm25', (SELECT count(*) FROM (SELECT bm25(ft5) b FROM ft5 WHERE ft5 MATCH 'static')); SELECT 'f5snip', (SELECT count(*) FROM (SELECT snippet(ft5,0,'<','>','...',4) FROM ft5 WHERE ft5 MATCH 'archives')); CREATE VIRTUAL TABLE rt USING rtree(id, x0, x1, y0, y1); INSERT INTO rt VALUES(1,0.0,1.0,0.0,1.0),(2,5.0,6.0,5.0,6.0),(3,0.5,1.5,0.5,1.5); SELECT 'rtall', count(*) FROM rt; SELECT 'rthit', count(*) FROM rt WHERE x0<=1.2 AND x1>=0.2; SELECT 'rtfar', group_concat(id,',') FROM (SELECT id FROM rt WHERE x0>=4.0 ORDER BY id); SELECT 'geoarea', cast(round(geopoly_area(geopoly_regular(0,0,1,4))) as int); SELECT 'geojson', geopoly_json('[[0,0],[1,0],[1,1],[0,0]]'); SELECT 'geowithin', geopoly_within('[[1,1],[2,1],[2,2],[1,1]]','[[0,0],[3,0],[3,3],[0,3],[0,0]]'); SELECT 'geooutside', geopoly_within('[[0,0],[3,0],[3,3],[0,3],[0,0]]','[[1,1],[2,1],[2,2],[1,1]]'); SELECT 'geopoint', geopoly_contains_point('[[0,0],[4,0],[4,4],[0,4],[0,0]]', 2, 2); SELECT 'geooverlap', geopoly_overlap('[[0,0],[2,0],[2,2],[0,0]]','[[1,1],[3,1],[3,3],[1,1]]') > 0; SQL ) check_eq sqlite3/ext-sweep-ran 3 "$(sq_v rtall "$X")" # FTS3 and FTS4 are one module: SQLITE_ENABLE_FTS4 turns SQLITE_ENABLE_FTS3 on # in the amalgamation, so both spellings must work or neither does. check_eq sqlite3/fts3-match 1 "$(sq_v f3match "$X")" check_eq sqlite3/fts3-phrase 1 "$(sq_v f3phrase "$X")" check_eq sqlite3/fts3-offsets "1 0 27 7" "$(sq_v f3offsets "$X")" check_eq sqlite3/fts3-snippet "sqlite runs in the sandbox" "$(sq_v f3snip "$X")" check_eq sqlite3/fts4-match 1 "$(sq_v f4match "$X")" check_eq sqlite3/fts4-prefix 1 "$(sq_v f4prefix "$X")" check_eq sqlite3/fts4-exclude 1 "$(sq_v f4minus "$X")" check_eq sqlite3/fts4-or 2 "$(sq_v f4or "$X")" # Shape fact 5: without SQLITE_ENABLE_FTS3_PARENTHESIS, infix NOT is three # tokens and matches nothing. Asserted so that defining it later is noticed. check_eq sqlite3/fts4-not-inert 0 "$(sq_v f4not "$X")" check_eq sqlite3/fts5-match 1 "$(sq_v f5match "$X")" # The porter stemmer reduces "running" to "run": a tokenizer that silently fell # back to unicode61 alone would return 0 here and pass every other fts5 case. check_eq sqlite3/fts5-porter 1 "$(sq_v f5stem "$X")" check_eq sqlite3/fts5-highlight "[static] archives only" "$(sq_v f5hl "$X")" check_eq sqlite3/fts5-near 1 "$(sq_v f5near "$X")" check_eq sqlite3/fts5-bm25 1 "$(sq_v f5bm25 "$X")" check_eq sqlite3/fts5-snippet 1 "$(sq_v f5snip "$X")" check_eq sqlite3/rtree-query 2 "$(sq_v rthit "$X")" check_eq sqlite3/rtree-far 2 "$(sq_v rtfar "$X")" check_eq sqlite3/geopoly-area 2 "$(sq_v geoarea "$X")" check_eq sqlite3/geopoly-json '[[0.0,0.0],[1.0,0.0],[1.0,1.0],[0.0,0.0]]' "$(sq_v geojson "$X")" # Both directions of a containment test: within is not symmetric, and asserting # only the true side passes on a function that returns 1 unconditionally. check_eq sqlite3/geopoly-within 1 "$(sq_v geowithin "$X")" check_eq sqlite3/geopoly-outside 0 "$(sq_v geooutside "$X")" check_eq sqlite3/geopoly-point 2 "$(sq_v geopoint "$X")" check_eq sqlite3/geopoly-overlap 1 "$(sq_v geooverlap "$X")" # ------------------------------------------------------- 5. the error contract # Shape fact 3: the shell returns the SQLite result code, so these three exit # statuses are three different numbers and each needs its own process. check_rc sqlite3/rc-syntax 1 sqlite3 :memory: 'selct 1;' check_rc sqlite3/rc-unknown-fn 1 sqlite3 :memory: 'select nosuchfn(1);' check_rc sqlite3/rc-constraint 19 sqlite3 :memory: \ 'create table t(a unique); insert into t values(1); insert into t values(1);' check_out sqlite3/err-syntax "syntax error" out_of sqlite3 :memory: 'selct 1;' check_out sqlite3/err-unknown "no such function: nosuchfn" out_of sqlite3 :memory: 'select nosuchfn(1);' check_out sqlite3/err-constraint "UNIQUE constraint failed: t.a" out_of sqlite3 :memory: \ 'create table t(a unique); insert into t values(1); insert into t values(1);' # A directory that cannot exist: the open must fail cleanly rather than create # anything or abort. This is also the arm's ENOENT path through the unix VFS. check_rc sqlite3/rc-unopenable 1 sqlite3 /nonexistent-dir-77/x.db 'select 1;' check_out sqlite3/err-unopenable "unable to open database" out_of \ sqlite3 /nonexistent-dir-77/x.db 'select 1;' # ------------------------------------------- 6. shell flags (process-boundary) # Output modes are the shell's own formatting layer, so each needs its own # invocation; these five are the ones a script on this image would use. check_out sqlite3/mode-csv "a,b" sqlite3 -csv :memory: "select 'a','b';" check_out sqlite3/mode-json '[{"n":1}]' sqlite3 -json :memory: "select 1 as n;" check_out sqlite3/mode-quote "'q'" sqlite3 -quote :memory: "select 'q';" check_out sqlite3/mode-line "five = 5" sqlite3 -line :memory: "select 5 as five;" check_out sqlite3/opt-header "col" sqlite3 -header -csv :memory: "select 1 as col;" check_out sqlite3/opt-separator "1;2" sqlite3 -list -separator ';' :memory: "select 1,2;" check_out sqlite3/opt-cmd "1,2" sqlite3 -cmd ".mode csv" :memory: "select 1,2;" # -bail must stop at the first error; without it the shell runs on to the next # statement. Both halves of that pair, since either alone proves nothing. check_rc sqlite3/opt-bail 1 sqlite3 -bail :memory: 'selct 1; select 99;' check_out sqlite3/no-bail-continues "99" out_of sqlite3 :memory: 'selct 1; select 99;' check_out sqlite3/stdin-script "2" sh -c "printf 'select 1+1;\n' | sqlite3 :memory:" check_out sqlite3/help-dot ".backup" sqlite3 :memory: '.help' # --------------------------------------- 7. what this build does NOT have # One process for the three absences, each asserted on its diagnostic. Written # as the SHIPPED CONTRACT rather than as defects: two are poky's recipe shape # (fact 2), one is the missing zlib (fact 4), and the load_extension case is the # arm — there are no shared objects on this image at all, so extension loading # has nothing it could ever load. # # Fed on STDIN rather than as three SQL arguments: an error in a command-line SQL # argument makes the shell stop processing the remaining arguments, so the # argument form reports only the first of the three and the other two cases go # red against a build that has neither. A stdin script keeps going after each # error, which is what makes one process enough. A=$(printf '%s\n' "select load_extension('/nonexistent-ext.so');" \ 'select count(*) from dbstat;' '.archive --help' | out_of sqlite3 :memory:) echo_a() { printf '%s\n' "$A"; } # Asserted on the error LINE, not on the dlerror wording: glibc says "cannot open # shared object file" and this kernel's dlopen staging says # wasm32_user_dlopen_load(...). Both are the same correct refusal, so the needle # names the path we asked for and the fact that it was an error. check_out sqlite3/no-load-extension "Runtime error near line 1: /nonexistent-ext.so" echo_a check_out sqlite3/no-dbstat-vtab "no such table: dbstat" echo_a check_out sqlite3/no-archive-cmd 'unknown command or invalid arguments: "archive"' echo_a # .dbinfo needs the sqlite_dbpage vtab, absent for the same reason as dbstat. check_out sqlite3/no-dbpage-vtab "sqlite_dbpage" out_of sqlite3 :memory: '.dbinfo' # ------------------------------------------------------ 8. the payload ships # Static-only arm: the deliverable is the archive plus the headers, and there # must be no shared object. sqlite3 is one of the few ports here that ships a # runnable binary AND a library, so both halves are asserted. H=/usr/include/sqlite3.h EH=/usr/include/sqlite3ext.h L=/usr/lib/libsqlite3.a PC=/usr/lib/pkgconfig/sqlite3.pc check sqlite3/hdr-present test -s "$H" check sqlite3/exthdr-present test -s "$EH" check sqlite3/lib-present test -s "$L" check sqlite3/pc-present test -s "$PC" check_out sqlite3/hdr-version 'define SQLITE_VERSION "3.45.3"' cat "$H" check_out sqlite3/hdr-vernumber "define SQLITE_VERSION_NUMBER 3045003" cat "$H" check_out sqlite3/hdr-open "sqlite3_open_v2" cat "$H" check_out sqlite3/hdr-prepare "sqlite3_prepare_v2" cat "$H" check_out sqlite3/hdr-step "sqlite3_step" cat "$H" check_out sqlite3/hdr-bind "sqlite3_bind_text" cat "$H" check_out sqlite3/hdr-column "sqlite3_column_int64" cat "$H" check_out sqlite3/hdr-errmsg "sqlite3_errmsg" cat "$H" check_out sqlite3/hdr-backup "sqlite3_backup_init" cat "$H" check_out sqlite3/hdr-blob "sqlite3_blob_open" cat "$H" # SQLITE_THREADSAFE is NOT a header macro — it is a build-time define on the # amalgamation, so the header carries the sqlite3_threadsafe() accessor instead # and the compile_options case above is what witnesses the value. Asserting # "define SQLITE_THREADSAFE 1" here is the natural mistake. check_out sqlite3/hdr-threadsafe-api "sqlite3_threadsafe" cat "$H" # -DSQLITE_ENABLE_COLUMN_METADATA is the recipe's own addition and it is the one # API whose absence a consumer discovers only at link time. check_out sqlite3/hdr-colmeta "sqlite3_column_table_name" cat "$H" check_out sqlite3/exthdr-api "sqlite3_api_routines" cat "$EH" check_out sqlite3/exthdr-entry "SQLITE_EXTENSION_INIT1" cat "$EH" check_out sqlite3/pc-name "Name: SQLite" cat "$PC" check_out sqlite3/pc-version "Version: 3.45.3" cat "$PC" check_out sqlite3/pc-libs "-lsqlite3" cat "$PC" check_out sqlite3/lib-is-archive "!" head -c 8 "$L" # Size floor: a stub archive would pass every symbol assertion that follows, # provided the index happened to be there. The native control of this same # configuration is 1.78 MB, so this floor separates a real engine from a stub # without pinning either arm's exact object encoding. check sqlite3/lib-size sh -c "[ \$(wc -c < $L) -gt 1000000 ]" # THE AMALGAMATION HAS NO INTERNAL SYMBOLS TO ASSERT, and that is the one way # this archive differs from every other in these suites. libsqlite3.a is a SINGLE # member built from sqlite3.c, in which SQLITE_PRIVATE expands to `static` — so # the pager, the b-tree, the VDBE and the query planner are all file-local and # appear in the index not at all (`nm` shows sqlite3PagerOpen as a local `t`). # Asserting sqlite3PagerOpen the way 77-gdbm.test asserts _gdbm_hash therefore # reports a perfectly good archive as broken, which is what a first draft of this # file did for twelve cases. Only the ~280 exported sqlite3_* entry points can be # asserted here, and the count floor below stands in for the internals: a # half-configured build (no FTS5, no RTREE) exports visibly fewer. T=${TMPDIR:-/tmp}/bst-sqlite3.$$ mkdir -p "$T" IDX="$T/idx"; head -c 262144 "$L" | tr -c '\40-\176\n' ' ' | tr '\n' ' ' > "$IDX" # The C API a consumer links against, one per layer: connection, statement, # binding, column access, errors, the backup API, incremental blob I/O, the # extension-registration entry points, and the two the recipe's own flags add. for s in sqlite3_open sqlite3_open_v2 sqlite3_close sqlite3_close_v2 \ sqlite3_prepare_v2 sqlite3_prepare_v3 sqlite3_step sqlite3_reset \ sqlite3_finalize sqlite3_exec sqlite3_bind_text sqlite3_bind_int64 \ sqlite3_bind_blob sqlite3_column_text sqlite3_column_int64 \ sqlite3_column_count sqlite3_column_decltype sqlite3_errmsg \ sqlite3_errcode sqlite3_extended_errcode sqlite3_backup_init \ sqlite3_backup_step sqlite3_blob_open sqlite3_blob_read \ sqlite3_create_function_v2 sqlite3_create_collation_v2 \ sqlite3_create_module_v2 sqlite3_libversion_number \ sqlite3_column_table_name sqlite3_db_config sqlite3_wal_checkpoint_v2 \ sqlite3_serialize sqlite3_deserialize sqlite3_threadsafe \ sqlite3_load_extension sqlite3_vfs_find sqlite3_mutex_alloc; do check_out "sqlite3/sym-$s" "$s " cat "$IDX" done # The structural check the missing internals leave room for: a floor on the # number of exported entry points. The native control exports 280. check sqlite3/sym-count sh -c \ "[ \$(head -c 262144 $L | tr -c '\40-\176\n' '\n' | grep -c '^sqlite3_') -ge 250 ]" # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740, released for sqlite3 by BRINTOS-1734 -- its consumers used to # refuse because a -shared link carried no compiler-rt), so a libsqlite3.so here # is the conversion working. What is asserted is that whatever ships under that # name is usable rather than a dangling link or an empty file. bst_lib_shape sqlite3/lib-shape "/usr/lib/libsqlite3.so*" # The libsqlite3 package is FILES = ${libdir}/*.so.*, which was empty while this # arm was static-only and is populated by the BRINTOS-1734 conversion above. The # shape check is what notices if it goes back to empty, or ships something # unusable. # # The .la case is TARGET-ONLY BY CONSTRUCTION and the one case in this file the # native control cannot pass: libtool installs libsqlite3.la and it is Yocto's # REMOVE_LIBTOOL_LA (default 1) that deletes it, so the control legitimately # fails here while the image legitimately passes. Kept because libsqlite3-dev # lists ${libdir}/*.la in FILES, so a build that stopped removing them would ship # a .la naming a nonexistent .so — and nothing else on the image would notice. check sqlite3/no-libtool-la sh -c "! ls /usr/lib/libsqlite3.la >/dev/null 2>&1" rm -rf "$T"