Line data Source code
1 : // Unit tests for the googlesql::Type -> proto FieldSchema reflector.
2 : //
3 : // We exercise the mapping by constructing GoogleSQL types via a
4 : // `TypeFactory` (the same path the analyzer uses) and asserting on
5 : // the resulting `FieldSchema` proto. Tests cover every BigQuery
6 : // scalar, ARRAY/STRUCT containers, and the error cases for kinds we
7 : // do not yet model.
8 :
9 : #include "backend/schema/googlesql_to_bq.h"
10 :
11 : #include <memory>
12 : #include <string>
13 : #include <utility>
14 : #include <vector>
15 :
16 : #include "absl/status/status.h"
17 : #include "absl/strings/string_view.h"
18 : #include "googlesql/public/type.h"
19 : #include "googlesql/public/types/struct_type.h"
20 : #include "googlesql/public/types/type_factory.h"
21 : #include "gtest/gtest.h"
22 : #include "proto/emulator.pb.h"
23 :
24 : namespace bigquery_emulator {
25 : namespace backend {
26 : namespace schema {
27 : namespace {
28 :
29 : using ::googlesql::StructType;
30 : using ::googlesql::Type;
31 : using ::googlesql::TypeFactory;
32 :
33 : class TypeReflectionTest : public ::testing::Test {
34 : protected:
35 : TypeFactory factory_{};
36 : };
37 :
38 1 : TEST_F(TypeReflectionTest, ScalarsRoundTripToBigQueryNames) {
39 1 : struct Case {
40 1 : const Type* type = nullptr;
41 1 : absl::string_view expected;
42 1 : };
43 1 : const Case cases[] = {
44 1 : {factory_.get_bool(), "BOOL"},
45 1 : {factory_.get_int64(), "INT64"},
46 1 : {factory_.get_double(), "FLOAT64"},
47 1 : {factory_.get_string(), "STRING"},
48 1 : {factory_.get_bytes(), "BYTES"},
49 1 : {factory_.get_date(), "DATE"},
50 1 : {factory_.get_time(), "TIME"},
51 1 : {factory_.get_datetime(), "DATETIME"},
52 1 : {factory_.get_timestamp(), "TIMESTAMP"},
53 1 : {factory_.get_numeric(), "NUMERIC"},
54 1 : {factory_.get_bignumeric(), "BIGNUMERIC"},
55 1 : {factory_.get_json(), "JSON"},
56 1 : {factory_.get_geography(), "GEOGRAPHY"},
57 1 : };
58 13 : for (const Case& c : cases) {
59 13 : v1::FieldSchema out;
60 13 : absl::Status status = TypeToFieldSchema(c.type, "col", &out);
61 26 : ASSERT_TRUE(status.ok()) << status.message();
62 13 : EXPECT_EQ(out.name(), "col");
63 13 : EXPECT_EQ(out.type(), c.expected);
64 13 : EXPECT_EQ(out.mode(), "");
65 13 : EXPECT_EQ(out.fields_size(), 0);
66 13 : }
67 1 : }
68 :
69 1 : TEST_F(TypeReflectionTest, ArrayOfScalarBecomesRepeated) {
70 1 : const Type* array_type = nullptr;
71 1 : ASSERT_TRUE(factory_.MakeArrayType(factory_.get_string(), &array_type).ok());
72 1 : v1::FieldSchema out;
73 1 : ASSERT_TRUE(TypeToFieldSchema(array_type, "tags", &out).ok());
74 1 : EXPECT_EQ(out.name(), "tags");
75 1 : EXPECT_EQ(out.type(), "STRING");
76 1 : EXPECT_EQ(out.mode(), "REPEATED");
77 1 : }
78 :
79 1 : TEST_F(TypeReflectionTest, NestedStructRecursesIntoFields) {
80 1 : std::vector<StructType::StructField> inner_fields = {
81 1 : {"city", factory_.get_string()},
82 1 : {"zip", factory_.get_int64()},
83 1 : };
84 1 : const StructType* inner = nullptr;
85 1 : ASSERT_TRUE(factory_.MakeStructType(inner_fields, &inner).ok());
86 1 : std::vector<StructType::StructField> outer_fields = {
87 1 : {"name", factory_.get_string()},
88 1 : {"address", inner},
89 1 : };
90 1 : const StructType* outer = nullptr;
91 1 : ASSERT_TRUE(factory_.MakeStructType(outer_fields, &outer).ok());
92 :
93 1 : v1::FieldSchema out;
94 1 : ASSERT_TRUE(TypeToFieldSchema(outer, "person", &out).ok());
95 1 : EXPECT_EQ(out.name(), "person");
96 1 : EXPECT_EQ(out.type(), "STRUCT");
97 1 : ASSERT_EQ(out.fields_size(), 2);
98 1 : EXPECT_EQ(out.fields(0).name(), "name");
99 1 : EXPECT_EQ(out.fields(0).type(), "STRING");
100 1 : EXPECT_EQ(out.fields(1).name(), "address");
101 1 : EXPECT_EQ(out.fields(1).type(), "STRUCT");
102 1 : ASSERT_EQ(out.fields(1).fields_size(), 2);
103 1 : EXPECT_EQ(out.fields(1).fields(0).name(), "city");
104 1 : EXPECT_EQ(out.fields(1).fields(0).type(), "STRING");
105 1 : EXPECT_EQ(out.fields(1).fields(1).name(), "zip");
106 1 : EXPECT_EQ(out.fields(1).fields(1).type(), "INT64");
107 1 : }
108 :
109 1 : TEST_F(TypeReflectionTest, ArrayOfStructPropagatesNestedFields) {
110 1 : std::vector<StructType::StructField> fields = {
111 1 : {"id", factory_.get_int64()},
112 1 : {"label", factory_.get_string()},
113 1 : };
114 1 : const StructType* st = nullptr;
115 1 : ASSERT_TRUE(factory_.MakeStructType(fields, &st).ok());
116 1 : const Type* arr = nullptr;
117 1 : ASSERT_TRUE(factory_.MakeArrayType(st, &arr).ok());
118 :
119 1 : v1::FieldSchema out;
120 1 : ASSERT_TRUE(TypeToFieldSchema(arr, "items", &out).ok());
121 1 : EXPECT_EQ(out.name(), "items");
122 1 : EXPECT_EQ(out.type(), "STRUCT");
123 1 : EXPECT_EQ(out.mode(), "REPEATED");
124 1 : ASSERT_EQ(out.fields_size(), 2);
125 1 : EXPECT_EQ(out.fields(0).type(), "INT64");
126 1 : EXPECT_EQ(out.fields(1).type(), "STRING");
127 1 : }
128 :
129 1 : TEST_F(TypeReflectionTest, StructFieldWithRepeatedArraySetsMode) {
130 1 : const Type* arr = nullptr;
131 1 : ASSERT_TRUE(factory_.MakeArrayType(factory_.get_string(), &arr).ok());
132 1 : std::vector<StructType::StructField> fields = {
133 1 : {"tags", arr},
134 1 : {"count", factory_.get_int64()},
135 1 : };
136 1 : const StructType* st = nullptr;
137 1 : ASSERT_TRUE(factory_.MakeStructType(fields, &st).ok());
138 :
139 1 : v1::FieldSchema out;
140 1 : ASSERT_TRUE(TypeToFieldSchema(st, "rec", &out).ok());
141 1 : ASSERT_EQ(out.fields_size(), 2);
142 1 : EXPECT_EQ(out.fields(0).name(), "tags");
143 1 : EXPECT_EQ(out.fields(0).type(), "STRING");
144 1 : EXPECT_EQ(out.fields(0).mode(), "REPEATED");
145 1 : EXPECT_EQ(out.fields(1).name(), "count");
146 1 : EXPECT_EQ(out.fields(1).type(), "INT64");
147 1 : EXPECT_EQ(out.fields(1).mode(), "");
148 1 : }
149 :
150 1 : TEST_F(TypeReflectionTest, ArrayOfArrayIsRejected) {
151 1 : const Type* inner = nullptr;
152 1 : ASSERT_TRUE(factory_.MakeArrayType(factory_.get_int64(), &inner).ok());
153 1 : const Type* outer = nullptr;
154 : // GoogleSQL's MakeArrayType refuses ARRAY<ARRAY<...>> with an
155 : // InvalidArgument; we mirror that signal here. If a future
156 : // GoogleSQL release ever loosens the restriction, this test will
157 : // fail and we want to keep the BigQuery side rejecting it
158 : // explicitly anyway.
159 1 : absl::Status make = factory_.MakeArrayType(inner, &outer);
160 1 : if (make.ok()) {
161 0 : v1::FieldSchema out;
162 0 : EXPECT_EQ(TypeToFieldSchema(outer, "bad", &out).code(),
163 0 : absl::StatusCode::kInvalidArgument);
164 1 : } else {
165 1 : EXPECT_EQ(make.code(), absl::StatusCode::kInvalidArgument);
166 1 : }
167 1 : }
168 :
169 1 : TEST_F(TypeReflectionTest, NullTypeAndNullOutputAreInvalid) {
170 1 : v1::FieldSchema out;
171 1 : EXPECT_EQ(TypeToFieldSchema(nullptr, "x", &out).code(),
172 1 : absl::StatusCode::kInvalidArgument);
173 1 : EXPECT_EQ(TypeToFieldSchema(factory_.get_int64(), "x", nullptr).code(),
174 1 : absl::StatusCode::kInvalidArgument);
175 1 : }
176 :
177 : } // namespace
178 : } // namespace schema
179 : } // namespace backend
180 : } // namespace bigquery_emulator
|