Uploaded image for project: 'Couchbase Lite'
  1. Couchbase Lite
  2. CBL-977

Full text search with Left Outer Join causes fields to be null

    XMLWordPrintable

Details

    • Bug
    • Resolution: Fixed
    • Major
    • 2.8.0
    • 2.7.0
    • LiteCore
    • Security Level: Public
    • None
    • 5

    Description

      Using a full text search with left outer join can cause some fields to be null when they should not. Note this only occurs if the item on the outer join is null and appears in the query result. Please see the following example.

       // Get the database (and create it if it doesn’t exist).
      DatabaseConfiguration config = new DatabaseConfiguration();
      Database database = new Database("humans", config);
       
      database.createIndex(
              "nameFTSIndex",
              IndexBuilder.fullTextIndex(
                      FullTextIndexItem.property("firstName"),
                      FullTextIndexItem.property("lastName")).ignoreAccents(false)
      );
       
      MutableDocument johnDoeDoc = new MutableDocument()
              .setString("type", "Person")
              .setString("firstName", "John")
              .setString("lastName", "Doe")
              .setString("gender", "male")
              .setString("companyID", "companyA");
      database.save(johnDoeDoc);
      MutableDocument janeDoeDoc = new MutableDocument()
              .setString("type", "Person")
              .setString("firstName", "Jane")
              .setString("lastName", "Doe")
              .setString("gender", "female");
      database.save(janeDoeDoc);
      MutableDocument companyADoc = new MutableDocument()
              .setString("type", "Company")
              .setString("id", "companyA")
              .setString("name", "Company A")
              .setString("location", "USA");
      database.save(companyADoc);
       
      Query query = QueryBuilder.select(
              SelectResult.expression(Expression.property("name").from("company")).as("companyName"), // PURPOSELY PUT FIELDS FROM OUTER TABLE FIRST
              SelectResult.expression(Expression.property("firstName").from("person")).as("firstName"),
              SelectResult.expression(Expression.property("lastName").from("person")).as("lastName"), // THIS WILL BE NULL IF COMPANY IS NULL EVEN THOUGH IT SHOULDN'T
              SelectResult.expression(Expression.property("gender").from("person")).as("gender"))
              .from(DataSource.database(database).as("person"))
              .join(Join.leftOuterJoin(DataSource.database(database).as("company")).on(
                      Expression.property("companyID").from("person").equalTo(Expression.property("id").from("company"))
              ))
              .where(
                      Expression.property("type").from("person").equalTo(Expression.string("Person"))
                              .and(FullTextExpression.index("nameFTSIndex").match("Doe"))
              );
              
      ResultSet result = query.execute();
      List<Result> results = result.allResults();
      Result johnDoe = results.get(0);
      Result janeDoe = results.get(1);
       
      if (janeDoe.getValue("lastName") == null) {
          Log.i(TAG, "This should not be null");
      }
      

      If an a row returns with a company the fields are populated but if the company is null then lastName is null. The field that is nullified seems to be relative to where ever a company field is placed. Note, ff the full text search is removed from the where expression the data comes back as expected.

      From my investigation it looks like the missingColumns bitmask from here is incorrect. This is also an issue in the iOS sdk, the only difference is that nil is used instead of null.

      We have tried moving the fields coming from the outer join to the bottom of the select expression with some success but this doesn't work as soon as you do more than one left outer join.

       

      Explain:

       

      SELECT
      fl_result(fl_value("company".body, 'name')),
      fl_result(fl_value("person".body, 'firstName')),
      fl_result(fl_value("person".body, 'lastName')),
      fl_result(fl_value("person".body, 'gender'))
      FROM kv_default AS "person"
      LEFT OUTER JOIN kv_default AS "company" ON
      (fl_value("person".body, 'companyID') = fl_value("company".body, 'id'))
      AND ("company".flags & 1 = 0)
      WHERE (
      fl_value("person".body, 'type') = 'Person')
      AND ("person".flags & 1 = 0)

      3|0|0| SCAN TABLE kv_default AS person
      11|0|0| SCAN TABLE kv_default AS company

      Unknown macro: {"FROM"}

      ,{"AS":"company","JOIN":"LEFT OUTER","ON":["=",[".person.companyID"],[".company.id"]]}],"WHAT":[[".company.name"],[".person.firstName"],[".person.lastName"],[".person.gender"]],"WHERE":["=",[".person.type"],"Person"]}

      Attachments

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            jimb Jim Borden
            jimb Jim Borden
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Gerrit Reviews

                There are no open Gerrit changes

                PagerDuty