/* * Copyright (C) 2007 - 2012 Vivien Malerba * Copyright (C) 2009 Bas Driessen * Copyright (C) 2010 David King * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include "gda-vprovider-data-model.h" #include "gda-vconnection-data-model.h" #include "gda-vconnection-data-model-private.h" #include #include #include #include #include #include "../gda-sqlite.h" #include #include #define GDA_DEBUG_VIRTUAL #undef GDA_DEBUG_VIRTUAL struct _GdaVproviderDataModelPrivate { int foo; }; static void gda_vprovider_data_model_class_init (GdaVproviderDataModelClass *klass); static void gda_vprovider_data_model_init (GdaVproviderDataModel *prov, GdaVproviderDataModelClass *klass); static void gda_vprovider_data_model_finalize (GObject *object); static GObjectClass *parent_class = NULL; static GdaConnection *gda_vprovider_data_model_create_connection (GdaServerProvider *provider); static gboolean gda_vprovider_data_model_open_connection (GdaServerProvider *provider, GdaConnection *cnc, GdaQuarkList *params, GdaQuarkList *auth, guint *task_id, GdaServerProviderAsyncCallback async_cb, gpointer cb_data); static gboolean gda_vprovider_data_model_close_connection (GdaServerProvider *provider, GdaConnection *cnc); static GObject *gda_vprovider_data_model_statement_execute (GdaServerProvider *provider, GdaConnection *cnc, GdaStatement *stmt, GdaSet *params, GdaStatementModelUsage model_usage, GType *col_types, GdaSet **last_inserted_row, guint *task_id, GdaServerProviderExecCallback async_cb, gpointer cb_data, GError **error); static const gchar *gda_vprovider_data_model_get_name (GdaServerProvider *provider); static GValue **create_gvalues_array_from_sqlite3_array (int argc, sqlite3_value **argv); /* * GdaVproviderDataModel class implementation */ static void gda_vprovider_data_model_class_init (GdaVproviderDataModelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GdaServerProviderClass *server_class = GDA_SERVER_PROVIDER_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->finalize = gda_vprovider_data_model_finalize; server_class->create_connection = gda_vprovider_data_model_create_connection; server_class->open_connection = gda_vprovider_data_model_open_connection; server_class->close_connection = gda_vprovider_data_model_close_connection; server_class->statement_execute = gda_vprovider_data_model_statement_execute; server_class->get_name = gda_vprovider_data_model_get_name; /* explicitly unimplement the DDL queries */ server_class->supports_operation = NULL; server_class->create_operation = NULL; server_class->render_operation = NULL; server_class->perform_operation = NULL; } static void gda_vprovider_data_model_init (GdaVproviderDataModel *prov, G_GNUC_UNUSED GdaVproviderDataModelClass *klass) { prov->priv = g_new (GdaVproviderDataModelPrivate, 1); } static void gda_vprovider_data_model_finalize (GObject *object) { GdaVproviderDataModel *prov = (GdaVproviderDataModel *) object; g_return_if_fail (GDA_IS_VPROVIDER_DATA_MODEL (prov)); /* free memory */ g_free (prov->priv); prov->priv = NULL; /* chain to parent class */ parent_class->finalize (object); } GType gda_vprovider_data_model_get_type (void) { static GType type = 0; if (G_UNLIKELY (type == 0)) { static GMutex registering; if (type == 0) { static GTypeInfo info = { sizeof (GdaVproviderDataModelClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) gda_vprovider_data_model_class_init, NULL, NULL, sizeof (GdaVproviderDataModel), 0, (GInstanceInitFunc) gda_vprovider_data_model_init, 0 }; g_mutex_lock (®istering); if (type == 0) type = g_type_register_static (GDA_TYPE_VIRTUAL_PROVIDER, "GdaVproviderDataModel", &info, 0); g_mutex_unlock (®istering); } } return type; } /** * gda_vprovider_data_model_new * * Creates a new GdaVirtualProvider object which allows one to * add and remove GdaDataModel objects as tables within a connection * * Returns: a new #GdaVirtualProvider object. */ GdaVirtualProvider * gda_vprovider_data_model_new (void) { GdaVirtualProvider *provider; provider = g_object_new (gda_vprovider_data_model_get_type (), NULL); return provider; } /* * Note about RowIDs and how SQLite uses them: * * SQLite considers that each virtual table has unique row IDs, absolute value available all the time. * When an UPDATE or DELETE statement is executed, SQLite does the following: * - xBegin (table): request a transaction start * - xOpen (table): create a new cursor * - xFilter (cursor): initialize the cursor * - moves the cursor one step at a time up to the end (xEof, xColumn and xNext). If it finds a * row needing to be updated or deleted, it calls xRowid (cursor) to get the RowID * - xClose (cursor): free the now useless cursor * - calls xUpdate (table) as many times as needed (one time for each row where xRowid was called) * - xSync (table) * - xCommit (table) * * This does not work well with Libgda because RowID are not pre-defined. To circumvent this problem, * for each data model, the RowID as returned for each cursor is the row number in the data model, plus * an offset defined uniquely for each table: * RowID (guint 64) = ID (guint32) << 32 + row (guint 31) */ #ifdef GDA_DEBUG_VIRTUAL #define TRACE(table,cursor) g_print ("== %s (table=>%p cursor=>%p)\n", __FUNCTION__, (table), (cursor)) #else #define TRACE(table,cursor) #endif typedef struct VirtualTable VirtualTable; typedef struct VirtualCursor VirtualCursor; struct VirtualTable { sqlite3_vtab base; GdaVconnectionDataModel *cnc; GdaVConnectionTableData *td; gboolean locked; guint32 rows_offset; }; #define MAX_VDATA_NUMBER 30 struct VirtualCursor { sqlite3_vtab_cursor base; /* base.pVtab is a pointer to the sqlite3_vtab virtual table */ VirtualFilteredData *data; /* a ref is held here */ gint row; /* starts at 0 */ }; static VirtualFilteredData * virtual_filtered_data_new (VirtualTable *vtable, GdaDataModel *model, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) { VirtualFilteredData *data; g_assert (model); data = g_new0 (VirtualFilteredData, 1); data->refcount = 1; data->reuseable = TRUE; data->idxNum = idxNum; data->idxStr = idxStr ? g_strdup (idxStr) : NULL; data->argc = argc; data->argv = create_gvalues_array_from_sqlite3_array (argc, argv); data->model = g_object_ref (model); if (GDA_IS_DATA_PROXY (model)) data->iter = g_object_new (GDA_TYPE_DATA_MODEL_ITER, "data-model", model, NULL); else data->iter = gda_data_model_create_iter (model); g_object_set (data->iter, "validate-changes", FALSE, NULL); gint n; n = gda_data_model_get_n_columns (model); n = (n >= 0) ? n : 1; data->values_array = g_array_new (FALSE, FALSE, sizeof (GValue)); data->ncols = gda_data_model_get_n_columns (model); data->nrows = -1; data->rowid_offset = vtable->rows_offset; vtable->rows_offset ++; #ifdef DEBUG_VCONTEXT g_print ("New VData %p for table [%s] idxNum=%d, idxStr=[%s], argc=%d\n", data, vtable->td->table_name, idxNum, idxStr, argc); for (n= 0; n < data->argc; n++) g_print (" [%s]\n", gda_value_stringify (data->argv [n])); #endif return data; } static void virtual_filtered_data_free (VirtualFilteredData *data) { g_return_if_fail (data != NULL); if (data->argv) { int i; for (i = 0; i < data->argc; i++) gda_value_free (data->argv [i]); g_free (data->argv); } g_free (data->idxStr); g_object_unref (data->model); if (data->iter) g_object_unref (data->iter); if (data->values_array) { guint i; for (i = 0; i < data->values_array->len; i++) { GValue *value; value = & g_array_index (data->values_array, GValue, i); g_value_reset (value); } g_array_free (data->values_array, TRUE); } g_free (data); #ifdef DEBUG_VCONTEXT g_print ("Freed VData %p\n", data); #endif } static VirtualFilteredData * virtual_filtered_data_ref (VirtualFilteredData *data) { g_return_val_if_fail (data != NULL, NULL); data->refcount ++; return data; } void _gda_vconnection_virtual_filtered_data_unref (VirtualFilteredData *data) { g_return_if_fail (data != NULL); data->refcount --; if (data->refcount == 0) virtual_filtered_data_free (data); } static void virtual_cursor_free (VirtualCursor *cursor) { if (!cursor) return; _gda_vconnection_virtual_filtered_data_unref (cursor->data); cursor->data = NULL; g_free (cursor); } /* module creation */ static int virtualCreate (sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlite3_vtab **ppVtab, char **pzErr); static int virtualConnect (sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlite3_vtab **ppVtab, char **pzErr); static int virtualDisconnect (sqlite3_vtab *pVtab); static int virtualDestroy (sqlite3_vtab *pVtab); static int virtualOpen (sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor); static int virtualClose (sqlite3_vtab_cursor *cur); static int virtualEof (sqlite3_vtab_cursor *cur); static int virtualNext (sqlite3_vtab_cursor *cur); static int virtualColumn (sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i); static int virtualRowid (sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid); static int virtualFilter (sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv); static int virtualBestIndex (sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo); static int virtualUpdate (sqlite3_vtab *tab, int nData, sqlite3_value **apData, sqlite_int64 *pRowid); static int virtualBegin (sqlite3_vtab *tab); static int virtualSync (sqlite3_vtab *tab); static int virtualCommit (sqlite3_vtab *tab); static int virtualRollback (sqlite3_vtab *tab); static int virtualRename (sqlite3_vtab *pVtab, const char *zNew); static sqlite3_module Module = { 1, /* iVersion */ virtualCreate, virtualConnect, virtualBestIndex, virtualDisconnect, virtualDestroy, virtualOpen, /* xOpen - open a cursor */ virtualClose, /* xClose - close a cursor */ virtualFilter, /* xFilter - configure scan constraints */ virtualNext, /* xNext - advance a cursor */ virtualEof, /* xEof */ virtualColumn, /* xColumn - read data */ virtualRowid, /* xRowid - read data */ virtualUpdate, /* xUpdate - write data */ virtualBegin, /* xBegin - begin transaction */ virtualSync, /* xSync - sync transaction */ virtualCommit, /* xCommit - commit transaction */ virtualRollback, /* xRollback - rollback transaction */ NULL, /* xFindFunction - function overloading */ virtualRename, /* Rename - Notification that the table will be given a new name */ #if SQLITE_VERSION_NUMBER >= 3007007 NULL, /* xSavepoint */ NULL, /* xRelease */ NULL /* xRollbackTo */ #endif }; static GdaConnection * gda_vprovider_data_model_create_connection (GdaServerProvider *provider) { GdaConnection *cnc; g_return_val_if_fail (GDA_IS_VPROVIDER_DATA_MODEL (provider), NULL); cnc = g_object_new (GDA_TYPE_VCONNECTION_DATA_MODEL, "provider", provider, NULL); return cnc; } static gboolean gda_vprovider_data_model_open_connection (GdaServerProvider *provider, GdaConnection *cnc, GdaQuarkList *params, GdaQuarkList *auth, G_GNUC_UNUSED guint *task_id, GdaServerProviderAsyncCallback async_cb, G_GNUC_UNUSED gpointer cb_data) { GdaQuarkList *m_params; g_return_val_if_fail (GDA_IS_VPROVIDER_DATA_MODEL (provider), FALSE); g_return_val_if_fail (GDA_IS_VCONNECTION_DATA_MODEL (cnc), FALSE); if (async_cb) { gda_connection_add_event_string (cnc, _("Provider does not support asynchronous connection open")); return FALSE; } if (params) { m_params = gda_quark_list_copy (params); gda_quark_list_add_from_string (m_params, "_IS_VIRTUAL=TRUE;EXTRA_FUNCTIONS=TRUE", TRUE); } else m_params = gda_quark_list_new_from_string ("_IS_VIRTUAL=TRUE;EXTRA_FUNCTIONS=TRUE"); if (! GDA_SERVER_PROVIDER_CLASS (parent_class)->open_connection (GDA_SERVER_PROVIDER (provider), cnc, m_params, auth, NULL, NULL, NULL)) { if (auth) gda_quark_list_protect_values (auth); gda_quark_list_free (m_params); return FALSE; } if (auth) gda_quark_list_protect_values (auth); gda_quark_list_free (m_params); SqliteConnectionData *scnc; scnc = (SqliteConnectionData*) gda_connection_internal_get_provider_data ((GdaConnection *) cnc); if (!scnc) { gda_connection_close_no_warning (cnc); gda_connection_add_event_string (cnc, _("Connection is closed")); return FALSE; } /* Module to declare wirtual tables */ if (SQLITE3_CALL (sqlite3_create_module) (scnc->connection, G_OBJECT_TYPE_NAME (provider), &Module, cnc) != SQLITE_OK) return FALSE; /*g_print ("==== Declared module for DB %p\n", scnc->connection);*/ return TRUE; } static void cnc_close_foreach_func (G_GNUC_UNUSED GdaDataModel *model, const gchar *table_name, GdaVconnectionDataModel *cnc) { /*g_print ("---- FOREACH: Removing virtual table '%s'\n", table_name);*/ GError *lerror = NULL; if (! gda_vconnection_data_model_remove (cnc, table_name, &lerror)) { g_warning ("Internal GdaVproviderDataModel error: %s", lerror && lerror->message ? lerror->message : _("No detail")); g_clear_error (&lerror); } } static gboolean gda_vprovider_data_model_close_connection (GdaServerProvider *provider, GdaConnection *cnc) { g_return_val_if_fail (GDA_IS_VPROVIDER_DATA_MODEL (provider), FALSE); g_return_val_if_fail (GDA_IS_VCONNECTION_DATA_MODEL (cnc), FALSE); gda_vconnection_data_model_foreach (GDA_VCONNECTION_DATA_MODEL (cnc), (GdaVconnectionDataModelFunc) cnc_close_foreach_func, cnc); return GDA_SERVER_PROVIDER_CLASS (parent_class)->close_connection (GDA_SERVER_PROVIDER (provider), cnc); } static GObject * gda_vprovider_data_model_statement_execute (GdaServerProvider *provider, GdaConnection *cnc, GdaStatement *stmt, GdaSet *params, GdaStatementModelUsage model_usage, GType *col_types, GdaSet **last_inserted_row, guint *task_id, GdaServerProviderExecCallback async_cb, gpointer cb_data, GError **error) { GObject *retval; if (async_cb) { g_set_error (error, GDA_SERVER_PROVIDER_ERROR, GDA_SERVER_PROVIDER_METHOD_NON_IMPLEMENTED_ERROR, "%s", _("Provider does not support asynchronous statement execution")); return NULL; } _gda_vconnection_set_working_obj ((GdaVconnectionDataModel*) cnc, (GObject*) stmt); retval = GDA_SERVER_PROVIDER_CLASS (parent_class)->statement_execute (provider, cnc, stmt, params, model_usage, col_types, last_inserted_row, task_id, async_cb, cb_data, error); if (retval) { if (! GDA_IS_DATA_MODEL (retval)) _gda_vconnection_set_working_obj ((GdaVconnectionDataModel*) cnc, NULL); gchar *sql; sql = gda_statement_to_sql (stmt, params, NULL); if (sql) { gchar *ptr = NULL; /* look for DROP TABLE to signal table removal */ if (! g_ascii_strncasecmp (sql, "DROP", 4)) ptr = sql + 4; else if (! g_ascii_strncasecmp (sql, "CREATE", 6)) ptr = sql + 6; if (ptr) { /* skip spaces */ for (; *ptr && (g_ascii_isspace (*ptr) || (*ptr == '\n')); ptr++); if (! g_ascii_strncasecmp (ptr, "TABLE", 5)) { /* skip spaces */ gchar delim = 0; gchar *table_name, *quoted; for (ptr = ptr+5; *ptr && (g_ascii_isspace (*ptr) || (*ptr == '\n')); ptr++); if (*ptr == '\'') { delim = '\''; ptr++; } else if (*ptr == '"') { delim = '"'; ptr++; } table_name = ptr; if (delim) for (; *ptr && (*ptr != delim) ; ptr++); else for (; *ptr && g_ascii_isalnum (*ptr); ptr++); *ptr = 0; quoted = _gda_connection_compute_table_virtual_name (GDA_CONNECTION (cnc), table_name); /*g_print ("%s() emits the 'vtable-dropped' signal for table [%s]\n", __FUNCTION__, quoted);*/ g_signal_emit_by_name (cnc, "vtable-dropped", quoted); g_free (quoted); } } g_free (sql); } } else _gda_vconnection_set_working_obj ((GdaVconnectionDataModel*) cnc, NULL); return retval; } static const gchar * gda_vprovider_data_model_get_name (G_GNUC_UNUSED GdaServerProvider *provider) { return "Virtual"; } static int virtualCreate (sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlite3_vtab **ppVtab, char **pzErr) { GdaVconnectionDataModel *cnc = GDA_VCONNECTION_DATA_MODEL (pAux); GString *sql; gint i, ncols; gchar *spec_name, *tmp; GdaVConnectionTableData *td; GHashTable *hash; TRACE (NULL, NULL); /* find Spec */ g_assert (argc == 4); spec_name = g_strdup (argv[3]); i = strlen (spec_name); if (spec_name [i-1] == '\'') spec_name [i-1] = 0; if (*spec_name == '\'') memmove (spec_name, spec_name+1, i); td = _gda_vconnection_get_table_data_by_unique_name (cnc, spec_name); g_free (spec_name); if (!td) { /* wrong usage! */ *pzErr = SQLITE3_CALL (sqlite3_mprintf) (_("Wrong usage of Libgda's virtual tables")); return SQLITE_ERROR; } /* preparations */ if (td->spec->data_model) { ncols = gda_data_model_get_n_columns (td->spec->data_model); if (ncols <= 0) { *pzErr = SQLITE3_CALL (sqlite3_mprintf) (_("Data model must have at least one column")); return SQLITE_ERROR; } td->real_model = td->spec->data_model; g_object_ref (td->real_model); } GError *error = NULL; if (!td->columns && td->spec->create_columns_func) td->columns = td->spec->create_columns_func (td->spec, &error); if (!td->columns) { if (error && error->message) { int len = strlen (error->message) + 1; *pzErr = SQLITE3_CALL (sqlite3_malloc) (sizeof (gchar) * len); memcpy (*pzErr, error->message, len); /* Flawfinder: ignore */ } else *pzErr = SQLITE3_CALL (sqlite3_mprintf) (_("Could not compute virtual table's columns")); return SQLITE_ERROR; } ncols = g_list_length (td->columns); td->n_columns = ncols; /* create the CREATE TABLE statement */ sql = g_string_new ("CREATE TABLE "); tmp = gda_connection_quote_sql_identifier (GDA_CONNECTION (cnc), argv[2]); g_string_append (sql, tmp); g_free (tmp); g_string_append (sql, " ("); hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); for (i = 0; i < ncols; i++) { GdaColumn *column; const gchar *name, *type; GType gtype; gchar *newcolname, *tmp; if (i != 0) g_string_append (sql, ", "); column = g_list_nth_data (td->columns, i); if (!column) { *pzErr = SQLITE3_CALL (sqlite3_mprintf) (_("Can't get data model description for column %d"), i); g_string_free (sql, TRUE); return SQLITE_ERROR; } name = gda_column_get_name (column); if (!name || !(*name)) newcolname = g_strdup_printf ("_%d", i + 1); else { GString *string; gchar *ptr; string = g_string_new (""); newcolname = gda_sql_identifier_quote (name, GDA_CONNECTION (cnc), NULL, FALSE, FALSE); for (ptr = newcolname; *ptr; ptr++) { if ((*ptr == '_') || ((*ptr >= 'a') && (*ptr <= 'z')) || ((*ptr >= 'A') && (*ptr <= 'Z'))) g_string_append_c (string, *ptr); else if ((*ptr >= '0') && (*ptr <= '9')) { if (ptr == name) g_string_append_c (string, '_'); else g_string_append_c (string, *ptr); } else g_string_append_c (string, '_'); } g_free (newcolname); newcolname = g_string_free (string, FALSE); } tmp = g_ascii_strdown (newcolname, -1); if (g_hash_table_lookup (hash, tmp)) { gint i; g_free (tmp); for (i = 0; ; i++) { gchar *nc2; nc2 = g_strdup_printf ("%s%d", newcolname, i); tmp = g_ascii_strdown (nc2, -1); if (! g_hash_table_lookup (hash, tmp)) { g_free (newcolname); newcolname = nc2; break; } g_free (tmp); g_free (nc2); } } gtype = gda_column_get_g_type (column); type = g_type_name (gtype); if (!type) { *pzErr = SQLITE3_CALL (sqlite3_mprintf) (_("Can't get data model's column type for column %d"), i); g_string_free (sql, TRUE); return SQLITE_ERROR; } else if (gtype == GDA_TYPE_BLOB) type = "blob"; else if (gtype == GDA_TYPE_BINARY) type = "binary"; else if (gtype == G_TYPE_STRING) type = "string"; else if (gtype == G_TYPE_INT) type = "integer"; else if (gtype == G_TYPE_UINT) type = "unsigned integer"; else if ((gtype == G_TYPE_INT64) || (gtype == G_TYPE_LONG)) type = "int64"; else if ((gtype == G_TYPE_UINT64) || (gtype == G_TYPE_ULONG)) type = "uint64"; else if ((gtype == G_TYPE_DOUBLE) || (gtype == G_TYPE_FLOAT)) type = "real"; else if (gtype == G_TYPE_DATE) type = "date"; else if (gtype == GDA_TYPE_TIME) type = "time"; else if (gtype == GDA_TYPE_TIMESTAMP) type = "timestamp"; else if (gtype == GDA_TYPE_SHORT) type = "short"; else if (gtype == GDA_TYPE_USHORT) type = "unsigned short"; else type = "text"; g_string_append (sql, newcolname); g_hash_table_insert (hash, tmp, (gpointer) 0x01); g_free (newcolname); g_string_append_c (sql, ' '); g_string_append (sql, type); if (! gda_column_get_allow_null (column)) g_string_append (sql, " NOT NULL"); if (gtype == G_TYPE_STRING) g_string_append (sql, " COLLATE LOCALE"); } g_hash_table_destroy (hash); /* add a hidden column which contains the row number of the data model */ if (ncols != 0) g_string_append (sql, ", "); g_string_append (sql, "__gda_row_nb hidden integer"); g_string_append_c (sql, ')'); /* VirtualTable structure */ VirtualTable *vtable; vtable = g_new0 (VirtualTable, 1); vtable->cnc = cnc; vtable->td = td; vtable->rows_offset = 0; *ppVtab = &(vtable->base); if (SQLITE3_CALL (sqlite3_declare_vtab) (db, sql->str) != SQLITE_OK) { *pzErr = SQLITE3_CALL (sqlite3_mprintf) (_("Can't declare virtual table (%s)"), sql->str); g_string_free (sql, TRUE); g_free (vtable); *ppVtab = NULL; return SQLITE_ERROR; } /*g_print ("VIRTUAL TABLE [%p]: %s\n", vtable, sql->str);*/ g_string_free (sql, TRUE); return SQLITE_OK; } static int virtualConnect (sqlite3 *db, void *pAux, int argc, const char *const *argv, sqlite3_vtab **ppVtab, char **pzErr) { TRACE (NULL, NULL); return virtualCreate (db, pAux, argc, argv, ppVtab, pzErr); } static int virtualDisconnect (sqlite3_vtab *pVtab) { /*VirtualTable *vtable = (VirtualTable *) pVtab;*/ TRACE (pVtab, NULL); g_free (pVtab); return SQLITE_OK; } static int virtualDestroy (sqlite3_vtab *pVtab) { TRACE (pVtab, NULL); return virtualDisconnect (pVtab); } static int virtualOpen (G_GNUC_UNUSED sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) { VirtualCursor *cursor; /* create empty cursor */ cursor = g_new0 (VirtualCursor, 1); *ppCursor = (sqlite3_vtab_cursor*) cursor; TRACE (pVTab, cursor); return SQLITE_OK; } static int virtualClose (sqlite3_vtab_cursor *cur) { VirtualCursor *cursor = (VirtualCursor*) cur; /*VirtualTable *vtable = (VirtualTable*) cur->pVtab;*/ TRACE (cur->pVtab, cur); virtual_cursor_free (cursor); return SQLITE_OK; } static int virtualEof (sqlite3_vtab_cursor *cur) { VirtualCursor *cursor = (VirtualCursor*) cur; TRACE (cur->pVtab, cur); if (cursor->data->iter) return FALSE; else { if (cursor->row >= cursor->data->nrows) return TRUE; else return FALSE; } } /* * handle data model exceptions and return appropriate code */ static int handle_data_model_exception (sqlite3_vtab *pVtab, GdaDataModel *model) { GError **exceptions; gint i; exceptions = gda_data_model_get_exceptions (model); if (!exceptions) return SQLITE_OK; GError *trunc_error = NULL; GError *fatal_error = NULL; for (i = 0; exceptions [i]; i++) { GError *e; e = exceptions [i]; if ((e->domain == GDA_DATA_MODEL_ERROR) && (e->code == GDA_DATA_MODEL_TRUNCATED_ERROR)) trunc_error = e; else { fatal_error = e; break; } } if (fatal_error || trunc_error) { GError *e; e = fatal_error; if (!e) e = trunc_error; if (pVtab->zErrMsg) SQLITE3_CALL (sqlite3_free) (pVtab->zErrMsg); pVtab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf) ("%s", e->message ? e->message : _("No detail")); if (fatal_error) return SQLITE_ERROR; else return SQLITE_IOERR_TRUNCATE; } return SQLITE_OK; } static int virtualNext (sqlite3_vtab_cursor *cur) { VirtualCursor *cursor = (VirtualCursor*) cur; VirtualFilteredData *data; /*VirtualTable *vtable = (VirtualTable*) cur->pVtab;*/ TRACE (cur->pVtab, cur); data = cursor->data; if (!data) { cur->pVtab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf) (_("Internal SQLite error: no data to iterate on")); return SQLITE_MISUSE; } cursor->row ++; if (data->iter && (gda_data_model_iter_get_row (data->iter) < cursor->row)) { /* move to next row */ if (gda_data_model_iter_move_next (data->iter)) { int exc_res; exc_res = handle_data_model_exception (cur->pVtab, data->model); if (exc_res != SQLITE_OK) goto onerror; /* load data for row */ GSList *list; gint count; for (count = 0, list = ((GdaSet*) data->iter)->holders; list; count++, list = list->next) { GdaHolder *h = (GdaHolder*) list->data; GError *lerror = NULL; if (! gda_holder_is_valid_e (h, &lerror)) { GValue value = {0}; g_value_init (&value, G_TYPE_ERROR); g_value_take_boxed (&value, lerror); g_array_append_val (data->values_array, value); } else { const GValue *cvalue; cvalue = gda_holder_get_value (h); if (cvalue && (G_VALUE_TYPE (cvalue) != 0)) { GValue copy = {0}; g_value_init (©, G_VALUE_TYPE (cvalue)); g_value_copy (cvalue, ©); g_array_append_val (data->values_array, copy); } else { GValue value = {0}; g_value_init (&value, G_TYPE_ERROR); g_value_take_boxed (&value, lerror); g_array_append_val (data->values_array, value); } } } g_assert (count == data->ncols); } else { int exc_res; exc_res = handle_data_model_exception (cur->pVtab, data->model); if (exc_res != SQLITE_OK) goto onerror; else { /* end of data */ g_object_unref (data->iter); data->iter = NULL; data->nrows = cursor->row; } } } return SQLITE_OK; onerror: cursor->row--; g_object_unref (data->iter); data->iter = NULL; data->nrows = cursor->row; return SQLITE_ERROR; } /* * @cursor may be %NULL */ static const GValue * get_data_value (VirtualTable *vtable, VirtualCursor *cursor, gint row, gint64 rowid, gint col, GError **error) { VirtualFilteredData *data = NULL; const GValue *value = NULL; if ((col < 0) || (col >= vtable->td->n_columns)) { g_set_error (error, 0, 0, _("Column %d out of range (0-%d)"), col, vtable->td->n_columns - 1); return NULL; } if (cursor) { data = cursor->data; g_assert (data); } else { g_assert (row < 0); row = (gint) (rowid & 0xFFFFFFFF); g_assert (vtable->td->context.current_vcontext); guint i; GArray *values_array = vtable->td->context.current_vcontext->context_data; for (i = 0; i < values_array->len; i++) { VirtualFilteredData *vd; vd = g_array_index (values_array, VirtualFilteredData*, i); if (vd->rowid_offset == (guint32) (rowid >> 32)) { data = vd; break; } } } if (data) value = & g_array_index (data->values_array, GValue, row * data->ncols + col); if (!value) g_set_error (error, 0, 0, _("Could not find requested value at row %d and col %d"), row, col); #ifdef DEBUG_VCONTEXT g_print ("Read from [%s] [%s] %d x %d x %lld\n", vtable->td->table_name, gda_value_stringify (value), col, row, rowid); #endif return value; } static int virtualColumn (sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i) { VirtualCursor *cursor = (VirtualCursor*) cur; TRACE (cur->pVtab, cur); if (i == ((VirtualTable*) cur->pVtab)->td->n_columns) { /* private hidden column, which returns the row number */ SQLITE3_CALL (sqlite3_result_int) (ctx, cursor->row); return SQLITE_OK; } if (i >= cursor->data->ncols) { SQLITE3_CALL (sqlite3_result_text) (ctx, _("Column not found"), -1, SQLITE_TRANSIENT); return SQLITE_MISUSE; } const GValue *value; GError *lerror = NULL; value = get_data_value ((VirtualTable*) cur->pVtab, cursor, cursor->row, 0, i, &lerror); if (! value) { g_hash_table_insert (error_blobs_hash, lerror, GINT_TO_POINTER (1)); SQLITE3_CALL (sqlite3_result_blob) (ctx, lerror, sizeof (GError), NULL); } else if (G_VALUE_TYPE (value) == G_TYPE_ERROR) { GError *lerror; lerror = g_value_get_boxed (value); //SQLITE3_CALL (sqlite3_result_error) (ctx, lerror && lerror->message ? lerror->message : _("No detail"), -1); if (lerror) lerror = g_error_copy (lerror); g_hash_table_insert (error_blobs_hash, lerror, GINT_TO_POINTER (1)); SQLITE3_CALL (sqlite3_result_blob) (ctx, lerror, sizeof (GError), NULL); } else if (!value || gda_value_is_null (value)) SQLITE3_CALL (sqlite3_result_null) (ctx); else if (G_VALUE_TYPE (value) == G_TYPE_INT) SQLITE3_CALL (sqlite3_result_int) (ctx, g_value_get_int (value)); else if (G_VALUE_TYPE (value) == G_TYPE_INT64) SQLITE3_CALL (sqlite3_result_int64) (ctx, g_value_get_int64 (value)); else if (G_VALUE_TYPE (value) == G_TYPE_DOUBLE) SQLITE3_CALL (sqlite3_result_double) (ctx, g_value_get_double (value)); else if (G_VALUE_TYPE (value) == GDA_TYPE_BLOB) { GdaBlob *blob; GdaBinary *bin; blob = (GdaBlob *) gda_value_get_blob (value); bin = (GdaBinary *) blob; if (blob->op && (bin->binary_length != gda_blob_op_get_length (blob->op))) gda_blob_op_read_all (blob->op, blob); SQLITE3_CALL (sqlite3_result_blob) (ctx, blob->data.data, blob->data.binary_length, SQLITE_TRANSIENT); } else if (G_VALUE_TYPE (value) == GDA_TYPE_BINARY) { const GdaBinary *bin; bin = gda_value_get_binary (value); SQLITE3_CALL (sqlite3_result_blob) (ctx, bin->data, bin->binary_length, SQLITE_TRANSIENT); } else { gchar *str = gda_value_stringify (value); SQLITE3_CALL (sqlite3_result_text) (ctx, str, -1, SQLITE_TRANSIENT); g_free (str); } return SQLITE_OK; } static int virtualRowid (sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid) { VirtualCursor *cursor = (VirtualCursor*) cur; TRACE ((VirtualTable*) cur->pVtab, cur); *pRowid = ((sqlite_int64) cursor->row) + (((sqlite_int64) cursor->data->rowid_offset) << 32); return SQLITE_OK; } /* NEVER returns %NULL */ static GValue * create_value_from_sqlite3_value_notype (sqlite3_value *svalue) { GValue *value; value = g_new0 (GValue, 1); switch (SQLITE3_CALL (sqlite3_value_type) (svalue)) { case SQLITE_INTEGER: g_value_init (value, G_TYPE_INT64); g_value_set_int64 (value, SQLITE3_CALL (sqlite3_value_int64) (svalue)); break; case SQLITE_FLOAT: g_value_init (value, G_TYPE_DOUBLE); g_value_set_double (value, SQLITE3_CALL (sqlite3_value_double) (svalue)); break; case SQLITE_BLOB: { GdaBinary *bin; g_value_init (value, GDA_TYPE_BINARY); bin = g_new0 (GdaBinary, 1); bin->binary_length = SQLITE3_CALL (sqlite3_value_bytes) (svalue); if (bin->binary_length > 0) { bin->data = g_new (guchar, bin->binary_length); memcpy (bin->data, SQLITE3_CALL (sqlite3_value_blob) (svalue), bin->binary_length); } else bin->binary_length = 0; gda_value_take_binary (value, bin); break; } case SQLITE_NULL: gda_value_set_null (value); break; case SQLITE3_TEXT: default: g_value_init (value, G_TYPE_STRING); g_value_set_string (value, (gchar *) SQLITE3_CALL (sqlite3_value_text) (svalue)); break; } return value; } /* * Returns: a new array of @argc values, and %NULL if @argc = 0 */ static GValue ** create_gvalues_array_from_sqlite3_array (int argc, sqlite3_value **argv) { GValue **array; gint i; if (argc == 0) return NULL; array = g_new (GValue *, argc); for (i = 0; i < argc; i++) array[i] = create_value_from_sqlite3_value_notype (argv[i]); return array; } static void virtual_table_manage_real_data_model (VirtualTable *vtable, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) { /*g_print ("================== %s (VTable=> %p, %s)\n", __FUNCTION__, vtable, vtable->td->table_name);*/ if (!vtable->td->spec->create_filtered_model_func && !vtable->td->spec->create_model_func) return; if (vtable->td->real_model) { g_object_unref (vtable->td->real_model); vtable->td->real_model = NULL; } /* actual data model creation */ if (vtable->td->spec->create_filtered_model_func) { GValue **gargv; gargv = create_gvalues_array_from_sqlite3_array (argc, argv); vtable->td->real_model = vtable->td->spec->create_filtered_model_func (vtable->td->spec, idxNum, idxStr, argc, gargv); if (gargv) { gint i; for (i = 0; i < argc; i++) gda_value_free (gargv[i]); g_free (gargv); } } else if (vtable->td->spec->create_model_func) vtable->td->real_model = vtable->td->spec->create_model_func (vtable->td->spec); if (! vtable->td->real_model) return; /* columns if not yet created */ if (! vtable->td->columns && vtable->td->spec->create_columns_func) vtable->td->columns = vtable->td->spec->create_columns_func (vtable->td->spec, NULL); if (vtable->td->columns) { /* columns */ GList *list; guint i, ncols; ncols = gda_data_model_get_n_columns (vtable->td->real_model); g_assert (ncols == g_list_length (vtable->td->columns)); for (i = 0, list = vtable->td->columns; i < ncols; i++, list = list->next) { GdaColumn *mcol = gda_data_model_describe_column (vtable->td->real_model, i); GdaColumn *ccol = (GdaColumn*) list->data; if (gda_column_get_g_type (mcol) == GDA_TYPE_NULL) gda_column_set_g_type (mcol, gda_column_get_g_type (ccol)); } } #ifdef GDA_DEBUG_VIRTUAL g_print ("Created real model %p for table %s\n", vtable->td->real_model, vtable->td->table_name); #endif } static int virtualFilter (sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) { VirtualCursor *cursor = (VirtualCursor*) pVtabCursor; VirtualTable *vtable = (VirtualTable*) pVtabCursor->pVtab; TRACE (pVtabCursor->pVtab, pVtabCursor); #ifdef GDA_DEBUG_VIRTUAL g_print ("\tidxStr=[%s], idxNum=[%d]\n", idxStr, idxNum); #endif /* find a VirtualFilteredData corresponding to this filter */ VirtualFilteredData *data = NULL; g_assert (vtable->td->context.current_vcontext); GArray *values_array = vtable->td->context.current_vcontext->context_data; if (values_array->len > 0) { guint i; for (i = 0; i < values_array->len; i++) { VirtualFilteredData *vd; vd = g_array_index (values_array, VirtualFilteredData*, i); if (vd->reuseable && (vd->idxNum == idxNum) && (vd->argc == argc) && ((!idxStr && !vd->idxStr) || (idxStr && vd->idxStr && !strcmp (idxStr, vd->idxStr)))) { GValue **avalues; gint i; gboolean equal = TRUE; avalues = create_gvalues_array_from_sqlite3_array (argc, argv); for (i = 0; i < argc; i++) { GValue *v1, *v2; v1 = vd->argv [i]; v2 = avalues [i]; if (! ((!v1 && !v2) || (v1 && v2 && (G_VALUE_TYPE (v1) == G_VALUE_TYPE (v2)) && !gda_value_differ (v1, v2)))) { equal = FALSE; break; } } for (i = 0; i < argc; i++) { GValue *v2; v2 = avalues [i]; if (v2) gda_value_free (v2); } g_free (avalues); if (equal) { data = vd; break; } } } } #ifdef DEBUG_VCONTEXT if (data) g_print ("REUSE VData %p\n", data); #endif if (!data) { virtual_table_manage_real_data_model (vtable, idxNum, idxStr, argc, argv); if (! vtable->td->real_model) return SQLITE_ERROR; data = virtual_filtered_data_new (vtable, vtable->td->real_model, idxNum, idxStr, argc, argv); g_array_prepend_val (values_array, data); #ifdef DEBUG_VCONTEXT g_print ("VData %p prepended to array %p wt %d\n", data, values_array, values_array->len); #endif if (values_array->len > MAX_VDATA_NUMBER) { VirtualFilteredData *ldata; gint index; index = values_array->len - 1; ldata = g_array_index (values_array, VirtualFilteredData*, index); _gda_vconnection_virtual_filtered_data_unref (ldata); g_array_remove_index (values_array, index); } } if (cursor->data != data) { if (cursor->data) _gda_vconnection_virtual_filtered_data_unref (cursor->data); cursor->data = virtual_filtered_data_ref (data); } /* initialize cursor */ cursor->row = -1; return virtualNext (pVtabCursor); } #ifdef GDA_DEBUG_VIRTUAL static void index_info_dump (sqlite3_index_info *pIdxInfo, gboolean dump_out) { int nc; if (dump_out) { g_print ("Dump of OUT sqlite3_index_info [%p]\n", pIdxInfo); for (nc = 0; nc < pIdxInfo->nConstraint; nc++) { struct sqlite3_index_constraint_usage *cons; cons = &(pIdxInfo->aConstraintUsage[nc]); g_print ("sqlite3_index_constraint_usage[%d]\n", nc); g_print (" argvIndex=%d\n", cons->argvIndex); g_print (" omit=%d\n", cons->omit); } g_print ("idxNum=%d\n", pIdxInfo->idxNum); g_print ("orderByConsumed=%d\n", pIdxInfo->orderByConsumed); } else { g_print ("Dump of IN sqlite3_index_info [%p]\n", pIdxInfo); for (nc = 0; nc < pIdxInfo->nConstraint; nc++) { struct sqlite3_index_constraint *cons; cons = &(pIdxInfo->aConstraint[nc]); g_print ("sqlite3_index_constraint[%d]\n", nc); g_print (" iColumn=%d\n", cons->iColumn); g_print (" op=%d\n", cons->op); g_print (" usable=%d\n", cons->usable); g_print (" iTermOffset=%d\n", cons->iTermOffset); } for (nc = 0; nc < pIdxInfo->nOrderBy; nc++) { struct sqlite3_index_orderby *cons; cons = &(pIdxInfo->aOrderBy[nc]); g_print ("sqlite3_index_orderby[%d]\n", nc); g_print (" iColumn=%d\n", cons->iColumn); g_print (" desc=%d\n", cons->desc); } } } #endif static void map_sqlite3_info_to_gda_filter (sqlite3_index_info *info, GdaVconnectionDataModelFilter *filter) { memset (filter, 0, sizeof (GdaVconnectionDataModelFilter)); if (info->nConstraint > 0) { gint i, j; filter->aConstraint = g_new (struct GdaVirtualConstraint, info->nConstraint); filter->aConstraintUsage = g_new (struct GdaVirtualConstraintUsage, info->nConstraint); for (i = 0, j = 0; i < info->nConstraint; i++) { if (! info->aConstraint[i].usable) continue; switch (info->aConstraint[i].op) { case SQLITE_INDEX_CONSTRAINT_EQ: filter->aConstraint[j].op = GDA_SQL_OPERATOR_TYPE_EQ; break; case SQLITE_INDEX_CONSTRAINT_GT: filter->aConstraint[j].op = GDA_SQL_OPERATOR_TYPE_GT; break; case SQLITE_INDEX_CONSTRAINT_LE: filter->aConstraint[j].op = GDA_SQL_OPERATOR_TYPE_LEQ; break; case SQLITE_INDEX_CONSTRAINT_LT: filter->aConstraint[j].op = GDA_SQL_OPERATOR_TYPE_LT; break; case SQLITE_INDEX_CONSTRAINT_GE: filter->aConstraint[j].op = GDA_SQL_OPERATOR_TYPE_GEQ; break; case SQLITE_INDEX_CONSTRAINT_MATCH: filter->aConstraint[j].op = GDA_SQL_OPERATOR_TYPE_REGEXP; break; #if HAVE_SQLITE case SQLITE_INDEX_CONSTRAINT_LIKE: filter->aConstraint[j].op = GDA_SQL_OPERATOR_TYPE_LIKE; break; case SQLITE_INDEX_CONSTRAINT_GLOB: filter->aConstraint[j].op = GDA_SQL_OPERATOR_TYPE_GLOB; break; #endif default: g_assert_not_reached (); } filter->aConstraint[j].iColumn = info->aConstraint[i].iColumn; filter->aConstraintUsage[j].argvIndex = 0; filter->aConstraintUsage[j].omit = FALSE; j++; } filter->nConstraint = j; } filter->nOrderBy = info->nOrderBy; if (filter->nOrderBy > 0) { gint i; filter->aOrderBy = g_new (struct GdaVirtualOrderby, filter->nOrderBy); for (i = 0; i < info->nOrderBy; i++) { filter->aOrderBy[i].iColumn = info->aOrderBy[i].iColumn; filter->aOrderBy[i].desc = info->aOrderBy[i].desc ? TRUE : FALSE; } } filter->idxNum = 0; filter->idxPointer = NULL; filter->orderByConsumed = FALSE; filter->estimatedCost = info->estimatedCost; } /* * Also frees @filter's dynamically allocated parts */ static void map_consume_gda_filter_to_sqlite3_info (GdaVconnectionDataModelFilter *filter, sqlite3_index_info *info) { g_assert (filter->nConstraint == info->nConstraint); if (info->nConstraint > 0) { gint i, j; for (i = 0, j = 0; i < info->nConstraint; i++) { if (! info->aConstraint[i].usable) continue; info->aConstraintUsage[i].argvIndex = filter->aConstraintUsage[j].argvIndex; info->aConstraintUsage[i].omit = filter->aConstraintUsage[j].omit ? 1 : 0; j++; } g_free (filter->aConstraint); g_free (filter->aConstraintUsage); } if (filter->nOrderBy > 0) g_free (filter->aOrderBy); info->idxNum = filter->idxNum; info->idxStr = (char*) filter->idxPointer; info->needToFreeIdxStr = 0; info->orderByConsumed = filter->orderByConsumed ? 1 : 0; info->estimatedCost = filter->estimatedCost; } static int virtualBestIndex (sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo) { VirtualTable *vtable = (VirtualTable *) tab; TRACE (tab, NULL); #ifdef GDA_DEBUG_VIRTUAL index_info_dump (pIdxInfo, FALSE); #endif if (vtable->td->spec->create_filter_func) { GdaVconnectionDataModelFilter filter; map_sqlite3_info_to_gda_filter (pIdxInfo, &filter); vtable->td->spec->create_filter_func (vtable->td->spec, &filter); map_consume_gda_filter_to_sqlite3_info (&filter, pIdxInfo); #ifdef GDA_DEBUG_VIRTUAL index_info_dump (pIdxInfo, TRUE); #endif } return SQLITE_OK; } /* * * Returns: >= 0 if Ok, -1 on error */ static gint param_name_to_number (gint maxrows, const gchar *str) { gchar *endptr [1]; long int i; i = strtol (str, endptr, 10); if ((**endptr == '\0') && (i < maxrows) && (i >= 0)) return i; else return -1; } /* * optype value: see virtualUpdate() */ static int update_data_select_model (sqlite3_vtab *tab, gint optype, int nData, sqlite3_value **apData) { VirtualTable *vtable = (VirtualTable *) tab; /* determine parameters required to execute MOD statement */ GdaStatement *stmt = NULL; ParamType ptype; switch (optype) { case 1: ptype = PARAMS_DELETE; if (! vtable->td->modif_stmt [ptype]) g_object_get (vtable->td->real_model, "delete-stmt", &stmt, NULL); break; case 2: ptype = PARAMS_INSERT; if (! vtable->td->modif_stmt [ptype]) g_object_get (vtable->td->real_model, "insert-stmt", &stmt, NULL); break; case 3: ptype = PARAMS_UPDATE; if (! vtable->td->modif_stmt [ptype]) g_object_get (vtable->td->real_model, "update-stmt", &stmt, NULL); break; default: g_assert_not_reached (); } if (! vtable->td->modif_stmt [ptype]) { if (! stmt) { tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf) (_("No statement specified to modify the data")); return SQLITE_READONLY; } GdaSet *params; if (! gda_statement_get_parameters (stmt, ¶ms, NULL) || !params) { tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf) (_("Invalid statement specified to modify the data")); g_object_unref (stmt); return SQLITE_READONLY; } vtable->td->modif_stmt [ptype] = stmt; vtable->td->modif_params [ptype] = params; } stmt = vtable->td->modif_stmt [ptype]; /* bind parameters */ GSList *list; for (list = vtable->td->modif_params [ptype]->holders; list; list = list->next) { const gchar *id; GdaHolder *holder = GDA_HOLDER (list->data); gboolean holder_value_set = FALSE; id = gda_holder_get_id (holder); if (!id) { tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf) (_("Invalid parameter in statement to modify the data")); return SQLITE_READONLY; } if (*id == '+' && id[1]) { long int i; i = param_name_to_number (vtable->td->n_columns, id+1); if (i >= 0) { GType type; GValue *value; type = gda_column_get_g_type (gda_data_model_describe_column (vtable->td->real_model, i)); if ((type != GDA_TYPE_NULL) && SQLITE3_CALL (sqlite3_value_text) (apData [i+2])) value = gda_value_new_from_string ((const gchar*) SQLITE3_CALL (sqlite3_value_text) (apData [i+2]), type); else value = gda_value_new_null (); if (gda_holder_take_value (holder, value, NULL)) holder_value_set = TRUE; } } else if (*id == '-') { gint64 rowid = SQLITE3_CALL (sqlite3_value_int64) (apData [0]); long int i; const GValue *value; i = param_name_to_number (vtable->td->n_columns, id+1); value = get_data_value (vtable, NULL, -1, rowid, i, NULL); if (value && gda_holder_set_value (holder, value, NULL)) holder_value_set = TRUE; } if (! holder_value_set) { GdaSet *exec_set; GdaHolder *eh; g_object_get (vtable->td->real_model, "exec-params", &exec_set, NULL); if (! exec_set) { /* can't give value to param named @id */ tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf) (_("Invalid parameter in statement to modify the data")); return SQLITE_READONLY; } eh = gda_set_get_holder (exec_set, id); if (! eh || ! gda_holder_set_bind (holder, eh, NULL)) { /* can't give value to param named @id */ tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf) (_("Invalid parameter in statement to modify the data")); return SQLITE_READONLY; } } } GdaConnection *cnc; cnc = gda_data_select_get_connection (GDA_DATA_SELECT (vtable->td->real_model)); GError *lerror = NULL; #ifdef GDA_DEBUG_NO gchar *sql; sql = gda_statement_to_sql (stmt, NULL, NULL); g_print ("SQL: [%s] ", sql); g_free (sql); sql = gda_statement_to_sql_extended (stmt, cnc, vtable->td->modif_params [ptype], GDA_STATEMENT_SQL_PRETTY, NULL, &lerror); if (sql) { g_print ("With params: [%s]\n", sql); g_free (sql); } else { g_print ("params ERROR [%s]\n", lerror && lerror->message ? lerror->message : "No detail"); } g_clear_error (&lerror); #endif if (!cnc || (gda_connection_statement_execute_non_select (cnc, stmt, vtable->td->modif_params [ptype], NULL, &lerror) == -1)) { /* failed to execute */ tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf) (_("Failed to modify data: %s"), lerror && lerror->message ? lerror->message : _("No detail")); g_clear_error (&lerror); return SQLITE_READONLY; } return SQLITE_OK; } /* * apData[0] apData[1] apData[2..] * * INTEGER DELETE * * INTEGER NULL (nCol args) UPDATE (do not set rowid) * INTEGER INTEGER (nCol args) UPDATE (with SET rowid = ) * * NULL NULL (nCol args) INSERT INTO (automatic rowid value) * NULL INTEGER (nCol args) INSERT (incl. rowid value) */ static int virtualUpdate (sqlite3_vtab *tab, int nData, sqlite3_value **apData, sqlite_int64 *pRowid) { VirtualTable *vtable = (VirtualTable *) tab; const gchar *api_misuse_error = NULL; gint optype; /* 1 => DELETE * 2 => INSERT * 3 => UPDATE */ TRACE (tab, NULL); g_assert (vtable->td->context.current_vcontext); GArray *values_array = vtable->td->context.current_vcontext->context_data; if (values_array) { guint i; for (i = 0; i < values_array->len; i++) { VirtualFilteredData *data; data = g_array_index (values_array, VirtualFilteredData*, i); data->reuseable = FALSE; } } /* determine operation type */ if (nData == 1) optype = 1; else if ((nData > 1) && (SQLITE3_CALL (sqlite3_value_type) (apData[0]) == SQLITE_NULL)) { optype = 2; if (SQLITE3_CALL (sqlite3_value_type) (apData[1]) != SQLITE_NULL) { /* argc>1 and argv[0] is not NULL: rowid is imposed by SQLite * which is not supported */ return SQLITE_READONLY; } } else if ((nData > 1) && (SQLITE3_CALL (sqlite3_value_type) (apData[0]) == SQLITE_INTEGER)) { optype = 3; if (SQLITE3_CALL (sqlite3_value_int) (apData[0]) != SQLITE3_CALL (sqlite3_value_int) (apData[1])) { /* argc>1 and argv[0]==argv[1]: rowid is imposed by SQLite * which is not supported */ return SQLITE_READONLY; } } else return SQLITE_MISUSE; /* handle data model */ if (! vtable->td->real_model) { virtual_table_manage_real_data_model (vtable, -1, NULL, 0, NULL); if (! vtable->td->real_model) return SQLITE_ERROR; } GdaDataModelAccessFlags access_flags; access_flags = gda_data_model_get_access_flags (vtable->td->real_model); if (((optype == 1) && ! (access_flags & GDA_DATA_MODEL_ACCESS_DELETE)) || ((optype == 2) && ! (access_flags & GDA_DATA_MODEL_ACCESS_INSERT)) || ((optype == 3) && ! (access_flags & GDA_DATA_MODEL_ACCESS_UPDATE))) { /* we can't use vtable->td->real_model because it can't be accessed correctly */ if (GDA_IS_DATA_SELECT (vtable->td->real_model)) return update_data_select_model (tab, optype, nData, apData); else { TO_IMPLEMENT; tab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf) (_("Data model representing the table is read only")); return SQLITE_READONLY; } } /* REM: when using the values of apData[], the limit is * (nData -1 ) and not nData because the last column of the corresponding CREATE TABLE ... * is an internal hidden field which does not correspond to any column of the real data model */ if (optype == 1) { /* DELETE */ if (SQLITE3_CALL (sqlite3_value_type) (apData[0]) == SQLITE_INTEGER) { gint rowid = SQLITE3_CALL (sqlite3_value_int) (apData [0]); return gda_data_model_remove_row (vtable->td->real_model, rowid, NULL) ? SQLITE_OK : SQLITE_READONLY; } else { api_misuse_error = "argc==1 and argv[0] is not an integer"; goto api_misuse; } } else if (optype == 2) { /* INSERT */ gint newrow, i; GList *values = NULL; for (i = 2; i < (nData - 1); i++) { GType type; GValue *value; type = gda_column_get_g_type (gda_data_model_describe_column (vtable->td->real_model, i - 2)); if ((type != GDA_TYPE_NULL) && SQLITE3_CALL (sqlite3_value_text) (apData [i])) value = gda_value_new_from_string ((const gchar*) SQLITE3_CALL (sqlite3_value_text) (apData [i]), type); else value = gda_value_new_null (); /*g_print ("TXT #%s# => value %p (type=%s) apData[]=%p\n", SQLITE3_CALL (sqlite3_value_text) (apData [i]), value, g_type_name (type), apData[i]);*/ values = g_list_prepend (values, value); } values = g_list_reverse (values); newrow = gda_data_model_append_values (vtable->td->real_model, values, NULL); g_list_foreach (values, (GFunc) gda_value_free, NULL); g_list_free (values); if (newrow < 0) return SQLITE_READONLY; *pRowid = newrow; } else if (optype == 3) { /* UPDATE */ gint i; for (i = 2; i < (nData - 1); i++) { GValue *value; GType type; gint rowid = SQLITE3_CALL (sqlite3_value_int) (apData [0]); gboolean res; GError *error = NULL; /*g_print ("%d => %s\n", i, SQLITE3_CALL (sqlite3_value_text) (apData [i]));*/ type = gda_column_get_g_type (gda_data_model_describe_column (vtable->td->real_model, i - 2)); value = gda_value_new_from_string ((const gchar*) SQLITE3_CALL (sqlite3_value_text) (apData [i]), type); res = gda_data_model_set_value_at (vtable->td->real_model, i - 2, rowid, value, &error); gda_value_free (value); if (!res) { /*g_print ("Error: %s\n", error && error->message ? error->message : "???");*/ g_clear_error (&error); return SQLITE_READONLY; } } return SQLITE_OK; } else { api_misuse_error = "argc>1 and argv[0] is not NULL and not an integer"; goto api_misuse; } return SQLITE_OK; api_misuse: g_warning ("Error in the xUpdate SQLite's virtual method: %s\n" "this is an SQLite error, please report it", api_misuse_error); return SQLITE_ERROR; } static int virtualBegin (G_GNUC_UNUSED sqlite3_vtab *tab) { VirtualTable *vtable = (VirtualTable *) tab; TRACE (tab, NULL); if (vtable->locked) return SQLITE_ERROR; else { vtable->locked = TRUE; return SQLITE_OK; } } static int virtualSync (G_GNUC_UNUSED sqlite3_vtab *tab) { TRACE (tab, NULL); /* no documentation currently available, don't do anything */ return SQLITE_OK; } static int virtualCommit (G_GNUC_UNUSED sqlite3_vtab *tab) { VirtualTable *vtable = (VirtualTable *) tab; TRACE (tab, NULL); vtable->locked = FALSE; return SQLITE_OK; } static int virtualRollback (G_GNUC_UNUSED sqlite3_vtab *tab) { VirtualTable *vtable = (VirtualTable *) tab; TRACE (tab, NULL); vtable->locked = FALSE; return SQLITE_OK; } static int virtualRename (G_GNUC_UNUSED sqlite3_vtab *pVtab, G_GNUC_UNUSED const char *zNew) { TRACE (pVtab, NULL); /* not yet analysed and implemented */ return SQLITE_OK; }