/** * gda_xa_transaction_begin: * @xa_trans: a #GdaXaTransaction object * @error: (nullable): a place to store errors, or %NULL * * Begins a distributed transaction (managed by @xa_trans). Please note that this phase may fail * for some connections if a (normal) transaction is already started (this depends on the database * provider being used), so it's better to avoid starting any (normal) transaction on any of the * connections registered with @xa_trans. * * Returns: TRUE if no error occurred */ gboolean gda_xa_transaction_begin (GdaXaTransaction *xa_trans, GError **error) { GList *list; g_return_val_if_fail (GDA_IS_XA_TRANSACTION (xa_trans), FALSE); GdaXaTransactionPrivate *priv = gda_xa_transaction_get_instance_private (xa_trans); for (list = priv->cnc_list; list; list = list->next) { GdaConnection *cnc; GdaServerProvider *prov; cnc = GDA_CONNECTION (list->data); prov = gda_connection_get_provider (cnc); if (cnc != priv->non_xa_cnc) { GdaBinary *branch; branch = g_hash_table_lookup (priv->cnc_hash, cnc); memcpy (priv->xid.data + priv->xid.gtrid_length, /* Flawfinder: ignore */ gda_binary_get_data (branch), gda_binary_get_size (branch)); if (! _gda_server_provider_xa_start (prov, cnc, &(priv->xid), error)) break; } else { /* do a simple BEGIN */ if (! gda_connection_begin_transaction (cnc, NULL, GDA_TRANSACTION_ISOLATION_UNKNOWN, error)) break; } } if (list) { /* something went wrong */ for (; list; list = list->prev) { GdaConnection *cnc; GdaServerProvider *prov; cnc = GDA_CONNECTION (list->data); prov = gda_connection_get_provider (cnc); if (cnc != priv->non_xa_cnc) { GdaBinary *branch; branch = g_hash_table_lookup (priv->cnc_hash, cnc); memcpy (priv->xid.data + priv->xid.gtrid_length, /* Flawfinder: ignore */ gda_binary_get_data (branch), gda_binary_get_size (branch)); _gda_server_provider_xa_rollback (prov, cnc, &(priv->xid), NULL); } else { /* do a simple ROLLBACK */ gda_connection_rollback_transaction (cnc, NULL, NULL); } } return FALSE; } return TRUE; }
int main (int argc, char** argv) { gchar *file; GdaBinary* bin; gchar *bin_data; gsize bin_length; GError *error = NULL; /* load binary data */ file = g_build_filename (CHECK_FILES, "data", "sales_test.db", NULL); if (! g_file_get_contents (file, &bin_data, &bin_length, &error)) { g_print ("Error reading binary file: %s\n", error->message); return EXIT_FAILURE; } g_free (file); bin = gda_binary_new (); gda_binary_set_data (bin, (guchar*) bin_data, bin_length); /* convert to string */ gchar *conv1; conv1 = gda_binary_to_string (bin, 0); /* convert back to binary */ GdaBinary *bin2; bin2 = gda_string_to_binary (conv1); /* compare bin */ if (gda_binary_get_size (bin) != gda_binary_get_size (bin2)) { g_print ("Error: binary length differ: from %ld to %ld\n", gda_binary_get_size (bin), gda_binary_get_size (bin2)); return EXIT_FAILURE; } gint i; for (i = 0; i < gda_binary_get_size (bin); i++) { guchar *buffer = gda_binary_get_data (bin); guchar *buffer2 = gda_binary_get_data (bin2); if (buffer[i] != buffer2[i]) { g_print ("Error: binary differ orig[%d]=%d and copy[%d]=%d\n", i, buffer[i], i, buffer2[i]); return EXIT_FAILURE; } } g_free (conv1); gda_binary_free (bin); gda_binary_free (bin2); g_print ("Ok (file size: %d)\n", bin_length); return EXIT_SUCCESS; }
/** * gda_xa_transaction_rollback: * @xa_trans: a #GdaXaTransaction object * @error: (nullable): a place to store errors, or %NULL * * Cancels a distributed transaction (managed by @xa_trans). * * Returns: %TRUE if no error occurred */ gboolean gda_xa_transaction_rollback (GdaXaTransaction *xa_trans, GError **error) { GList *list; g_return_val_if_fail (GDA_IS_XA_TRANSACTION (xa_trans), FALSE); GdaXaTransactionPrivate *priv = gda_xa_transaction_get_instance_private (xa_trans); for (list = priv->cnc_list; list; list = list->next) { GdaConnection *cnc; GdaServerProvider *prov; cnc = GDA_CONNECTION (list->data); prov = gda_connection_get_provider (cnc); if (cnc == priv->non_xa_cnc) gda_connection_rollback_transaction (cnc, NULL, NULL); else { GdaBinary *branch; branch = g_hash_table_lookup (priv->cnc_hash, cnc); memcpy (priv->xid.data + priv->xid.gtrid_length, /* Flawfinder: ignore */ gda_binary_get_data (branch), gda_binary_get_size (branch)); GError *lerror = NULL; _gda_server_provider_xa_rollback (prov, cnc, &(priv->xid), &lerror); if (error && !*error) g_propagate_error (error, lerror); else g_clear_error (&lerror); } } return TRUE; }
/* * Blob read request */ static glong gda_sqlite_blob_op_read (GdaBlobOp *op, GdaBlob *blob, glong offset, glong size) { GdaSqliteBlobOp *bop; GdaBinary *bin; gpointer buffer = NULL; int rc; g_return_val_if_fail (GDA_IS_SQLITE_BLOB_OP (op), -1); bop = GDA_SQLITE_BLOB_OP (op); GdaSqliteBlobOpPrivate *priv = gda_sqlite_blob_op_get_instance_private (bop); g_return_val_if_fail (priv->sblob, -1); if (offset >= G_MAXINT) return -1; g_return_val_if_fail (blob, -1); if (offset > G_MAXINT) return -1; if (size > G_MAXINT) return -1; GdaSqliteProvider *prov = g_weak_ref_get (&priv->provider); g_return_val_if_fail (prov != NULL, -1); bin = gda_blob_get_binary (blob); gda_binary_set_data (bin, (guchar*) "", 0); /* fetch blob data using C API into bin->data, and set bin->binary_length */ int rsize; int len; len = SQLITE3_CALL (prov, sqlite3_blob_bytes) (priv->sblob); if (len < 0){ g_object_unref (prov); return -1; } else if (len == 0) { g_object_unref (prov); return 0; } rsize = (int) size; if (offset >= len) { g_object_unref (prov); return -1; } if (len - offset < rsize) rsize = len - offset; rc = SQLITE3_CALL (prov, sqlite3_blob_read) (priv->sblob, buffer, rsize, offset); if (rc != SQLITE_OK) { gda_binary_reset_data (bin); g_object_unref (prov); return -1; } gda_binary_set_data (bin, buffer, rsize); g_object_unref (prov); return gda_binary_get_size (bin); }
static glong gda_dir_blob_op_write (GdaBlobOp *op, GdaBlob *blob, glong offset) { GdaDirBlobOp *dirop; GdaBinary *bin; FILE *file; glong nbwritten; g_return_val_if_fail (GDA_IS_DIR_BLOB_OP (op), -1); dirop = GDA_DIR_BLOB_OP (op); GdaDirBlobOpPrivate *priv = gda_dir_blob_op_get_instance_private (dirop); if (offset >= G_MAXINT) return -1; g_return_val_if_fail (blob, -1); /* open file */ file = fopen (priv->complete_filename, "w+b"); /* Flawfinder: ignore */ if (!file) return -1; /* go to offset */ if (offset > 0) { if (fseek (file, offset, SEEK_SET) != 0) { fclose (file); return -1; } } if (gda_blob_get_op (blob) && (gda_blob_get_op (blob) != op)) { /* use data through blob->op */ #define buf_size 16384 gint nread = 0; GdaBlob *tmpblob = gda_blob_new (); gda_blob_set_op (tmpblob, gda_blob_get_op (blob)); nbwritten = 0; for (nread = gda_blob_op_read (gda_blob_get_op (tmpblob), tmpblob, 0, buf_size); nread > 0; nread = gda_blob_op_read (gda_blob_get_op (tmpblob), tmpblob, nbwritten, buf_size)) { GdaBinary *bin = gda_blob_get_binary (tmpblob); glong tmp_written; tmp_written = fwrite ((char *) gda_binary_get_data (bin), sizeof (guchar), gda_binary_get_size (bin), file); if (tmp_written < gda_binary_get_size (bin)) { /* error writing stream */ fclose (file); gda_blob_free (tmpblob); return -1; } nbwritten += tmp_written; if (nread < buf_size) /* nothing more to read */ break; } fclose (file); gda_blob_free (tmpblob); } else { bin = (GdaBinary *) blob; nbwritten = fwrite ((char *) (gda_binary_get_data (bin)), 1, gda_binary_get_size (bin), file); fclose (file); } return (nbwritten >= 0) ? nbwritten : -1; }
/** * gda_xa_transaction_commit_recovered: * @xa_trans: a #GdaXaTransaction object * @cnc_to_recover: (nullable) (element-type Gda.Connection) (out callee-allocates): a place to store the list of connections for which the there were data to recover and which failed to be actually committed, or %NULL * @error: (nullable): a place to store errors, or %NULL * * Tries to commit the data prepared but which failed to commit (see gda_xa_transaction_commit()). This * method allows one to terminate a distributed transaction which succeeded but for which some * connections needed to be recovered. * * Returns: %TRUE if all the data which was still uncommitted has been committed */ gboolean gda_xa_transaction_commit_recovered (GdaXaTransaction *xa_trans, GSList **cnc_to_recover, GError **error) { GList *list; gboolean retval = TRUE; if (cnc_to_recover) *cnc_to_recover = NULL; g_return_val_if_fail (GDA_IS_XA_TRANSACTION (xa_trans), FALSE); GdaXaTransactionPrivate *priv = gda_xa_transaction_get_instance_private (xa_trans); for (list = priv->cnc_list; list; list = list->next) { GdaConnection *cnc; GdaServerProvider *prov; cnc = GDA_CONNECTION (list->data); prov = gda_connection_get_provider (cnc); if (cnc == priv->non_xa_cnc) continue; else { GList *recov_xid_list; GdaBinary *branch; GList *xlist; gboolean commit_needed = FALSE; recov_xid_list = _gda_server_provider_xa_recover (prov, cnc, error); if (!recov_xid_list) continue; branch = g_hash_table_lookup (priv->cnc_hash, cnc); memcpy (priv->xid.data + priv->xid.gtrid_length, /* Flawfinder: ignore */ gda_binary_get_data (branch), gda_binary_get_size (branch)); for (xlist = recov_xid_list; xlist; xlist = xlist->next) { GdaXaTransactionId *xid = (GdaXaTransactionId*) xlist->data; if (!xid) /* ignore unknown XID format */ continue; if (!commit_needed && (xid->format == priv->xid.format) && (xid->gtrid_length == priv->xid.gtrid_length) && (xid->bqual_length == priv->xid.bqual_length) && ! memcmp (priv->xid.data, xid->data, xid->bqual_length + xid->bqual_length)) /* found a transaction to commit */ commit_needed = TRUE; g_free (xid); } g_list_free (recov_xid_list); if (commit_needed) { retval = _gda_server_provider_xa_commit (prov, cnc, &(priv->xid), error); if (!retval) if (cnc_to_recover) *cnc_to_recover = g_slist_prepend (*cnc_to_recover, cnc); } } } return retval; }
/** * gda_xa_transaction_commit: * @xa_trans: a #GdaXaTransaction object * @cnc_to_recover: (nullable) (element-type Gda.Connection) (out callee-allocates): a place to store the list of connections for which the commit phase failed, or %NULL * @error: a place to store errors, or %NULL * * Commits a distributed transaction (managed by @xa_trans). The commit is composed of two phases: * <itemizedlist> * <listitem><para>a PREPARE phase where all the connections are required to store their transaction data to a * permanent place (to be able to complete the commit should a problem occur afterwards)</para></listitem> * <listitem><para>a COMMIT phase where the transaction data is actually written to the database</para></listitem> * </itemizedlist> * * If the PREPARE phase fails for any of the connection registered with @xa_trans, then the distributed commit * fails and FALSE is returned. During the COMMIT phase, some commit may actually fail but the transaction can * still be completed because the PREPARE phase succeeded (through the recover method). * * Returns: TRUE if no error occurred (there may be some connections to recover, though) */ gboolean gda_xa_transaction_commit (GdaXaTransaction *xa_trans, GSList **cnc_to_recover, GError **error) { GList *list; if (cnc_to_recover) *cnc_to_recover = NULL; g_return_val_if_fail (GDA_IS_XA_TRANSACTION (xa_trans), FALSE); GdaXaTransactionPrivate *priv = gda_xa_transaction_get_instance_private (xa_trans); /* * PREPARE phase */ for (list = priv->cnc_list; list; list = list->next) { GdaConnection *cnc = NULL; GdaServerProvider *prov; GdaBinary *branch; if (cnc == priv->non_xa_cnc) continue; cnc = GDA_CONNECTION (list->data); prov = gda_connection_get_provider (cnc); branch = g_hash_table_lookup (priv->cnc_hash, cnc); memcpy (priv->xid.data + priv->xid.gtrid_length, /* Flawfinder: ignore */ gda_binary_get_data (branch), gda_binary_get_size (branch)); if (!_gda_server_provider_xa_end (prov, cnc, &(priv->xid), error)) break; if (!_gda_server_provider_xa_prepare (prov, cnc, &(priv->xid), error)) break; } if (list) { /* something went wrong during the PREPARE phase => rollback everything */ for (; list; list = list->prev) { GdaConnection *cnc = NULL; GdaServerProvider *prov; if (cnc == priv->non_xa_cnc) gda_connection_rollback_transaction (cnc, NULL, NULL); else { GdaBinary *branch; cnc = GDA_CONNECTION (list->data); prov = gda_connection_get_provider (cnc); branch = g_hash_table_lookup (priv->cnc_hash, cnc); memcpy (priv->xid.data + priv->xid.gtrid_length, /* Flawfinder: ignore */ gda_binary_get_data (branch), gda_binary_get_size (branch)); _gda_server_provider_xa_rollback (prov, cnc, &(priv->xid), NULL); } } return FALSE; } /* * COMMIT phase */ if (priv->non_xa_cnc && ! gda_connection_commit_transaction (priv->non_xa_cnc, NULL, error)) { /* something went wrong => rollback everything */ for (list = priv->cnc_list; list; list = list->next) { GdaConnection *cnc = NULL; GdaServerProvider *prov; if (cnc == priv->non_xa_cnc) gda_connection_rollback_transaction (cnc, NULL, NULL); else { GdaBinary *branch; cnc = GDA_CONNECTION (list->data); prov = gda_connection_get_provider (cnc); branch = g_hash_table_lookup (priv->cnc_hash, cnc); memcpy (priv->xid.data + priv->xid.gtrid_length, /* Flawfinder: ignore */ gda_binary_get_data (branch), gda_binary_get_size (branch)); _gda_server_provider_xa_rollback (prov, cnc, &(priv->xid), NULL); } } return FALSE; } for (list = priv->cnc_list; list; list = list->next) { GdaConnection *cnc = NULL; GdaServerProvider *prov; GdaBinary *branch; if (cnc == priv->non_xa_cnc) continue; cnc = GDA_CONNECTION (list->data); prov = gda_connection_get_provider (cnc); branch = g_hash_table_lookup (priv->cnc_hash, cnc); memcpy (priv->xid.data + priv->xid.gtrid_length, /* Flawfinder: ignore */ gda_binary_get_data (branch), gda_binary_get_size (branch)); if (! _gda_server_provider_xa_commit (prov, cnc, &(priv->xid), error) && cnc_to_recover) *cnc_to_recover = g_slist_prepend (*cnc_to_recover, cnc); } return TRUE; }
/* * Blob write request */ static glong gda_sqlite_blob_op_write (GdaBlobOp *op, GdaBlob *blob, glong offset) { GdaSqliteBlobOp *bop; GdaBinary *bin; glong nbwritten = -1; int len; g_return_val_if_fail (GDA_IS_SQLITE_BLOB_OP (op), -1); bop = GDA_SQLITE_BLOB_OP (op); GdaSqliteBlobOpPrivate *priv = gda_sqlite_blob_op_get_instance_private (bop); g_return_val_if_fail (priv->sblob, -1); g_return_val_if_fail (blob, -1); GdaSqliteProvider *prov = g_weak_ref_get (&priv->provider); g_return_val_if_fail (prov != NULL, -1); len = SQLITE3_CALL (prov, sqlite3_blob_bytes) (priv->sblob); if (len < 0) { g_object_unref (prov); return -1; } if (gda_blob_get_op (blob) && (gda_blob_get_op (blob) != op)) { /* use data through blob->op */ #define buf_size 16384 gint nread = 0; GdaBlob *tmpblob = gda_blob_new (); gda_blob_set_op (tmpblob, gda_blob_get_op (blob)); nbwritten = 0; for (nread = gda_blob_op_read (gda_blob_get_op (tmpblob), tmpblob, nbwritten, buf_size); nread > 0; nread = gda_blob_op_read (gda_blob_get_op (tmpblob), tmpblob, nbwritten, buf_size)) { int tmp_written; int rc; int wlen; if (nread + offset + nbwritten > len) wlen = len - offset - nbwritten; else wlen = nread; rc = SQLITE3_CALL (prov, sqlite3_blob_write) (priv->sblob, gda_binary_get_data (gda_blob_get_binary (tmpblob)), wlen, offset + nbwritten); if (rc != SQLITE_OK) tmp_written = -1; else tmp_written = wlen; if (tmp_written < 0) { /* treat error */ gda_blob_free ((gpointer) tmpblob); g_object_unref (prov); return -1; } nbwritten += tmp_written; if (nread < buf_size) /* nothing more to read */ break; } gda_blob_free ((gpointer) tmpblob); } else { /* write blob using bin->data and bin->binary_length */ int rc; int wlen; bin = gda_blob_get_binary (blob); if (gda_binary_get_size (bin) + offset > len) wlen = len - offset; else wlen = gda_binary_get_size (bin); rc = SQLITE3_CALL (prov, sqlite3_blob_write) (priv->sblob, gda_binary_get_data (bin), wlen, offset); if (rc != SQLITE_OK) nbwritten = -1; else nbwritten = wlen; } g_object_unref (prov); return nbwritten; }