diff --git a/app/builtin/register.c b/app/builtin/register.c index fb5d231d..f1a98e15 100644 --- a/app/builtin/register.c +++ b/app/builtin/register.c @@ -46,8 +46,8 @@ static int main_register_aux(int argc, const char *argv[]) { fprintf(stderr, "No extension id provided\n"), err = 1; else if (!strcmp(extension_id, "--help") || !strcmp(extension_id, "-h")) return register_help(do_register); - else if (strlen(extension_id) != 2) - fprintf(stderr, "Extension id must be exactly two characters\n"), err = 1; + else if (strlen(extension_id) < ZSV_EXTENSION_ID_MIN_LEN || strlen(extension_id) > ZSV_EXTENSION_ID_MAX_LEN) + fprintf(stderr, "Extension id must be 1 to 8 bytes\n"), err = 1; else if (config_init(&config, !do_register, 1, 1)) config_free(&config); // unable to init config else { diff --git a/app/cli.c b/app/cli.c index 090f3ccb..51c4c7e2 100644 --- a/app/cli.c +++ b/app/cli.c @@ -27,6 +27,7 @@ #endif #include "sheet/procedure.h" #include "sheet/key-bindings.h" +#include "sql_internal.h" struct cli_config { struct zsv_ext *extensions; @@ -385,11 +386,15 @@ static struct zsv_ext_callbacks *zsv_ext_callbacks_init(struct zsv_ext_callbacks e->ext_parser_opts = ext_parser_opts; e->ext_opts_used = ext_opts_used; + e->ext_sqlite3_add_csv = zsv_sqlite3_add_csv; + e->ext_sqlite3_db_delete = zsv_sqlite3_db_delete; + e->ext_sqlite3_db_new = zsv_sqlite3_db_new; #ifdef ZSVSHEET_BUILD e->ext_sheet_keypress = zsvsheet_ext_keypress; e->ext_sheet_prompt = zsvsheet_ext_prompt; e->ext_sheet_buffer_set_ctx = zsvsheet_buffer_set_ctx; e->ext_sheet_buffer_get_ctx = zsvsheet_buffer_get_ctx; + e->ext_sheet_buffer_set_cell_attrs = zsvsheet_buffer_set_cell_attrs; e->ext_sheet_buffer_get_zsv_opts = zsvsheet_buffer_get_zsv_opts; e->ext_sheet_set_status = zsvsheet_set_status; e->ext_sheet_buffer_current = zsvsheet_buffer_current; @@ -509,7 +514,6 @@ 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) { const char *dash = strchr(arg, '-'); if (dash && dash < arg + ZSV_EXTENSION_ID_MAX_LEN && dash[1] != '\0') diff --git a/app/cli_ini.c b/app/cli_ini.c index 84ea8566..49c95eea 100644 --- a/app/cli_ini.c +++ b/app/cli_ini.c @@ -6,6 +6,7 @@ #include #include #include +#include #define INI_HANDLER_LINENO 1 #define INI_CALL_HANDLER_ON_NEW_SECTION 1 @@ -104,8 +105,14 @@ static struct zsv_ext *load_extension_dl(const unsigned char *extension_id, char // load an extension and if successful, add to config->extensions head static int add_extension(const char *id, struct zsv_ext **exts, char ignore_err, char verbose) { int err = 0; - size_t len = 2; - unsigned char *extension_id = zsv_strtolowercase((const unsigned char *)id, &len); + const char *dash = strchr(id, '-'); + unsigned char *extension_id = NULL; + size_t len; + if (dash) + len = dash - id; + else + len = strlen(id); + extension_id = zsv_strtolowercase((const unsigned char *)id, &len); if (extension_id) { struct zsv_ext *ext = NULL; if (!extension_id_ok(extension_id)) @@ -133,8 +140,9 @@ static int config_ini_handler(void *ctx, const char *section, const char *name, if (section) { if (!name && !value) { // initialize section if (zsv_stricmp((const unsigned char *)section, (const unsigned char *)"default")) { - if (strlen(section) != 2) { - fprintf(stderr, "Invalid extension id: %s\n", section); + if (!(strlen(section) >= ZSV_EXTENSION_ID_MIN_LEN && strlen(section) <= ZSV_EXTENSION_ID_MAX_LEN)) { + fprintf(stderr, "Invalid extension id: %s. Length must be between %i and %i\n", section, + ZSV_EXTENSION_ID_MIN_LEN, ZSV_EXTENSION_ID_MAX_LEN); err = 1; } else { struct zsv_ext *ext = find_extension(config, section); @@ -168,7 +176,7 @@ static int parse_extensions_ini(struct cli_config *config, char err_if_not_found if (!(f = fopen(config->filepath, "r"))) { if (err_if_not_found) { err = -1; - fprintf(stderr, "No extensions configured%s%s\n", verbose ? "or file not found: " : "", + fprintf(stderr, "No extensions configured%s%s\n", verbose ? " or file not found: " : "", verbose ? config->filepath : ""); } } else { diff --git a/app/compare.c b/app/compare.c index 2b593caa..5e5d403b 100644 --- a/app/compare.c +++ b/app/compare.c @@ -290,6 +290,8 @@ static void zsv_compare_print_row(struct zsv_compare_data *data, static void zsv_compare_input_free(struct zsv_compare_input *input) { zsv_delete(input->parser); zsv_compare_unique_colnames_delete(&input->colnames); + if (input->added) + sqlite3_zsv_list_remove(input->path); free(input->out2in); if (input->stream) fclose(input->stream); @@ -447,6 +449,7 @@ static void zsv_compare_set_sorted_callbacks(struct zsv_compare_data *data) { static enum zsv_compare_status zsv_compare_init_sorted(struct zsv_compare_data *data) { int rc; + // to do: use sql_internal.h interface const char *db_url = data->sort_in_memory ? "file::memory:" : ""; if ((rc = sqlite3_open_v2(db_url, &data->sort_db, SQLITE_OPEN_URI | SQLITE_OPEN_READWRITE, NULL)) == SQLITE_OK && data->sort_db && (rc = sqlite3_create_module(data->sort_db, "csv", &CsvModule, 0) == SQLITE_OK)) { diff --git a/app/compare_internal.h b/app/compare_internal.h index 3e20b741..e0559901 100644 --- a/app/compare_internal.h +++ b/app/compare_internal.h @@ -53,7 +53,8 @@ struct zsv_compare_input { unsigned char row_loaded : 1; unsigned char missing : 1; unsigned char done : 1; - unsigned char _ : 5; + unsigned char added : 1; + unsigned char _ : 4; }; struct zsv_compare_key { diff --git a/app/compare_sort.c b/app/compare_sort.c index 20af3c21..e0be0552 100644 --- a/app/compare_sort.c +++ b/app/compare_sort.c @@ -2,18 +2,17 @@ * To implement sorting, we will use sqlite, create a table for each CSV file and run "select * order by ..." */ +#include "external/sqlite3/sqlite3_csv_vtab-mem.h" + static int zsv_compare_sort_prep_table(struct zsv_compare_data *data, const char *fname, const char *opts_used, - int max_columns, char **err_msg, unsigned int table_ix) { + char **err_msg, unsigned int table_ix) { #define ZSV_COMPARE_MAX_TABLES 1000 char *sql = NULL; if (table_ix > ZSV_COMPARE_MAX_TABLES) return -1; - if (max_columns == 0) - max_columns = 2048; - - sql = sqlite3_mprintf("CREATE VIRTUAL TABLE data%i USING csv(filename=%Q,options_used=%Q,max_columns=%i)", table_ix, - fname, opts_used, max_columns); + sql = sqlite3_mprintf("CREATE VIRTUAL TABLE data%i USING csv(filename=%Q,options_used=%Q)", table_ix, fname, + opts_used); //, max_columns); if (!sql) return -1; @@ -22,9 +21,7 @@ static int zsv_compare_sort_prep_table(struct zsv_compare_data *data, const char return rc; } -static int zsv_compare_sort_stmt_prep(sqlite3 *db, sqlite3_stmt **stmtp, - // struct zsv_compare_sort *sort, - struct zsv_compare_key *keys, unsigned ix) { +static int zsv_compare_sort_stmt_prep(sqlite3 *db, sqlite3_stmt **stmtp, struct zsv_compare_key *keys, unsigned ix) { sqlite3_str *select_clause = sqlite3_str_new(db); if (!select_clause) { fprintf(stderr, "Out of memory!\n"); @@ -43,12 +40,13 @@ static int zsv_compare_sort_stmt_prep(sqlite3 *db, sqlite3_stmt **stmtp, } static enum zsv_compare_status input_init_sorted(struct zsv_compare_data *data, struct zsv_compare_input *input, - struct zsv_opts *_opts, struct zsv_prop_handler *_prop_handler, + struct zsv_opts *opts, struct zsv_prop_handler *custom_prop_handler, const char *opts_used) { - (void)(_opts); - (void)(_prop_handler); char *err_msg = NULL; - int rc = zsv_compare_sort_prep_table(data, input->path, opts_used, 0, &err_msg, input->index); + if (!sqlite3_zsv_list_add(input->path, opts, custom_prop_handler)) + input->added = 1; + int rc = zsv_compare_sort_prep_table(data, input->path, opts_used, &err_msg, input->index); + if (err_msg) { fprintf(stderr, "%s\n", err_msg); sqlite3_free(err_msg); diff --git a/app/curses.h b/app/curses.h new file mode 100644 index 00000000..6d5f03f6 --- /dev/null +++ b/app/curses.h @@ -0,0 +1,15 @@ +#if defined(WIN32) || defined(_WIN32) +#ifdef HAVE_NCURSESW +#include +#else +#include +#endif // HAVE_NCURSESW +#else +#if __has_include() +#include +#elif __has_include() +#include +#else +#error Cannot find ncurses include file! +#endif +#endif diff --git a/app/ext_example/Makefile b/app/ext_example/Makefile index 49fce34c..3994b94a 100644 --- a/app/ext_example/Makefile +++ b/app/ext_example/Makefile @@ -1,4 +1,4 @@ -# Makefile for use with GNU make +#Makefile for use with GNU make THIS_MAKEFILE_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) THIS_DIR:=$(shell basename "${THIS_MAKEFILE_DIR}") @@ -65,6 +65,7 @@ THIS_LIB_BASE=$(shell cd ../.. && pwd) CCBN=$(shell basename ${CC}) BUILD_DIR=${THIS_LIB_BASE}/build/${BUILD_SUBDIR}/${CCBN} TARGET=${BUILD_DIR}/bin/zsvextmy.${SO} +TARGET_SHEET=${BUILD_DIR}/bin/zsvextmysheet.${SO} COLOR_NONE=\033[0m COLOR_GREEN=\033[1;32m @@ -88,15 +89,16 @@ endif UTILS1+=writer UTILS=$(addprefix ${BUILD_DIR}/objs/utils/,$(addsuffix .o,${UTILS1})) -CFLAGS+= -I${THIS_LIB_BASE}/include +CFLAGS+= -I${THIS_LIB_BASE}/include -I${PREFIX}/include -all: ${TARGET} +all: ${TARGET} ${TARGET_SHEET} @echo Built ${TARGET} + @echo Built ${TARGET_SHEET} ifneq ($(findstring emcc,$(CC)),) # emcc install: ${INSTALLED_EXTENSION} -${INSTALLED_EXTENSION}: ${TARGET} +${INSTALLED_EXTENSION}: ${TARGET} ${TARGET_SHEET} @mkdir -p `dirname "$@"` cp -p $< $@ endif @@ -179,13 +181,18 @@ test-thirdparty: test-%: ${CLI} ${TARGET} @cmp /tmp/zsvext-$@.out test/expected/zsvext-$@.out && ${TEST_PASS} || ${TEST_FAIL} clean: - @rm -f ${TARGET} /tmp/zsvext-test*.out + @rm -f ${TARGET} ${TARGET_SHEET} /tmp/zsvext-test*.out ${BUILD_DIR}/objs/%.o : ${THIS_LIB_BASE}/src/%.c ${PARSER_DEPS} ${MAKE} -C ${THIS_LIB_BASE}/src CONFIGFILE=${CONFIGFILEPATH} DEBUG=${DEBUG} WIN=${WIN} $@ -${TARGET}: my_extension.c ${UTILS} + +YAJL_SRC_DIR=${THIS_MAKEFILE_DIR}/../external/yajl +YAJL_INCLUDE=-I${YAJL_SRC_DIR}/build/yajl-2.1.1/include +YAJL_HELPER_INCLUDE=-I${THIS_MAKEFILE_DIR}/../external/yajl_helper +${TARGET_SHEET}: LIBS="../external/sqlite3/sqlite3.c" -lzsv -lzsvutil -L${PREFIX}/lib +${TARGET} ${TARGET_SHEET}: ${BUILD_DIR}/bin/zsvext%.${SO} : %_extension.c ${UTILS} @mkdir -p `dirname "$@"` - ${CC} ${CFLAGS} ${CFLAGS_SHARED} $< ${UTILS} -o $@ + ${CC} ${CFLAGS} ${CFLAGS_SHARED} $< ${UTILS} -o $@ ${LIBS} ${YAJL_INCLUDE} ${YAJL_HELPER_INCLUDE} .PHONY: all test test-% clean install diff --git a/app/ext_example/mysheet_extension.c b/app/ext_example/mysheet_extension.c new file mode 100644 index 00000000..77b0684f --- /dev/null +++ b/app/ext_example/mysheet_extension.c @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2021 Liquidaty and zsv contributors. All rights reserved. + * This file is part of zsv/lib, distributed under the license defined at + * https://opensource.org/licenses/MIT + */ + +#include +#include +#include +#include +#include +#include "../external/sqlite3/sqlite3.h" +#include +#include +#include +#include +#include +#include "../curses.h" + +/** + * This is an example to demonstrate various extension capabilities + * specific to the `sheet` subcommand. For examples of extending the + * CLI outside of `sheet`, see my_extension.c + * + * In this example, we will re-implement the built-in pivot table command + * + * We will name our extension "mysheet", so our shared library will be named + * zsvextmysheet.so (non-win) or zsvextmysheet.dll (win). After the shared lib is + * built, place it anywhere in the PATH or in the same folder as the zsv binary. + * Our extension can then be invoked by first running `sheet`, and then pressing + * 'z' + * + * in addition, a description of our extension is displayed in the built-in help + * command (?) + * + */ + +/** + * *Required*: define our extension id, of up to 8 bytes in length + */ +const char *zsv_ext_id(void) { + return "mysheet"; +} + +/** + * When our library is initialized, zsv will pass it the address of the zsvlib + * functions we will be using. We can keep track of this any way we want; + * in this example, we use a global variable to store the function pointers + */ +static struct zsv_ext_callbacks zsv_cb; + +#include "../sql_internal.h" + +struct pivot_row { + char *value; // to do: this will be the drill-down criteria +}; + +struct pivot_data { + char *value_sql; // the sql expression entered by the user e.g. City + struct { + struct pivot_row *data; // for each row, the value of the sql expression e.g. New York + size_t capacity; + size_t used; + } rows; +}; + +static struct pivot_data *pivot_data_new(void) { + struct pivot_data *pd = calloc(1, sizeof(*pd)); + return pd; +} + +static void pivot_data_delete(void *h) { + struct pivot_data *pd = h; + if (pd) { + free(pd->rows.data); + free(pd); + } +} + +#define ZSV_MYSHEET_EXTENSION_PIVOT_DATA_ROWS_INITIAL 32 +static int pivot_data_grow(struct pivot_data *pd) { + if (pd->rows.used == pd->rows.capacity) { + size_t new_capacity = + pd->rows.capacity == 0 ? ZSV_MYSHEET_EXTENSION_PIVOT_DATA_ROWS_INITIAL : pd->rows.capacity * 2; + struct pivot_row *new_data = realloc(pd->rows.data, new_capacity * sizeof(*pd->rows.data)); + if (!new_data) + return ENOMEM; + pd->rows.data = new_data; + pd->rows.capacity = new_capacity; + } + return 0; +} + +static int add_pivot_row(struct pivot_data *pd, const char *value, size_t len) { + int err = pivot_data_grow(pd); + if (!err) + pd->rows.data[pd->rows.used++].value = NULL; // to do: set a value + return err; +} + +static enum zsv_ext_status get_cell_attrs(void *pdh, int *attrs, size_t start_row, size_t row_count, size_t cols) { + struct pivot_data *pd = pdh; + size_t end_row = start_row + row_count; + if (end_row > pd->rows.used) + end_row = pd->rows.used; + for (size_t i = start_row; i < end_row; i++) + attrs[i * cols] = A_ITALIC | A_BOLD | A_ITALIC; +} + +/** + * Here we define a custom command for the zsv `sheet` feature + */ +zsvsheet_status my_pivot_table_command_handler(zsvsheet_proc_context_t ctx) { + char result_buffer[256] = {0}; + int ch = zsv_cb.ext_sheet_keypress(ctx); + if (ch < 0) + return zsvsheet_status_error; + zsvsheet_buffer_t buff = zsv_cb.ext_sheet_buffer_current(ctx); + const char *data_filename = NULL; + if (buff) + data_filename = zsv_cb.ext_sheet_buffer_data_filename(buff); + if (!data_filename) { // TO DO: check that the underlying data is a tabular file and we know how to parse + zsv_cb.ext_sheet_set_status(ctx, "Pivot table only available for tabular data buffers"); + return zsvsheet_status_ok; + } + struct zsv_opts opts = zsv_cb.ext_sheet_buffer_get_zsv_opts(buff); + zsv_cb.ext_sheet_prompt(ctx, result_buffer, sizeof(result_buffer), "Pivot table: Enter group-by SQL expr"); + if (*result_buffer == '\0') + return zsvsheet_status_ok; + + enum zsvsheet_status zst = zsvsheet_status_ok; + struct zsv_sqlite3_dbopts dbopts = {0}; + struct zsv_sqlite3_db *zdb = zsv_cb.ext_sqlite3_db_new(&dbopts); + sqlite3_str *sql_str = NULL; + struct pivot_data *pd = NULL; + char *tmp_fn = NULL; + const char *err_msg = NULL; + if (!zdb || !(sql_str = sqlite3_str_new(zdb->db))) + zst = zsvsheet_status_memory; + else if (zdb->rc == SQLITE_OK && zsv_cb.ext_sqlite3_add_csv(zdb, data_filename, NULL, NULL, NULL) == SQLITE_OK) { + sqlite3_stmt *stmt = NULL; + sqlite3_str_appendf(sql_str, "select %s as value, count(1) as Count from data group by %s", result_buffer, + result_buffer); + if ((zdb->rc = sqlite3_prepare_v2(zdb->db, sqlite3_str_value(sql_str), -1, &stmt, NULL)) == SQLITE_OK) { + tmp_fn = zsv_get_temp_filename("zsv_mysheet_ext_XXXXXXXX"); + struct zsv_csv_writer_options writer_opts = zsv_writer_get_default_opts(); + zsv_csv_writer cw = NULL; + if (!tmp_fn || !(pd = pivot_data_new())) + zst = zsvsheet_status_memory; + else if (!(writer_opts.stream = fopen(tmp_fn, "wb"))) { + zst = zsvsheet_status_error; + err_msg = strerror(errno); + } else if (!(cw = zsv_writer_new(&writer_opts))) + zst = zsvsheet_status_memory; + else { + unsigned char cw_buff[1024]; + zsv_writer_set_temp_buff(cw, cw_buff, sizeof(cw_buff)); + + int col_count = sqlite3_column_count(stmt); + // write header row + for (int i = 0; i < col_count; i++) { + const char *colname = sqlite3_column_name(stmt, i); + zsv_writer_cell(cw, !i, (const unsigned char *)colname, colname ? strlen(colname) : 0, 1); + add_pivot_row(pd, NULL, 0); + } + + // write sql results + while (sqlite3_step(stmt) == SQLITE_ROW) { + for (int i = 0; i < col_count; i++) { + const unsigned char *text = sqlite3_column_text(stmt, i); + int len = text ? sqlite3_column_bytes(stmt, i) : 0; + zsv_writer_cell(cw, !i, text, len, 1); + if (i == 0) + add_pivot_row(pd, text, len); + } + } + } + if (cw) + zsv_writer_delete(cw); + if (writer_opts.stream) + fclose(writer_opts.stream); + } + if (stmt) + sqlite3_finalize(stmt); + + if (!(tmp_fn && zsv_file_exists(tmp_fn))) { + if (zst == zsvsheet_status_ok) { + zst = zsvsheet_status_error; // to do: make this more specific + if (!err_msg && zdb && zdb->rc != SQLITE_OK) + err_msg = sqlite3_errmsg(zdb->db); + } + } + + if (zst != zsv_status_ok) { + if (err_msg) + zsv_cb.ext_sheet_set_status(ctx, "Error: %s", err_msg); + } else { + zst = zsv_cb.ext_sheet_open_file(ctx, tmp_fn, NULL); + zsvsheet_buffer_t buff = zsv_cb.ext_sheet_buffer_current(ctx); + zsv_cb.ext_sheet_buffer_set_ctx(buff, pd, pivot_data_delete); + zsv_cb.ext_sheet_buffer_set_cell_attrs(buff, get_cell_attrs); + pd = NULL; // so that it isn't cleaned up below + // TO DO: add param to ext_sheet_open_file to set filename vs data_filename, and set buffer type or proc owner + // TO DO: add way to attach custom context, and custom context destructor, to the new buffer + // TO DO: add cell highlighting + // TO DO: add drill-down + } + } + +out: + zsv_cb.ext_sqlite3_db_delete(zdb); + free(tmp_fn); + if (sql_str) + sqlite3_free(sqlite3_str_finish(sql_str)); + pivot_data_delete(pd); + return zst; +} + +/** + * *Required*. Initialization is called when our extension is loaded + * See my_extension.c for details + */ + +enum zsv_ext_status zsv_ext_init(struct zsv_ext_callbacks *cb, zsv_execution_context ctx) { + zsv_cb = *cb; + zsv_cb.ext_set_help(ctx, "Sample zsv sheet extension"); + zsv_cb.ext_set_license(ctx, + "Unlicense. See https://github.com/spdx/license-list-data/blob/master/text/Unlicense.txt"); + const char *third_party_licenses[] = {"If we used any third-party software, we would list each license here", NULL}; + zsv_cb.ext_set_thirdparty(ctx, third_party_licenses); + int proc_id = zsv_cb.ext_sheet_register_proc("my-sheet-pivot", "my sheet pivot", my_pivot_table_command_handler); + if (proc_id < 0) + return zsv_ext_status_error; + zsv_cb.ext_sheet_register_proc_key_binding('v', proc_id); + return zsv_ext_status_ok; +} + +/** + * exit: called once by zsv before the library is unloaded, if `zsv_ext_init()` was + * previously called + */ +enum zsv_ext_status zsv_ext_exit(void) { + fprintf(stderr, "Exiting mysheet extension example!\n"); + return zsv_ext_status_ok; +} diff --git a/app/external/sqlite3/sqlite3_csv_vtab-mem.c b/app/external/sqlite3/sqlite3_csv_vtab-mem.c index 93b9a58b..3b6ecf44 100644 --- a/app/external/sqlite3/sqlite3_csv_vtab-mem.c +++ b/app/external/sqlite3/sqlite3_csv_vtab-mem.c @@ -1,6 +1,3 @@ -#ifndef SQLITE3_CSV_VTAB_ZSV_H -#define SQLITE3_CSV_VTAB_ZSV_H - #include /** @@ -77,7 +74,7 @@ static struct sqlite3_zsv_data *sqlite3_zsv_data_new(const char *filename, struc return NULL; } -int sqlite3_zsv_data_add(const char *filename, struct zsv_opts *opts, struct zsv_prop_handler *custom_prop_handler) { +int sqlite3_zsv_list_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) { @@ -143,5 +140,3 @@ int sqlite3_zsv_list_remove(const char *filename) { } return ENOENT; // not found } - -#endif diff --git a/app/external/sqlite3/sqlite3_csv_vtab-mem.h b/app/external/sqlite3/sqlite3_csv_vtab-mem.h index 68350ca0..04cf6728 100644 --- a/app/external/sqlite3/sqlite3_csv_vtab-mem.h +++ b/app/external/sqlite3/sqlite3_csv_vtab-mem.h @@ -37,7 +37,7 @@ 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); +int sqlite3_zsv_list_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); diff --git a/app/external/sqlite3/sqlite3_csv_vtab-zsv.c b/app/external/sqlite3/sqlite3_csv_vtab-zsv.c index d34c51e8..67915bd6 100644 --- a/app/external/sqlite3/sqlite3_csv_vtab-zsv.c +++ b/app/external/sqlite3/sqlite3_csv_vtab-zsv.c @@ -164,7 +164,6 @@ unsigned blank_column_name_count = 0; * Parameters: * filename=FILENAME Name of file containing CSV content * options_used=OPTIONS_USED Used options (passed to zsv_new_with_properties()) - * max_columns=N Error out if we encounter more cols than this * * The number of columns in the first row of the input file determines the * column names and column count @@ -181,7 +180,7 @@ static int zsvtabConnect( 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" + "filename", "options_used" }; char *azPValue[ZSVTABCONNECT_PARAM_MAX]; /* Parameter values */ memset(azPValue, 0, sizeof(azPValue)); @@ -202,16 +201,16 @@ static int zsvtabConnect( } if( j 2000){ asprintf(&errmsg, "max_columns= value must be > 0 and < 2000"); goto zsvtab_connect_error; } - }else - { + */ + } else { asprintf(&errmsg, "bad parameter: '%s'", z); goto zsvtab_connect_error; } diff --git a/app/external/sqlite3/vtab_helper.c b/app/external/sqlite3/vtab_helper.c index 8d32cc36..cb1816a2 100644 --- a/app/external/sqlite3/vtab_helper.c +++ b/app/external/sqlite3/vtab_helper.c @@ -2,48 +2,53 @@ * Excerpted from https://sqlite.org/src/doc/tip/ext/misc/csv.c */ - /* Skip leading whitespace. Return a pointer to the first non-whitespace ** character, or to the zero terminator if the string has only whitespace */ -static const char *csv_skip_whitespace(const char *z){ - while( isspace((unsigned char)z[0]) ) z++; +static const char *csv_skip_whitespace(const char *z) { + while (isspace((unsigned char)z[0])) + z++; return z; } /* Remove trailing whitespace from the end of string z[] */ -static void csv_trim_whitespace(char *z){ +static void csv_trim_whitespace(char *z) { size_t n = strlen(z); - while( n>0 && isspace((unsigned char)z[n]) ) n--; + while (n > 0 && isspace((unsigned char)z[n])) + n--; z[n] = 0; } /* Dequote the string */ -static void csv_dequote(char *z){ +static void csv_dequote(char *z) { int j; char cQuote = z[0]; size_t i, n; - if( cQuote!='\'' && cQuote!='"' ) return; + if (cQuote != '\'' && cQuote != '"') + return; n = strlen(z); - if( n<2 || z[n-1]!=z[0] ) return; - for(i=1, j=0; izErr. If there are no errors, p->zErr[0]==0. */ -static int csv_string_parameter( - char **errmsg, /* Leave the error message here, if there is one */ - const char *zParam, /* Parameter we are checking for */ - const char *zArg, /* Raw text of the virtual table argment */ - char **pzVal /* Write the dequoted string value here */ -){ +static int csv_string_parameter(char **errmsg, /* Leave the error message here, if there is one */ + const char *zParam, /* Parameter we are checking for */ + const char *zArg, /* Raw text of the virtual table argment */ + char **pzVal /* Write the dequoted string value here */ +) { const char *zValue; - zValue = csv_parameter(zParam,(int)strlen(zParam),zArg); - if( zValue==0 ) return 0; - if( *pzVal ){ + if (!zParam) { + asprintf(errmsg, "unrecognized connection parameter: %s", zArg); + return 1; + } + zValue = csv_parameter(zParam, (int)strlen(zParam), zArg); + if (zValue == 0) + return 0; + if (*pzVal) { asprintf(errmsg, "more than one '%s' parameter", zParam); return 1; } *pzVal = sqlite3_mprintf("%s", zValue); - if( *pzVal==0 ){ + if (*pzVal == 0) { asprintf(errmsg, "out of memory"); return 1; } diff --git a/app/sheet.c b/app/sheet.c index d2d74ff6..aabbcaf6 100644 --- a/app/sheet.c +++ b/app/sheet.c @@ -16,21 +16,7 @@ #include -#if defined(WIN32) || defined(_WIN32) -#ifdef HAVE_NCURSESW -#include -#else -#include -#endif // HAVE_NCURSESW -#else -#if __has_include() -#include -#elif __has_include() -#include -#else -#error Cannot find ncurses include file! -#endif -#endif +#include "curses.h" #include #include @@ -427,6 +413,7 @@ static zsvsheet_status zsvsheet_filter_handler(struct zsvsheet_proc_context *ctx const char *data_filename = zsvsheet_buffer_data_filename(current_ui_buffer); char is_filtered_file = !(data_filename == current_ui_buffer->filename); struct zsv_opts *zsv_opts = is_filtered_file ? NULL : ¤t_ui_buffer->zsv_opts; + if ((err = zsvsheet_ui_buffer_open_file(data_filename, zsv_opts, prompt_buffer, state->custom_prop_handler, NULL, di->ui_buffers.base, di->ui_buffers.current))) { if (err > 0) @@ -440,6 +427,7 @@ static zsvsheet_status zsvsheet_filter_handler(struct zsvsheet_proc_context *ctx struct zsvsheet_ui_buffer *new_ui_buffer = *state->display_info.ui_buffers.current; if (is_filtered_file) { + // TO DO: move this into zsvsheet_ui_buffer_open_file() free(new_ui_buffer->filename); new_ui_buffer->filename = strdup(current_ui_buffer->filename); } diff --git a/app/sheet/handlers.c b/app/sheet/handlers.c index 35d8410e..33c7b4a1 100644 --- a/app/sheet/handlers.c +++ b/app/sheet/handlers.c @@ -117,6 +117,17 @@ enum zsv_ext_status zsvsheet_buffer_get_ctx(zsvsheet_buffer_t h, void **ctx_out) return zsv_ext_status_ok; } +/** Set callback for fetching cell attributes **/ +void zsvsheet_buffer_set_cell_attrs(zsvsheet_buffer_t h, + enum zsv_ext_status (*get_cell_attrs)(void *ext_ctx, int *, size_t start_row, + size_t row_count, size_t col_count)) { + if (h) { + struct zsvsheet_ui_buffer *buff = h; + buff->get_cell_attrs = get_cell_attrs; + zsvsheet_ui_buffer_update_cell_attr(buff); + } +} + /** Get zsv_opts use to open the buffer's data file **/ struct zsv_opts zsvsheet_buffer_get_zsv_opts(zsvsheet_buffer_t h) { if (h) { diff --git a/app/sheet/handlers_internal.h b/app/sheet/handlers_internal.h index 038e965d..64ea9ccd 100644 --- a/app/sheet/handlers_internal.h +++ b/app/sheet/handlers_internal.h @@ -88,6 +88,13 @@ enum zsv_ext_status zsvsheet_buffer_set_ctx(zsvsheet_buffer_t h, void *ctx, void */ enum zsv_ext_status zsvsheet_buffer_get_ctx(zsvsheet_buffer_t h, void **ctx_out); +/** + * Set custom cell attributes + */ +void zsvsheet_buffer_set_cell_attrs(zsvsheet_buffer_t h, + enum zsv_ext_status (*get_cell_attrs)(void *ext_ctx, int *, size_t start_row, + size_t row_count, size_t col_count)); + /** Get zsv_opts use to open the buffer's data file **/ struct zsv_opts zsvsheet_buffer_get_zsv_opts(zsvsheet_buffer_t h); diff --git a/app/sheet/index.c b/app/sheet/index.c index feeb745d..93c3e963 100644 --- a/app/sheet/index.c +++ b/app/sheet/index.c @@ -76,7 +76,8 @@ enum zsv_index_status build_memory_index(struct zsvsheet_index_opts *optsp) { if (!temp_filename) return ret; - *optsp->data_filenamep = temp_filename; + // *optsp->data_filenamep = temp_filename; + optsp->uib->data_filename = temp_filename; struct zsv_csv_writer_options writer_opts = {0}; if (!(writer_opts.stream = temp_f = fopen(temp_filename, "w+"))) @@ -128,7 +129,8 @@ enum zsv_index_status build_memory_index(struct zsvsheet_index_opts *optsp) { if (zst == zsv_status_no_more_input) { ret = zsv_index_status_ok; - *optsp->index = ixr.ix; + // *optsp->index = ixr.ix; + optsp->uib->index = ixr.ix; } else zsv_index_delete(ixr.ix); diff --git a/app/sheet/read-data.c b/app/sheet/read-data.c index a3b49b14..95e85e35 100644 --- a/app/sheet/read-data.c +++ b/app/sheet/read-data.c @@ -33,11 +33,11 @@ static void get_data_index_async(struct zsvsheet_ui_buffer *uibuffp, const char struct zsvsheet_index_opts *ixopts = calloc(1, sizeof(*ixopts)); ixopts->mutexp = mutexp; ixopts->filename = filename; - ixopts->data_filenamep = &uibuffp->data_filename; + // ixopts->data_filenamep = &uibuffp->data_filename; ixopts->zsv_opts = *optsp; ixopts->row_filter = row_filter; - ixopts->index = &uibuffp->index; - ixopts->index_ready = &uibuffp->index_ready; + // ixopts->index = &uibuffp->index; + // ixopts->index_ready = &uibuffp->index_ready; ixopts->custom_prop_handler = custom_prop_handler; // ixopts->opts_used = opts_used; ixopts->uib = uibuffp; @@ -256,7 +256,8 @@ static void *get_data_index(void *gdi) { } pthread_mutex_lock(mutexp); - *d->index_ready = 1; + d->uib->index_ready = 1; + // *d->index_ready = 1; if (d->uib) { free(d->uib->status); diff --git a/app/sheet/screen_buffer.c b/app/sheet/screen_buffer.c index 5112cd80..00181884 100644 --- a/app/sheet/screen_buffer.c +++ b/app/sheet/screen_buffer.c @@ -57,6 +57,7 @@ void zsvsheet_screen_buffer_delete(zsvsheet_screen_buffer_t buff) { free_long_cell(buff, offset); } } + free(buff->cell_attrs); free(buff->data); free(buff); } @@ -146,7 +147,7 @@ enum zsvsheet_priv_status zsvsheet_screen_buffer_write_cell(zsvsheet_screen_buff int zsvsheet_screen_buffer_cell_attrs(zsvsheet_screen_buffer_t buff, size_t row, size_t col) { if (buff->cell_attrs) { - size_t offset = row * buff->cols * buff->opts.cell_buff_len + col * buff->opts.cell_buff_len; + size_t offset = row * buff->cols + col; return buff->cell_attrs[offset]; } return 0; diff --git a/app/sheet/ui_buffer.c b/app/sheet/ui_buffer.c index f8e2c2c1..95066fb3 100644 --- a/app/sheet/ui_buffer.c +++ b/app/sheet/ui_buffer.c @@ -34,12 +34,15 @@ struct zsvsheet_ui_buffer { // if non-null, called when buffer is closed void (*ext_on_close)(void *); - unsigned char index_ready; + enum zsv_ext_status (*get_cell_attrs)(void *ext_ctx, int *attrs, size_t start_row, size_t row_count, + size_t col_count); + + unsigned char index_ready : 1; unsigned char rownum_col_offset : 1; unsigned char index_started : 1; unsigned char has_row_num : 1; unsigned char mutex_inited : 1; - unsigned char _ : 4; + unsigned char _ : 3; }; void zsvsheet_ui_buffer_delete(struct zsvsheet_ui_buffer *ub) { @@ -93,6 +96,23 @@ struct zsvsheet_ui_buffer *zsvsheet_ui_buffer_new(zsvsheet_screen_buffer_t buffe return uib; } +int zsvsheet_ui_buffer_update_cell_attr(struct zsvsheet_ui_buffer *uib) { + if (uib && uib->buffer && uib->buffer->opts.rows && uib->buffer->cols) { + size_t row_sz = uib->buffer->cols * sizeof(*uib->buffer->cell_attrs); + if (uib->get_cell_attrs) { + if (!uib->buffer->cell_attrs) { + uib->buffer->cell_attrs = calloc(uib->buffer->opts.rows, row_sz); + if (!uib->buffer->cell_attrs) + return ENOMEM; + } + memset(uib->buffer->cell_attrs, 0, uib->buffer->opts.rows * row_sz); + uib->get_cell_attrs(uib->ext_ctx, uib->buffer->cell_attrs, uib->input_offset.row, uib->buff_used_rows, + uib->buffer->cols); + } + } + return 0; +} + enum zsvsheet_priv_status zsvsheet_ui_buffer_new_blank(struct zsvsheet_ui_buffer **uibp) { enum zsvsheet_priv_status status; zsvsheet_screen_buffer_t buffer = zsvsheet_screen_buffer_new(1, NULL, &status); diff --git a/app/sql.c b/app/sql.c index ea8107c7..89737bce 100644 --- a/app/sql.c +++ b/app/sql.c @@ -83,7 +83,6 @@ static void zsv_sql_finalize(struct zsv_sql_data *data) { static void zsv_sql_cleanup(struct zsv_sql_data *data) { if (data->in && data->in != stdin) fclose(data->in); - sqlite3_zsv_list_remove(data->input_filename); free(data->sql_dynamic); free(data->join_indexes); if (data->join_column_names) { @@ -98,7 +97,6 @@ static void zsv_sql_cleanup(struct zsv_sql_data *data) { if (data->more_input_filenames) { struct string_list *next; for (struct string_list *tmp = data->more_input_filenames; tmp; tmp = next) { - sqlite3_zsv_list_remove(tmp->value); next = tmp->next; free(tmp); } @@ -121,18 +119,12 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op * b. input file path * c. cmd-line options used in (a), so that we can print warnings in case of * conflict between (a) and properties of (b) - * - * For file path and options_used, we will pass as part of the - * CREATE VIRTUAL TABLE connection string. - * For zsv opts and custom_prop_handler, we will pass via - * sqlite3_zsv_data_add() */ int err = 0; if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) err = zsv_sql_usage(argc < 2 ? stderr : stdout); else { struct zsv_sql_data data = {0}; - int max_cols = 0; // TO DO: remove this; use parser_opts.max_columns const char *my_sql = NULL; struct string_list **next_input_filename = &data.more_input_filenames; @@ -208,21 +200,13 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op data.in_memory = 1; else if (!strcmp(arg, "-b")) writer_opts.with_bom = 1; - else if (!strcmp(arg, "-C") || !strcmp(arg, "--max-cols")) { - if (arg_i + 1 < argc && atoi(argv[arg_i + 1]) > 0 && atoi(argv[arg_i + 1]) <= 2000) - max_cols = atoi(argv[++arg_i]); - else { - fprintf(stderr, "maximum columns value not provided or not between 0 and 2000\n"); - err = 1; - } - } else if (*arg != '-') { + else if (*arg != '-') { if (!data.input_filename) { data.input_filename = arg; if (!(data.in = fopen(arg, "rb"))) { fprintf(stderr, "Unable to open for reading: %s\n", arg); err = 1; - } else - err = sqlite3_zsv_data_add(arg, opts, custom_prop_handler); + } } else { // another input file FILE *tmp_f; if (!(tmp_f = fopen(arg, "rb"))) { @@ -237,9 +221,6 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op tmp->value = (char *)arg; *next_input_filename = tmp; next_input_filename = &tmp->next; - - // TO DO: option to only apply specified opts to first input? - err = sqlite3_zsv_data_add(arg, opts, custom_prop_handler); } } } @@ -308,13 +289,18 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op unsigned char cw_buff[1024]; zsv_writer_set_temp_buff(cw, cw_buff, sizeof(cw_buff)); - const char *csv_filename = tmpfn ? (const char *)tmpfn : data.input_filename; - struct zsv_sqlite3_db *zdb = - zsv_sqlite3_db_new(csv_filename, data.in_memory, opts_used, max_cols, SQLITE_OPEN_URI | SQLITE_OPEN_READWRITE); - if (zdb) { - if (zdb->rc == SQLITE_OK) { + struct zsv_sqlite3_dbopts dbopts = { + .in_memory = data.in_memory, + }; + struct zsv_sqlite3_db *zdb = zsv_sqlite3_db_new(&dbopts); + if (zdb && zdb->rc == SQLITE_OK) { + const char *csv_filename = tmpfn ? (const char *)tmpfn : data.input_filename; + + // for simplicity, we assume the same opts, custom_prop_handler and opts_used for every input + // it may be desirable later to make this customizable for each input + if (zsv_sqlite3_add_csv(zdb, csv_filename, opts, custom_prop_handler, opts_used) == SQLITE_OK) { for (struct string_list *sl = data.more_input_filenames; sl; sl = sl->next) - if (zsv_sqlite3_add_csv(zdb, sl->value, opts_used, max_cols) != SQLITE_OK) + if (zsv_sqlite3_add_csv(zdb, sl->value, opts, custom_prop_handler, opts_used) != SQLITE_OK) break; } @@ -433,6 +419,7 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op zsv_writer_cell(cw, !i, (const unsigned char *)colname, colname ? strlen(colname) : 0, 1); } + // write sql results while (sqlite3_step(stmt) == SQLITE_ROW) { for (int i = 0; i < col_count; i++) { const unsigned char *text = sqlite3_column_text(stmt, i); diff --git a/app/sql_internal.c b/app/sql_internal.c index 7ba905b0..b9de73b1 100644 --- a/app/sql_internal.c +++ b/app/sql_internal.c @@ -1,32 +1,37 @@ #include "sql_internal.h" -struct zsv_sqlite3_db *zsv_sqlite3_db_new(const char *csv_filename, char in_memory, const char *opts_used, - - size_t max_cols, int sqlite3_flags) { +struct zsv_sqlite3_db *zsv_sqlite3_db_new(struct zsv_sqlite3_dbopts *dbopts) { struct zsv_sqlite3_db *zdb = calloc(1, sizeof(*zdb)); if (!zdb) { perror(NULL); return NULL; } - const char *db_url = in_memory ? "file::memory:" : ""; - zdb->rc = sqlite3_open_v2(db_url, &zdb->db, sqlite3_flags, NULL); - if (zdb->rc == SQLITE_OK && zdb->db) { + const char *db_url = dbopts && dbopts->in_memory ? "file::memory:" : ""; + int flags = SQLITE_OPEN_URI | SQLITE_OPEN_READWRITE; + zdb->rc = sqlite3_open_v2(db_url, &zdb->db, flags, NULL); + if (zdb->rc == SQLITE_OK && zdb->db) zdb->rc = sqlite3_create_module(zdb->db, "csv", &CsvModule, 0); - if (zdb->rc == SQLITE_OK) - zsv_sqlite3_add_csv(zdb, csv_filename, opts_used, max_cols); - } if (zdb->rc != SQLITE_OK && !zdb->err_msg) zdb->err_msg = strdup(sqlite3_errstr(zdb->rc)); return zdb; } void zsv_sqlite3_db_delete(struct zsv_sqlite3_db *zdb) { - if (zdb && zdb->db) - sqlite3_close(zdb->db); - free(zdb); + if (zdb) { + for (struct zsv_sqlite3_csv_file *next, *zcf = zdb->csv_files; zcf; zcf = next) { + next = zcf->next; + if (zcf->added) + sqlite3_zsv_list_remove(zcf->path); + free(zcf->path); + free(zcf); + } + if (zdb->db) + sqlite3_close(zdb->db); + free(zdb); + } } -static int create_virtual_csv_table(const char *fname, sqlite3 *db, const char *opts_used, int max_columns, +static int create_virtual_csv_table(const char *fname, sqlite3 *db, const char *opts_used, // int max_columns, char **err_msgp, int table_ix) { // TO DO: set customizable maximum number of columns to prevent // runaway in case no line ends found @@ -40,12 +45,14 @@ static int create_virtual_csv_table(const char *fname, sqlite3 *db, const char * else snprintf(table_name_suffix, sizeof(table_name_suffix), "%i", table_ix + 1); + /* if (max_columns) sql = sqlite3_mprintf("CREATE VIRTUAL TABLE data%s USING csv(filename=%Q,options_used=%Q,max_columns=%i)", table_name_suffix, fname, opts_used, max_columns); else - sql = sqlite3_mprintf("CREATE VIRTUAL TABLE data%s USING csv(filename=%Q,options_used=%Q)", table_name_suffix, - fname, opts_used); + */ + sql = sqlite3_mprintf("CREATE VIRTUAL TABLE data%s USING csv(filename=%Q,options_used=%Q)", table_name_suffix, fname, + opts_used); char *err_msg_tmp; int rc = sqlite3_exec(db, sql, NULL, NULL, &err_msg_tmp); @@ -57,9 +64,33 @@ static int create_virtual_csv_table(const char *fname, sqlite3 *db, const char * return rc; } -int zsv_sqlite3_add_csv(struct zsv_sqlite3_db *zdb, const char *csv_filename, const char *opts_used, size_t max_cols) { - zdb->rc = create_virtual_csv_table(csv_filename, zdb->db, opts_used, max_cols, &zdb->err_msg, zdb->table_count); - if (zdb->rc == SQLITE_OK) - zdb->table_count++; +int zsv_sqlite3_add_csv(struct zsv_sqlite3_db *zdb, const char *csv_filename, struct zsv_opts *opts, + struct zsv_prop_handler *custom_prop_handler, const char *opts_used) { + struct zsv_sqlite3_csv_file *zcf = calloc(1, sizeof(*zcf)); + if (!zcf || !(zcf->path = strdup(csv_filename))) + zdb->rc = SQLITE_ERROR; + else { + int err = 0; + if (opts) { + err = sqlite3_zsv_list_add(csv_filename, opts, custom_prop_handler); + if (!err) + zcf->added = 1; + } + if (err) + zdb->rc = SQLITE_ERROR; + else { + zcf->next = zdb->csv_files; + zdb->csv_files = zcf; + zdb->rc = create_virtual_csv_table(csv_filename, zdb->db, opts_used, &zdb->err_msg, zdb->table_count); + if (zdb->rc == SQLITE_OK) { + zdb->table_count++; + return SQLITE_OK; + } + } + } + if (zcf) { + free(zcf->path); + free(zcf); + } return zdb->rc; } diff --git a/app/sql_internal.h b/app/sql_internal.h index 81fcf0a3..384c46ee 100644 --- a/app/sql_internal.h +++ b/app/sql_internal.h @@ -1,20 +1,40 @@ #ifndef SQL_INTERNAL_H #define SQL_INTERNAL_H +#include +#include +#include "external/sqlite3/sqlite3.h" +#include "external/sqlite3/sqlite3_csv_vtab-mem.h" +#include +#include + extern sqlite3_module CsvModule; +struct zsv_sqlite3_csv_file { + struct zsv_sqlite3_csv_file *next; + char *path; + unsigned char added : 1; + unsigned char _ : 7; +}; + struct zsv_sqlite3_db { sqlite3 *db; int table_count; char *err_msg; + struct zsv_sqlite3_csv_file *csv_files; int rc; }; -struct zsv_sqlite3_db *zsv_sqlite3_db_new(const char *csv_filename, char in_memory, const char *opts_used, - size_t max_cols, int sqlite3_flags); +#include "../include/zsv/utils/sql.h" +struct zsv_sqlite3_db *zsv_sqlite3_db_new(struct zsv_sqlite3_dbopts *dbopts); void zsv_sqlite3_db_delete(struct zsv_sqlite3_db *zdb); -int zsv_sqlite3_add_csv(struct zsv_sqlite3_db *zdb, const char *csv_filename, const char *opts_used, size_t max_cols); - +/** + * @param opts: if non-null, opts and custom_prop_handler will be saved for use by the sqlite3 csvModule + * and the saved entry will be removed by zsv_sqlite3_db_delete() + * if NULL, csvModule will rely on any previously saved opts/custom_prop_handler + */ +int zsv_sqlite3_add_csv(struct zsv_sqlite3_db *zdb, const char *csv_filename, struct zsv_opts *opts, + struct zsv_prop_handler *custom_prop_handler, const char *opts_used); #endif diff --git a/include/zsv/ext.h b/include/zsv/ext.h index 5d71d238..2251a4ce 100644 --- a/include/zsv/ext.h +++ b/include/zsv/ext.h @@ -9,9 +9,14 @@ #ifndef ZSV_EXT_H #define ZSV_EXT_H +#define ZSV_EXTENSION_ID_MIN_LEN 2 +#define ZSV_EXTENSION_ID_MAX_LEN 8 + #include #include "common.h" #include "ext/sheet.h" +#include "utils/sql.h" +#include "utils/prop.h" /** * @file ext.h @@ -231,10 +236,25 @@ struct zsv_ext_callbacks { */ enum zsv_ext_status (*ext_sheet_buffer_get_ctx)(zsvsheet_buffer_t h, void **ctx_out); + /** + * Set custom cell attributes + */ + void (*ext_sheet_buffer_set_cell_attrs)(zsvsheet_buffer_t h, + enum zsv_ext_status (*get_cell_attrs)(void *pdh, int *attrs, size_t start_row, + size_t row_count, size_t cols)); + /** * Get zsv_opts used to open the buffer's data file */ struct zsv_opts (*ext_sheet_buffer_get_zsv_opts)(zsvsheet_buffer_t h); + + /** + * SQLITE3 helpers + */ + int (*ext_sqlite3_add_csv)(struct zsv_sqlite3_db *zdb, const char *csv_filename, struct zsv_opts *opts, + struct zsv_prop_handler *custom_prop_handler, const char *opts_used); + void (*ext_sqlite3_db_delete)(zsv_sqlite3_db_t); + zsv_sqlite3_db_t (*ext_sqlite3_db_new)(struct zsv_sqlite3_dbopts *dbopts); }; /** @} */ diff --git a/include/zsv/utils/sql.h b/include/zsv/utils/sql.h new file mode 100644 index 00000000..d056da5e --- /dev/null +++ b/include/zsv/utils/sql.h @@ -0,0 +1,11 @@ +#ifndef ZSV_UTILS_SQL_H +#define ZSV_UTILS_SQL_H + +typedef struct zsv_sqlite3_db *zsv_sqlite3_db_t; + +struct zsv_sqlite3_dbopts { + unsigned char in_memory : 1; + unsigned char _ : 7; +}; + +#endif