YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/yb/common/schema-test.cc
Line
Count
Source (jump to first uncovered line)
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  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,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
//
18
// The following only applies to changes made to this file as part of YugaByte development.
19
//
20
// Portions Copyright (c) YugaByte, Inc.
21
//
22
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
23
// in compliance with the License.  You may obtain a copy of the License at
24
//
25
// http://www.apache.org/licenses/LICENSE-2.0
26
//
27
// Unless required by applicable law or agreed to in writing, software distributed under the License
28
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
29
// or implied.  See the License for the specific language governing permissions and limitations
30
// under the License.
31
//
32
#include <unordered_map>
33
#include <vector>
34
35
#include <glog/logging.h>
36
#include <gtest/gtest.h>
37
38
#include "yb/common/common.pb.h"
39
#include "yb/common/row.h"
40
#include "yb/common/schema.h"
41
42
#include "yb/gutil/strings/substitute.h"
43
#include "yb/util/test_macros.h"
44
45
namespace yb {
46
namespace tablet {
47
48
using std::unordered_map;
49
using std::vector;
50
using strings::Substitute;
51
52
// Copy a row and its referenced data into the given Arena.
53
static Status CopyRowToArena(const Slice &row,
54
                             const Schema &schema,
55
                             Arena *dst_arena,
56
2
                             ContiguousRow *copied) {
57
2
  Slice row_data;
58
59
  // Copy the direct row data to arena
60
2
  if (!dst_arena->RelocateSlice(row, &row_data)) {
61
0
    return STATUS(IOError, "no space for row data in arena");
62
0
  }
63
64
2
  copied->Reset(row_data.mutable_data());
65
2
  RETURN_NOT_OK(RelocateIndirectDataToArena(copied, dst_arena));
66
2
  return Status::OK();
67
2
}
68
69
70
71
// Test basic functionality of Schema definition
72
1
TEST(TestSchema, TestSchema) {
73
1
  Schema empty_schema;
74
1
  ASSERT_GT(empty_schema.memory_footprint_excluding_this(), 0);
75
76
1
  ColumnSchema col1("key", STRING);
77
1
  ColumnSchema col2("uint32val", UINT32, true);
78
1
  ColumnSchema col3("int32val", INT32);
79
80
1
  vector<ColumnSchema> cols = { col1, col2, col3 };
81
1
  Schema schema(cols, 1);
82
83
1
  ASSERT_EQ(sizeof(Slice) + sizeof(uint32_t) + sizeof(int32_t),
84
1
            schema.byte_size());
85
1
  ASSERT_EQ(3, schema.num_columns());
86
1
  ASSERT_EQ(0, schema.column_offset(0));
87
1
  ASSERT_EQ(sizeof(Slice), schema.column_offset(1));
88
1
  ASSERT_GT(schema.memory_footprint_excluding_this(),
89
1
            empty_schema.memory_footprint_excluding_this());
90
91
1
  EXPECT_EQ("Schema [\n"
92
1
            "\tkey[string NOT NULL NOT A PARTITION KEY],\n"
93
1
            "\tuint32val[uint32 NULLABLE NOT A PARTITION KEY],\n"
94
1
            "\tint32val[int32 NOT NULL NOT A PARTITION KEY]\n"
95
1
            "]\nproperties: contain_counters: false is_transactional: false "
96
1
            "consistency_level: STRONG "
97
1
            "use_mangled_column_name: false "
98
1
            "is_ysql_catalog_table: false "
99
1
            "retain_delete_markers: false",
100
1
            schema.ToString());
101
1
  EXPECT_EQ("key[string NOT NULL NOT A PARTITION KEY]", schema.column(0).ToString());
102
1
  EXPECT_EQ("uint32 NULLABLE NOT A PARTITION KEY", schema.column(1).TypeToString());
103
1
}
104
105
1
TEST(TestSchema, TestSwap) {
106
1
  TableProperties properties1;
107
1
  properties1.SetDefaultTimeToLive(1000);
108
1
  Schema schema1({ ColumnSchema("col1", STRING),
109
1
                   ColumnSchema("col2", STRING),
110
1
                   ColumnSchema("col3", UINT32) },
111
1
                 2, properties1);
112
1
  TableProperties properties2;
113
1
  properties2.SetDefaultTimeToLive(2000);
114
1
  Schema schema2({ ColumnSchema("col3", UINT32),
115
1
                   ColumnSchema("col2", STRING) },
116
1
                 1, properties2);
117
1
  schema1.swap(schema2);
118
1
  ASSERT_EQ(2, schema1.num_columns());
119
1
  ASSERT_EQ(1, schema1.num_key_columns());
120
1
  ASSERT_EQ(3, schema2.num_columns());
121
1
  ASSERT_EQ(2, schema2.num_key_columns());
122
1
  ASSERT_EQ(2000, schema1.table_properties().DefaultTimeToLive());
123
1
  ASSERT_EQ(1000, schema2.table_properties().DefaultTimeToLive());
124
1
}
125
126
1
TEST(TestSchema, TestReset) {
127
1
  Schema schema;
128
1
  ASSERT_FALSE(schema.initialized());
129
130
1
  ASSERT_OK(schema.Reset({ ColumnSchema("col3", UINT32),
131
1
                           ColumnSchema("col2", STRING) },
132
1
                         1));
133
1
  ASSERT_TRUE(schema.initialized());
134
135
  // Swap the initialized schema with an uninitialized one.
136
1
  Schema schema2;
137
1
  schema2.swap(schema);
138
1
  ASSERT_FALSE(schema.initialized());
139
1
  ASSERT_TRUE(schema2.initialized());
140
1
}
141
142
// Test for KUDU-943, a bug where we suspected that Variant didn't behave
143
// correctly with empty strings.
144
1
TEST(TestSchema, TestEmptyVariant) {
145
1
  Slice empty_val("");
146
1
  Slice nonempty_val("test");
147
148
1
  Variant v(STRING, &nonempty_val);
149
1
  ASSERT_EQ("test", (static_cast<const Slice*>(v.value()))->ToString());
150
1
  v.Reset(STRING, &empty_val);
151
1
  ASSERT_EQ("", (static_cast<const Slice*>(v.value()))->ToString());
152
1
  v.Reset(STRING, &nonempty_val);
153
1
  ASSERT_EQ("test", (static_cast<const Slice*>(v.value()))->ToString());
154
1
}
155
156
1
TEST(TestSchema, TestProjectSubset) {
157
1
  Schema schema1({ ColumnSchema("col1", STRING),
158
1
                   ColumnSchema("col2", STRING),
159
1
                   ColumnSchema("col3", UINT32) },
160
1
                 1);
161
162
1
  Schema schema2({ ColumnSchema("col3", UINT32),
163
1
                   ColumnSchema("col2", STRING) },
164
1
                 0);
165
166
1
  RowProjector row_projector(&schema1, &schema2);
167
1
  ASSERT_OK(row_projector.Init());
168
169
  // Verify the mapping
170
1
  ASSERT_EQ(2, row_projector.base_cols_mapping().size());
171
1
  ASSERT_EQ(0, row_projector.adapter_cols_mapping().size());
172
173
1
  const vector<RowProjector::ProjectionIdxMapping>& mapping = row_projector.base_cols_mapping();
174
1
  ASSERT_EQ(mapping[0].first, 0);  // col3 schema2
175
1
  ASSERT_EQ(mapping[0].second, 2); // col3 schema1
176
1
  ASSERT_EQ(mapping[1].first, 1);  // col2 schema2
177
1
  ASSERT_EQ(mapping[1].second, 1); // col2 schema1
178
1
}
179
180
// Test projection when the type of the projected column
181
// doesn't match the original type.
182
1
TEST(TestSchema, TestProjectTypeMismatch) {
183
1
  Schema schema1({ ColumnSchema("key", STRING),
184
1
                   ColumnSchema("val", UINT32) },
185
1
                 1);
186
1
  Schema schema2({ ColumnSchema("val", STRING) }, 0);
187
188
1
  RowProjector row_projector(&schema1, &schema2);
189
1
  Status s = row_projector.Init();
190
1
  ASSERT_TRUE(s.IsInvalidArgument());
191
1
  ASSERT_STR_CONTAINS(s.message().ToString(), "must have type");
192
1
}
193
194
// Test projection when the some columns in the projection
195
// are not present in the base schema
196
1
TEST(TestSchema, TestProjectMissingColumn) {
197
1
  Schema schema1({ ColumnSchema("key", STRING), ColumnSchema("val", UINT32) }, 1);
198
1
  Schema schema2({ ColumnSchema("val", UINT32), ColumnSchema("non_present", STRING) }, 0);
199
1
  Schema schema3({ ColumnSchema("val", UINT32), ColumnSchema("non_present", UINT32, true) }, 0);
200
201
1
  RowProjector row_projector(&schema1, &schema2);
202
1
  Status s = row_projector.Init();
203
1
  ASSERT_TRUE(s.IsInvalidArgument());
204
1
  ASSERT_STR_CONTAINS(s.message().ToString(),
205
1
    "does not exist in the projection, and it does not have a nullable type");
206
207
  // Verify Default nullable column with no default value
208
1
  ASSERT_OK(row_projector.Reset(&schema1, &schema3));
209
210
1
  ASSERT_EQ(1, row_projector.base_cols_mapping().size());
211
1
  ASSERT_EQ(0, row_projector.adapter_cols_mapping().size());
212
213
1
  ASSERT_EQ(row_projector.base_cols_mapping()[0].first, 0);  // val schema2
214
1
  ASSERT_EQ(row_projector.base_cols_mapping()[0].second, 1); // val schema1
215
1
}
216
217
// Test projection mapping using IDs.
218
// This simulate a column rename ('val' -> 'val_renamed')
219
// and a new column added ('non_present')
220
1
TEST(TestSchema, TestProjectRename) {
221
1
  SchemaBuilder builder;
222
1
  ASSERT_OK(builder.AddKeyColumn("key", STRING));
223
1
  ASSERT_OK(builder.AddColumn("val", UINT32));
224
1
  Schema schema1 = builder.Build();
225
226
1
  builder.Reset(schema1);
227
1
  ASSERT_OK(builder.AddNullableColumn("non_present", UINT32));
228
1
  ASSERT_OK(builder.RenameColumn("val", "val_renamed"));
229
1
  Schema schema2 = builder.Build();
230
231
1
  RowProjector row_projector(&schema1, &schema2);
232
1
  ASSERT_OK(row_projector.Init());
233
234
1
  ASSERT_EQ(2, row_projector.base_cols_mapping().size());
235
1
  ASSERT_EQ(0, row_projector.adapter_cols_mapping().size());
236
237
1
  ASSERT_EQ(row_projector.base_cols_mapping()[0].first, 0);  // key schema2
238
1
  ASSERT_EQ(row_projector.base_cols_mapping()[0].second, 0); // key schema1
239
240
1
  ASSERT_EQ(row_projector.base_cols_mapping()[1].first, 1);  // val_renamed schema2
241
1
  ASSERT_EQ(row_projector.base_cols_mapping()[1].second, 1); // val schema1
242
1
}
243
244
245
// Test that the schema can be used to compare and stringify rows.
246
1
TEST(TestSchema, TestRowOperations) {
247
1
  Schema schema({ ColumnSchema("col1", STRING),
248
1
                  ColumnSchema("col2", STRING),
249
1
                  ColumnSchema("col3", UINT32),
250
1
                  ColumnSchema("col4", INT32) },
251
1
                1);
252
253
1
  Arena arena(1024, 256*1024);
254
255
1
  RowBuilder rb(schema);
256
1
  rb.AddString(string("row_a_1"));
257
1
  rb.AddString(string("row_a_2"));
258
1
  rb.AddUint32(3);
259
1
  rb.AddInt32(-3);
260
1
  ContiguousRow row_a(&schema);
261
1
  ASSERT_OK(CopyRowToArena(rb.data(), schema, &arena, &row_a));
262
263
1
  rb.Reset();
264
1
  rb.AddString(string("row_b_1"));
265
1
  rb.AddString(string("row_b_2"));
266
1
  rb.AddUint32(3);
267
1
  rb.AddInt32(-3);
268
1
  ContiguousRow row_b(&schema);
269
1
  ASSERT_OK(CopyRowToArena(rb.data(), schema, &arena, &row_b));
270
271
1
  ASSERT_GT(schema.Compare(row_b, row_a), 0);
272
1
  ASSERT_LT(schema.Compare(row_a, row_b), 0);
273
274
1
  ASSERT_EQ(string("(string col1=row_a_1, string col2=row_a_2, uint32 col3=3, int32 col4=-3)"),
275
1
            schema.DebugRow(row_a));
276
1
}
277
278
1
TEST(TestSchema, TestDecodeKeys_CompoundStringKey) {
279
1
  Schema schema({ ColumnSchema("col1", STRING),
280
1
                  ColumnSchema("col2", STRING),
281
1
                  ColumnSchema("col3", STRING) },
282
1
                2);
283
284
1
  EXPECT_EQ("(string col1=foo, string col2=bar)",
285
1
            schema.DebugEncodedRowKey(Slice("foo\0\0bar", 8), Schema::START_KEY));
286
1
  EXPECT_EQ("(string col1=fo\\000o, string col2=bar)",
287
1
            schema.DebugEncodedRowKey(Slice("fo\x00\x01o\0\0""bar", 10), Schema::START_KEY));
288
1
  EXPECT_EQ("(string col1=fo\\000o, string col2=bar\\000xy)",
289
1
            schema.DebugEncodedRowKey(Slice("fo\x00\x01o\0\0""bar\0xy", 13), Schema::START_KEY));
290
291
1
  EXPECT_EQ("<start of table>",
292
1
            schema.DebugEncodedRowKey("", Schema::START_KEY));
293
1
  EXPECT_EQ("<end of table>",
294
1
            schema.DebugEncodedRowKey("", Schema::END_KEY));
295
1
}
296
297
// Test that appropriate statuses are returned when trying to decode an invalid
298
// encoded key.
299
1
TEST(TestSchema, TestDecodeKeys_InvalidKeys) {
300
1
  Schema schema({ ColumnSchema("col1", STRING),
301
1
                  ColumnSchema("col2", UINT32),
302
1
                  ColumnSchema("col3", STRING) },
303
1
                2);
304
305
1
  EXPECT_EQ("<invalid key: Invalid argument: Error decoding composite key component"
306
1
            " 'col1': Missing separator after composite key string component: foo>",
307
1
            schema.DebugEncodedRowKey(Slice("foo"), Schema::START_KEY));
308
1
  EXPECT_EQ("<invalid key: Invalid argument: Error decoding composite key component 'col2': "
309
1
            "key too short>",
310
1
            schema.DebugEncodedRowKey(Slice("foo\x00\x00", 5), Schema::START_KEY));
311
1
  EXPECT_EQ("<invalid key: Invalid argument: Error decoding composite key component 'col2': "
312
1
            "key too short: FFFF>",
313
1
            schema.DebugEncodedRowKey(Slice("foo\x00\x00\xff\xff", 7), Schema::START_KEY));
314
1
}
315
316
1
TEST(TestSchema, TestCreateProjection) {
317
1
  Schema schema({ ColumnSchema("col1", STRING),
318
1
                  ColumnSchema("col2", STRING),
319
1
                  ColumnSchema("col3", STRING),
320
1
                  ColumnSchema("col4", STRING),
321
1
                  ColumnSchema("col5", STRING) },
322
1
                2);
323
1
  Schema schema_with_ids = SchemaBuilder(schema).Build();
324
1
  Schema partial_schema;
325
326
  // By names, without IDs
327
1
  ASSERT_OK(schema.CreateProjectionByNames({ "col1", "col2", "col4" }, &partial_schema));
328
1
  EXPECT_EQ("Schema [\n"
329
1
            "\tcol1[string NOT NULL NOT A PARTITION KEY],\n"
330
1
            "\tcol2[string NOT NULL NOT A PARTITION KEY],\n"
331
1
            "\tcol4[string NOT NULL NOT A PARTITION KEY]\n"
332
1
            "]\nproperties: contain_counters: false is_transactional: false "
333
1
            "consistency_level: STRONG "
334
1
            "use_mangled_column_name: false "
335
1
            "is_ysql_catalog_table: false "
336
1
            "retain_delete_markers: false",
337
1
            partial_schema.ToString());
338
339
  // By names, with IDS
340
1
  ASSERT_OK(schema_with_ids.CreateProjectionByNames({ "col1", "col2", "col4" }, &partial_schema));
341
1
  EXPECT_EQ(Format("Schema [\n"
342
1
                   "\t$0:col1[string NOT NULL NOT A PARTITION KEY],\n"
343
1
                   "\t$1:col2[string NOT NULL NOT A PARTITION KEY],\n"
344
1
                   "\t$2:col4[string NOT NULL NOT A PARTITION KEY]\n"
345
1
                   "]\nproperties: contain_counters: false is_transactional: false "
346
1
                   "consistency_level: STRONG "
347
1
                   "use_mangled_column_name: false "
348
1
                   "is_ysql_catalog_table: false "
349
1
                   "retain_delete_markers: false",
350
1
                   schema_with_ids.column_id(0),
351
1
                   schema_with_ids.column_id(1),
352
1
                   schema_with_ids.column_id(3)),
353
1
            partial_schema.ToString());
354
355
  // By names, with missing names.
356
1
  Status s = schema.CreateProjectionByNames({ "foobar" }, &partial_schema);
357
1
  EXPECT_EQ("Not found: Column not found: foobar", s.ToString(/* no file/line */ false));
358
359
  // By IDs
360
1
  ASSERT_OK(schema_with_ids.CreateProjectionByIdsIgnoreMissing({ schema_with_ids.column_id(0),
361
1
                                                                 schema_with_ids.column_id(1),
362
1
                                                                 ColumnId(1000), // missing column
363
1
                                                                 schema_with_ids.column_id(3) },
364
1
                                                               &partial_schema));
365
1
  EXPECT_EQ(Format("Schema [\n"
366
1
                   "\t$0:col1[string NOT NULL NOT A PARTITION KEY],\n"
367
1
                   "\t$1:col2[string NOT NULL NOT A PARTITION KEY],\n"
368
1
                   "\t$2:col4[string NOT NULL NOT A PARTITION KEY]\n"
369
1
                   "]\nproperties: contain_counters: false is_transactional: false "
370
1
                   "consistency_level: STRONG "
371
1
                   "use_mangled_column_name: false "
372
1
                   "is_ysql_catalog_table: false "
373
1
                   "retain_delete_markers: false",
374
1
                   schema_with_ids.column_id(0),
375
1
                   schema_with_ids.column_id(1),
376
1
                   schema_with_ids.column_id(3)),
377
1
            partial_schema.ToString());
378
1
}
379
380
1
TEST(TestSchema, TestCopyFrom) {
381
1
  TableProperties properties;
382
1
  properties.SetDefaultTimeToLive(1000);
383
1
  Schema schema1({ ColumnSchema("col1", STRING),
384
1
                     ColumnSchema("col2", STRING),
385
1
                     ColumnSchema("col3", UINT32) },
386
1
                 2, properties);
387
1
  Schema schema2;
388
1
  schema2.CopyFrom(schema1);
389
1
  ASSERT_EQ(3, schema2.num_columns());
390
1
  ASSERT_EQ(2, schema2.num_key_columns());
391
1
  ASSERT_EQ(1000, schema2.table_properties().DefaultTimeToLive());
392
1
}
393
394
1
TEST(TestSchema, TestSchemaBuilder) {
395
1
  TableProperties properties;
396
1
  properties.SetDefaultTimeToLive(1000);
397
1
  Schema schema1({ ColumnSchema("col1", STRING),
398
1
                     ColumnSchema("col2", STRING),
399
1
                     ColumnSchema("col3", UINT32) },
400
1
                 2, properties);
401
1
  SchemaBuilder builder(schema1);
402
1
  Schema schema2 = builder.Build();
403
1
  ASSERT_TRUE(schema1.Equals(schema2));
404
1
}
405
406
1
TEST(TestSchema, TestTableProperties) {
407
1
  TableProperties properties;
408
1
  ASSERT_FALSE(properties.HasDefaultTimeToLive());
409
410
1
  properties.SetDefaultTimeToLive(1000);
411
1
  ASSERT_TRUE(properties.HasDefaultTimeToLive());
412
1
  ASSERT_EQ(1000, properties.DefaultTimeToLive());
413
414
1
  TableProperties properties1;
415
1
  properties1.SetDefaultTimeToLive(1000);
416
1
  ASSERT_EQ(properties, properties1);
417
418
1
  TablePropertiesPB pb;
419
1
  properties.ToTablePropertiesPB(&pb);
420
1
  ASSERT_TRUE(pb.has_default_time_to_live());
421
1
  ASSERT_EQ(1000, pb.default_time_to_live());
422
423
1
  auto properties2 = TableProperties::FromTablePropertiesPB(pb);
424
1
  ASSERT_TRUE(properties2.HasDefaultTimeToLive());
425
1
  ASSERT_EQ(1000, properties2.DefaultTimeToLive());
426
427
1
  properties.Reset();
428
1
  pb.Clear();
429
1
  ASSERT_FALSE(properties.HasDefaultTimeToLive());
430
1
  properties.ToTablePropertiesPB(&pb);
431
1
  ASSERT_FALSE(pb.has_default_time_to_live());
432
1
  auto properties3 = TableProperties::FromTablePropertiesPB(pb);
433
1
  ASSERT_FALSE(properties3.HasDefaultTimeToLive());
434
1
}
435
436
} // namespace tablet
437
} // namespace yb