-
Notifications
You must be signed in to change notification settings - Fork 59
/
Copy pathtable.cpp
269 lines (215 loc) · 8.58 KB
/
table.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
// Copyright 2017 The Native Object Protocols Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <errno.h>
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <utility>
#include <nop/base/table.h>
#include <nop/serializer.h>
#include <nop/status.h>
#include <nop/utility/die.h>
#include <nop/utility/stream_reader.h>
#include <nop/utility/stream_writer.h>
#include "stream_utilities.h"
#include "string_to_hex.h"
using nop::ActiveEntry;
using nop::DeletedEntry;
using nop::Deserializer;
using nop::Entry;
using nop::ErrorStatus;
using nop::Serializer;
using nop::Status;
using nop::StreamReader;
using nop::StreamWriter;
using nop::StringToHex;
//
// This example is a simple demonstration of tables. Tables are similar to
// regular serializable structures with some extra features to support
// bi-directional binary compatibility. The main advantage of tables is that
// data generated by one version of a table can be handled by other versions of
// the table, both older and newer. This property is important in many
// situations where requirements are expected to evolve over time.
//
// A table is a class or structure with members of type nop::Entry<T, Id>. These
// members are called the table's entries. The macro NOP_TABLE*() is used to
// describe the table and its entries to the serialization engine. Each entry
// has a type, which may be any serializable type, and a numeric id that is
// unique among the entries of the same table. Entry ids should not change or be
// reused as a table evolves over time or else compatability between different
// versions of data will be broken. Entries may be either public, protected, or
// private as needed by the particular use case.
//
// nop::Entry<T, Id> may either be empty or store a value of type T. When an
// entry is empty it is not written during serialization, saving space in the
// output. Application code can test whether an entry is empty and take
// appropriate default action in case it is. This property supports both
// optionality and version compatability in a consistent manner.
//
// In this example three different versions of the same table are defined. In
// the real world these would all have the same C++ type name and the changes
// would be separated in time. Since all three versions need to coexist in this
// example, the three versions are defined in separate namespaces to keep the
// compiler happy.
//
namespace version1 {
// The first version of the table with a single member.
struct TableA {
Entry<std::string, 0> a;
NOP_TABLE_NS("TableA", TableA, a);
};
} // namespace version1
namespace version2 {
// The second version of the table that adds a member.
struct TableA {
Entry<std::string, 0> a;
Entry<std::vector<int>, 1> b;
NOP_TABLE_NS("TableA", TableA, a, b);
};
} // namespace version2
namespace version3 {
// The third version of the table that deletes a member.
struct TableA {
Entry<std::string, 0> a;
Entry<std::vector<int>, 1, DeletedEntry> b;
NOP_TABLE_NS("TableA", TableA, a, b);
};
} // namespace version3
namespace {
template <typename T, std::uint64_t Id>
std::ostream& operator<<(std::ostream& stream,
const Entry<T, Id, ActiveEntry>& entry) {
if (entry)
stream << entry.get();
else
stream << "<empty>";
return stream;
}
template <typename T, std::uint64_t Id>
std::ostream& operator<<(std::ostream& stream,
const Entry<T, Id, DeletedEntry>& /*entry*/) {
stream << "<deleted>";
return stream;
}
std::ostream& operator<<(std::ostream& stream, const version1::TableA& table) {
stream << "version1::TableA{" << table.a << "}";
return stream;
}
std::ostream& operator<<(std::ostream& stream, const version2::TableA& table) {
stream << "version2::TableA{" << table.a << ", " << table.b << "}";
return stream;
}
std::ostream& operator<<(std::ostream& stream, const version3::TableA& table) {
stream << "version3::TableA{" << table.a << ", " << table.b << "}";
return stream;
}
// Prints an error message to std::cerr when the Status<T> || Die() expression
// evaluates to false.
auto Die(const char* error_message = "Error") {
return nop::Die(std::cerr, error_message);
}
} // namespace
int main(int /*argc*/, char** /*argv*/) {
Serializer<StreamWriter<std::stringstream>> serializer;
// Initialize the first version of TableA and serialize it. The serialized
// form is stored as a std::string in t1_data for reading back later.
version1::TableA t1{"Version 1"};
serializer.Write(t1) || Die("Failed to write t1");
const std::string t1_data = serializer.writer().stream().str();
serializer.writer().stream().str("");
std::cout << "Wrote t1: " << t1 << std::endl;
std::cout << "Serialized data: " << StringToHex(t1_data) << std::endl;
std::cout << t1_data.size() << " bytes" << std::endl << std::endl;
// Initialize the second version of TableA and serialize it. The serialized
// form is stored as a std::string in t2_data for reading back later.
version2::TableA t2{"Version 2", {1, 2, 3, 4}};
serializer.Write(t2) || Die("Failed to write t2");
const std::string t2_data = serializer.writer().stream().str();
serializer.writer().stream().str("");
std::cout << "Wrote t2: " << t2 << std::endl;
std::cout << "Serialized data: " << StringToHex(t2_data) << std::endl;
std::cout << t2_data.size() << " bytes" << std::endl << std::endl;
// Initialize the third version of TableA and serialize it. The serialized
// form is stored as a std::string in t3_data for reading back later.
version3::TableA t3{"Version 3", {}};
serializer.Write(t3) || Die("Failed to write t3");
const std::string t3_data = serializer.writer().stream().str();
serializer.writer().stream().str("");
std::cout << "Wrote t3: " << t3 << std::endl;
std::cout << "Serialized data: " << StringToHex(t3_data) << std::endl;
std::cout << t3_data.size() << " bytes" << std::endl << std::endl;
Deserializer<StreamReader<std::stringstream>> deserializer{};
// Use the first version of TableA to read back the serialized data from each
// version of the table.
{
deserializer.reader().stream().str(t1_data);
version1::TableA table;
deserializer.Read(&table) || Die("Failed to read t1_data");
std::cout << "Read t1_data: " << table << std::endl;
}
{
deserializer.reader().stream().str(t2_data);
version1::TableA table;
deserializer.Read(&table) || Die("Failed to read t2_data");
std::cout << "Read t2_data: " << table << std::endl;
}
{
deserializer.reader().stream().str(t3_data);
version1::TableA table;
deserializer.Read(&table) || Die("Failed to read t3_data");
std::cout << "Read t3_data: " << table << std::endl;
}
// Use the second version of TableA to read back the serialized data from each
// version of the table.
{
deserializer.reader().stream().str(t1_data);
version2::TableA table;
deserializer.Read(&table) || Die("Failed to read t1_data");
std::cout << "Read t1_data: " << table << std::endl;
}
{
deserializer.reader().stream().str(t2_data);
version2::TableA table;
deserializer.Read(&table) || Die("Failed to read t2_data");
std::cout << "Read t2_data: " << table << std::endl;
}
{
deserializer.reader().stream().str(t3_data);
version2::TableA table;
deserializer.Read(&table) || Die("Failed to read t3_data");
std::cout << "Read t3_data: " << table << std::endl;
}
// Use the third version of TableA to read back the serialized data from each
// version of the table.
{
deserializer.reader().stream().str(t1_data);
version3::TableA table;
deserializer.Read(&table) || Die("Failed to read t1_data");
std::cout << "Read t1_data: " << table << std::endl;
}
{
deserializer.reader().stream().str(t2_data);
version3::TableA table;
deserializer.Read(&table) || Die("Failed to read t2_data");
std::cout << "Read t2_data: " << table << std::endl;
}
{
deserializer.reader().stream().str(t3_data);
version3::TableA table;
deserializer.Read(&table) || Die("Failed to read t3_data");
std::cout << "Read t3_data: " << table << std::endl;
}
return 0;
}