@@ -4046,6 +4046,220 @@ void testFlatCollectionArrayAnyOnJsonbArray(String dataStoreName) {
40464046 // ids 1 and 5 have "Blue" in their colors array
40474047 assertEquals (2 , count , "Should find 2 items with 'Blue' color (ids 1, 5)" );
40484048 }
4049+
4050+ /**
4051+ * Tests for relational operators on JSONB nested fields in flat collections. Tests: CONTAINS,
4052+ * NOT_CONTAINS, IN, NOT_IN, EQ, NEQ, LT, GT on JSONB columns.
4053+ */
4054+ @ Nested
4055+ class FlatCollectionJsonbRelationalOperatorTest {
4056+
4057+ /**
4058+ * Tests CONTAINS and NOT_CONTAINS operators on JSONB array fields. - CONTAINS: finds
4059+ * documents where array contains the value - NOT_CONTAINS: finds documents where array
4060+ * doesn't contain the value (including NULL)
4061+ */
4062+ @ ParameterizedTest
4063+ @ ArgumentsSource (PostgresProvider .class )
4064+ void testJsonbArrayContainsOperators (String dataStoreName ) {
4065+ Datastore datastore = datastoreMap .get (dataStoreName );
4066+ Collection flatCollection =
4067+ datastore .getCollectionForType (FLAT_COLLECTION_NAME , DocumentType .FLAT );
4068+
4069+ // Test 1: CONTAINS - props.colors CONTAINS "Green"
4070+ // Expected: 1 document (id=1, Dettol Soap has ["Green", "White"])
4071+ Query containsQuery =
4072+ Query .builder ()
4073+ .setFilter (
4074+ RelationalExpression .of (
4075+ JsonIdentifierExpression .of ("props" , "colors" ),
4076+ CONTAINS ,
4077+ ConstantExpression .of ("Green" )))
4078+ .build ();
4079+
4080+ long containsCount = flatCollection .count (containsQuery );
4081+ assertEquals (1 , containsCount , "CONTAINS: Should find 1 document with Green color" );
4082+
4083+ // Test 2: NOT_CONTAINS - props.colors NOT_CONTAINS "Green" AND _id <= 8
4084+ // Expected: 7 documents (all except id=1 which has Green, limited to first 8)
4085+ Query notContainsQuery =
4086+ Query .builder ()
4087+ .setFilter (
4088+ LogicalExpression .builder ()
4089+ .operator (LogicalOperator .AND )
4090+ .operand (
4091+ RelationalExpression .of (
4092+ JsonIdentifierExpression .of ("props" , "colors" ),
4093+ NOT_CONTAINS ,
4094+ ConstantExpression .of ("Green" )))
4095+ .operand (
4096+ RelationalExpression .of (
4097+ IdentifierExpression .of ("_id" ), LTE , ConstantExpression .of (8 )))
4098+ .build ())
4099+ .build ();
4100+
4101+ long notContainsCount = flatCollection .count (notContainsQuery );
4102+ assertEquals (
4103+ 7 , notContainsCount , "NOT_CONTAINS: Should find 7 documents without Green color" );
4104+ }
4105+
4106+ /**
4107+ * Tests IN and NOT_IN operators on JSONB scalar fields. - IN: finds documents where field
4108+ * value is in the provided list - NOT_IN: finds documents where field value is not in the
4109+ * list (including NULL)
4110+ */
4111+ @ ParameterizedTest
4112+ @ ArgumentsSource (PostgresProvider .class )
4113+ void testJsonbScalarInOperators (String dataStoreName ) {
4114+ Datastore datastore = datastoreMap .get (dataStoreName );
4115+ Collection flatCollection =
4116+ datastore .getCollectionForType (FLAT_COLLECTION_NAME , DocumentType .FLAT );
4117+
4118+ // Test 1: IN - props.brand IN ["Dettol", "Lifebuoy"]
4119+ // Expected: 2 documents (id=1 Dettol, id=5 Lifebuoy)
4120+ Query inQuery =
4121+ Query .builder ()
4122+ .setFilter (
4123+ RelationalExpression .of (
4124+ JsonIdentifierExpression .of ("props" , "brand" ),
4125+ IN ,
4126+ ConstantExpression .ofStrings (List .of ("Dettol" , "Lifebuoy" ))))
4127+ .build ();
4128+
4129+ long inCount = flatCollection .count (inQuery );
4130+ assertEquals (2 , inCount , "IN: Should find 2 documents with Dettol or Lifebuoy brand" );
4131+
4132+ // Test 2: NOT_IN - props.brand NOT_IN ["Dettol"] AND _id <= 8
4133+ // Expected: 7 documents (all except id=1 which is Dettol, limited to first 8)
4134+ Query notInQuery =
4135+ Query .builder ()
4136+ .setFilter (
4137+ LogicalExpression .builder ()
4138+ .operator (LogicalOperator .AND )
4139+ .operand (
4140+ RelationalExpression .of (
4141+ JsonIdentifierExpression .of ("props" , "brand" ),
4142+ NOT_IN ,
4143+ ConstantExpression .ofStrings (List .of ("Dettol" ))))
4144+ .operand (
4145+ RelationalExpression .of (
4146+ IdentifierExpression .of ("_id" ), LTE , ConstantExpression .of (8 )))
4147+ .build ())
4148+ .build ();
4149+
4150+ long notInCount = flatCollection .count (notInQuery );
4151+ assertEquals (7 , notInCount , "NOT_IN: Should find 7 documents without Dettol brand" );
4152+ }
4153+
4154+ /**
4155+ * Tests EQ and NEQ operators on JSONB scalar fields. - EQ: finds documents where field equals
4156+ * the value - NEQ: finds documents where field doesn't equal the value (excluding NULL)
4157+ */
4158+ @ ParameterizedTest
4159+ @ ArgumentsSource (PostgresProvider .class )
4160+ void testJsonbScalarEqualityOperators (String dataStoreName ) {
4161+ Datastore datastore = datastoreMap .get (dataStoreName );
4162+ Collection flatCollection =
4163+ datastore .getCollectionForType (FLAT_COLLECTION_NAME , DocumentType .FLAT );
4164+
4165+ // Test 1: EQ - props.brand EQ "Dettol"
4166+ // Expected: 1 document (id=1, Dettol Soap)
4167+ Query eqQuery =
4168+ Query .builder ()
4169+ .setFilter (
4170+ RelationalExpression .of (
4171+ JsonIdentifierExpression .of ("props" , "brand" ),
4172+ EQ ,
4173+ ConstantExpression .of ("Dettol" )))
4174+ .build ();
4175+
4176+ long eqCount = flatCollection .count (eqQuery );
4177+ assertEquals (1 , eqCount , "EQ: Should find 1 document with Dettol brand" );
4178+
4179+ // Test 2: NEQ - props.brand NEQ "Dettol" (no _id filter needed)
4180+ // Expected: 2 documents (id=3 Sunsilk, id=5 Lifebuoy, excluding NULL props)
4181+ Query neqQuery =
4182+ Query .builder ()
4183+ .setFilter (
4184+ RelationalExpression .of (
4185+ JsonIdentifierExpression .of ("props" , "brand" ),
4186+ NEQ ,
4187+ ConstantExpression .of ("Dettol" )))
4188+ .build ();
4189+
4190+ long neqCount = flatCollection .count (neqQuery );
4191+ assertEquals (2 , neqCount , "NEQ: Should find 2 documents without Dettol brand" );
4192+ }
4193+
4194+ /**
4195+ * Tests LT, GT, LTE, GTE comparison operators on JSONB numeric fields. Tests deeply nested
4196+ * numeric fields like props.seller.address.pincode. Data: ids 1,3 have pincode 400004; ids
4197+ * 5,7 have pincode 700007; rest are NULL
4198+ */
4199+ @ ParameterizedTest
4200+ @ ArgumentsSource (PostgresProvider .class )
4201+ void testJsonbNumericComparisonOperators (String dataStoreName ) {
4202+ Datastore datastore = datastoreMap .get (dataStoreName );
4203+ Collection flatCollection =
4204+ datastore .getCollectionForType (FLAT_COLLECTION_NAME , DocumentType .FLAT );
4205+
4206+ // Test 1: GT - props.seller.address.pincode > 500000
4207+ // Expected: 2 documents (ids 5,7 with pincode 700007 in Kolkata)
4208+ Query gtQuery =
4209+ Query .builder ()
4210+ .setFilter (
4211+ RelationalExpression .of (
4212+ JsonIdentifierExpression .of ("props" , "seller" , "address" , "pincode" ),
4213+ GT ,
4214+ ConstantExpression .of (500000 )))
4215+ .build ();
4216+
4217+ long gtCount = flatCollection .count (gtQuery );
4218+ assertEquals (2 , gtCount , "GT: Should find 2 documents with pincode > 500000" );
4219+
4220+ // Test 2: LT - props.seller.address.pincode < 500000
4221+ // Expected: 2 documents (ids 1,3 with pincode 400004 in Mumbai)
4222+ Query ltQuery =
4223+ Query .builder ()
4224+ .setFilter (
4225+ RelationalExpression .of (
4226+ JsonIdentifierExpression .of ("props" , "seller" , "address" , "pincode" ),
4227+ LT ,
4228+ ConstantExpression .of (500000 )))
4229+ .build ();
4230+
4231+ long ltCount = flatCollection .count (ltQuery );
4232+ assertEquals (2 , ltCount , "LT: Should find 2 documents with pincode < 500000" );
4233+
4234+ // Test 3: GTE - props.seller.address.pincode >= 700000
4235+ // Expected: 2 documents (ids 5,7 with pincode 700007)
4236+ Query gteQuery =
4237+ Query .builder ()
4238+ .setFilter (
4239+ RelationalExpression .of (
4240+ JsonIdentifierExpression .of ("props" , "seller" , "address" , "pincode" ),
4241+ GTE ,
4242+ ConstantExpression .of (700000 )))
4243+ .build ();
4244+
4245+ long gteCount = flatCollection .count (gteQuery );
4246+ assertEquals (2 , gteCount , "GTE: Should find 2 documents with pincode >= 700000" );
4247+
4248+ // Test 4: LTE - props.seller.address.pincode <= 400004
4249+ // Expected: 2 documents (ids 1,3 with pincode 400004)
4250+ Query lteQuery =
4251+ Query .builder ()
4252+ .setFilter (
4253+ RelationalExpression .of (
4254+ JsonIdentifierExpression .of ("props" , "seller" , "address" , "pincode" ),
4255+ LTE ,
4256+ ConstantExpression .of (400004 )))
4257+ .build ();
4258+
4259+ long lteCount = flatCollection .count (lteQuery );
4260+ assertEquals (2 , lteCount , "LTE: Should find 2 documents with pincode <= 400004" );
4261+ }
4262+ }
40494263 }
40504264
40514265 @ Nested
0 commit comments