Package com.factual.driver

Source Code of com.factual.driver.Query

package com.factual.driver;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.List;

import com.factual.data_science_toolkit.Coord;
import com.factual.data_science_toolkit.DataScienceToolkit;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;

/**
* Represents a top level Factual query. Knows how to represent the query as URL
* encoded key value pairs, ready for the query string in a GET request. (See
* {@link #toUrlQuery()})
*
* @author aaron
*/
public class Query {
  private String fullTextSearch;
  private String[] selectFields;
  private long limit;
  private long offset;
  private boolean includeRowCount;
  private Circle circle;

  /**
   * Holds all row filters for this Query. Implicit top-level AND.
   */
  private final List<Filter> rowFilters = Lists.newArrayList();

  /**
   * Holds all results sorts for this Query. Example contents:
   * <tt>"$distance:desc","name:asc","locality:asc"</tt>
   */
  private final List<String> sorts = Lists.newArrayList();


  /**
   * Sets a full text search query. Factual will use this value to perform a
   * full text search against various attributes of the underlying table, such
   * as entity name, address, etc.
   *
   * @param term
   *          the text for which to perform a full text search.
   * @return this Query
   */
  public Query search(String term) {
    this.fullTextSearch = term;
    return this;
  }

  /**
   * Sets the maximum amount of records to return from this Query.
   * @param limit the maximum amount of records to return from this Query.
   * @return this Query
   */
  public Query limit(long limit) {
    this.limit = limit;
    return this;
  }

  /**
   * Sets the fields to select. This is optional; default behaviour is generally
   * to select all fields in the schema.
   *
   * @param fields
   *          the fields to select.
   * @return this Query
   */
  public Query only(String... fields) {
    this.selectFields = fields;
    return this;
  }

  /**
   * @return array of select fields set by only(), null if none.
   */
  public String[] getSelectFields() {
    return selectFields;
  }

  /**
   * Sets this Query to sort field in ascending order.
   *
   * @param field
   *          the field to sort in ascending order.
   * @return this Query
   */
  public Query sortAsc(String field) {
    sorts.add(field + ":asc");
    return this;
  }

  /**
   * Sets this Query to sort field in descending order.
   *
   * @param field
   *          the field to sort in descending order.
   * @return this Query
   */
  public Query sortDesc(String field) {
    sorts.add(field + ":desc");
    return this;
  }

  /**
   * Sets how many records in to start getting results (i.e., the page offset)
   * for this Query.
   *
   * @param offset
   *          the page offset for this Query.
   * @return this Query
   */
  public Query offset(long offset) {
    this.offset = offset;
    return this;
  }

  /**
   * The response will include a count of the total number of rows in the table
   * that conform to the request based on included filters. This will increase
   * the time required to return a response. The default behavior is to NOT
   * include a row count.
   *
   * @return this Query, marked to return total row count when run.
   */
  public Query includeRowCount() {
    return includeRowCount(true);
  }

  /**
   * When true, the response will include a count of the total number of rows in
   * the table that conform to the request based on included filters.
   * Requesting the row count will increase the time required to return a
   * response. The default behavior is to NOT include a row count.
   *
   * @param includeRowCount
   *          true if you want the results to include a count of the total
   *          number of rows in the table that conform to the request based on
   *          included filters.
   * @return this Query.
   */
  public Query includeRowCount(boolean includeRowCount) {
    this.includeRowCount = includeRowCount;
    return this;
  }

  /**
   * Begins construction of a new row filter.
   *
   * @param field
   *          the name of the field on which to filter.
   * @return A partial representation of the new row filter.
   * @deprecated use {@link #field(String)}
   */
  @Deprecated
  public QueryBuilder criteria(String field) {
    return new QueryBuilder(this, field);
  }

  /**
   * Begins construction of a new row filter for this Query.
   *
   * @param field
   *          the name of the field on which to filter.
   * @return A partial representation of the new row filter.
   */
  public QueryBuilder field(String field) {
    return new QueryBuilder(this, field);
  }

  public Query near(String text, int meters) {
    Coord coord = new DataScienceToolkit().streetToCoord(text);
    if(coord != null) {
      return within(new Circle(coord, meters));
    } else {
      throw new FactualApiException("Could not locate place based on text: '" + text + "'");
    }
  }

  /**
   * Adds a filter so that results can only be (roughly) within the specified
   * geographic circle.
   *
   * @param circle The circle within which to bound the results.
   * @return this Query.
   */
  public Query within(Circle circle) {
    this.circle = circle;
    return this;
  }

  /**
   * Used to nest AND'ed predicates.
   */
  public Query and(Query... queries) {
    return popFilters("$and", queries);
  }

  /**
   * Used to nest OR'ed predicates.
   */
  public Query or(Query... queries) {
    return popFilters("$or", queries);
  }

  /**
   * Adds <tt>filter</tt> to this Query.
   */
  public void add(Filter filter) {
    rowFilters.add(filter);
  }

  /**
   * Builds and returns the query string to represent this Query when talking to
   * Factual's API. Provides proper URL encoding and escaping.
   * <p>
   * Example output:
   * <pre>
   * filters=%7B%22%24and%22%3A%5B%7B%22region%22%3A%7B%22%24in%22%3A%22MA%2CVT%2CNH%22%7D%7D%2C%7B%22%24or%22%3A%5B%7B%22first_name%22%3A%7B%22%24eq%22%3A%22Chun%22%7D%7D%2C%7B%22last_name%22%3A%7B%22%24eq%22%3A%22Kok%22%7D%7D%5D%7D%5D%7D
   * </pre>
   * <p>
   * (After decoding, the above example would be used by the server as:)
   * <pre>
   * filters={"$and":[{"region":{"$in":"MA,VT,NH"}},{"$or":[{"first_name":{"$eq":"Chun"}},{"last_name":{"$eq":"Kok"}}]}]}
   * </pre>
   *
   * @return the query string to represent this Query when talking to Factual's
   *         API.
   */
  protected String toUrlQuery() {
    return Joiner.on("&").skipNulls().join(
        urlPair("select", fieldsJsonOrNull()),
        urlPair("q", fullTextSearch),
        urlPair("sort", sortsJsonOrNull()),
        (limit > 0 ? urlPair("limit", limit) : null),
        (offset > 0 ? urlPair("offset", offset) : null),
        (includeRowCount ? urlPair("include_count", true) : null),
        urlPair("filters", rowFiltersJsonOrNull()),
        urlPair("geo", geoBoundsJsonOrNull()));
  }

  @Override
  public String toString() {
    try {
      return URLDecoder.decode(toUrlQuery(), "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }

  private String urlPair(String name, Object val) {
    if(val != null) {
      try {
        return name + "=" + (val instanceof String ? URLEncoder.encode(val.toString(), "UTF-8") : val);
      } catch (UnsupportedEncodingException e) {
        throw new RuntimeException(e);
      }
    } else {
      return null;
    }
  }

  private String fieldsJsonOrNull() {
    if(selectFields != null) {
      return Joiner.on(",").join(selectFields);
    } else {
      return null;
    }
  }

  private String sortsJsonOrNull() {
    if(!sorts.isEmpty()) {
      return Joiner.on(",").join(sorts);
    } else {
      return null;
    }
  }

  private String geoBoundsJsonOrNull() {
    if(circle != null) {
      return circle.toJsonStr();
    } else {
      return null;
    }
  }

  private String rowFiltersJsonOrNull() {
    if(rowFilters.isEmpty()) {
      return null;
    } else if(rowFilters.size() == 1) {
      return rowFilters.get(0).toJsonStr();
    } else {
      return new FilterGroup(rowFilters).toJsonStr();
    }
  }

  /**
   * Pops the newest Filter from each of <tt>queries</tt>,
   * grouping each popped Filter into one new FilterGroup.
   * Adds that new FilterGroup as the newest Filter in this
   * Query.
   * <p>
   * The FilterGroup's logic will be determined by <tt>op</tt>.
   */
  private Query popFilters(String op, Query... queries) {
    FilterGroup group = new FilterGroup().op(op);
    for(Query q : queries) {
      group.add(pop(q.rowFilters));
    }
    add(group);
    return this;
  }

  private Filter pop(List<Filter> list) {
    return list.remove(list.size()-1);
  }

}
TOP

Related Classes of com.factual.driver.Query

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.