• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / thomas.goyne_478

02 Aug 2024 05:19PM UTC coverage: 91.089% (-0.01%) from 91.1%
thomas.goyne_478

Pull #7944

Evergreen

tgoyne
Only track pending client resets done by the same core version

If the previous attempt at performing a client reset was done with a different
core version then we should retry the client reset as the new version may have
fixed a bug that made the previous attempt fail (or may be a downgrade to a
version before when the bug was introduced). This also simplifies the tracking
as it means that we don't need to be able to read trackers created by different
versions.

This also means that we can freely change the schema of the table, which this
takes advantage of to drop the unused primary key and make the error required,
as we never actually stored null and the code reading it would have crashed if
it encountered a null error.
Pull Request #7944: Only track pending client resets done by the same core version

102704 of 181534 branches covered (56.58%)

138 of 153 new or added lines in 10 files covered. (90.2%)

85 existing lines in 16 files now uncovered.

216717 of 237917 relevant lines covered (91.09%)

5947762.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.5
/src/realm/sync/noinst/sync_metadata_schema.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2022 Realm, Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 **************************************************************************/
18

19
#include <realm/sync/noinst/sync_metadata_schema.hpp>
20

21
#include <realm/data_type.hpp>
22
#include <realm/transaction.hpp>
23
#include <realm/util/flat_map.hpp>
24
#include <stdexcept>
25

