LCOV - code coverage report
Current view: top level - backend/storage - row_restriction_test.cc (source / functions) Coverage Total Hit
Test: _coverage_report.dat Lines: 100.0 % 184 184
Test Date: 2026-07-02 21:01:18 Functions: 100.0 % 26 26

            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
        

Generated by: LCOV version 2.0-1