Line data Source code
1 : // Unit tests for the `<column> = <literal>` parser that backs the
2 : // Storage Read API's `ReadOptions.row_restriction` field.
3 : //
4 : // The parser supports exactly three literal kinds (INT64 / BOOL /
5 : // STRING) against top-level scalar columns. Every other shape —
6 : // connectives,
7 : // relational ops, range, IN, NULL, ARRAY / STRUCT columns, FLOAT64 /
8 : // NUMERIC / DATE literals — is rejected at parse time with
9 : // INVALID_ARGUMENT so the gateway can surface the BigQuery REST 400
10 : // envelope before any rows are read.
11 :
12 : #include "backend/storage/row_restriction.h"
13 :
14 : #include <string>
15 :
16 : #include "absl/status/status.h"
17 : #include "backend/schema/schema.h"
18 : #include "gtest/gtest.h"
19 :
20 : namespace bigquery_emulator {
21 : namespace backend {
22 : namespace storage {
23 : namespace {
24 :
25 : // Four-column toy schema covering all three supported literal kinds
26 : // plus an array column so the rejection paths have something to
27 : // point at.
28 25 : schema::TableSchema PeopleSchema() {
29 25 : schema::TableSchema s;
30 25 : schema::ColumnSchema id;
31 25 : id.name = "id";
32 25 : id.type = schema::ColumnType::kInt64;
33 25 : id.mode = schema::ColumnMode::kRequired;
34 25 : schema::ColumnSchema name;
35 25 : name.name = "name";
36 25 : name.type = schema::ColumnType::kString;
37 25 : name.mode = schema::ColumnMode::kNullable;
38 25 : schema::ColumnSchema active;
39 25 : active.name = "active";
40 25 : active.type = schema::ColumnType::kBool;
41 25 : active.mode = schema::ColumnMode::kNullable;
42 25 : schema::ColumnSchema tags;
43 25 : tags.name = "tags";
44 25 : tags.type = schema::ColumnType::kString;
45 25 : tags.mode = schema::ColumnMode::kRepeated;
46 25 : s.columns = {id, name, active, tags};
47 25 : return s;
48 25 : }
49 :
50 1 : TEST(ParseRowRestriction, EmptyRestrictionLeavesOutUntouched) {
51 1 : EqualityPredicate pred;
52 1 : pred.column = "should-stay-empty-after-parse";
53 1 : ASSERT_TRUE(ParseRowRestriction("", PeopleSchema(), &pred).ok());
54 1 : ASSERT_TRUE(ParseRowRestriction(" \t ", PeopleSchema(), &pred).ok());
55 : // Sentinel value untouched; caller treats this as "no predicate".
56 1 : EXPECT_EQ(pred.column, "should-stay-empty-after-parse");
57 1 : }
58 :
59 1 : TEST(ParseRowRestriction, ParsesInt64Equality) {
60 1 : EqualityPredicate pred;
61 1 : ASSERT_TRUE(ParseRowRestriction("id = 42", PeopleSchema(), &pred).ok());
62 1 : EXPECT_EQ(pred.column, "id");
63 1 : EXPECT_EQ(pred.column_index, 0u);
64 1 : EXPECT_EQ(pred.kind, EqualityPredicate::Kind::kInt64);
65 1 : EXPECT_EQ(pred.int64_value, 42);
66 1 : }
67 :
68 1 : TEST(ParseRowRestriction, ParsesNegativeInt64Equality) {
69 1 : EqualityPredicate pred;
70 1 : ASSERT_TRUE(ParseRowRestriction("id = -7", PeopleSchema(), &pred).ok());
71 1 : EXPECT_EQ(pred.int64_value, -7);
72 1 : }
73 :
74 1 : TEST(ParseRowRestriction, ParsesBoolEqualityTrue) {
75 1 : EqualityPredicate pred;
76 1 : ASSERT_TRUE(ParseRowRestriction("active = true", PeopleSchema(), &pred).ok());
77 1 : EXPECT_EQ(pred.kind, EqualityPredicate::Kind::kBool);
78 1 : EXPECT_TRUE(pred.bool_value);
79 1 : }
80 :
81 1 : TEST(ParseRowRestriction, ParsesBoolEqualityFalseCaseInsensitive) {
82 1 : EqualityPredicate pred;
83 1 : ASSERT_TRUE(
84 1 : ParseRowRestriction("active = FALSE", PeopleSchema(), &pred).ok());
85 1 : EXPECT_EQ(pred.kind, EqualityPredicate::Kind::kBool);
86 1 : EXPECT_FALSE(pred.bool_value);
87 1 : }
88 :
89 1 : TEST(ParseRowRestriction, ParsesStringEquality) {
90 1 : EqualityPredicate pred;
91 1 : ASSERT_TRUE(ParseRowRestriction("name = 'ada'", PeopleSchema(), &pred).ok());
92 1 : EXPECT_EQ(pred.kind, EqualityPredicate::Kind::kString);
93 1 : EXPECT_EQ(pred.column_index, 1u);
94 1 : EXPECT_EQ(pred.string_value, "ada");
95 1 : }
96 :
97 1 : TEST(ParseRowRestriction, ParsesStringEqualityWithEscapedQuote) {
98 1 : EqualityPredicate pred;
99 1 : ASSERT_TRUE(
100 1 : ParseRowRestriction("name = 'O''Reilly'", PeopleSchema(), &pred).ok());
101 1 : EXPECT_EQ(pred.string_value, "O'Reilly");
102 1 : }
103 :
104 1 : TEST(ParseRowRestriction, ParsesDoubleQuotedStringEquality) {
105 1 : schema::TableSchema s;
106 1 : schema::ColumnSchema state;
107 1 : state.name = "state";
108 1 : state.type = schema::ColumnType::kString;
109 1 : state.mode = schema::ColumnMode::kNullable;
110 1 : s.columns = {state};
111 :
112 1 : EqualityPredicate pred;
113 1 : ASSERT_TRUE(ParseRowRestriction(R"(state = "WA")", s, &pred).ok());
114 1 : EXPECT_EQ(pred.column, "state");
115 1 : EXPECT_EQ(pred.kind, EqualityPredicate::Kind::kString);
116 1 : EXPECT_EQ(pred.string_value, "WA");
117 1 : }
118 :
119 1 : TEST(ParseRowRestriction, ParsesBacktickQuotedColumn) {
120 1 : EqualityPredicate pred;
121 1 : ASSERT_TRUE(ParseRowRestriction("`id` = 42", PeopleSchema(), &pred).ok());
122 1 : EXPECT_EQ(pred.column, "id");
123 1 : EXPECT_EQ(pred.int64_value, 42);
124 1 : }
125 :
126 1 : TEST(ParseRowRestriction, IgnoresSurroundingWhitespace) {
127 1 : EqualityPredicate pred;
128 1 : ASSERT_TRUE(
129 1 : ParseRowRestriction(" id = 42 ", PeopleSchema(), &pred).ok());
130 1 : EXPECT_EQ(pred.column, "id");
131 1 : EXPECT_EQ(pred.int64_value, 42);
132 1 : }
133 :
134 : // ---------------------------------------------------------------------------
135 : // Rejection paths
136 : // ---------------------------------------------------------------------------
137 :
138 1 : TEST(ParseRowRestriction, RejectsAndConnective) {
139 1 : EqualityPredicate pred;
140 1 : auto s =
141 1 : ParseRowRestriction("id = 1 AND name = 'ada'", PeopleSchema(), &pred);
142 1 : ASSERT_FALSE(s.ok());
143 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
144 1 : }
145 :
146 1 : TEST(ParseRowRestriction, RejectsOrConnective) {
147 1 : EqualityPredicate pred;
148 1 : auto s = ParseRowRestriction("id = 1 OR id = 2", PeopleSchema(), &pred);
149 1 : ASSERT_FALSE(s.ok());
150 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
151 1 : }
152 :
153 1 : TEST(ParseRowRestriction, RejectsRangeOperator) {
154 1 : EqualityPredicate pred;
155 1 : auto s = ParseRowRestriction("id > 1", PeopleSchema(), &pred);
156 1 : ASSERT_FALSE(s.ok());
157 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
158 1 : }
159 :
160 1 : TEST(ParseRowRestriction, RejectsInequalityOperator) {
161 1 : EqualityPredicate pred;
162 1 : auto s = ParseRowRestriction("id != 1", PeopleSchema(), &pred);
163 1 : ASSERT_FALSE(s.ok());
164 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
165 1 : }
166 :
167 1 : TEST(ParseRowRestriction, RejectsInList) {
168 1 : EqualityPredicate pred;
169 1 : auto s = ParseRowRestriction("id IN (1, 2)", PeopleSchema(), &pred);
170 1 : ASSERT_FALSE(s.ok());
171 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
172 1 : }
173 :
174 1 : TEST(ParseRowRestriction, RejectsIsNull) {
175 1 : EqualityPredicate pred;
176 1 : auto s = ParseRowRestriction("name IS NULL", PeopleSchema(), &pred);
177 1 : ASSERT_FALSE(s.ok());
178 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
179 1 : }
180 :
181 1 : TEST(ParseRowRestriction, RejectsUnknownColumn) {
182 1 : EqualityPredicate pred;
183 1 : auto s = ParseRowRestriction("nope = 1", PeopleSchema(), &pred);
184 1 : ASSERT_FALSE(s.ok());
185 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
186 1 : }
187 :
188 1 : TEST(ParseRowRestriction, RejectsRepeatedColumn) {
189 1 : EqualityPredicate pred;
190 1 : auto s = ParseRowRestriction("tags = 'kernel'", PeopleSchema(), &pred);
191 1 : ASSERT_FALSE(s.ok());
192 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
193 1 : }
194 :
195 1 : TEST(ParseRowRestriction, RejectsMissingLiteral) {
196 1 : EqualityPredicate pred;
197 1 : auto s = ParseRowRestriction("id =", PeopleSchema(), &pred);
198 1 : ASSERT_FALSE(s.ok());
199 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
200 1 : }
201 :
202 1 : TEST(ParseRowRestriction, RejectsMissingEquals) {
203 1 : EqualityPredicate pred;
204 1 : auto s = ParseRowRestriction("id 42", PeopleSchema(), &pred);
205 1 : ASSERT_FALSE(s.ok());
206 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
207 1 : }
208 :
209 1 : TEST(ParseRowRestriction, RejectsFloat64Literal) {
210 1 : EqualityPredicate pred;
211 1 : auto s = ParseRowRestriction("id = 1.5", PeopleSchema(), &pred);
212 1 : ASSERT_FALSE(s.ok());
213 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
214 1 : }
215 :
216 1 : TEST(ParseRowRestriction, RejectsBoolLiteralOnInt64Column) {
217 1 : EqualityPredicate pred;
218 1 : auto s = ParseRowRestriction("id = true", PeopleSchema(), &pred);
219 : // Lit is "true" which fails INT64 parse — caller sees
220 : // INVALID_ARGUMENT with the message naming the failure.
221 1 : ASSERT_FALSE(s.ok());
222 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
223 1 : }
224 :
225 1 : TEST(ParseRowRestriction, RejectsStringLiteralOnInt64Column) {
226 1 : EqualityPredicate pred;
227 1 : auto s = ParseRowRestriction("id = '42'", PeopleSchema(), &pred);
228 1 : ASSERT_FALSE(s.ok());
229 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
230 1 : }
231 :
232 1 : TEST(ParseRowRestriction, RejectsIntLiteralOnStringColumn) {
233 1 : EqualityPredicate pred;
234 1 : auto s = ParseRowRestriction("name = 42", PeopleSchema(), &pred);
235 1 : ASSERT_FALSE(s.ok());
236 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
237 1 : }
238 :
239 1 : TEST(ParseRowRestriction, RejectsUnterminatedString) {
240 1 : EqualityPredicate pred;
241 1 : auto s = ParseRowRestriction("name = 'ada", PeopleSchema(), &pred);
242 1 : ASSERT_FALSE(s.ok());
243 1 : EXPECT_EQ(s.code(), absl::StatusCode::kInvalidArgument);
244 1 : }
245 :
246 : } // namespace
247 : } // namespace storage
248 : } // namespace backend
249 : } // namespace bigquery_emulator
|