0
0
mirror of https://github.com/tursodatabase/libsql.git synced 2025-03-08 23:41:50 +00:00
Glauber Costa d3a156caf5 bundle SQLean extensions
A common complain with libSQL is how to run extensions. The main
mechanism, with a .so, has a lot of issues around how those .so are
distributed.

The most common extensions are the ones in the sqlean package. We can
improve this experience by bundling them in our sqlite build.

Not all SQLean extensions are kosher: some of them, like fileio, use
the vfs. Others, are deemed too complex.

The extensions included here are a subset that we deem important enough,
and low risk enough, to just be a part of the main bundle.
2025-01-16 22:25:16 -05:00

301 lines
7.5 KiB
C

// Copyright (c) 2023 Anton Zhiyanov, MIT License
// https://github.com/nalgeon/sqlean
// scanfile(name)
// Reads a file with the specified name line by line.
// Implemented as a table-valued function.
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(_MSC_VER)
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#else
#include <sys/types.h>
#endif
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT3
/*
* readline reads chars from the input `stream` until it encounters \n char.
* Returns the number or characters read.
*
* `lineptr` points to the first character read.
* `n` equals the current buffer size.
*/
static ssize_t readline(char** lineptr, size_t* n, FILE* stream) {
char* bufptr = NULL;
char* p = bufptr;
size_t size;
int c;
if (lineptr == NULL) {
return -1;
}
if (stream == NULL) {
return -1;
}
if (n == NULL) {
return -1;
}
bufptr = *lineptr;
size = *n;
c = fgetc(stream);
if (c == EOF) {
return -1;
}
if (bufptr == NULL) {
bufptr = malloc(128);
if (bufptr == NULL) {
return -1;
}
size = 128;
}
p = bufptr;
while (c != EOF) {
if ((ssize_t)(p - bufptr) > (ssize_t)(size - 1)) {
size = size + 128;
bufptr = realloc(bufptr, size);
if (bufptr == NULL) {
return -1;
}
}
*p++ = c;
if (c == '\n') {
break;
}
c = fgetc(stream);
}
*p++ = '\0';
*lineptr = bufptr;
*n = size;
return p - bufptr - 1;
}
typedef struct {
sqlite3_vtab base;
} Table;
typedef struct {
sqlite3_vtab_cursor base;
const char* name;
FILE* in;
bool eof;
char* line;
sqlite3_int64 rowid;
} Cursor;
#define COLUMN_ROWID -1
#define COLUMN_VALUE 0
#define COLUMN_NAME 1
// xconnect creates the virtual table.
static int xconnect(sqlite3* db,
void* aux,
int argc,
const char* const* argv,
sqlite3_vtab** vtabptr,
char** errptr) {
(void)aux;
(void)argc;
(void)argv;
(void)errptr;
int rc = sqlite3_declare_vtab(db, "CREATE TABLE x(value text, name hidden)");
if (rc != SQLITE_OK) {
return rc;
}
Table* table = sqlite3_malloc(sizeof(*table));
*vtabptr = (sqlite3_vtab*)table;
if (table == NULL) {
return SQLITE_NOMEM;
}
memset(table, 0, sizeof(*table));
sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY);
return SQLITE_OK;
}
// xdisconnect destroys the virtual table.
static int xdisconnect(sqlite3_vtab* vtable) {
Table* table = (Table*)vtable;
sqlite3_free(table);
return SQLITE_OK;
}
// xopen creates a new cursor.
static int xopen(sqlite3_vtab* vtable, sqlite3_vtab_cursor** curptr) {
(void)vtable;
Cursor* cursor = sqlite3_malloc(sizeof(*cursor));
if (cursor == NULL) {
return SQLITE_NOMEM;
}
memset(cursor, 0, sizeof(*cursor));
*curptr = &cursor->base;
return SQLITE_OK;
}
// xclose destroys the cursor.
static int xclose(sqlite3_vtab_cursor* cur) {
Cursor* cursor = (Cursor*)cur;
if (cursor->in != NULL) {
fclose(cursor->in);
}
if (cursor->line != NULL) {
free(cursor->line);
}
sqlite3_free(cur);
return SQLITE_OK;
}
// xnext advances the cursor to its next row of output.
static int xnext(sqlite3_vtab_cursor* cur) {
Cursor* cursor = (Cursor*)cur;
cursor->rowid++;
size_t bufsize = 0;
ssize_t len = readline(&cursor->line, &bufsize, cursor->in);
if (len == -1) {
cursor->eof = true;
}
if (len >= 1 && cursor->line[len - 1] == '\n') {
cursor->line[len - 1] = '\0';
}
if (len >= 2 && cursor->line[len - 2] == '\r') {
cursor->line[len - 2] = '\0';
}
return SQLITE_OK;
}
// xcolumn returns the current cursor value.
static int xcolumn(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col_idx) {
(void)col_idx;
Cursor* cursor = (Cursor*)cur;
switch (col_idx) {
case COLUMN_VALUE:
sqlite3_result_text(ctx, (const char*)cursor->line, -1, SQLITE_TRANSIENT);
break;
case COLUMN_NAME:
sqlite3_result_text(ctx, cursor->name, -1, SQLITE_TRANSIENT);
break;
default:
break;
}
return SQLITE_OK;
}
// xrowid returns the rowid for the current row.
static int xrowid(sqlite3_vtab_cursor* cur, sqlite_int64* rowid_ptr) {
Cursor* cursor = (Cursor*)cur;
*rowid_ptr = cursor->rowid;
return SQLITE_OK;
}
// xeof returns TRUE if the cursor has been moved off of the last row of output.
static int xeof(sqlite3_vtab_cursor* cur) {
Cursor* cursor = (Cursor*)cur;
return cursor->eof;
}
// xfilter rewinds the cursor back to the first row of output.
static int xfilter(sqlite3_vtab_cursor* cur,
int idx_num,
const char* idx_str,
int argc,
sqlite3_value** argv) {
(void)idx_num;
(void)idx_str;
if (argc != 1) {
return SQLITE_ERROR;
}
const char* name = (const char*)sqlite3_value_text(argv[0]);
Cursor* cursor = (Cursor*)cur;
sqlite3_vtab* vtable = (cursor->base).pVtab;
// free resources from the previous file, if any
if (cursor->in != NULL) {
fclose(cursor->in);
}
if (cursor->line != NULL) {
free(cursor->line);
}
// reset the cursor
cursor->name = name;
cursor->eof = false;
cursor->line = NULL;
cursor->rowid = 0;
cursor->in = fopen(cursor->name, "r");
if (cursor->in == NULL) {
vtable->zErrMsg = sqlite3_mprintf("cannot open '%s' for reading", cursor->name);
return SQLITE_ERROR;
}
return xnext(cur);
}
// xbest_index instructs SQLite to pass certain arguments to xFilter.
static int xbest_index(sqlite3_vtab* vtable, sqlite3_index_info* index_info) {
// for (size_t i = 0; i < index_info->nConstraint; i++) {
// const struct sqlite3_index_constraint* constraint = index_info->aConstraint + i;
// printf("i=%zu iColumn=%d, op=%d, usable=%d\n", i, constraint->iColumn, constraint->op,
// constraint->usable);
// }
// only the name argument is supported
if (index_info->nConstraint != 1) {
vtable->zErrMsg = sqlite3_mprintf("scanfile() expects a single constraint (name)");
return SQLITE_ERROR;
}
const struct sqlite3_index_constraint* constraint = index_info->aConstraint;
if (constraint->iColumn != COLUMN_NAME) {
vtable->zErrMsg = sqlite3_mprintf("scanfile() expects a name constraint)");
return SQLITE_ERROR;
}
if (constraint->usable == 0) {
// unusable contraint
return SQLITE_CONSTRAINT;
}
// pass the name argument to xFilter
index_info->aConstraintUsage[0].argvIndex = COLUMN_NAME;
index_info->aConstraintUsage[0].omit = 1;
index_info->estimatedCost = (double)1000;
index_info->estimatedRows = 1000;
return SQLITE_OK;
}
static sqlite3_module scan_module = {
.xConnect = xconnect,
.xBestIndex = xbest_index,
.xDisconnect = xdisconnect,
.xOpen = xopen,
.xClose = xclose,
.xFilter = xfilter,
.xNext = xnext,
.xEof = xeof,
.xColumn = xcolumn,
.xRowid = xrowid,
};
int fileio_scan_init(sqlite3* db) {
sqlite3_create_module(db, "fileio_scan", &scan_module, 0);
sqlite3_create_module(db, "scanfile", &scan_module, 0);
return SQLITE_OK;
}