really kinda silly - someone spent some time making this super easy to extend, and noone actually wants to use that ability :) This is just for reference, I understand there's apparently a difference between open source and open contribution; I don't care if you don't use my exact bytes to provide the functionality; be contrarian. https://github.com/d3x0r/sqlite/tree/sqlite3_column_table_alias (which has a git PR representing the diff between master and it; not this is relevant information) xx.diff ``` diff src/loadext.c | 6 ++ src/select.c | 25 +++++--- src/sqlite.h.in | 7 ++- src/sqlite3ext.h | 6 ++ src/test1.c | 2 + src/vdbe.h | 13 ++-- src/vdbeapi.c | 17 +++++ test/capi3f.test | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 249 insertions(+), 15 deletions(-) diff --git a/src/loadext.c b/src/loadext.c index 4edefec0c..0fb2c41e5 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -30,6 +30,8 @@ # define sqlite3_column_database_name16 0 # define sqlite3_column_table_name 0 # define sqlite3_column_table_name16 0 +# define sqlite3_column_table_alias 0 +# define sqlite3_column_table_alias16 0 # define sqlite3_column_origin_name 0 # define sqlite3_column_origin_name16 0 #endif @@ -61,6 +63,7 @@ # define sqlite3_value_text16le 0 # define sqlite3_column_database_name16 0 # define sqlite3_column_table_name16 0 +# define sqlite3_column_table_alias16 0 # define sqlite3_column_origin_name16 0 #endif @@ -485,6 +488,9 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_total_changes64, /* Version 3.37.0 and later */ sqlite3_autovacuum_pages, + /* Version 3.38.0 and later */ + sqlite3_column_table_alias, + sqlite3_column_table_alias16, }; /* True if x is the directory separator character diff --git a/src/select.c b/src/select.c index ebb746467..e50b42c49 100644 --- a/src/select.c +++ b/src/select.c @@ -1751,9 +1751,9 @@ static void generateSortTail( ** the SQLITE_ENABLE_COLUMN_METADATA compile-time option is used. */ #ifdef SQLITE_ENABLE_COLUMN_METADATA -# define columnType(A,B,C,D,E) columnTypeImpl(A,B,C,D,E) +# define columnType(A,B,C,D,E,F) columnTypeImpl(A,B,C,D,E,F) #else /* if !defined(SQLITE_ENABLE_COLUMN_METADATA) */ -# define columnType(A,B,C,D,E) columnTypeImpl(A,B) +# define columnType(A,B,C,D,E,F) columnTypeImpl(A,B) #endif static const char *columnTypeImpl( NameContext *pNC, @@ -1763,7 +1763,8 @@ static const char *columnTypeImpl( Expr *pExpr, const char **pzOrigDb, const char **pzOrigTab, - const char **pzOrigCol + const char **pzOrigCol, + const char **pzOrigTabAlias #endif ){ char const *zType = 0; @@ -1772,6 +1773,7 @@ static const char *columnTypeImpl( char const *zOrigDb = 0; char const *zOrigTab = 0; char const *zOrigCol = 0; + char const *zOrigTabAlias = 0; #endif assert( pExpr!=0 ); @@ -1791,6 +1793,10 @@ static const char *columnTypeImpl( if( j<pTabList->nSrc ){ pTab = pTabList->a[j].pTab; pS = pTabList->a[j].pSelect; +#ifdef SQLITE_ENABLE_COLUMN_METADATA + zOrigTabAlias = + pTabList->a[j].zAlias?pTabList->a[j].zAlias:pTabList->a[j].zName; +#endif }else{ pNC = pNC->pNext; } @@ -1839,7 +1845,7 @@ static const char *columnTypeImpl( sNC.pSrcList = pS->pSrc; sNC.pNext = pNC; sNC.pParse = pNC->pParse; - zType = columnType(&sNC, p,&zOrigDb,&zOrigTab,&zOrigCol); + zType = columnType(&sNC, p,&zOrigDb,&zOrigTab,&zOrigCol,&zOrigTabAlias); } }else{ /* A real table or a CTE table */ @@ -1885,7 +1891,7 @@ static const char *columnTypeImpl( sNC.pSrcList = pS->pSrc; sNC.pNext = pNC; sNC.pParse = pNC->pParse; - zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol); + zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol, &zOrigTabAlias); break; } #endif @@ -1897,6 +1903,7 @@ static const char *columnTypeImpl( *pzOrigDb = zOrigDb; *pzOrigTab = zOrigTab; *pzOrigCol = zOrigCol; + *pzOrigTabAlias = zOrigTabAlias; } #endif return zType; @@ -1925,7 +1932,8 @@ static void generateColumnTypes( const char *zOrigDb = 0; const char *zOrigTab = 0; const char *zOrigCol = 0; - zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol); + const char *zOrigTabAlias = 0; + zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol, &zOrigTabAlias); /* The vdbe must make its own copy of the column-type and other ** column specific strings, in case the schema is reset before this @@ -1934,8 +1942,9 @@ static void generateColumnTypes( sqlite3VdbeSetColName(v, i, COLNAME_DATABASE, zOrigDb, SQLITE_TRANSIENT); sqlite3VdbeSetColName(v, i, COLNAME_TABLE, zOrigTab, SQLITE_TRANSIENT); sqlite3VdbeSetColName(v, i, COLNAME_COLUMN, zOrigCol, SQLITE_TRANSIENT); + sqlite3VdbeSetColName(v, i, COLNAME_TABLE_ALIAS, zOrigTabAlias, SQLITE_TRANSIENT); #else - zType = columnType(&sNC, p, 0, 0, 0); + zType = columnType(&sNC, p, 0, 0, 0, 0); #endif sqlite3VdbeSetColName(v, i, COLNAME_DECLTYPE, zType, SQLITE_TRANSIENT); } @@ -2199,7 +2208,7 @@ void sqlite3SelectAddColumnTypeAndCollation( int n, m; pTab->tabFlags |= (pCol->colFlags & COLFLAG_NOINSERT); p = a[i].pExpr; - zType = columnType(&sNC, p, 0, 0, 0); + zType = columnType(&sNC, p, 0, 0, 0, 0); /* pCol->szEst = ... // Column size est for SELECT tables never used */ pCol->affinity = sqlite3ExprAffinity(p); if( zType ){ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 98a028b0b..d86391bce 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -4656,7 +4656,10 @@ const void *sqlite3_column_name16(sqlite3_stmt*, int N); ** ^The name of the database or table or column can be returned as ** either a UTF-8 or UTF-16 string. ^The _database_ routines return ** the database name, the _table_ routines return the table name, and -** the origin_ routines return the column name. +** the origin_ routines return the column name. _table_alias_ results +** with the alias(if any) of the table associated with the column; +** _table_ always returns the source table name, even if it has been +** aliased, this returns the original table name if there is no alias. ** ^The returned string is valid until the [prepared statement] is destroyed ** using [sqlite3_finalize()] or until the statement is automatically ** reprepared by the first call to [sqlite3_step()] for a particular run @@ -4694,6 +4697,8 @@ const char *sqlite3_column_table_name(sqlite3_stmt*,int); const void *sqlite3_column_table_name16(sqlite3_stmt*,int); const char *sqlite3_column_origin_name(sqlite3_stmt*,int); const void *sqlite3_column_origin_name16(sqlite3_stmt*,int); +const char *sqlite3_column_table_alias(sqlite3_stmt*,int); +const void *sqlite3_column_table_alias16(sqlite3_stmt*,int); /* ** CAPI3REF: Declared Datatype Of A Query Result diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 9767daa01..cecb80691 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -344,6 +344,9 @@ struct sqlite3_api_routines { int (*autovacuum_pages)(sqlite3*, unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), void*, void(*)(void*)); + /* Version 3.38.0 and later */ + const char * (*column_table_alias)(sqlite3_stmt*,int); + const void * (*column_table_alias16)(sqlite3_stmt*,int); }; /* @@ -655,6 +658,9 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_total_changes64 sqlite3_api->total_changes64 /* Version 3.37.0 and later */ #define sqlite3_autovacuum_pages sqlite3_api->autovacuum_pages +/* Version 3.38.0 and later */ +#define sqlite3_column_table_alias sqlite3_api->column_table_alias +#define sqlite3_column_table_alias16 sqlite3_api->column_table_alias16 #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/test1.c b/src/test1.c index 6060290b9..c0d8a4682 100644 --- a/src/test1.c +++ b/src/test1.c @@ -8506,6 +8506,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ #ifdef SQLITE_ENABLE_COLUMN_METADATA { "sqlite3_column_database_name",test_stmt_utf8,(void*)sqlite3_column_database_name}, { "sqlite3_column_table_name",test_stmt_utf8,(void*)sqlite3_column_table_name}, +{ "sqlite3_column_table_alias",test_stmt_utf8,(void*)sqlite3_column_table_alias}, { "sqlite3_column_origin_name",test_stmt_utf8,(void*)sqlite3_column_origin_name}, #endif @@ -8521,6 +8522,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ {"sqlite3_column_database_name16", test_stmt_utf16, (void*)sqlite3_column_database_name16}, {"sqlite3_column_table_name16", test_stmt_utf16, (void*)sqlite3_column_table_name16}, +{"sqlite3_column_table_alias16", test_stmt_utf16, (void*)sqlite3_column_table_alias16}, {"sqlite3_column_origin_name16", test_stmt_utf16, (void*)sqlite3_column_origin_name16}, #endif #endif diff --git a/src/vdbe.h b/src/vdbe.h index 3257ff68a..1ee2b66e3 100644 --- a/src/vdbe.h +++ b/src/vdbe.h @@ -140,13 +140,14 @@ typedef struct VdbeOpList VdbeOpList; ** The Vdbe.aColName array contains 5n Mem structures, where n is the ** number of columns of data returned by the statement. */ -#define COLNAME_NAME 0 -#define COLNAME_DECLTYPE 1 -#define COLNAME_DATABASE 2 -#define COLNAME_TABLE 3 -#define COLNAME_COLUMN 4 +#define COLNAME_NAME 0 +#define COLNAME_DECLTYPE 1 +#define COLNAME_DATABASE 2 +#define COLNAME_TABLE 3 +#define COLNAME_COLUMN 4 +#define COLNAME_TABLE_ALIAS 5 #ifdef SQLITE_ENABLE_COLUMN_METADATA -# define COLNAME_N 5 /* Number of COLNAME_xxx symbols */ +# define COLNAME_N 6 /* Number of COLNAME_xxx symbols */ #else # ifdef SQLITE_OMIT_DECLTYPE # define COLNAME_N 1 /* Store only the name */ diff --git a/src/vdbeapi.c b/src/vdbeapi.c index 5eeb5d1c0..6548b6a85 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -1321,6 +1321,23 @@ const void *sqlite3_column_origin_name16(sqlite3_stmt *pStmt, int N){ return columnName(pStmt, N, 1, COLNAME_COLUMN); } #endif /* SQLITE_OMIT_UTF16 */ + +/* +** Return the alias or, if no alias specified, the name of the table from +** which a result column derives. NULL is returned if the result name is +** an expression or constant or anything else which is not an unambiguous +** reference to a database table. +*/ +const char *sqlite3_column_table_alias(sqlite3_stmt *pStmt, int N){ + return columnName( + pStmt, N, 0, COLNAME_TABLE_ALIAS); +} +#ifndef SQLITE_OMIT_UTF16 +const void *sqlite3_column_table_alias16(sqlite3_stmt *pStmt, int N){ + return columnName( + pStmt, N, 1, COLNAME_TABLE_ALIAS); +} +#endif /* SQLITE_OMIT_UTF16 */ #endif /* SQLITE_ENABLE_COLUMN_METADATA */ diff --git a/test/capi3f.test b/test/capi3f.test new file mode 100644 index 000000000..ca4efab4c --- /dev/null +++ b/test/capi3f.test @@ -0,0 +1,188 @@ +# 2021 January 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. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. The +# focus of this script testing the callback-free C/C++ API. +# +# $Id: capi3f.test,v 1.70 2009/01/09 02:49:32 d3x0r Exp $ +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix capi3f + +# Do not use a codec for tests in this file, as the database file is +# manipulated directly using tcl scripts (using the [hexio_write] command). +# +do_not_use_codec + +# Return the UTF-16 representation of the supplied UTF-8 string $str. +# If $nt is true, append two 0x00 bytes as a nul terminator. +proc utf16 {str {nt 1}} { + set r [encoding convertto unicode $str] + if {$nt} { + append r "\x00\x00" + } + return $r +} + +# Return the UTF-8 representation of the supplied UTF-16 string $str. +proc utf8 {str} { + # If $str ends in two 0x00 0x00 bytes, knock these off before + # converting to UTF-8 using TCL. + binary scan $str \c* vals + if {[lindex $vals end]==0 && [lindex $vals end-1]==0} { + set str [binary format \c* [lrange $vals 0 end-2]] + } + + set r [encoding convertfrom unicode $str] + return $r +} + + +# This proc is used to test the following API calls: +# +# sqlite3_column_origin_name +# sqlite3_column_origin_name16 +# sqlite3_column_table_name +# sqlite3_column_table_name16 +# sqlite3_column_database_name +# sqlite3_column_database_name16 +# sqlite3_column_table_alias +# sqlite3_column_table_alias16 + +# +# $STMT is a compiled SQL statement. $test is a prefix +# to use for test names within this proc. $names is a list +# of the column names that should be returned by $STMT. +# $decltypes is a list of column declaration types for $STMT. +# +# Example: +# +# set STMT [sqlite3_prepare "SELECT 1, 2, 2;" -1 DUMMY] +# check_header test1.1 {1 2 3} {"" "" ""} +# +proc check_origin_header2 {STMT test dbs tables als cols} { + # If sqlite3_column_origin_name() and friends are not compiled into + # this build, this proc is a no-op. + ifcapable columnmetadata { + # Use the return value of sqlite3_column_count() to build + # a list of column indexes. i.e. If sqlite3_column_count + # is 3, build the list {0 1 2}. + set ::idxlist [list] + set ::numcols [sqlite3_column_count $STMT] + for {set i 0} {$i < $::numcols} {incr i} {lappend ::idxlist $i} + + # Database names in UTF-8 + do_test $test.1 { + set cnamelist [list] + foreach i $idxlist { + lappend cnamelist [sqlite3_column_database_name $STMT $i] + } + set cnamelist + } $dbs + + # Database names in UTF-16 + ifcapable {utf16} { + do_test $test.2 { + set cnamelist [list] + foreach i $idxlist { + lappend cnamelist [utf8 [sqlite3_column_database_name16 $STMT $i]] + } + set cnamelist + } $dbs + } + + # Table alias in UTF-8 + do_test $test.3 { + set caliaslist [list] + foreach i $idxlist { + lappend caliaslist [sqlite3_column_table_alias $STMT $i] + } + set caliaslist + } $als + + # Table alias in UTF-16 + ifcapable {utf16} { + do_test $test.4 { + set caliaslist [list] + foreach i $idxlist { + lappend caliaslist [utf8 [sqlite3_column_table_alias16 $STMT $i]] + } + set caliaslist + } $als + } + + # Table names in UTF-8 + do_test $test.5 { + set cnamelist [list] + foreach i $idxlist { + lappend cnamelist [sqlite3_column_table_name $STMT $i] + } + set cnamelist + } $tables + + # Table names in UTF-16 + ifcapable {utf16} { + do_test $test.6 { + set cnamelist [list] + foreach i $idxlist { + lappend cnamelist [utf8 [sqlite3_column_table_name16 $STMT $i]] + } + set cnamelist + } $tables + } + + # Origin names in UTF-8 + do_test $test.7 { + set cnamelist [list] + foreach i $idxlist { + lappend cnamelist [sqlite3_column_origin_name $STMT $i] + } + set cnamelist + } $cols + + # Origin declaration types in UTF-16 + ifcapable {utf16} { + do_test $test.8 { + set cnamelist [list] + foreach i $idxlist { + lappend cnamelist [utf8 [sqlite3_column_origin_name16 $STMT $i]] + } + set cnamelist + } $cols + } + } +} + +do_test capi3f-1 { + execsql { + CREATE TABLE t1(a VARINT, b BLOB, c VARCHAR(16)); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES('one', 'two', NULL); + INSERT INTO t1 VALUES(1.2, 1.3, 1.4); + } + set sql "SELECT * FROM t1 tz" + set STMT [sqlite3_prepare_v2 $DB $sql -1 TAIL] + sqlite3_column_count $STMT +} 3 + +check_origin_header2 $STMT capi3f-1 {main main main} {t1 t1 t1} {tz tz tz} {a b c} + +do_test capi3f-2 { + set sql2 "SELECT * FROM t1" + set STMT [sqlite3_prepare_v2 $DB $sql2 -1 TAIL] + sqlite3_column_count $STMT +} 3 + +check_origin_header2 $STMT capi3f-2 {main main main} {t1 t1 t1} {t1 t1 t1} {a b c} + + +finish_test ```