Skip to content

Commit

Permalink
add internal structure (#296)
Browse files Browse the repository at this point in the history
* add internal structure for cell-level display formatting (e.g. bold etc)
* refactor sql
* sheet: fix mutex not destroyed
* sql: improve how zsv_opts and custom_prop_handler are passed to sqlite3 csv module

TO DO: example extension w sql + cell highlighting + drill-down
  • Loading branch information
liquidaty authored Nov 20, 2024
1 parent 0229758 commit ad2cfe2
Show file tree
Hide file tree
Showing 13 changed files with 490 additions and 219 deletions.
1 change: 1 addition & 0 deletions app/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ ${CLI} ${STANDALONE_PFX}2json${EXE}: MORE_OBJECTS+= ${BUILD_DIR}/objs/utils/db.o
# pretty uses termcap
${CLI} ${STANDALONE_PFX}pretty${EXE}: MORE_LIBS+=${LDFLAGS_TERMCAP}


${STANDALONE_PFX}%${EXE}: %.c ${OBJECTS} ${MORE_OBJECTS} ${LIBZSV_INSTALL} ${UTF8PROC_OBJECT}
@mkdir -p `dirname "$@"`
${CC} ${CFLAGS} -I${INCLUDE_DIR} -o $@ $< ${OBJECTS} ${MORE_OBJECTS} ${MORE_SOURCE} -L${LIBDIR} ${LIBZSV_L} ${UTF8PROC_OBJECT} ${LDFLAGS} ${LDFLAGS_OPT} ${MORE_LIBS} ${STATIC_LIB_FLAGS}
Expand Down
8 changes: 5 additions & 3 deletions app/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -509,9 +509,11 @@ static struct builtin_cmd *find_builtin(const char *cmd_name) {
#include "builtin/version.c"
#include "builtin/register.c"

#define ZSV_EXTENSION_ID_MAX_LEN 8
static const char *extension_cmd_from_arg(const char *arg) {
if (strlen(arg) > 3 && arg[2] == '-')
return arg + 3;
const char *dash = strchr(arg, '-');
if (dash && dash < arg + ZSV_EXTENSION_ID_MAX_LEN && dash[1] != '\0')
return dash + 1;
return NULL;
}

Expand Down Expand Up @@ -567,7 +569,7 @@ int ZSV_CLI_MAIN(int argc, const char *argv[]) {
}

int err = 1;
if (strlen(argv[1]) > 3 && argv[1][2] == '-') { // this is an extension command
if (extension_cmd_from_arg(argv[1]) != NULL) { // this is an extension command
struct cli_config config;
memset(&config, 0, sizeof(config));
if (!(err = add_extension(argv[1], &config.extensions, 0, 0)))
Expand Down
14 changes: 7 additions & 7 deletions app/ext_example/my_extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/**
* `zsv` can easily be extended by simply creating a shared library
* that implements the interface specified in zsv/ext/implementation.h
* for any two-character extension id
* for any extension id of up to 8 bytes
*
* Once the library file is created, you can run any commands it implements
* by naming the library file zsvext<id>, placing it in any folder that is
Expand All @@ -30,21 +30,21 @@
*
* We will name our extension "my", so our shared library will be named
* zsvextmy.so (non-win) or zsvextmy.dll (win). After the shared lib is built,
* a user can place it anywhere in their path or in the same folder as the zsv
* binary, and invoke our operations as follows:
* place it anywhere in the PATH or in the same folder as the zsv binary.
* Our extension commands can then be invoked by running:
* `zsv my-count`
* `zsv my-echo`
*
* in addition, users will see a brief description of our module if they execute:
* in addition, a description of our extension is available via:
* `zsv help`
*
* or
* and command-specific help displayed via:
* `zsv help my-<command>`
*
*/

/**
* *Required*: define our extension id, which must be two characters in length
* *Required*: define our extension id, of up to 8 bytes in length
*/
const char *zsv_ext_id(void) {
return "my";
Expand Down Expand Up @@ -114,7 +114,7 @@ zsvsheet_status my_test_command_handler(zsvsheet_proc_context_t ctx) {
* initialization routine uses `ext_add_command` to register our commands and
* `ext_set_help` to set the help text. When we register a command, we provide a
* callback-- in our cases, those will be `count_main()` and `echo_main()`-- for
* zsv to invoke when a user runs our command
* zsv to invoke when our command is run
*
* @param callbacks pointers to zsvlib functions that we must save for later use
* @param ctx context to be passed whenever we execute a zsvlib function from our init
Expand Down
147 changes: 147 additions & 0 deletions app/external/sqlite3/sqlite3_csv_vtab-mem.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#ifndef SQLITE3_CSV_VTAB_ZSV_H
#define SQLITE3_CSV_VTAB_ZSV_H

#include <pthread.h>

/**
* see sqlite3_csv_vtab-mem.h for background info
*/
#if defined(_WIN32) || defined(_WIN64)
#include <process.h>
#else
#include <unistd.h>
#endif

struct sqlite3_zsv_data {
struct sqlite3_zsv_data *next;
pid_t pid;
char *filename;
struct zsv_opts opts;
struct zsv_prop_handler custom_prop_handler;
};

pthread_mutex_t sqlite3_zsv_data_mutex;
struct sqlite3_zsv_data *sqlite3_zsv_data_g = NULL;

/**
* Our shared memory structure should be locked for read/write
*/
static int sqlite3_zsv_data_lock(void) {
#ifndef NO_THREADING
pthread_mutex_lock(&sqlite3_zsv_data_mutex);
#endif
return 0;
}

static int sqlite3_zsv_data_unlock(void) {
#ifndef NO_THREADING
pthread_mutex_unlock(&sqlite3_zsv_data_mutex);
#endif
return 0;
}

static void sqlite3_zsv_data_delete(struct sqlite3_zsv_data *e) {
if (e) {
free(e->filename);
}
free(e);
}

void sqlite3_zsv_list_delete(void **list) {
for (struct sqlite3_zsv_data *next, *e = *list; e; e = next) {
next = e->next;
sqlite3_zsv_data_delete(e);
}
#ifndef NO_THREADING
pthread_mutex_destroy(&sqlite3_zsv_data_mutex);
#endif
*list = NULL;
}

static struct sqlite3_zsv_data *sqlite3_zsv_data_new(const char *filename, struct zsv_opts *opts,
struct zsv_prop_handler *custom_prop_handler) {
if (!filename)
return NULL;
struct sqlite3_zsv_data *e = calloc(1, sizeof(*e));
if (e) {
e->pid = getpid();
e->filename = strdup(filename);
if (opts)
e->opts = *opts;
if (custom_prop_handler)
e->custom_prop_handler = *custom_prop_handler;
if (e->filename)
return e;
}
sqlite3_zsv_data_delete(e);
return NULL;
}

int sqlite3_zsv_data_add(const char *filename, struct zsv_opts *opts, struct zsv_prop_handler *custom_prop_handler) {
struct sqlite3_zsv_data **list = &sqlite3_zsv_data_g;
struct sqlite3_zsv_data *e = sqlite3_zsv_data_new(filename, opts, custom_prop_handler);
if (e) {
struct sqlite3_zsv_data **next;
if (sqlite3_zsv_data_lock()) {
sqlite3_zsv_data_delete(e);
return -1;
} else {
for (next = list; *next; next = &(*next)->next)
;
*next = e;
sqlite3_zsv_data_unlock();
return 0;
}
}
return ENOMEM;
}

static int sqlite3_zsv_data_cmp(struct sqlite3_zsv_data *x, const char *filename, pid_t pid) {
return strcmp(x->filename, filename) && x->pid == pid;
}

struct sqlite3_zsv_data *sqlite3_zsv_data_find(const char *filename) {
struct sqlite3_zsv_data *list = sqlite3_zsv_data_g;
struct sqlite3_zsv_data *found = NULL;
pid_t pid = getpid();
if (!sqlite3_zsv_data_lock()) {
for (struct sqlite3_zsv_data *e = list; e && !found; e = e->next) {
if (!sqlite3_zsv_data_cmp(e, filename, pid))
found = e;
}
if (sqlite3_zsv_data_unlock())
fprintf(stderr, "Error unlocking sqlite3-csv-zsv shared mem lock\n");
}
return found;
}

int sqlite3_zsv_list_remove(const char *filename) {
if (!filename)
return 0;
struct sqlite3_zsv_data **list = &sqlite3_zsv_data_g;
struct sqlite3_zsv_data *found = NULL;
pid_t pid = getpid();
if (*list) {
if (!sqlite3_zsv_data_cmp(*list, filename, pid)) {
// found a match at the head of list
found = *list;
*list = found->next;
} else {
// look for a match somewhere after the first element
for (struct sqlite3_zsv_data *prior = *list; prior->next != NULL; prior = prior->next) {
if (!sqlite3_zsv_data_cmp(prior->next, filename, pid)) {
found = prior->next;
prior->next = prior->next->next;
break;
}
}
}
}
if (found) {
sqlite3_zsv_data_delete(found);
return 0;
}
return ENOENT; // not found
}

#endif
49 changes: 49 additions & 0 deletions app/external/sqlite3/sqlite3_csv_vtab-mem.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#ifndef SQLITE3_CSV_VTAB_ZSV_H
#define SQLITE3_CSV_VTAB_ZSV_H

#include <zsv.h>
#include <zsv/utils/prop.h>

/**
* when sqlite3 opens a CSV file using ZSV, it needs a way to know
* what options to open with (such as user-specified delimiter, header offset
* or span, etc
*
* In particular, it needs access to:
* - zsv options (struct zsv_opts)
* - custom property handler (struct zsv_prop_handler *)
* - options used (const char *) [but this can be passed via connection string]
*
* Some ways to pass this info are:
* - Embed it in the text of the URI passed to the module's xConnect function.
* This is not practical because we need to pass pointers
* - Use a single global variable that can hold only one set of data at a time.
* This was the old approach, via `zsv_set_default_opts` etc, which has the
* usual drawbacks of using a single global variable structure
* - Use a shared memory structure that can support multiple sets of data
* That is the approach implemented here. Data is identified by the related
* filename and caller pid
*
* sqlite3_create_module_v2 is passed the shared memory root pointer,
* but it's not really needed because there is no way for it to be
* dynamic so it always has to point to the single global location
*
* Prior to calling xConnect, the caller should save data for the related
* file via `sqlite3_zsv_data_add()`; xConnect then does a lookup to
* locate and use the saved data
*/

struct sqlite3_zsv_data;

void sqlite3_zsv_list_delete(struct sqlite3_zsv_data **list);

int sqlite3_zsv_data_add(const char *filename, struct zsv_opts *opts, struct zsv_prop_handler *custom_prop_handler);

struct sqlite3_zsv_data *sqlite3_csv_vtab_zsv_find(const char *filename);

/**
* Remove from list. Return 0 on success, non-zero on error
*/
int sqlite3_zsv_list_remove(const char *filename);

#endif
44 changes: 27 additions & 17 deletions app/external/sqlite3/sqlite3_csv_vtab-zsv.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* clang-format off */
/*
* This file has been modified from its original form, in order to use the ZSV csv parser
* The preamble / disclaimer to the original file is included below
Expand Down Expand Up @@ -51,6 +52,7 @@ SQLITE_EXTENSION_INIT1
#include <zsv/utils/string.h>
#include <zsv/utils/arg.h>
#include <zsv/utils/prop.h>
#include "sqlite3_csv_vtab-mem.c"

#ifndef SQLITE_OMIT_VIRTUALTABLE

Expand Down Expand Up @@ -102,12 +104,15 @@ typedef struct zsvTable {
sqlite_int64 rowCount;
} zsvTable;

struct zsvTable *zsvTable_new() {
struct zsvTable *zsvTable_new(const char *filename) {
struct zsvTable *z = sqlite3_malloc(sizeof(*z));
if(z) {
memset(z, 0, sizeof(*z));
z->parser_opts = zsv_get_default_opts();
z->custom_prop_handler = zsv_get_default_custom_prop_handler();
struct sqlite3_zsv_data *d = sqlite3_zsv_data_find(filename);
if(d) {
z->parser_opts = d->opts; // zsv_get_default_opts();
z->custom_prop_handler = d->custom_prop_handler; // zsv_get_default_custom_prop_handler();
}
}
return z;
}
Expand All @@ -120,7 +125,6 @@ typedef struct zsvCursor {
sqlite3_vtab_cursor base; /* Base class. Must be first */
} zsvCursor;


/*
** The xConnect and xCreate methods do the same thing, but they must be
** different so that the virtual table is not an eponymous virtual table.
Expand Down Expand Up @@ -172,25 +176,20 @@ static int zsvtabConnect(
sqlite3_vtab **ppVtab,
char **pzErr
){
zsvTable *pNew = NULL;
(void)(_pAux);
zsvTable pTmp = { 0 };
int rc = SQLITE_OK; /* Result code from this routine */
#define ZSVTABCONNECT_PARAM_MAX 3
static const char *azParam[ZSVTABCONNECT_PARAM_MAX] = {
"filename", "options_used", "max_columns"
};
char *azPValue[ZSVTABCONNECT_PARAM_MAX]; /* Parameter values */
memset(azPValue, 0, sizeof(azPValue));
# define CSV_FILENAME (azPValue[0])
# define ZSV_OPTS_USED (azPValue[1])

char *schema = NULL;
pNew = zsvTable_new();
if(!pNew)
return SQLITE_NOMEM;

pNew->parser_opts.max_columns = 2000; /* default max columns */

(void)(_pAux);
memset(azPValue, 0, sizeof(azPValue));
zsvTable *pNew = NULL;

char *errmsg = NULL;
// set parameters
Expand All @@ -203,11 +202,11 @@ static int zsvtabConnect(
}
if( j<sizeof(azParam)/sizeof(azParam[0]) ){
if( errmsg ) goto zsvtab_connect_error;
}else
} else
// optional values
if( (zValue = csv_parameter("max_columns",11,z))!=0 ){
pNew->parser_opts.max_columns = atoi(zValue);
if(pNew->parser_opts.max_columns<=0 || pNew->parser_opts.max_columns > 2000){
pTmp.parser_opts.max_columns = atoi(zValue);
if(pTmp.parser_opts.max_columns<=0 || pTmp.parser_opts.max_columns > 2000){
asprintf(&errmsg, "max_columns= value must be > 0 and < 2000");
goto zsvtab_connect_error;
}
Expand All @@ -223,6 +222,14 @@ static int zsvtabConnect(
goto zsvtab_connect_error;
}

pNew = zsvTable_new(CSV_FILENAME);
if(!pNew)
goto zsvtab_connect_oom;
if(pTmp.parser_opts.max_columns)
pNew->parser_opts.max_columns = pTmp.parser_opts.max_columns;
else if(!pNew->parser_opts.max_columns)
pNew->parser_opts.max_columns = 2000; /* default max columns */

if(!(pNew->parser_opts.stream = fopen(CSV_FILENAME, "rb"))) {
asprintf(&errmsg, "Unable to open for reading: %s", CSV_FILENAME);
goto zsvtab_connect_error;
Expand Down Expand Up @@ -477,7 +484,9 @@ int sqlite3_csv_init(
#ifndef SQLITE_OMIT_VIRTUALTABLE
int rc;
SQLITE_EXTENSION_INIT2(pApi);
rc = sqlite3_create_module(db, "csv", &CsvModule, 0);
pthread_mutex_t init = PTHREAD_MUTEX_INITIALIZER;
memcpy(&sqlite3_zsv_data_mutex, &init, sizeof(init));
rc = sqlite3_create_module_v2(db, "csv", &CsvModule, &sqlite3_zsv_data_g, (void (*)(void *))sqlite3_zsv_list_delete);
#ifdef SQLITE_TEST
if( rc==SQLITE_OK ){
rc = sqlite3_create_module(db, "csv_wr", &CsvModuleFauxWrite, 0);
Expand All @@ -488,3 +497,4 @@ int sqlite3_csv_init(
return SQLITE_OK;
#endif
}
/* clang-format on */
Loading

0 comments on commit ad2cfe2

Please sign in to comment.