aboutsummaryrefslogtreecommitdiff
path: root/ext/misc
diff options
context:
space:
mode:
Diffstat (limited to 'ext/misc')
-rw-r--r--ext/misc/fileio.c147
-rw-r--r--ext/misc/vtablog.c59
-rw-r--r--ext/misc/windirent.h163
3 files changed, 317 insertions, 52 deletions
diff --git a/ext/misc/fileio.c b/ext/misc/fileio.c
index 96a7f82bd..22d61df61 100644
--- a/ext/misc/fileio.c
+++ b/ext/misc/fileio.c
@@ -67,6 +67,7 @@
** data: For a regular file, a blob containing the file data. For a
** symlink, a text value containing the text of the link. For a
** directory, NULL.
+** level: Directory hierarchy level. Topmost is 1.
**
** If a non-NULL value is specified for the optional $dir parameter and
** $path is a relative path, then $path is interpreted relative to $dir.
@@ -92,13 +93,11 @@ SQLITE_EXTENSION_INIT1
# include <dirent.h>
# include <utime.h>
# include <sys/time.h>
+# define STRUCT_STAT struct stat
#else
-# include "windows.h"
-# include <io.h>
+# include "windirent.h"
# include <direct.h>
-# include "test_windirent.h"
-# define dirent DIRENT
-# define stat _stat
+# define STRUCT_STAT struct _stat
# define chmod(path,mode) fileio_chmod(path,mode)
# define mkdir(path,mode) fileio_mkdir(path)
#endif
@@ -116,14 +115,16 @@ SQLITE_EXTENSION_INIT1
/*
** Structure of the fsdir() table-valued function
*/
- /* 0 1 2 3 4 5 */
-#define FSDIR_SCHEMA "(name,mode,mtime,data,path HIDDEN,dir HIDDEN)"
+ /* 0 1 2 3 4 5 6 */
+#define FSDIR_SCHEMA "(name,mode,mtime,data,level,path HIDDEN,dir HIDDEN)"
+
#define FSDIR_COLUMN_NAME 0 /* Name of the file */
#define FSDIR_COLUMN_MODE 1 /* Access mode */
#define FSDIR_COLUMN_MTIME 2 /* Last modification time */
#define FSDIR_COLUMN_DATA 3 /* File content */
-#define FSDIR_COLUMN_PATH 4 /* Path to top of search */
-#define FSDIR_COLUMN_DIR 5 /* Path is relative to this directory */
+#define FSDIR_COLUMN_LEVEL 4 /* Level. Topmost is 1 */
+#define FSDIR_COLUMN_PATH 5 /* Path to top of search */
+#define FSDIR_COLUMN_DIR 6 /* Path is relative to this directory */
/*
** UTF8 chmod() function for Windows
@@ -289,7 +290,7 @@ LPWSTR utf8_to_utf16(const char *z){
*/
static void statTimesToUtc(
const char *zPath,
- struct stat *pStatBuf
+ STRUCT_STAT *pStatBuf
){
HANDLE hFindFile;
WIN32_FIND_DATAW fd;
@@ -317,7 +318,7 @@ static void statTimesToUtc(
*/
static int fileStat(
const char *zPath,
- struct stat *pStatBuf
+ STRUCT_STAT *pStatBuf
){
#if defined(_WIN32)
sqlite3_int64 sz = strlen(zPath);
@@ -341,7 +342,7 @@ static int fileStat(
*/
static int fileLinkStat(
const char *zPath,
- struct stat *pStatBuf
+ STRUCT_STAT *pStatBuf
){
#if defined(_WIN32)
return fileStat(zPath, pStatBuf);
@@ -374,7 +375,7 @@ static int makeDirectory(
int i = 1;
while( rc==SQLITE_OK ){
- struct stat sStat;
+ STRUCT_STAT sStat;
int rc2;
for(; zCopy[i]!='/' && i<nCopy; i++);
@@ -424,7 +425,7 @@ static int writeFile(
** be an error though - if there is already a directory at the same
** path and either the permissions already match or can be changed
** to do so using chmod(), it is not an error. */
- struct stat sStat;
+ STRUCT_STAT sStat;
if( errno!=EEXIST
|| 0!=fileStat(zFile, &sStat)
|| !S_ISDIR(sStat.st_mode)
@@ -620,13 +621,14 @@ struct fsdir_cursor {
sqlite3_vtab_cursor base; /* Base class - must be first */
int nLvl; /* Number of entries in aLvl[] array */
+ int mxLvl; /* Maximum level */
int iLvl; /* Index of current entry */
FsdirLevel *aLvl; /* Hierarchy of directories being traversed */
const char *zBase;
int nBase;
- struct stat sStat; /* Current lstat() results */
+ STRUCT_STAT sStat; /* Current lstat() results */
char *zPath; /* Path to current entry */
sqlite3_int64 iRowid; /* Current rowid */
};
@@ -738,7 +740,7 @@ static int fsdirNext(sqlite3_vtab_cursor *cur){
mode_t m = pCur->sStat.st_mode;
pCur->iRowid++;
- if( S_ISDIR(m) ){
+ if( S_ISDIR(m) && pCur->iLvl+3<pCur->mxLvl ){
/* Descend into this directory */
int iNew = pCur->iLvl + 1;
FsdirLevel *pLvl;
@@ -846,7 +848,11 @@ static int fsdirColumn(
}else{
readFileContents(ctx, pCur->zPath);
}
+ break;
}
+ case FSDIR_COLUMN_LEVEL:
+ sqlite3_result_int(ctx, pCur->iLvl+2);
+ break;
case FSDIR_COLUMN_PATH:
default: {
/* The FSDIR_COLUMN_PATH and FSDIR_COLUMN_DIR are input parameters.
@@ -880,8 +886,11 @@ static int fsdirEof(sqlite3_vtab_cursor *cur){
/*
** xFilter callback.
**
-** idxNum==1 PATH parameter only
-** idxNum==2 Both PATH and DIR supplied
+** idxNum bit Meaning
+** 0x01 PATH=N
+** 0x02 DIR=N
+** 0x04 LEVEL<N
+** 0x08 LEVEL<=N
*/
static int fsdirFilter(
sqlite3_vtab_cursor *cur,
@@ -890,6 +899,7 @@ static int fsdirFilter(
){
const char *zDir = 0;
fsdir_cursor *pCur = (fsdir_cursor*)cur;
+ int i;
(void)idxStr;
fsdirResetCursor(pCur);
@@ -898,14 +908,24 @@ static int fsdirFilter(
return SQLITE_ERROR;
}
- assert( argc==idxNum && (argc==1 || argc==2) );
+ assert( (idxNum & 0x01)!=0 && argc>0 );
zDir = (const char*)sqlite3_value_text(argv[0]);
if( zDir==0 ){
fsdirSetErrmsg(pCur, "table function fsdir requires a non-NULL argument");
return SQLITE_ERROR;
}
- if( argc==2 ){
- pCur->zBase = (const char*)sqlite3_value_text(argv[1]);
+ i = 1;
+ if( (idxNum & 0x02)!=0 ){
+ assert( argc>i );
+ pCur->zBase = (const char*)sqlite3_value_text(argv[i++]);
+ }
+ if( (idxNum & 0x0c)!=0 ){
+ assert( argc>i );
+ pCur->mxLvl = sqlite3_value_int(argv[i++]);
+ if( idxNum & 0x08 ) pCur->mxLvl++;
+ if( pCur->mxLvl<=0 ) pCur->mxLvl = 1000000000;
+ }else{
+ pCur->mxLvl = 1000000000;
}
if( pCur->zBase ){
pCur->nBase = (int)strlen(pCur->zBase)+1;
@@ -934,10 +954,11 @@ static int fsdirFilter(
** In this implementation idxNum is used to represent the
** query plan. idxStr is unused.
**
-** The query plan is represented by values of idxNum:
+** The query plan is represented by bits in idxNum:
**
-** (1) The path value is supplied by argv[0]
-** (2) Path is in argv[0] and dir is in argv[1]
+** 0x01 The path value is supplied by argv[0]
+** 0x02 dir is in argv[1]
+** 0x04 maxdepth is in argv[1] or [2]
*/
static int fsdirBestIndex(
sqlite3_vtab *tab,
@@ -946,6 +967,9 @@ static int fsdirBestIndex(
int i; /* Loop over constraints */
int idxPath = -1; /* Index in pIdxInfo->aConstraint of PATH= */
int idxDir = -1; /* Index in pIdxInfo->aConstraint of DIR= */
+ int idxLevel = -1; /* Index in pIdxInfo->aConstraint of LEVEL< or <= */
+ int idxLevelEQ = 0; /* 0x08 for LEVEL<= or LEVEL=. 0x04 for LEVEL< */
+ int omitLevel = 0; /* omit the LEVEL constraint */
int seenPath = 0; /* True if an unusable PATH= constraint is seen */
int seenDir = 0; /* True if an unusable DIR= constraint is seen */
const struct sqlite3_index_constraint *pConstraint;
@@ -953,25 +977,48 @@ static int fsdirBestIndex(
(void)tab;
pConstraint = pIdxInfo->aConstraint;
for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
- if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
- switch( pConstraint->iColumn ){
- case FSDIR_COLUMN_PATH: {
- if( pConstraint->usable ){
- idxPath = i;
- seenPath = 0;
- }else if( idxPath<0 ){
- seenPath = 1;
+ if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){
+ switch( pConstraint->iColumn ){
+ case FSDIR_COLUMN_PATH: {
+ if( pConstraint->usable ){
+ idxPath = i;
+ seenPath = 0;
+ }else if( idxPath<0 ){
+ seenPath = 1;
+ }
+ break;
}
- break;
- }
- case FSDIR_COLUMN_DIR: {
- if( pConstraint->usable ){
- idxDir = i;
- seenDir = 0;
- }else if( idxDir<0 ){
- seenDir = 1;
+ case FSDIR_COLUMN_DIR: {
+ if( pConstraint->usable ){
+ idxDir = i;
+ seenDir = 0;
+ }else if( idxDir<0 ){
+ seenDir = 1;
+ }
+ break;
+ }
+ case FSDIR_COLUMN_LEVEL: {
+ if( pConstraint->usable && idxLevel<0 ){
+ idxLevel = i;
+ idxLevelEQ = 0x08;
+ omitLevel = 0;
+ }
+ break;
}
- break;
+ }
+ }else
+ if( pConstraint->iColumn==FSDIR_COLUMN_LEVEL
+ && pConstraint->usable
+ && idxLevel<0
+ ){
+ if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_LE ){
+ idxLevel = i;
+ idxLevelEQ = 0x08;
+ omitLevel = 1;
+ }else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_LT ){
+ idxLevel = i;
+ idxLevelEQ = 0x04;
+ omitLevel = 1;
}
}
}
@@ -988,14 +1035,20 @@ static int fsdirBestIndex(
}else{
pIdxInfo->aConstraintUsage[idxPath].omit = 1;
pIdxInfo->aConstraintUsage[idxPath].argvIndex = 1;
+ pIdxInfo->idxNum = 0x01;
+ pIdxInfo->estimatedCost = 1.0e9;
+ i = 2;
if( idxDir>=0 ){
pIdxInfo->aConstraintUsage[idxDir].omit = 1;
- pIdxInfo->aConstraintUsage[idxDir].argvIndex = 2;
- pIdxInfo->idxNum = 2;
- pIdxInfo->estimatedCost = 10.0;
- }else{
- pIdxInfo->idxNum = 1;
- pIdxInfo->estimatedCost = 100.0;
+ pIdxInfo->aConstraintUsage[idxDir].argvIndex = i++;
+ pIdxInfo->idxNum |= 0x02;
+ pIdxInfo->estimatedCost /= 1.0e4;
+ }
+ if( idxLevel>=0 ){
+ pIdxInfo->aConstraintUsage[idxLevel].omit = omitLevel;
+ pIdxInfo->aConstraintUsage[idxLevel].argvIndex = i++;
+ pIdxInfo->idxNum |= idxLevelEQ;
+ pIdxInfo->estimatedCost /= 1.0e4;
}
}
diff --git a/ext/misc/vtablog.c b/ext/misc/vtablog.c
index 2cc29c285..44acc32e6 100644
--- a/ext/misc/vtablog.c
+++ b/ext/misc/vtablog.c
@@ -14,6 +14,13 @@
** on stdout when its key interfaces are called. This is intended for
** interactive analysis and debugging of virtual table interfaces.
**
+** To build this extension as a separately loaded shared library or
+** DLL, use compiler command-lines similar to the following:
+**
+** (linux) gcc -fPIC -shared vtablog.c -o vtablog.so
+** (mac) clang -fPIC -dynamiclib vtablog.c -o vtablog.dylib
+** (windows) cl vtablog.c -link -dll -out:vtablog.dll
+**
** Usage example:
**
** .load ./vtablog
@@ -240,7 +247,7 @@ static int vtablogConnect(
/*
-** This method is the destructor for vtablog_cursor objects.
+** This method is the destructor for vtablog_vtab objects.
*/
static int vtablogDisconnect(sqlite3_vtab *pVtab){
vtablog_vtab *pTab = (vtablog_vtab*)pVtab;
@@ -252,7 +259,7 @@ static int vtablogDisconnect(sqlite3_vtab *pVtab){
}
/*
-** This method is the destructor for vtablog_cursor objects.
+** This method is (also) the destructor for vtablog_vtab objects.
*/
static int vtablogDestroy(sqlite3_vtab *pVtab){
vtablog_vtab *pTab = (vtablog_vtab*)pVtab;
@@ -436,6 +443,39 @@ static int vtablogFilter(
}
/*
+** Return an sqlite3_index_info operator name in static space.
+** The name is possibly overwritten on subsequent calls.
+*/
+static char *vtablogOpName(unsigned char op){
+ static char zUnknown[30];
+ char *zOut;
+ switch( op ){
+ case SQLITE_INDEX_CONSTRAINT_EQ: zOut = "EQ"; break;
+ case SQLITE_INDEX_CONSTRAINT_GT: zOut = "GT"; break;
+ case SQLITE_INDEX_CONSTRAINT_LE: zOut = "LE"; break;
+ case SQLITE_INDEX_CONSTRAINT_LT: zOut = "LT"; break;
+ case SQLITE_INDEX_CONSTRAINT_GE: zOut = "GE"; break;
+ case SQLITE_INDEX_CONSTRAINT_MATCH: zOut = "MATCH"; break;
+ case SQLITE_INDEX_CONSTRAINT_LIKE: zOut = "LIKE"; break;
+ case SQLITE_INDEX_CONSTRAINT_GLOB: zOut = "GLOB"; break;
+ case SQLITE_INDEX_CONSTRAINT_REGEXP: zOut = "REGEXP"; break;
+ case SQLITE_INDEX_CONSTRAINT_NE: zOut = "NE"; break;
+ case SQLITE_INDEX_CONSTRAINT_ISNOT: zOut = "ISNOT"; break;
+ case SQLITE_INDEX_CONSTRAINT_ISNOTNULL: zOut = "ISNOTNULL"; break;
+ case SQLITE_INDEX_CONSTRAINT_ISNULL: zOut = "ISNULL"; break;
+ case SQLITE_INDEX_CONSTRAINT_IS: zOut = "IS"; break;
+ case SQLITE_INDEX_CONSTRAINT_LIMIT: zOut = "LIMIT"; break;
+ case SQLITE_INDEX_CONSTRAINT_OFFSET: zOut = "OFFSET"; break;
+ case SQLITE_INDEX_CONSTRAINT_FUNCTION: zOut = "FUNCTION"; break;
+ default:
+ sqlite3_snprintf(sizeof(zUnknown),zUnknown,"%d",op);
+ zOut = zUnknown;
+ break;
+ }
+ return zOut;
+}
+
+/*
** SQLite will invoke this method one or more times while planning a query
** that uses the vtablog virtual table. This routine needs to create
** a query plan for each invocation and compute an estimated cost for that
@@ -451,14 +491,23 @@ static int vtablogBestIndex(
printf(" colUsed: 0x%016llx\n", p->colUsed);
printf(" nConstraint: %d\n", p->nConstraint);
for(i=0; i<p->nConstraint; i++){
+ sqlite3_value *pVal = 0;
+ int rc = sqlite3_vtab_rhs_value(p, i, &pVal);
printf(
- " constraint[%d]: col=%d termid=%d op=%d usabled=%d collseq=%s\n",
+ " constraint[%d]: col=%d termid=%d op=%s usabled=%d coll=%s rhs=",
i,
p->aConstraint[i].iColumn,
p->aConstraint[i].iTermOffset,
- p->aConstraint[i].op,
+ vtablogOpName(p->aConstraint[i].op),
p->aConstraint[i].usable,
- sqlite3_vtab_collation(p,i));
+ sqlite3_vtab_collation(p,i)
+ );
+ if( rc==SQLITE_OK ){
+ vtablogQuote(pVal);
+ printf("\n");
+ }else{
+ printf("N/A\n");
+ }
}
printf(" nOrderBy: %d\n", p->nOrderBy);
for(i=0; i<p->nOrderBy; i++){
diff --git a/ext/misc/windirent.h b/ext/misc/windirent.h
new file mode 100644
index 000000000..f84491e46
--- /dev/null
+++ b/ext/misc/windirent.h
@@ -0,0 +1,163 @@
+/*
+** 2025-06-05
+**
+** 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.
+**
+*************************************************************************
+**
+** An implementation of opendir(), readdir(), and closedir() for Windows,
+** based on the FindFirstFile(), FindNextFile(), and FindClose() APIs
+** of Win32.
+**
+** #include this file inside any C-code module that needs to use
+** opendir()/readdir()/closedir(). This file is a no-op on non-Windows
+** machines. On Windows, static functions are defined that implement
+** those standard interfaces.
+*/
+#if defined(_WIN32) && defined(_MSC_VER) && !defined(SQLITE_WINDIRENT_H)
+#define SQLITE_WINDIRENT_H
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#ifndef FILENAME_MAX
+# define FILENAME_MAX (260)
+#endif
+#ifndef S_ISREG
+#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
+#endif
+#ifndef S_ISDIR
+#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+#endif
+#ifndef S_ISLNK
+#define S_ISLNK(m) (0)
+#endif
+typedef unsigned short mode_t;
+
+/* The dirent object for Windows is abbreviated. The only field really
+** usable by applications is d_name[].
+*/
+struct dirent {
+ int d_ino; /* Inode number (synthesized) */
+ unsigned d_attributes; /* File attributes */
+ char d_name[FILENAME_MAX]; /* Null-terminated filename */
+};
+
+/* The internals of DIR are opaque according to standards. So it
+** does not matter what we put here. */
+typedef struct DIR DIR;
+struct DIR {
+ intptr_t d_handle; /* Handle for findfirst()/findnext() */
+ struct dirent cur; /* Current entry */
+};
+
+/* Ignore hidden and system files */
+#define WindowsFileToIgnore(a) \
+ ((((a).attrib)&_A_HIDDEN) || (((a).attrib)&_A_SYSTEM))
+
+/*
+** Close a previously opened directory
+*/
+static int closedir(DIR *pDir){
+ int rc = 0;
+ if( pDir==0 ){
+ return EINVAL;
+ }
+ if( pDir->d_handle!=0 && pDir->d_handle!=(-1) ){
+ rc = _findclose(pDir->d_handle);
+ }
+ sqlite3_free(pDir);
+ return rc;
+}
+
+/*
+** Open a new directory. The directory name should be UTF-8 encoded.
+** appropriate translations happen automatically.
+*/
+static DIR *opendir(const char *zDirName){
+ DIR *pDir;
+ wchar_t *b1;
+ sqlite3_int64 sz;
+ struct _wfinddata_t data;
+
+ pDir = sqlite3_malloc64( sizeof(DIR) );
+ if( pDir==0 ) return 0;
+ memset(pDir, 0, sizeof(DIR));
+ memset(&data, 0, sizeof(data));
+ sz = strlen(zDirName);
+ b1 = sqlite3_malloc64( (sz+3)*sizeof(b1[0]) );
+ if( b1==0 ){
+ closedir(pDir);
+ return NULL;
+ }
+ sz = MultiByteToWideChar(CP_UTF8, 0, zDirName, sz, b1, sz);
+ b1[sz++] = '\\';
+ b1[sz++] = '*';
+ b1[sz] = 0;
+ if( sz+1>sizeof(data.name)/sizeof(data.name[0]) ){
+ closedir(pDir);
+ sqlite3_free(b1);
+ return NULL;
+ }
+ memcpy(data.name, b1, (sz+1)*sizeof(b1[0]));
+ sqlite3_free(b1);
+ pDir->d_handle = _wfindfirst(data.name, &data);
+ if( pDir->d_handle<0 ){
+ closedir(pDir);
+ return NULL;
+ }
+ while( WindowsFileToIgnore(data) ){
+ memset(&data, 0, sizeof(data));
+ if( _wfindnext(pDir->d_handle, &data)==-1 ){
+ closedir(pDir);
+ return NULL;
+ }
+ }
+ pDir->cur.d_ino = 0;
+ pDir->cur.d_attributes = data.attrib;
+ WideCharToMultiByte(CP_UTF8, 0, data.name, -1,
+ pDir->cur.d_name, FILENAME_MAX, 0, 0);
+ return pDir;
+}
+
+/*
+** Read the next entry from a directory.
+**
+** The returned struct-dirent object is managed by DIR. It is only
+** valid until the next readdir() or closedir() call. Only the
+** d_name[] field is meaningful. The d_name[] value has been
+** translated into UTF8.
+*/
+static struct dirent *readdir(DIR *pDir){
+ struct _wfinddata_t data;
+ if( pDir==0 ) return 0;
+ if( (pDir->cur.d_ino++)==0 ){
+ return &pDir->cur;
+ }
+ do{
+ memset(&data, 0, sizeof(data));
+ if( _wfindnext(pDir->d_handle, &data)==-1 ){
+ return NULL;
+ }
+ }while( WindowsFileToIgnore(data) );
+ pDir->cur.d_attributes = data.attrib;
+ WideCharToMultiByte(CP_UTF8, 0, data.name, -1,
+ pDir->cur.d_name, FILENAME_MAX, 0, 0);
+ return &pDir->cur;
+}
+
+#endif /* defined(_WIN32) && defined(_MSC_VER) */