26
namespace realm::sync {
27
namespace {
28

29
constexpr static std::string_view c_flx_metadata_table("flx_metadata");
30
constexpr static std::string_view c_sync_internal_schemas_table("sync_internal_schemas");
31
constexpr static std::string_view c_meta_schema_version_field("schema_version");
32
constexpr static std::string_view c_meta_schema_schema_group_field("schema_group_name");
33

34
} // namespace
35

36
void create_sync_metadata_schema(Group& g, std::vector<SyncMetadataTable>* tables)
37
{
12,096✔
38
    util::FlatMap<std::string_view, TableRef> found_tables;
12,096✔
39
    for (auto& table : *tables) {
15,836✔
40
        if (g.has_table(table.name)) {
15,836✔
41
            throw RuntimeError(
×
42
                ErrorCodes::RuntimeError,
×
43
                util::format("table %1 already existed when creating internal tables for sync", table.name));
×
44
        }
×
45
        TableRef table_ref;
15,836✔
46
        if (table.is_embedded) {
15,836✔
47
            table_ref = g.add_table(table.name, Table::Type::Embedded);
3,740✔
48
        }
3,740✔
49
        else if (table.pk_info) {
12,096✔
50
            table_ref = g.add_table_with_primary_key(table.name, table.pk_info->data_type, table.pk_info->name,
3,992✔
51
                                                     table.pk_info->is_optional);
3,992✔
52
            *table.pk_info->key_out = table_ref->get_primary_key_column();
3,992✔
53
        }
3,992✔
54
        else {
8,104✔
55
            table_ref = g.add_table(table.name);
8,104✔
56
        }
8,104✔
57

58
        found_tables.insert({table.name, table_ref});
15,836✔
59
        *table.key_out = table_ref->get_key();
15,836✔
60
    }
15,836✔
61

62
    for (auto& table : *tables) {
15,836✔
63
        auto& table_ref = found_tables.at(table.name);
15,836✔
64
        for (auto& column : table.columns) {
80,136✔
65
            if (column.data_type == type_Link) {
80,136✔
66
                auto target_table_it = found_tables.find(column.target_table);
3,740✔
67
                if (target_table_it == found_tables.end()) {
3,740✔
68
                    throw LogicError(ErrorCodes::InvalidArgument,
×
69
                                     util::format("cannot link to non-existant table %1 from internal sync table %2",
×
70
                                                  column.target_table, table.name));
×
71
                }
×
72
                if (column.is_list) {
3,740✔
73
                    *column.key_out = table_ref->add_column_list(*target_table_it->second, column.name);
2,520✔
74
                }
2,520✔
75
                else {
1,220✔
76
                    *column.key_out = table_ref->add_column(*target_table_it->second, column.name);
1,220✔
77
                }
1,220✔
78
            }
3,740✔
79
            else {
76,396✔
80
                *column.key_out = table_ref->add_column(column.data_type, column.name, column.is_optional);
76,396✔
81
            }
76,396✔
82
        }
80,136✔
83
    }
15,836✔
84
}
12,096✔
85

86
void load_sync_metadata_schema(const Group& g, std::vector<SyncMetadataTable>* tables)
87
{
4,842✔
88
    if (auto status = try_load_sync_metadata_schema(g, tables); !status.is_ok()) {
4,842✔
NEW
89
        throw Exception(std::move(status));
×
NEW
90
    }
×
91
}
4,842✔
92

93
Status try_load_sync_metadata_schema(const Group& g, std::vector<SyncMetadataTable>* tables)
94
{
31,710✔
95
    for (auto& table : *tables) {
33,224✔
96
        auto table_ref = g.get_table(table.name);
33,224✔
97
        if (!table_ref) {
33,224✔
98
            return Status(ErrorCodes::RuntimeError,
25,854✔
99
                          util::format("could not find internal sync table %1", table.name));
25,854✔
100
        }
25,854✔
101

102
        *table.key_out = table_ref->get_key();
7,370✔
103
        if (table.pk_info) {
7,370✔
104
            auto pk_col = table_ref->get_primary_key_column();
4,802✔
105
            if (auto pk_name = table_ref->get_column_name(pk_col); pk_name != table.pk_info->name) {
4,802✔
NEW
106
                return Status(
×
107
                    ErrorCodes::RuntimeError,
×
108
                    util::format(
×
109
                        "primary key name of sync internal table %1 does not match (stored: %2, defined: %3)",
×
110
                        table.name, pk_name, table.pk_info->name));
×
111
            }
×
112
            if (auto pk_type = table_ref->get_column_type(pk_col); pk_type != table.pk_info->data_type) {
4,802✔
NEW
113
                return Status(
×
114
                    ErrorCodes::RuntimeError,
×
115
                    util::format(
×
116
                        "primary key type of sync internal table %1 does not match (stored: %2, defined: %3)",
×
117
                        table.name, pk_type, table.pk_info->data_type));
×
118
            }
×
119
            if (auto is_nullable = table_ref->is_nullable(pk_col); is_nullable != table.pk_info->is_optional) {
4,802✔
NEW
120
                return Status(
×
121
                    ErrorCodes::RuntimeError,
×
122
                    util::format(
×
123
                        "primary key nullabilty of sync internal table %1 does not match (stored: %2, defined: %3)",
×
124
                        table.name, is_nullable, table.pk_info->is_optional));
×
125
            }
×
126
            *table.pk_info->key_out = pk_col;
4,802✔
127
        }
4,802✔
128
        else if (table.is_embedded && !table_ref->is_embedded()) {
2,568✔
NEW
129
            return Status(ErrorCodes::RuntimeError,
×
NEW
130
                          util::format("internal sync table %1 should be embedded, but is not", table.name));
×
UNCOV
131
        }
×
132

133
        if (table.columns.size() + size_t(table.pk_info ? 1 : 0) != table_ref->get_column_count()) {
7,370✔
134
            return Status(
16✔
135
                ErrorCodes::RuntimeError,
16✔
136
                util::format("sync internal table %1 has a different number of columns than its schema", table.name));
16✔
137
        }
16✔
138

139
        for (auto& col : table.columns) {
21,494✔
140
            auto col_key = table_ref->get_column_key(col.name);
21,494✔
141
            if (!col_key) {
21,494✔
NEW
142
                return Status(ErrorCodes::RuntimeError,
×
NEW
143
                              util::format("column %1 is missing in sync internal table %2", col.name, table.name));
×
UNCOV
144
            }
×
145

146
            auto found_col_type = table_ref->get_column_type(col_key);
21,494✔
147
            if (found_col_type != col.data_type) {
21,494✔
NEW
148
                return Status(
×
149
                    ErrorCodes::RuntimeError,
×
150
                    util::format("column %1 in sync internal table %2 is the wrong type", col.name, table.name));
×
151
            }
×
152

153
            if (col.is_optional != table_ref->is_nullable(col_key)) {
21,494✔
NEW
154
                return Status(
×
155
                    ErrorCodes::RuntimeError,
×
156
                    util::format("column %1 in sync internal table %2 has different nullabilty than in its schema",
×
157
                                 col.name, table.name));
×
158
            }
×
159

160
            if (col.data_type == type_Link) {
21,494✔
161
                if (table_ref->get_link_target(col_key)->get_name() != col.target_table) {
1,514✔
NEW
162
                    return Status(ErrorCodes::RuntimeError,
×
NEW
163
                                  util::format("column %1 in sync internal table %2 links to the wrong table %3",
×
NEW
164
                                               col.name, table.name,
×
NEW
165
                                               table_ref->get_link_target(col_key)->get_name()));
×
UNCOV
166
                }
×
167
            }
1,514✔
168
            *col.key_out = col_key;
21,494✔
169
        }
21,494✔
170
    }
7,354✔
171
    return Status::OK();
5,840✔
172
}
31,710✔
173

174
SyncMetadataSchemaVersionsReader::SyncMetadataSchemaVersionsReader(const TransactionRef& tr)
175
{
10,048✔
176
    std::vector<SyncMetadataTable> unified_schema_version_table_def{
10,048✔
177
        {&m_table,
10,048✔
178
         c_sync_internal_schemas_table,
10,048✔
179
         {&m_schema_group_field, c_meta_schema_schema_group_field, type_String},
10,048✔
180
         {{&m_version_field, c_meta_schema_version_field, type_Int}}}};
10,048✔
181

182
    // Any type of transaction is allowed, including frozen and write, as long as it supports reading
183
    REALM_ASSERT_EX(tr->get_transact_stage() != DB::transact_Ready, tr->get_transact_stage());
10,048✔
184
    // If the legacy_meta_table exists, then this table hasn't been converted and
185
    // the metadata schema versions information has not been upgraded/not accurate
186
    if (tr->has_table(c_flx_metadata_table)) {
10,048✔
187
        return;
20✔
188
    }
20✔
189

190
    if (tr->has_table(c_sync_internal_schemas_table)) {
10,028✔
191
        // Load m_table with the table/schema information
192
        load_sync_metadata_schema(*tr, &unified_schema_version_table_def);
3,898✔
193
    }
3,898✔
194
}
10,028✔
195

196
std::optional<int64_t> SyncMetadataSchemaVersionsReader::get_legacy_version(const TransactionRef& tr)
197
{
4,128✔
198
    if (!tr->has_table(c_flx_metadata_table)) {
4,128✔
199
        return std::nullopt;
4,112✔
200
    }
4,112✔
201

202
    TableKey legacy_table_key;
16✔
203
    ColKey legacy_version_key;
16✔
204
    std::vector<SyncMetadataTable> legacy_table_def{
16✔
205
        {&legacy_table_key, c_flx_metadata_table, {{&legacy_version_key, c_meta_schema_version_field, type_Int}}}};
16✔
206

207
    // Convert the legacy table to the regular schema versions table if it exists
208
    load_sync_metadata_schema(*tr, &legacy_table_def);
16✔
209

210
    if (auto legacy_meta_table = tr->get_table(legacy_table_key);
16✔
211
        legacy_meta_table && legacy_meta_table->size() > 0) {
16✔
212
        auto legacy_obj = legacy_meta_table->get_object(0);
16✔
213
        return legacy_obj.get<int64_t>(legacy_version_key);
16✔
214
    }
16✔
215

216
    return std::nullopt;
×
217
}
16✔
218

219
std::optional<int64_t> SyncMetadataSchemaVersionsReader::get_version_for(const TransactionRef& tr,
220
                                                                         std::string_view schema_group_name)
221
{
7,260✔
222
    if (!m_table) {
7,260✔
223
        // The legacy version only applies to the subscription store, don't query otherwise
224
        if (schema_group_name == internal_schema_groups::c_flx_subscription_store) {
4,674✔
225
            if (auto legacy_version = get_legacy_version(tr)) {
1,276✔
226
                return legacy_version;
4✔
227
            }
4✔
228
        }
1,276✔
229
        return util::none;
4,670✔
230
    }
4,674✔
231

232
    auto schema_versions = tr->get_table(m_table);
2,586✔
233
    auto obj_key = schema_versions->find_primary_key(Mixed{StringData(schema_group_name)});
2,586✔
234
    if (!obj_key) {
2,586✔
235
        return util::none;
1,598✔
236
    }
1,598✔
237
    auto metadata_obj = schema_versions->get_object(obj_key);
988✔
238
    if (!metadata_obj) {
988✔
239
        return util::none;
×
240
    }
×
241

242
    return metadata_obj.get<int64_t>(m_version_field);
988✔
243
}
988✔
244

245
SyncMetadataSchemaVersions::SyncMetadataSchemaVersions(const TransactionRef& tr)
246
    : SyncMetadataSchemaVersionsReader(tr)
1,422✔
247
{
2,852✔
248
    std::vector<SyncMetadataTable> unified_schema_version_table_def{
2,852✔
249
        {&m_table,
2,852✔
250
         c_sync_internal_schemas_table,
2,852✔
251
         {&m_schema_group_field, c_meta_schema_schema_group_field, type_String},
2,852✔
252
         {{&m_version_field, c_meta_schema_version_field, type_Int}}}};
2,852✔
253

254
    DB::TransactStage orig = tr->get_transact_stage();
2,852✔
255
    bool modified = false;
2,852✔
256

257
    // Read and write transactions are allowed, but not frozen
258
    REALM_ASSERT_EX((orig == DB::transact_Reading || orig == DB::transact_Writing), orig);
2,852✔
259
    // If the versions table exists, then m_table would have been initialized by the reader constructor
260
    // If the versions table doesn't exist, then initialize it now
261
    if (!m_table) {
2,852✔
262
        // table should have already been initialized or needs to be created,
263
        // but re-initialize in case it isn't (e.g. both unified and legacy tables exist in DB)
264
        if (REALM_UNLIKELY(tr->has_table(c_sync_internal_schemas_table))) {
1,476✔
265
            load_sync_metadata_schema(*tr, &unified_schema_version_table_def);
4✔
266
        }
4✔
267
        else {
1,472✔
268
            // Only write the versions table if it doesn't exist
269
            if (tr->get_transact_stage() != DB::transact_Writing) {
1,472✔
270
                tr->promote_to_write();
16✔
271
            }
16✔
272
            create_sync_metadata_schema(*tr, &unified_schema_version_table_def);
1,472✔
273
            modified = true;
1,472✔
274
        }
1,472✔
275
    }
1,476✔
276

277
    if (auto legacy_version = get_legacy_version(tr)) {
2,852✔
278
        // Migrate from just having a subscription store metadata table to having multiple table groups with multiple
279
        // versions.
280
        if (tr->get_transact_stage() != DB::transact_Writing) {
12✔
281
            tr->promote_to_write();
4✔
282
        }
4✔
283
        // Only the flx subscription store can potentially have the legacy metadata table
284
        set_version_for(tr, internal_schema_groups::c_flx_subscription_store, *legacy_version);
12✔
285
        tr->remove_table(c_flx_metadata_table);
12✔
286
        modified = true;
12✔
287
    }
12✔
288
    if (!modified)
2,852✔
289
        return; // nothing to commit
1,376✔
290

291
    // Commit and revert to the original transact stage
292
    if (orig == DB::transact_Reading)
1,476✔
293
        tr->commit_and_continue_as_read();
20✔
294
    else
1,456✔
295
        tr->commit_and_continue_writing();
1,456✔
296
}
1,476✔
297

298
void SyncMetadataSchemaVersions::set_version_for(const TransactionRef& tr, std::string_view schema_group_name,
299
                                                 int64_t version)
300
{
2,864✔
301
    REALM_ASSERT(m_table);
2,864✔
302

303
    auto schema_versions = tr->get_table(m_table);
2,864✔
304
    auto metadata_obj = schema_versions->create_object_with_primary_key(Mixed{StringData(schema_group_name)});
2,864✔
305
    metadata_obj.set(m_version_field, version);
2,864✔
306
}
2,864✔
307

308
} // namespace realm::sync
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc