From 00d6b2755fe197558019dc5ccf4e26740a7356a5 Mon Sep 17 00:00:00 2001 From: drh <> Date: Thu, 15 Dec 2022 20:03:08 +0000 Subject: [PATCH] Create a new affinity called FLEXNUM that works like NUMERIC except that it never tries to convert integer to real or real to integer. The affinity is only used internally - it is not possible to create a table column with this affinity. This affinity is used on subqueries and views that are built off of a compound SELECT and where the datatype is controlled by a CAST expression. dbsqlfuzz c9ee6f9a0a8b8fefb02cf69de2a8b67ca39525c8 FossilOrigin-Name: 44135d6ea84f7ba6b36549954b38a8bc048d5ffea5a9779e35950afa4eb2dfb2 --- manifest | 24 ++++++++++++------------ manifest.uuid | 2 +- src/build.c | 7 +++++-- src/expr.c | 2 +- src/select.c | 33 +++++++++------------------------ src/sqliteInt.h | 1 + src/vdbe.c | 8 ++++++-- test/cast.test | 40 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 75 insertions(+), 42 deletions(-) diff --git a/manifest b/manifest index 8fe1f810b7..e4ec20ca89 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\san\sincompatibility\sbetween\sthe\sTcl\sinterface\sand\sthe\s"memdb"\svfs\sby\sallowing\smemdb\sto\saccept\sfilenames\sthat\sbegin\swith\s'\\'\scharacters. -D 2022-12-15T18:56:12.441 +C Create\sa\snew\saffinity\scalled\sFLEXNUM\sthat\sworks\slike\sNUMERIC\sexcept\sthat\sit\nnever\stries\sto\sconvert\sinteger\sto\sreal\sor\sreal\sto\sinteger.\s\sThe\saffinity\sis\nonly\sused\sinternally\s-\sit\sis\snot\spossible\sto\screate\sa\stable\scolumn\swith\sthis\naffinity.\s\sThis\saffinity\sis\sused\son\ssubqueries\sand\sviews\sthat\sare\sbuilt\soff\nof\sa\scompound\sSELECT\sand\swhere\sthe\sdatatype\sis\scontrolled\sby\sa\sCAST\sexpression.\ndbsqlfuzz\sc9ee6f9a0a8b8fefb02cf69de2a8b67ca39525c8 +D 2022-12-15T20:03:08.225 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -587,7 +587,7 @@ F src/btmutex.c 6ffb0a22c19e2f9110be0964d0731d2ef1c67b5f7fabfbaeb7b9dabc4b7740ca F src/btree.c 2f794c217e52fdf4322bf37ee7778331b4d93aed2c00b5d67f914c0239a9edcc F src/btree.h 49da925329574798be3cbb745a49d069a9e67c99900d8a0d04b1e934d60394ea F src/btreeInt.h 88ad499c92b489afedbfefc3f067c4d15023ec021afe622db240dc9d2277cfa5 -F src/build.c 51b46b657d914877dc64d0f66e2d82021b6ff622f43287cebc105efee0165600 +F src/build.c ea069a5655797f174403ee6f32c532e69ddcf4031bc0e65cca4863cb28cba603 F src/callback.c 4cd7225b26a97f7de5fee5ae10464bed5a78f2adefe19534cc2095b3a8ca484a F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c 20507cc0b0a6c19cd882fcd0eaeda32ae6a4229fb4b024cfdf3183043d9b703d @@ -595,7 +595,7 @@ F src/date.c 94ce83b4cd848a387680a5f920c9018c16655db778c4d36525af0a0f34679ac5 F src/dbpage.c f1a87f4ebcf22284e0aaf0697862f4ccfc120dcd6db3d8dfa3b049b2580c01d8 F src/dbstat.c a56a7ad1163a9888d46cd5820be2e65354fb1aa04ed6909f7c3e5831e0ee2c29 F src/delete.c 86573edae75e3d3e9a8b590d87db8e47222103029df4f3e11fa56044459b514e -F src/expr.c 63e0b77a5444c75908fb1d2db9b15248c3b31b4fd5ea38f1a45a2b4ac2f14cf8 +F src/expr.c 6dc067ab8244627d62d0d81bd9d9cc78173325cf2fe3f494f0a65a418ce2b9f8 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c 722f20779f5342a787922deded3628d8c74b5249cab04098cf17ee2f2aaff002 F src/func.c 7e86074afc4dc702691a29b7801f6dcc191db092b52e8bbe69dcd2f7be52194d @@ -645,12 +645,12 @@ F src/printf.c e99ee9741e79ae3873458146f59644276657340385ade4e76a5f5d1c25793764 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c efea4e5fbecfd6d0a9071b0be0d952620991673391b6ffaaf4c277b0bb674633 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 -F src/select.c a6c97f07b148ebf2e47c973b3ed292b43950da5bccec4a98f38ab7db6d6a0283 +F src/select.c c4f845e116a425c71b8115b7ffc221237cb59569de3253134cbd570b7e04cf0f F src/shell.c.in 47d491325ea7e01d9d4b7f6166a83fbfc6729b3d8a546d857d627a58a5d46d73 F src/sqlite.h.in e752f82b9d71f1d42b259b1900e4b1caf0965e844d756cd5cc91cc2cf45ed925 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h c4b9fa7a7e2bcdf850cfeb4b8a91d5ec47b7a00033bc996fd2ee96cbf2741f5f -F src/sqliteInt.h 58dac6ab1352193715dc7de71cf900cafce6c3064a7331dc8b2029a6165a2b92 +F src/sqliteInt.h 4bcf6f13b8195de4fd020cd58ebb605a27752b3d82d0d5071d4435a446f00487 F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 @@ -717,7 +717,7 @@ F src/upsert.c 5303dc6c518fa7d4b280ec65170f465c7a70b7ac2b22491598f6d0b4875b3145 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 F src/util.c 313f3154e2b85a447326f5dd15de8d31a4df6ab0c3579bd58f426ff634ec9050 F src/vacuum.c 84ce7f01f8a7a08748e107a441db83bcec13970190ddcb0c9ff522adbc1c23fd -F src/vdbe.c 705df5b079978ea98ae18db8ffcefbdf529f7465e9c8fa19b022530556fc68e1 +F src/vdbe.c c1a65b1bb9e2fdb8831cfb4a7ff4ef3088647dd5f096bcedd5231c03181459f4 F src/vdbe.h 73b904a6b3bb27f308c6cc287a5751ebc7f1f89456be0ed068a12b92844c6e8c F src/vdbeInt.h 8651e4c4e04d1860d0bdcf330cb8294e3778a9d4222be30ce4c490d9220af783 F src/vdbeapi.c df3f73a4d0a487f2068e3c84776cd6e3fba5ae80ff612659dcfda4307686420b @@ -863,7 +863,7 @@ F test/capi3c.test 54e2dc0c8fd7c34ad1590d1be6864397da2438c95a9f5aee2f8fbc60c112e F test/capi3d.test 8b778794af891b0dca3d900bd345fbc8ebd2aa2aae425a9dccdd10d5233dfbde F test/capi3e.test 3d49c01ef2a1a55f41d73cba2b23b5059ec460fe F test/carray01.test d55d57bf66b1af1c7ac55fae66ff4910884a8f5d21a90a18797ce386212a2634 -F test/cast.test 46a5963a216c2b14220557b8636b968d6de9d3292d79616becbf7109ca00e1ad +F test/cast.test e3a7e452f37efec0df0a89e55aa2f04861ba6613deb16075101414668bf4bb24 F test/cffault.test 9d6b20606afe712374952eec4f8fd74b1a8097ef F test/changes.test 9dd8e597d84072122fc8a4fcdea837f4a54a461e6e536053ea984303e8ca937b F test/changes2.test d222c0cbf5ab0ac4d7c180594e486c1bf20b2098d33e56ce33b8e12eba6823b9 @@ -2067,8 +2067,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 eb27feecea508f3491a09699f2339951facd2345d479cfd3020183dc2af703b2 -R d9029a8d7af89f7bcaaa52e77671d993 -U dan -Z 711e972efe20ba3f8cde77248740eec0 +P bd537f2057a4800bd30e7dd57405c3e57df649471104c80bd32573a89568029e +R 37c0f0e978d6f512d45ea1c89699be07 +U drh +Z f6d3f39ad91532557d8a7968a4b1fd8b # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 29eb87a2b2..b16f3354de 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -bd537f2057a4800bd30e7dd57405c3e57df649471104c80bd32573a89568029e \ No newline at end of file +44135d6ea84f7ba6b36549954b38a8bc048d5ffea5a9779e35950afa4eb2dfb2 \ No newline at end of file diff --git a/src/build.c b/src/build.c index 3e5abb7ee8..29a798e6c7 100644 --- a/src/build.c +++ b/src/build.c @@ -2140,7 +2140,8 @@ static char *createTableStmt(sqlite3 *db, Table *p){ /* SQLITE_AFF_TEXT */ " TEXT", /* SQLITE_AFF_NUMERIC */ " NUM", /* SQLITE_AFF_INTEGER */ " INT", - /* SQLITE_AFF_REAL */ " REAL" + /* SQLITE_AFF_REAL */ " REAL", + /* SQLITE_AFF_FLEXNUM */ " NUM", }; int len; const char *zType; @@ -2156,10 +2157,12 @@ static char *createTableStmt(sqlite3 *db, Table *p){ testcase( pCol->affinity==SQLITE_AFF_NUMERIC ); testcase( pCol->affinity==SQLITE_AFF_INTEGER ); testcase( pCol->affinity==SQLITE_AFF_REAL ); + testcase( pCol->affinity==SQLITE_AFF_FLEXNUM ); zType = azType[pCol->affinity - SQLITE_AFF_BLOB]; len = sqlite3Strlen30(zType); - assert( pCol->affinity==SQLITE_AFF_BLOB + assert( pCol->affinity==SQLITE_AFF_BLOB + || pCol->affinity==SQLITE_AFF_FLEXNUM || pCol->affinity==sqlite3AffinityType(zType, 0) ); memcpy(&zStmt[k], zType, len); k += len; diff --git a/src/expr.c b/src/expr.c index fe316550a7..b1ccde4afd 100644 --- a/src/expr.c +++ b/src/expr.c @@ -4250,7 +4250,7 @@ expr_code_doover: assert( pExpr->y.pTab!=0 ); aff = sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); if( aff>SQLITE_AFF_BLOB ){ - static const char zAff[] = "B\000C\000D\000E"; + static const char zAff[] = "B\000C\000D\000E\000F"; assert( SQLITE_AFF_BLOB=='A' ); assert( SQLITE_AFF_TEXT=='B' ); sqlite3VdbeAddOp4(v, OP_Affinity, iReg, 1, 0, diff --git a/src/select.c b/src/select.c index 7ba1b8e93c..d34746fb36 100644 --- a/src/select.c +++ b/src/select.c @@ -2288,14 +2288,6 @@ int sqlite3ColumnsFromExprList( return SQLITE_OK; } -/* -** This bit, when added to the "aff" parameter of -** sqlite3ColumnTypeOfSubquery() means that result set -** expressions of the form "CAST(expr AS NUMERIC)" should result in -** NONE affinity rather than NUMERIC affinity. -*/ -#define SQLITE_AFF_FLAG1 0x10 - /* ** pTab is a transient Table object that represents a subquery of some ** kind (maybe a parenthesized subquery in the FROM clause of a larger @@ -2306,17 +2298,12 @@ int sqlite3ColumnsFromExprList( ** * The datatype name, as it might appear in a CREATE TABLE statement ** * Which collating sequence to use for the column ** * The affinity of the column -** -** The SQLITE_AFF_FLAG1 bit added to parameter aff means that a -** result set column of the form "CAST(expr AS NUMERIC)" should use -** NONE affinity rather than NUMERIC affinity. See the -** 2022-12-10 "reopen" of ticket https://sqlite.org/src/tktview/57c47526c3. */ void sqlite3SubqueryColumnTypes( Parse *pParse, /* Parsing contexts */ Table *pTab, /* Add column type information to this table */ Select *pSelect, /* SELECT used to determine types and collations */ - char aff /* Default affinity. Maybe with SQLITE_AFF_FLAG1 too */ + char aff /* Default affinity. */ ){ sqlite3 *db = pParse->db; Column *pCol; @@ -2328,6 +2315,7 @@ void sqlite3SubqueryColumnTypes( assert( pSelect!=0 ); assert( (pSelect->selFlags & SF_Resolved)!=0 ); assert( pTab->nCol==pSelect->pEList->nExpr || db->mallocFailed ); + assert( aff==SQLITE_AFF_NONE || aff==SQLITE_AFF_BLOB ); if( db->mallocFailed ) return; while( pSelect->pPrior ) pSelect = pSelect->pPrior; a = pSelect->pEList->a; @@ -2339,13 +2327,9 @@ void sqlite3SubqueryColumnTypes( /* pCol->szEst = ... // Column size est for SELECT tables never used */ pCol->affinity = sqlite3ExprAffinity(p); if( pCol->affinity<=SQLITE_AFF_NONE ){ - assert( (SQLITE_AFF_FLAG1 & SQLITE_AFF_MASK)==0 ); - pCol->affinity = aff & SQLITE_AFF_MASK; - } - if( aff & SQLITE_AFF_FLAG1 ){ - if( pCol->affinity==SQLITE_AFF_NUMERIC && p->op==TK_CAST ){ - pCol->affinity = SQLITE_AFF_NONE; - } + pCol->affinity = aff; + }else if( pCol->affinity>=SQLITE_AFF_NUMERIC && p->op==TK_CAST ){ + pCol->affinity = SQLITE_AFF_FLEXNUM; } if( pCol->affinity>=SQLITE_AFF_TEXT && pSelect->pNext ){ int m = 0; @@ -2360,7 +2344,9 @@ void sqlite3SubqueryColumnTypes( pCol->affinity = SQLITE_AFF_BLOB; } } - if( pCol->affinity==SQLITE_AFF_NUMERIC ){ + if( pCol->affinity==SQLITE_AFF_NUMERIC + || pCol->affinity==SQLITE_AFF_FLEXNUM + ){ zType = "NUM"; }else{ zType = 0; @@ -6254,8 +6240,7 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ /* A sub-query in the FROM clause of a SELECT */ Select *pSel = pFrom->pSelect; if( pSel ){ - sqlite3SubqueryColumnTypes(pParse, pTab, pSel, - SQLITE_AFF_NONE|SQLITE_AFF_FLAG1); + sqlite3SubqueryColumnTypes(pParse, pTab, pSel, SQLITE_AFF_NONE); } } } diff --git a/src/sqliteInt.h b/src/sqliteInt.h index c012451a3c..8c97699d95 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -2239,6 +2239,7 @@ struct CollSeq { #define SQLITE_AFF_NUMERIC 0x43 /* 'C' */ #define SQLITE_AFF_INTEGER 0x44 /* 'D' */ #define SQLITE_AFF_REAL 0x45 /* 'E' */ +#define SQLITE_AFF_FLEXNUM 0x46 /* 'F' */ #define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC) diff --git a/src/vdbe.c b/src/vdbe.c index 82a9938477..a3390d984f 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -371,6 +371,10 @@ static void applyNumericAffinity(Mem *pRec, int bTryForInt){ ** always preferred, even if the affinity is REAL, because ** an integer representation is more space efficient on disk. ** +** SQLITE_AFF_FLEXNUM: +** If the value is text, then try to convert it into a number of +** some kind (integer or real) but do not make any other changes. +** ** SQLITE_AFF_TEXT: ** Convert pRec to a text representation. ** @@ -385,11 +389,11 @@ static void applyAffinity( ){ if( affinity>=SQLITE_AFF_NUMERIC ){ assert( affinity==SQLITE_AFF_INTEGER || affinity==SQLITE_AFF_REAL - || affinity==SQLITE_AFF_NUMERIC ); + || affinity==SQLITE_AFF_NUMERIC || affinity==SQLITE_AFF_FLEXNUM ); if( (pRec->flags & MEM_Int)==0 ){ /*OPTIMIZATION-IF-FALSE*/ if( (pRec->flags & MEM_Real)==0 ){ if( pRec->flags & MEM_Str ) applyNumericAffinity(pRec,1); - }else{ + }else if( affinity<=SQLITE_AFF_REAL ){ sqlite3VdbeIntegerAffinity(pRec); } } diff --git a/test/cast.test b/test/cast.test index c4c0d8908d..7f7be8b620 100644 --- a/test/cast.test +++ b/test/cast.test @@ -515,4 +515,44 @@ do_execsql_test cast-9.13 { SELECT x, typeof(x) FROM dual CROSS JOIN (SELECT CAST(4.5 AS NUMERIC) AS x); } {4.5 real} +# 2022-12-15 dbsqlfuzz c9ee6f9a0a8b8fefb02cf69de2a8b67ca39525c8 +# +# Added a new SQLITE_AFF_FLEXNUM that does not try to convert int to real or +# real to int. +# +do_execsql_test cast-10.1 { + VALUES(CAST(44 AS REAL)),(55); +} {44.0 55} +do_execsql_test cast-10.2 { + SELECT CAST(44 AS REAL) AS 'm' UNION ALL SELECT 55; +} {44.0 55} +do_execsql_test cast-10.3 { + SELECT * FROM (VALUES(CAST(44 AS REAL)),(55)); +} {44.0 55} +do_execsql_test cast-10.4 { + SELECT * FROM (SELECT CAST(44 AS REAL) AS 'm' UNION ALL SELECT 55); +} {44.0 55} +do_execsql_test cast-10.5 { + SELECT * FROM dual CROSS JOIN (VALUES(CAST(44 AS REAL)),(55)); +} {X 44.0 X 55} +do_execsql_test cast-10.6 { + SELECT * FROM dual CROSS JOIN (SELECT CAST(44 AS REAL) AS 'm' + UNION ALL SELECT 55); +} {X 44.0 X 55} +do_execsql_test cast-10.7 { + DROP VIEW v1; + CREATE VIEW v1 AS SELECT CAST(44 AS REAL) AS 'm' UNION ALL SELECT 55; + SELECT name, type FROM pragma_table_info('v1'); +} {m NUM} +do_execsql_test cast-10.8 { + CREATE VIEW v2 AS VALUES(CAST(44 AS REAL)),(55); + SELECT type FROM pragma_table_info('v2'); +} {NUM} +do_execsql_test cast-10.9 { + SELECT * FROM v1; +} {44.0 55} +do_execsql_test cast-10.10 { + SELECT * FROM v2; +} {44.0 55} + finish_test