2525import static com .google .firestore .v1 .StructuredQuery .FieldFilter .Operator .IN ;
2626import static com .google .firestore .v1 .StructuredQuery .FieldFilter .Operator .LESS_THAN ;
2727import static com .google .firestore .v1 .StructuredQuery .FieldFilter .Operator .LESS_THAN_OR_EQUAL ;
28+ import static com .google .firestore .v1 .StructuredQuery .FieldFilter .Operator .NOT_EQUAL ;
29+ import static com .google .firestore .v1 .StructuredQuery .FieldFilter .Operator .NOT_IN ;
2830
2931import com .google .api .core .ApiFuture ;
3032import com .google .api .core .InternalExtensionOnly ;
@@ -492,6 +494,48 @@ public Query whereEqualTo(@Nonnull FieldPath fieldPath, @Nullable Object value)
492494 }
493495 }
494496
497+ /**
498+ * Creates and returns a new Query with the additional filter that documents must contain the
499+ * specified field and its value does not equal the specified value.
500+ *
501+ * @param field The name of the field to compare.
502+ * @param value The value for comparison.
503+ * @return The created Query.
504+ */
505+ @ Nonnull
506+ public Query whereNotEqualTo (@ Nonnull String field , @ Nullable Object value ) {
507+ return whereNotEqualTo (FieldPath .fromDotSeparatedString (field ), value );
508+ }
509+
510+ /**
511+ * Creates and returns a new Query with the additional filter that documents must contain the
512+ * specified field and the value does not equal the specified value.
513+ *
514+ * @param fieldPath The path of the field to compare.
515+ * @param value The value for comparison.
516+ * @return The created Query.
517+ */
518+ @ Nonnull
519+ public Query whereNotEqualTo (@ Nonnull FieldPath fieldPath , @ Nullable Object value ) {
520+ Preconditions .checkState (
521+ options .getStartCursor () == null && options .getEndCursor () == null ,
522+ "Cannot call whereNotEqualTo() after defining a boundary with startAt(), "
523+ + "startAfter(), endBefore() or endAt()." );
524+
525+ if (isUnaryComparison (value )) {
526+ Builder newOptions = options .toBuilder ();
527+ StructuredQuery .UnaryFilter .Operator op =
528+ value == null
529+ ? StructuredQuery .UnaryFilter .Operator .IS_NOT_NULL
530+ : StructuredQuery .UnaryFilter .Operator .IS_NOT_NAN ;
531+ UnaryFilter newFieldFilter = new UnaryFilter (fieldPath .toProto (), op );
532+ newOptions .setFieldFilters (append (options .getFieldFilters (), newFieldFilter ));
533+ return new Query (rpcContext , newOptions .build ());
534+ } else {
535+ return whereHelper (fieldPath , NOT_EQUAL , value );
536+ }
537+ }
538+
495539 /**
496540 * Creates and returns a new Query with the additional filter that documents must contain the
497541 * specified field and the value should be less than the specified value.
@@ -617,7 +661,8 @@ public Query whereGreaterThanOrEqualTo(@Nonnull FieldPath fieldPath, @Nonnull Ob
617661 * specified field, the value must be an array, and that the array must contain the provided
618662 * value.
619663 *
620- * <p>A Query can have only one whereArrayContains() filter.
664+ * <p>A Query can have only one whereArrayContains() filter and it cannot be combined with
665+ * whereArrayContainsAny().
621666 *
622667 * @param field The name of the field containing an array to search
623668 * @param value The value that must be contained in the array
@@ -633,7 +678,8 @@ public Query whereArrayContains(@Nonnull String field, @Nonnull Object value) {
633678 * specified field, the value must be an array, and that the array must contain the provided
634679 * value.
635680 *
636- * <p>A Query can have only one whereArrayContains() filter.
681+ * <p>A Query can have only one whereArrayContains() filter and it cannot be combined with
682+ * whereArrayContainsAny().
637683 *
638684 * @param fieldPath The path of the field containing an array to search
639685 * @param value The value that must be contained in the array
@@ -732,6 +778,46 @@ public Query whereIn(@Nonnull FieldPath fieldPath, @Nonnull List<? extends Objec
732778 return whereHelper (fieldPath , IN , values );
733779 }
734780
781+ /**
782+ * Creates and returns a new Query with the additional filter that documents must contain the
783+ * specified field and the value does not equal any of the values from the provided list.
784+ *
785+ * <p>A Query can have only one whereNotIn() filter and it cannot be combined with
786+ * whereArrayContains(), whereArrayContainsAny(), whereIn(), or whereNotEqualTo().
787+ *
788+ * @param field The name of the field to search.
789+ * @param values The list that contains the values to match.
790+ * @return The created Query.
791+ */
792+ @ Nonnull
793+ public Query whereNotIn (@ Nonnull String field , @ Nonnull List <? extends Object > values ) {
794+ Preconditions .checkState (
795+ options .getStartCursor () == null && options .getEndCursor () == null ,
796+ "Cannot call whereNotIn() after defining a boundary with startAt(), "
797+ + "startAfter(), endBefore() or endAt()." );
798+ return whereHelper (FieldPath .fromDotSeparatedString (field ), NOT_IN , values );
799+ }
800+
801+ /**
802+ * Creates and returns a new Query with the additional filter that documents must contain the
803+ * specified field and the value does not equal any of the values from the provided list.
804+ *
805+ * <p>A Query can have only one whereNotIn() filter, and it cannot be combined with
806+ * whereArrayContains(), whereArrayContainsAny(), whereIn(), or whereNotEqualTo().
807+ *
808+ * @param fieldPath The path of the field to search.
809+ * @param values The list that contains the values to match.
810+ * @return The created Query.
811+ */
812+ @ Nonnull
813+ public Query whereNotIn (@ Nonnull FieldPath fieldPath , @ Nonnull List <? extends Object > values ) {
814+ Preconditions .checkState (
815+ options .getStartCursor () == null && options .getEndCursor () == null ,
816+ "Cannot call whereNotIn() after defining a boundary with startAt(), "
817+ + "startAfter(), endBefore() or endAt()." );
818+ return whereHelper (fieldPath , NOT_IN , values );
819+ }
820+
735821 private Query whereHelper (
736822 FieldPath fieldPath , StructuredQuery .FieldFilter .Operator operator , Object value ) {
737823 Preconditions .checkArgument (
@@ -745,7 +831,7 @@ private Query whereHelper(
745831 String .format (
746832 "Invalid query. You cannot perform '%s' queries on FieldPath.documentId()." ,
747833 operator .toString ()));
748- } else if (operator == IN ) {
834+ } else if (operator == IN | operator == NOT_IN ) {
749835 if (!(value instanceof List ) || ((List <?>) value ).isEmpty ()) {
750836 throw new IllegalArgumentException (
751837 String .format (
0 commit comments