Skip to content

Commit 84d7dda

Browse files
committed
CDRIVER-2558 prohibit per-operation options in txn
Users cannot pass read concern, write concern, or read preference to "with_opts" functions in a transaction. To prevent this, keep the user's read preference separate from the inherited read preference until it can be checked in _mongoc_client_command_with_opts(). Most drivers don't allow these 3 options to be set per-operation, so the Transactions Spec doesn't test that setting them is prohibited in a transaction. The new test file opts.yml uses the Transaction Spec's test format for C Driver-specific tests of this scenario. Also implement CDRIVER-2704, included upsertedCount in replies from mongoc_collection_update_one, update_many, and replace_one: it's useful and allows us to use standard CRUD test specifications. Update our test runner to use CRUD functions like mongoc_collection_insert_one instead of using mongoc_bulk_operation_t for all writes.
1 parent 5ba1986 commit 84d7dda

19 files changed

+1630
-296
lines changed

src/libmongoc/src/mongoc/mongoc-client-private.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ _mongoc_client_command_with_opts (mongoc_client_t *client,
159159
mongoc_command_mode_t mode,
160160
const bson_t *opts,
161161
mongoc_query_flags_t flags,
162+
const mongoc_read_prefs_t *user_prefs,
162163
const mongoc_read_prefs_t *default_prefs,
163164
mongoc_read_concern_t *default_rc,
164165
mongoc_write_concern_t *default_wc,

src/libmongoc/src/mongoc/mongoc-client.c

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,8 +1569,9 @@ _mongoc_client_retryable_write_command_with_stream (
15691569
retry_server_stream =
15701570
mongoc_cluster_stream_for_writes (&client->cluster, &ignored_error);
15711571

1572-
if (retry_server_stream && retry_server_stream->sd->max_wire_version >=
1573-
WIRE_VERSION_RETRY_WRITES) {
1572+
if (retry_server_stream &&
1573+
retry_server_stream->sd->max_wire_version >=
1574+
WIRE_VERSION_RETRY_WRITES) {
15741575
parts->assembled.server_stream = retry_server_stream;
15751576
bson_destroy (reply);
15761577
GOTO (retry);
@@ -1671,13 +1672,13 @@ mongoc_client_command_simple (mongoc_client_t *client,
16711672
*
16721673
* Execute a command on the server. If mode is MONGOC_CMD_READ or
16731674
* MONGOC_CMD_RW, then read concern is applied from @opts, or else from
1674-
* @default_rc, and read preferences are applied from @default_prefs.
1675-
* If mode is MONGOC_CMD_WRITE or MONGOC_CMD_RW, then write concern is
1676-
* applied from @opts if present, or else from @default_wc.
1675+
* @default_rc, and read preferences are applied from @user_prefs, or else
1676+
* from @default_prefs. If mode is MONGOC_CMD_WRITE or MONGOC_CMD_RW, then
1677+
* write concern is applied from @opts if present, or else @default_wc.
16771678
*
16781679
* If mode is MONGOC_CMD_RAW, then read concern and write concern are
16791680
* applied from @opts only. Read preferences are applied from
1680-
* @read_prefs.
1681+
* @user_prefs.
16811682
*
16821683
* The mongoc_client_t's read preference, read concern, and write concern
16831684
* are *NOT* applied.
@@ -1699,6 +1700,7 @@ _mongoc_client_command_with_opts (mongoc_client_t *client,
16991700
mongoc_command_mode_t mode,
17001701
const bson_t *opts,
17011702
mongoc_query_flags_t flags,
1703+
const mongoc_read_prefs_t *user_prefs,
17021704
const mongoc_read_prefs_t *default_prefs,
17031705
mongoc_read_concern_t *default_rc,
17041706
mongoc_write_concern_t *default_wc,
@@ -1708,6 +1710,7 @@ _mongoc_client_command_with_opts (mongoc_client_t *client,
17081710
mongoc_read_write_opts_t read_write_opts;
17091711
mongoc_cmd_parts_t parts;
17101712
const char *command_name;
1713+
const mongoc_read_prefs_t *prefs = COALESCE (user_prefs, default_prefs);
17111714
mongoc_server_stream_t *server_stream = NULL;
17121715
mongoc_cluster_t *cluster;
17131716
bson_t reply_local;
@@ -1740,18 +1743,47 @@ _mongoc_client_command_with_opts (mongoc_client_t *client,
17401743
GOTO (err);
17411744
}
17421745

1746+
if (_mongoc_client_session_in_txn (read_write_opts.client_session)) {
1747+
if ((mode == MONGOC_CMD_READ || mode == MONGOC_CMD_RAW) &&
1748+
!IS_PREF_PRIMARY (user_prefs)) {
1749+
bson_set_error (error,
1750+
MONGOC_ERROR_COMMAND,
1751+
MONGOC_ERROR_COMMAND_INVALID_ARG,
1752+
"Read preference in a transaction must be primary");
1753+
GOTO (err);
1754+
}
1755+
1756+
if (!bson_empty (&read_write_opts.readConcern)) {
1757+
bson_set_error (error,
1758+
MONGOC_ERROR_COMMAND,
1759+
MONGOC_ERROR_COMMAND_INVALID_ARG,
1760+
"Cannot set read concern after starting transaction");
1761+
GOTO (err);
1762+
}
1763+
1764+
if (read_write_opts.writeConcern &&
1765+
strcmp (command_name, "commitTransaction") != 0 &&
1766+
strcmp (command_name, "abortTransaction") != 0) {
1767+
bson_set_error (error,
1768+
MONGOC_ERROR_COMMAND,
1769+
MONGOC_ERROR_COMMAND_INVALID_ARG,
1770+
"Cannot set write concern after starting transaction");
1771+
GOTO (err);
1772+
}
1773+
}
1774+
17431775
reply_ptr = reply ? reply : &reply_local;
17441776

17451777
if (mode == MONGOC_CMD_READ || mode == MONGOC_CMD_RAW) {
17461778
/* NULL read pref is ok */
1747-
if (!_mongoc_read_prefs_validate (default_prefs, error)) {
1779+
if (!_mongoc_read_prefs_validate (prefs, error)) {
17481780
GOTO (err);
17491781
}
17501782

1751-
parts.read_prefs = default_prefs;
1783+
parts.read_prefs = prefs;
17521784
} else {
17531785
/* this is a command that writes */
1754-
default_prefs = NULL;
1786+
prefs = NULL;
17551787
}
17561788

17571789
cluster = &client->cluster;
@@ -1768,7 +1800,7 @@ _mongoc_client_command_with_opts (mongoc_client_t *client,
17681800
server_stream = mongoc_cluster_stream_for_writes (cluster, error);
17691801
} else {
17701802
server_stream = mongoc_cluster_stream_for_reads (
1771-
cluster, default_prefs, read_write_opts.client_session, error);
1803+
cluster, prefs, read_write_opts.client_session, error);
17721804
}
17731805

17741806
if (!server_stream) {
@@ -1856,18 +1888,18 @@ mongoc_client_read_command_with_opts (mongoc_client_t *client,
18561888
bson_t *reply,
18571889
bson_error_t *error)
18581890
{
1859-
return _mongoc_client_command_with_opts (
1860-
client,
1861-
db_name,
1862-
command,
1863-
MONGOC_CMD_READ,
1864-
opts,
1865-
MONGOC_QUERY_NONE,
1866-
COALESCE (read_prefs, client->read_prefs),
1867-
client->read_concern,
1868-
client->write_concern,
1869-
reply,
1870-
error);
1891+
return _mongoc_client_command_with_opts (client,
1892+
db_name,
1893+
command,
1894+
MONGOC_CMD_READ,
1895+
opts,
1896+
MONGOC_QUERY_NONE,
1897+
read_prefs,
1898+
client->read_prefs,
1899+
client->read_concern,
1900+
client->write_concern,
1901+
reply,
1902+
error);
18711903
}
18721904

18731905

@@ -1885,6 +1917,7 @@ mongoc_client_write_command_with_opts (mongoc_client_t *client,
18851917
MONGOC_CMD_WRITE,
18861918
opts,
18871919
MONGOC_QUERY_NONE,
1920+
NULL,
18881921
client->read_prefs,
18891922
client->read_concern,
18901923
client->write_concern,
@@ -1903,18 +1936,18 @@ mongoc_client_read_write_command_with_opts (
19031936
bson_t *reply,
19041937
bson_error_t *error)
19051938
{
1906-
return _mongoc_client_command_with_opts (
1907-
client,
1908-
db_name,
1909-
command,
1910-
MONGOC_CMD_RW,
1911-
opts,
1912-
MONGOC_QUERY_NONE,
1913-
COALESCE (read_prefs, client->read_prefs),
1914-
client->read_concern,
1915-
client->write_concern,
1916-
reply,
1917-
error);
1939+
return _mongoc_client_command_with_opts (client,
1940+
db_name,
1941+
command,
1942+
MONGOC_CMD_RW,
1943+
opts,
1944+
MONGOC_QUERY_NONE,
1945+
read_prefs,
1946+
client->read_prefs,
1947+
client->read_concern,
1948+
client->write_concern,
1949+
reply,
1950+
error);
19181951
}
19191952

19201953

@@ -1934,6 +1967,7 @@ mongoc_client_command_with_opts (mongoc_client_t *client,
19341967
opts,
19351968
MONGOC_QUERY_NONE,
19361969
read_prefs,
1970+
NULL,
19371971
client->read_concern,
19381972
client->write_concern,
19391973
reply,

src/libmongoc/src/mongoc/mongoc-cmd.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ mongoc_cmd_parts_assemble (mongoc_cmd_parts_t *parts,
801801

802802
if (_mongoc_client_session_in_txn (cs)) {
803803
if (!IS_PREF_PRIMARY (cs->txn.opts.read_prefs) &&
804-
parts->is_read_command) {
804+
!parts->is_write_command) {
805805
bson_set_error (error,
806806
MONGOC_ERROR_TRANSACTION,
807807
MONGOC_ERROR_TRANSACTION_INVALID_STATE,

0 commit comments

Comments
 (0)