MD-726: Add support for `read_numbers_as_double` and `all_text_mode` options
+ MD-773: Add support to push-down filters on Date, Time and Timestamp + Fix the offsets in Date and Time data types in returned values + Updated support for DATE, TIME, and TIMESTAMP types + Modified unit-tests to check for operation push-down Project: http://git-wip-us.apache.org/repos/asf/drill/repo Commit: http://git-wip-us.apache.org/repos/asf/drill/commit/c327f113 Tree: http://git-wip-us.apache.org/repos/asf/drill/tree/c327f113 Diff: http://git-wip-us.apache.org/repos/asf/drill/diff/c327f113 Branch: refs/heads/master Commit: c327f113b7c32bbe210b3047591f4f3a2339edd1 Parents: b1218f3 Author: Smidth Panchamia <spancha...@mapr.com> Authored: Wed Mar 2 22:32:37 2016 -0800 Committer: Aditya Kishore <a...@apache.org> Committed: Fri Sep 9 10:08:37 2016 -0700 ---------------------------------------------------------------------- .../store/maprdb/MapRDBFormatPluginConfig.java | 52 ++++- .../exec/store/maprdb/MapRDBGroupScan.java | 4 + .../store/maprdb/MapRDBPushFilterIntoScan.java | 11 +- .../store/maprdb/MapRDBScanBatchCreator.java | 2 +- .../drill/exec/store/maprdb/MapRDBSubScan.java | 11 +- .../maprdb/binary/BinaryTableGroupScan.java | 2 +- .../maprdb/binary/MapRDBFilterBuilder.java | 1 - .../maprdb/json/CompareFunctionsProcessor.java | 21 +- .../store/maprdb/json/JsonConditionBuilder.java | 6 +- .../exec/store/maprdb/json/JsonSubScanSpec.java | 21 +- .../store/maprdb/json/JsonTableGroupScan.java | 2 +- .../maprdb/json/MaprDBJsonRecordReader.java | 197 ++++++++++++++++--- .../drill/maprdb/tests/json/TestSimpleJson.java | 26 ++- .../src/test/resources/json/business.json | 18 +- 14 files changed, 297 insertions(+), 77 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/drill/blob/c327f113/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBFormatPluginConfig.java ---------------------------------------------------------------------- diff --git a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBFormatPluginConfig.java b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBFormatPluginConfig.java index 7eff4e4..eb341d9 100644 --- a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBFormatPluginConfig.java +++ b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBFormatPluginConfig.java @@ -17,12 +17,19 @@ */ package org.apache.drill.exec.store.maprdb; -import com.fasterxml.jackson.annotation.JsonTypeName; import org.apache.drill.common.logical.FormatPluginConfig; -@JsonTypeName("maprdb") +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; + +@JsonTypeName("maprdb") @JsonInclude(Include.NON_DEFAULT) public class MapRDBFormatPluginConfig implements FormatPluginConfig { + private boolean allTextMode = false; + private boolean readAllNumbersAsDouble = false; + @Override public int hashCode() { return 53; @@ -30,7 +37,46 @@ public class MapRDBFormatPluginConfig implements FormatPluginConfig { @Override public boolean equals(Object obj) { - return obj instanceof MapRDBFormatPluginConfig; + if (this == obj) { + return true; + } + + if (obj == null) { + return false; + } + + if (getClass() != obj.getClass()) { + return false; + } + + MapRDBFormatPluginConfig other = (MapRDBFormatPluginConfig)obj; + + if (readAllNumbersAsDouble != other.readAllNumbersAsDouble) { + return false; + } + + if (allTextMode != other.allTextMode) { + return false; + } + + return true; + } + + public boolean isReadAllNumbersAsDouble() { + return readAllNumbersAsDouble; + } + + public boolean isAllTextMode() { + return allTextMode; + } + + @JsonProperty("allTextMode") + public void setAllTextMode(boolean mode) { + allTextMode = mode; } + @JsonProperty("readAllNumbersAsDouble") + public void setReadAllNumbersAsDouble(boolean read) { + readAllNumbersAsDouble = read; + } } http://git-wip-us.apache.org/repos/asf/drill/blob/c327f113/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBGroupScan.java ---------------------------------------------------------------------- diff --git a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBGroupScan.java b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBGroupScan.java index 4d30399..393bfe5 100644 --- a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBGroupScan.java +++ b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBGroupScan.java @@ -53,6 +53,8 @@ public abstract class MapRDBGroupScan extends AbstractGroupScan { private MapRDBFormatPlugin formatPlugin; + protected MapRDBFormatPluginConfig formatPluginConfig; + protected List<SchemaPath> columns; protected Map<Integer, List<MapRDBSubScanSpec>> endpointFragmentMapping; @@ -76,6 +78,7 @@ public abstract class MapRDBGroupScan extends AbstractGroupScan { super(that); this.columns = that.columns; this.formatPlugin = that.formatPlugin; + this.formatPluginConfig = that.formatPluginConfig; this.storagePlugin = that.storagePlugin; this.regionsToScan = that.regionsToScan; this.filterPushedDown = that.filterPushedDown; @@ -86,6 +89,7 @@ public abstract class MapRDBGroupScan extends AbstractGroupScan { super(userName); this.storagePlugin = storagePlugin; this.formatPlugin = formatPlugin; + this.formatPluginConfig = (MapRDBFormatPluginConfig)formatPlugin.getConfig(); this.columns = columns; } http://git-wip-us.apache.org/repos/asf/drill/blob/c327f113/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBPushFilterIntoScan.java ---------------------------------------------------------------------- diff --git a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBPushFilterIntoScan.java b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBPushFilterIntoScan.java index 8a0902e..634bf9a 100644 --- a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBPushFilterIntoScan.java +++ b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBPushFilterIntoScan.java @@ -17,6 +17,11 @@ */ package org.apache.drill.exec.store.maprdb; +import org.apache.calcite.plan.RelOptRuleCall; +import org.apache.calcite.plan.RelOptRuleOperand; +import org.apache.calcite.plan.RelOptUtil; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rex.RexNode; import org.apache.drill.common.expression.LogicalExpression; import org.apache.drill.exec.planner.logical.DrillOptiq; import org.apache.drill.exec.planner.logical.DrillParseContext; @@ -32,12 +37,6 @@ import org.apache.drill.exec.store.maprdb.binary.MapRDBFilterBuilder; import org.apache.drill.exec.store.maprdb.json.JsonConditionBuilder; import org.apache.drill.exec.store.maprdb.json.JsonScanSpec; import org.apache.drill.exec.store.maprdb.json.JsonTableGroupScan; -import org.apache.calcite.rel.RelNode; -import org.apache.calcite.plan.RelOptRuleCall; -import org.apache.calcite.plan.RelOptRuleOperand; -import org.apache.calcite.plan.RelOptUtil; -import org.apache.calcite.rex.RexNode; -import org.ojai.store.QueryCondition; import com.google.common.collect.ImmutableList; http://git-wip-us.apache.org/repos/asf/drill/blob/c327f113/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBScanBatchCreator.java ---------------------------------------------------------------------- diff --git a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBScanBatchCreator.java b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBScanBatchCreator.java index 058de61..1cd33ca 100644 --- a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBScanBatchCreator.java +++ b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBScanBatchCreator.java @@ -48,7 +48,7 @@ public class MapRDBScanBatchCreator implements BatchCreator<MapRDBSubScan>{ if (BinaryTableGroupScan.TABLE_BINARY.equals(subScan.getTableType())) { readers.add(new HBaseRecordReader(conf, getHBaseSubScanSpec(scanSpec), subScan.getColumns(), context)); } else { - readers.add(new MaprDBJsonRecordReader(scanSpec, subScan.getColumns(), context)); + readers.add(new MaprDBJsonRecordReader(scanSpec, subScan.getFormatPluginConfig(), subScan.getColumns(), context)); } } catch (Exception e1) { throw new ExecutionSetupException(e1); http://git-wip-us.apache.org/repos/asf/drill/blob/c327f113/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBSubScan.java ---------------------------------------------------------------------- diff --git a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBSubScan.java b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBSubScan.java index 39e45f5..7ea4cbf 100644 --- a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBSubScan.java +++ b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/MapRDBSubScan.java @@ -46,6 +46,7 @@ public class MapRDBSubScan extends AbstractBase implements SubScan { @JsonProperty public final StoragePluginConfig storage; @JsonIgnore + private final MapRDBFormatPluginConfig fsFormatPluginConfig; private final FileSystemPlugin fsStoragePlugin; private final List<MapRDBSubScanSpec> regionScanSpecList; private final List<SchemaPath> columns; @@ -54,11 +55,13 @@ public class MapRDBSubScan extends AbstractBase implements SubScan { @JsonCreator public MapRDBSubScan(@JacksonInject StoragePluginRegistry registry, @JsonProperty("userName") String userName, + @JsonProperty("formatPluginConfig") MapRDBFormatPluginConfig formatPluginConfig, @JsonProperty("storage") StoragePluginConfig storage, @JsonProperty("regionScanSpecList") List<MapRDBSubScanSpec> regionScanSpecList, @JsonProperty("columns") List<SchemaPath> columns, @JsonProperty("tableType") String tableType) throws ExecutionSetupException { super(userName); + this.fsFormatPluginConfig = formatPluginConfig; this.fsStoragePlugin = (FileSystemPlugin) registry.getPlugin(storage); this.regionScanSpecList = regionScanSpecList; this.storage = storage; @@ -66,9 +69,10 @@ public class MapRDBSubScan extends AbstractBase implements SubScan { this.tableType = tableType; } - public MapRDBSubScan(String userName, FileSystemPlugin storagePlugin, StoragePluginConfig config, + public MapRDBSubScan(String userName, MapRDBFormatPluginConfig formatPluginConfig, FileSystemPlugin storagePlugin, StoragePluginConfig config, List<MapRDBSubScanSpec> maprSubScanSpecs, List<SchemaPath> columns, String tableType) { super(userName); + fsFormatPluginConfig = formatPluginConfig; fsStoragePlugin = storagePlugin; storage = config; this.regionScanSpecList = maprSubScanSpecs; @@ -97,7 +101,7 @@ public class MapRDBSubScan extends AbstractBase implements SubScan { @Override public PhysicalOperator getNewWithChildren(List<PhysicalOperator> children) { Preconditions.checkArgument(children.isEmpty()); - return new MapRDBSubScan(getUserName(), fsStoragePlugin, storage, regionScanSpecList, columns, tableType); + return new MapRDBSubScan(getUserName(), fsFormatPluginConfig, fsStoragePlugin, storage, regionScanSpecList, columns, tableType); } @Override @@ -114,4 +118,7 @@ public class MapRDBSubScan extends AbstractBase implements SubScan { return tableType; } + public MapRDBFormatPluginConfig getFormatPluginConfig() { + return fsFormatPluginConfig; + } } http://git-wip-us.apache.org/repos/asf/drill/blob/c327f113/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/binary/BinaryTableGroupScan.java ---------------------------------------------------------------------- diff --git a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/binary/BinaryTableGroupScan.java b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/binary/BinaryTableGroupScan.java index 69fda9c..59d0d01 100644 --- a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/binary/BinaryTableGroupScan.java +++ b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/binary/BinaryTableGroupScan.java @@ -171,7 +171,7 @@ public class BinaryTableGroupScan extends MapRDBGroupScan implements DrillHBaseC assert minorFragmentId < endpointFragmentMapping.size() : String.format( "Mappings length [%d] should be greater than minor fragment id [%d] but it isn't.", endpointFragmentMapping.size(), minorFragmentId); - return new MapRDBSubScan(getUserName(), getStoragePlugin(), getStoragePlugin().getConfig(), + return new MapRDBSubScan(getUserName(), formatPluginConfig, getStoragePlugin(), getStoragePlugin().getConfig(), endpointFragmentMapping.get(minorFragmentId), columns, TABLE_BINARY); } http://git-wip-us.apache.org/repos/asf/drill/blob/c327f113/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/binary/MapRDBFilterBuilder.java ---------------------------------------------------------------------- diff --git a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/binary/MapRDBFilterBuilder.java b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/binary/MapRDBFilterBuilder.java index 07c3364..c4de6bb 100644 --- a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/binary/MapRDBFilterBuilder.java +++ b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/binary/MapRDBFilterBuilder.java @@ -34,7 +34,6 @@ import org.apache.hadoop.hbase.filter.ByteArrayComparable; import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp; import org.apache.hadoop.hbase.filter.Filter; import org.apache.hadoop.hbase.filter.NullComparator; -import org.apache.hadoop.hbase.filter.PrefixFilter; import org.apache.hadoop.hbase.filter.RegexStringComparator; import org.apache.hadoop.hbase.filter.RowFilter; import org.apache.hadoop.hbase.filter.SingleColumnValueFilter; http://git-wip-us.apache.org/repos/asf/drill/blob/c327f113/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/CompareFunctionsProcessor.java ---------------------------------------------------------------------- diff --git a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/CompareFunctionsProcessor.java b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/CompareFunctionsProcessor.java index ba44145..dc5c2b7 100644 --- a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/CompareFunctionsProcessor.java +++ b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/CompareFunctionsProcessor.java @@ -17,7 +17,6 @@ */ package org.apache.drill.exec.store.maprdb.json; -import org.apache.drill.common.expression.CastExpression; import org.apache.drill.common.expression.FunctionCall; import org.apache.drill.common.expression.LogicalExpression; import org.apache.drill.common.expression.SchemaPath; @@ -31,8 +30,13 @@ import org.apache.drill.common.expression.ValueExpressions.IntExpression; import org.apache.drill.common.expression.ValueExpressions.LongExpression; import org.apache.drill.common.expression.ValueExpressions.QuotedString; import org.apache.drill.common.expression.ValueExpressions.TimeExpression; +import org.apache.drill.common.expression.ValueExpressions.TimeStampExpression; import org.apache.drill.common.expression.visitors.AbstractExprVisitor; +import org.joda.time.LocalTime; import org.ojai.Value; +import org.ojai.types.ODate; +import org.ojai.types.OTime; +import org.ojai.types.OTimestamp; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -59,7 +63,7 @@ class CompareFunctionsProcessor extends AbstractExprVisitor<Boolean, LogicalExpr public Boolean visitUnknown(LogicalExpression e, LogicalExpression valueArg) throws RuntimeException { return false; } - + public static CompareFunctionsProcessor process(FunctionCall call) { String functionName = call.getName(); LogicalExpression nameArg = call.args.get(0); @@ -151,15 +155,20 @@ class CompareFunctionsProcessor extends AbstractExprVisitor<Boolean, LogicalExpr this.path = path; return true; } -/* + if (valueArg instanceof DateExpression) { - this.value = KeyValueBuilder.initFrom(new ODate(((DateExpression)valueArg).getDate())); + long d = ((DateExpression)valueArg).getDate(); + final long MILLISECONDS_IN_A_DAY = (long)1000 * 60 * 60 * 24; + int daysSinceEpoch = (int)(d / MILLISECONDS_IN_A_DAY); + this.value = KeyValueBuilder.initFrom(ODate.fromDaysSinceEpoch(daysSinceEpoch)); this.path = path; return true; } if (valueArg instanceof TimeExpression) { - this.value = KeyValueBuilder.initFrom(new OTime(((TimeExpression)valueArg).getTime())); + int t = ((TimeExpression)valueArg).getTime(); + LocalTime lT = LocalTime.fromMillisOfDay(t); + this.value = KeyValueBuilder.initFrom(new OTime(lT.getHourOfDay(), lT.getMinuteOfHour(), lT.getSecondOfMinute(), lT.getMillisOfSecond())); this.path = path; return true; } @@ -169,7 +178,7 @@ class CompareFunctionsProcessor extends AbstractExprVisitor<Boolean, LogicalExpr this.path = path; return true; } -*/ + return false; } http://git-wip-us.apache.org/repos/asf/drill/blob/c327f113/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonConditionBuilder.java ---------------------------------------------------------------------- diff --git a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonConditionBuilder.java b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonConditionBuilder.java index ef32436..889f5a9 100644 --- a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonConditionBuilder.java +++ b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonConditionBuilder.java @@ -17,8 +17,6 @@ */ package org.apache.drill.exec.store.maprdb.json; -import static org.ojai.DocumentConstants.ID_KEY; - import org.apache.drill.common.expression.BooleanOperator; import org.apache.drill.common.expression.FunctionCall; import org.apache.drill.common.expression.LogicalExpression; @@ -164,8 +162,6 @@ public class JsonConditionBuilder extends AbstractExprVisitor<JsonScanSpec, Void SchemaPath field = processor.getPath(); Value fieldValue = processor.getValue(); - boolean isRowKey = field.getAsUnescapedPath().equals(ID_KEY); - QueryCondition cond = null; switch (functionName) { case "equal": @@ -231,7 +227,7 @@ public class JsonConditionBuilder extends AbstractExprVisitor<JsonScanSpec, Void case "like": cond = MapRDB.newCondition().like(field.getAsUnescapedPath(), fieldValue.getString()).build(); break; - + default: } http://git-wip-us.apache.org/repos/asf/drill/blob/c327f113/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonSubScanSpec.java ---------------------------------------------------------------------- diff --git a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonSubScanSpec.java b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonSubScanSpec.java index 996f658..aa5375a 100644 --- a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonSubScanSpec.java +++ b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonSubScanSpec.java @@ -18,10 +18,10 @@ package org.apache.drill.exec.store.maprdb.json; import java.nio.ByteBuffer; +import java.util.Arrays; import org.apache.drill.exec.store.maprdb.MapRDBSubScanSpec; import org.apache.hadoop.hbase.HConstants; -import org.bouncycastle.util.Arrays; import org.ojai.DocumentConstants; import org.ojai.Value; import org.ojai.store.QueryCondition; @@ -45,17 +45,17 @@ public class JsonSubScanSpec extends MapRDBSubScanSpec { @JsonProperty("stopRow") byte[] stopRow, @JsonProperty("cond") QueryCondition cond) { super(tableName, regionServer, null, null, null, null); - + this.condition = MapRDB.newCondition().and(); - + if (cond != null) { this.condition.condition(cond); } - + if (startRow != null && - Arrays.areEqual(startRow, HConstants.EMPTY_START_ROW) == false) { + Arrays.equals(startRow, HConstants.EMPTY_START_ROW) == false) { Value startVal = IdCodec.decode(startRow); - + switch(startVal.getType()) { case BINARY: this.condition.is(DocumentConstants.ID_FIELD, Op.GREATER_OR_EQUAL, startVal.getBinary()); @@ -68,11 +68,11 @@ public class JsonSubScanSpec extends MapRDBSubScanSpec { + " for _id"); } } - + if (stopRow != null && - Arrays.areEqual(stopRow, HConstants.EMPTY_END_ROW) == false) { + Arrays.equals(stopRow, HConstants.EMPTY_END_ROW) == false) { Value stopVal = IdCodec.decode(stopRow); - + switch(stopVal.getType()) { case BINARY: this.condition.is(DocumentConstants.ID_FIELD, Op.LESS, stopVal.getBinary()); @@ -85,7 +85,7 @@ public class JsonSubScanSpec extends MapRDBSubScanSpec { + " for _id"); } } - + this.condition.close().build(); } @@ -98,6 +98,7 @@ public class JsonSubScanSpec extends MapRDBSubScanSpec { return this.condition; } + @Override public byte[] getSerializedFilter() { if (this.condition != null) { ByteBuffer bbuf = ((ConditionImpl)this.condition).getDescriptor().getSerialized(); http://git-wip-us.apache.org/repos/asf/drill/blob/c327f113/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonTableGroupScan.java ---------------------------------------------------------------------- diff --git a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonTableGroupScan.java b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonTableGroupScan.java index db27137..6d896aa 100644 --- a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonTableGroupScan.java +++ b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/JsonTableGroupScan.java @@ -146,7 +146,7 @@ public class JsonTableGroupScan extends MapRDBGroupScan { assert minorFragmentId < endpointFragmentMapping.size() : String.format( "Mappings length [%d] should be greater than minor fragment id [%d] but it isn't.", endpointFragmentMapping.size(), minorFragmentId); - return new MapRDBSubScan(getUserName(), getStoragePlugin(), getStoragePlugin().getConfig(), + return new MapRDBSubScan(getUserName(), formatPluginConfig, getStoragePlugin(), getStoragePlugin().getConfig(), endpointFragmentMapping.get(minorFragmentId), columns, TABLE_JSON); } http://git-wip-us.apache.org/repos/asf/drill/blob/c327f113/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/MaprDBJsonRecordReader.java ---------------------------------------------------------------------- diff --git a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/MaprDBJsonRecordReader.java b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/MaprDBJsonRecordReader.java index 1d1ef44..8f22a2a 100644 --- a/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/MaprDBJsonRecordReader.java +++ b/contrib/format-maprdb/src/main/java/org/apache/drill/exec/store/maprdb/json/MaprDBJsonRecordReader.java @@ -18,8 +18,10 @@ package org.apache.drill.exec.store.maprdb.json; import static org.ojai.DocumentConstants.ID_KEY; +import io.netty.buffer.DrillBuf; import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -37,6 +39,7 @@ import org.apache.drill.exec.ops.OperatorContext; import org.apache.drill.exec.ops.OperatorStats; import org.apache.drill.exec.physical.impl.OutputMutator; import org.apache.drill.exec.store.AbstractRecordReader; +import org.apache.drill.exec.store.maprdb.MapRDBFormatPluginConfig; import org.apache.drill.exec.store.maprdb.MapRDBSubScanSpec; import org.apache.drill.exec.vector.BaseValueVector; import org.apache.drill.exec.vector.complex.impl.VectorContainerWriter; @@ -51,6 +54,7 @@ import org.ojai.FieldPath; import org.ojai.FieldSegment; import org.ojai.Value; import org.ojai.store.QueryCondition; +import org.ojai.types.OTime; import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; @@ -64,8 +68,6 @@ import com.mapr.db.ojai.DBDocumentReaderBase; import com.mapr.db.util.ByteBufs; import com.mapr.org.apache.hadoop.hbase.util.Bytes; -import io.netty.buffer.DrillBuf; - public class MaprDBJsonRecordReader extends AbstractRecordReader { private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(MaprDBJsonRecordReader.class); @@ -75,7 +77,7 @@ public class MaprDBJsonRecordReader extends AbstractRecordReader { private QueryCondition condition; private FieldPath[] projectedFields; - private String tableName; + private final String tableName; private OperatorContext operatorContext; private VectorContainerWriter writer; @@ -87,9 +89,13 @@ public class MaprDBJsonRecordReader extends AbstractRecordReader { private boolean includeId; private boolean idOnly; - private boolean unionEnabled; + private final boolean unionEnabled; + private final boolean readNumbersAsDouble; + private final boolean allTextMode; + private final long MILLISECONDS_IN_A_DAY = (long)1000 * 60 * 60 * 24; public MaprDBJsonRecordReader(MapRDBSubScanSpec subScanSpec, + MapRDBFormatPluginConfig formatPluginConfig, List<SchemaPath> projectedColumns, FragmentContext context) { buffer = context.getManagedBuffer(); projectedFields = null; @@ -97,9 +103,17 @@ public class MaprDBJsonRecordReader extends AbstractRecordReader { documentReaderIterators = null; includeId = false; idOnly = false; - condition = com.mapr.db.impl.ConditionImpl.parseFrom(ByteBufs.wrap(subScanSpec.getSerializedFilter())); + byte[] serializedFilter = subScanSpec.getSerializedFilter(); + condition = null; + + if (serializedFilter != null) { + condition = com.mapr.db.impl.ConditionImpl.parseFrom(ByteBufs.wrap(serializedFilter)); + } + setColumns(projectedColumns); unionEnabled = context.getOptions().getOption(ExecConstants.ENABLE_UNION_TYPE); + readNumbersAsDouble = formatPluginConfig.isReadAllNumbersAsDouble(); + allTextMode = formatPluginConfig.isAllTextMode(); } @Override @@ -180,7 +194,11 @@ public class MaprDBJsonRecordReader extends AbstractRecordReader { recordCount++; break; case BINARY: - writeBinary(map.varBinary(ID_KEY), id.getBinary()); + if (allTextMode) { + writeString(map.varChar(ID_KEY), new String(id.getBinary().array(), Charset.forName("UTF-8"))); + } else { + writeBinary(map.varBinary(ID_KEY), id.getBinary()); + } recordCount++; break; default: @@ -224,42 +242,104 @@ public class MaprDBJsonRecordReader extends AbstractRecordReader { case NULL: break; // not setting the field will leave it as null case BINARY: - writeBinary(map.varBinary(fieldName), reader.getBinary()); + if (allTextMode) { + writeString(map.varChar(fieldName), new String(reader.getBinary().array(), Charset.forName("UTF-8"))); + } else { + writeBinary(map.varBinary(fieldName), reader.getBinary()); + } break; case BOOLEAN: - map.bit(fieldName).writeBit(reader.getBoolean() ? 1 : 0); + if (allTextMode) { + writeString(map.varChar(fieldName), String.valueOf(reader.getBoolean())); + } else { + map.bit(fieldName).writeBit(reader.getBoolean() ? 1 : 0); + } break; case STRING: writeString(map.varChar(fieldName), reader.getString()); break; case BYTE: - map.tinyInt(fieldName).writeTinyInt(reader.getByte()); + if (allTextMode) { + writeString(map.varChar(fieldName), String.valueOf(reader.getByte())); + } else if (readNumbersAsDouble) { + map.float8(fieldName).writeFloat8(reader.getByte()); + } else { + map.tinyInt(fieldName).writeTinyInt(reader.getByte()); + } break; case SHORT: - map.smallInt(fieldName).writeSmallInt(reader.getShort()); + if (allTextMode) { + writeString(map.varChar(fieldName), String.valueOf(reader.getShort())); + } else if (readNumbersAsDouble) { + map.float8(fieldName).writeFloat8(reader.getShort()); + } else { + map.smallInt(fieldName).writeSmallInt(reader.getShort()); + } break; case INT: - map.integer(fieldName).writeInt(reader.getInt()); + if (allTextMode) { + writeString(map.varChar(fieldName), String.valueOf(reader.getInt())); + } else if (readNumbersAsDouble) { + map.float8(fieldName).writeFloat8(reader.getInt()); + } else { + map.integer(fieldName).writeInt(reader.getInt()); + } break; case LONG: - map.bigInt(fieldName).writeBigInt(reader.getLong()); + if (allTextMode) { + writeString(map.varChar(fieldName), String.valueOf(reader.getLong())); + } else if (readNumbersAsDouble) { + map.float8(fieldName).writeFloat8(reader.getLong()); + } else { + map.bigInt(fieldName).writeBigInt(reader.getLong()); + } break; case FLOAT: - map.float4(fieldName).writeFloat4(reader.getFloat()); + if (allTextMode) { + writeString(map.varChar(fieldName), String.valueOf(reader.getFloat())); + } else if (readNumbersAsDouble) { + map.float8(fieldName).writeFloat8(reader.getFloat()); + } else { + map.float4(fieldName).writeFloat4(reader.getFloat()); + } break; case DOUBLE: - map.float8(fieldName).writeFloat8(reader.getDouble()); + if (allTextMode) { + writeString(map.varChar(fieldName), String.valueOf(reader.getDouble())); + } else { + map.float8(fieldName).writeFloat8(reader.getDouble()); + } break; case DECIMAL: throw unsupportedError("Decimal type is currently not supported."); case DATE: - map.date(fieldName).writeDate(reader.getDate().toDate().getTime()); + if (allTextMode) { + writeString(map.varChar(fieldName), reader.getDate().toString()); + } else { + + long milliSecondsSinceEpoch = reader.getDate().toDaysSinceEpoch() * MILLISECONDS_IN_A_DAY; + map.date(fieldName).writeDate(milliSecondsSinceEpoch); + } break; case TIME: - map.time(fieldName).writeTime(reader.getTimeInt()); + if (allTextMode) { + writeString(map.varChar(fieldName), reader.getTime().toString()); + } else { + OTime t = reader.getTime(); + int h = t.getHour(); + int m = t.getMinute(); + int s = t.getSecond(); + int ms = t.getMilliSecond(); + int millisOfDay = ms + (s + ((m + (h * 60)) * 60)) * 1000; + map.time(fieldName).writeTime(millisOfDay); + } break; case TIMESTAMP: - map.timeStamp(fieldName).writeTimeStamp(reader.getTimestampLong()); + if (allTextMode) { + writeString(map.varChar(fieldName), reader.getTimestamp().toString()); + } else { + map.timeStamp(fieldName).writeTimeStamp(reader.getTimestampLong()); + } break; case INTERVAL: throw unsupportedError("Interval type is currently not supported."); @@ -292,42 +372,103 @@ public class MaprDBJsonRecordReader extends AbstractRecordReader { case NULL: throw unsupportedError("Null values are not supported in lists."); case BINARY: - writeBinary(list.varBinary(), reader.getBinary()); + if (allTextMode) { + writeString(list.varChar(), new String(reader.getBinary().array(), Charset.forName("UTF-8"))); + } else { + writeBinary(list.varBinary(), reader.getBinary()); + } break; case BOOLEAN: - list.bit().writeBit(reader.getBoolean() ? 1 : 0); + if (allTextMode) { + writeString(list.varChar(), String.valueOf(reader.getBoolean())); + } else { + list.bit().writeBit(reader.getBoolean() ? 1 : 0); + } break; case STRING: writeString(list.varChar(), reader.getString()); break; case BYTE: - list.tinyInt().writeTinyInt(reader.getByte()); + if (allTextMode) { + writeString(list.varChar(), String.valueOf(reader.getByte())); + } else if (readNumbersAsDouble) { + list.float8().writeFloat8(reader.getByte()); + } else { + list.tinyInt().writeTinyInt(reader.getByte()); + } break; case SHORT: - list.smallInt().writeSmallInt(reader.getShort()); + if (allTextMode) { + writeString(list.varChar(), String.valueOf(reader.getShort())); + } else if (readNumbersAsDouble) { + list.float8().writeFloat8(reader.getShort()); + } else { + list.smallInt().writeSmallInt(reader.getShort()); + } break; case INT: - list.integer().writeInt(reader.getInt()); + if (allTextMode) { + writeString(list.varChar(), String.valueOf(reader.getInt())); + } else if (readNumbersAsDouble) { + list.float8().writeFloat8(reader.getInt()); + } else { + list.integer().writeInt(reader.getInt()); + } break; case LONG: - list.bigInt().writeBigInt(reader.getLong()); + if (allTextMode) { + writeString(list.varChar(), String.valueOf(reader.getLong())); + } else if (readNumbersAsDouble) { + list.float8().writeFloat8(reader.getLong()); + } else { + list.bigInt().writeBigInt(reader.getLong()); + } break; case FLOAT: - list.float4().writeFloat4(reader.getFloat()); + if (allTextMode) { + writeString(list.varChar(), String.valueOf(reader.getFloat())); + } else if (readNumbersAsDouble) { + list.float8().writeFloat8(reader.getFloat()); + } else { + list.float4().writeFloat4(reader.getFloat()); + } break; case DOUBLE: - list.float8().writeFloat8(reader.getDouble()); + if (allTextMode) { + writeString(list.varChar(), String.valueOf(reader.getDouble())); + } else { + list.float8().writeFloat8(reader.getDouble()); + } break; case DECIMAL: throw unsupportedError("Decimals are currently not supported."); case DATE: - list.date().writeDate(reader.getDate().toDate().getTime()); + if (allTextMode) { + writeString(list.varChar(), reader.getDate().toString()); + } else { + long milliSecondsSinceEpoch = reader.getDate().toDaysSinceEpoch() * MILLISECONDS_IN_A_DAY; + list.date().writeDate(milliSecondsSinceEpoch); + } break; case TIME: - list.time().writeTime(reader.getTimeInt()); + if (allTextMode) { + writeString(list.varChar(), reader.getTime().toString()); + } else { + OTime t = reader.getTime(); + int h = t.getHour(); + int m = t.getMinute(); + int s = t.getSecond(); + int ms = t.getMilliSecond(); + int millisOfDay = ms + (s + ((m + (h * 60)) * 60)) * 1000; + list.time().writeTime(millisOfDay); + } break; case TIMESTAMP: - list.timeStamp().writeTimeStamp(reader.getTimestampLong()); + if (allTextMode) { + writeString(list.varChar(), reader.getTimestamp().toString()); + } else { + list.timeStamp().writeTimeStamp(reader.getTimestampLong()); + } break; case INTERVAL: throw unsupportedError("Interval is currently not supported."); http://git-wip-us.apache.org/repos/asf/drill/blob/c327f113/contrib/format-maprdb/src/test/java/com/mapr/drill/maprdb/tests/json/TestSimpleJson.java ---------------------------------------------------------------------- diff --git a/contrib/format-maprdb/src/test/java/com/mapr/drill/maprdb/tests/json/TestSimpleJson.java b/contrib/format-maprdb/src/test/java/com/mapr/drill/maprdb/tests/json/TestSimpleJson.java index b74b1c5..f8d4ac4 100644 --- a/contrib/format-maprdb/src/test/java/com/mapr/drill/maprdb/tests/json/TestSimpleJson.java +++ b/contrib/format-maprdb/src/test/java/com/mapr/drill/maprdb/tests/json/TestSimpleJson.java @@ -330,7 +330,7 @@ public class TestSimpleJson extends BaseJsonTest { PlanTestBase.testPlanMatchingPatterns(sql, expectedPlan, excludedPlan); } - /* + @Test public void testPushDownSubField5() throws Exception { final String sql = "SELECT\n" @@ -341,6 +341,11 @@ public class TestSimpleJson extends BaseJsonTest { + " business.`hours.Tuesday.open` < TIME '10:30:00'" ; runSQLAndVerifyCount(sql, 1); + + final String[] expectedPlan = {"condition=\\(hours.Tuesday.open < \\{\"\\$time\":\"10:30:00\"\\}\\)"}; + final String[] excludedPlan = {}; + + PlanTestBase.testPlanMatchingPatterns(sql, expectedPlan, excludedPlan); } @Test @@ -352,7 +357,12 @@ public class TestSimpleJson extends BaseJsonTest { + "WHERE\n" + " business.`hours.Sunday.close` > TIME '20:30:00'" ; - runSQLAndVerifyCount(sql, 4); + runSQLAndVerifyCount(sql, 3); + + final String[] expectedPlan = {"condition=\\(hours.Sunday.close > \\{\"\\$time\":\"20:30:00\"\\}\\)"}; + final String[] excludedPlan = {}; + + PlanTestBase.testPlanMatchingPatterns(sql, expectedPlan, excludedPlan); } @Test @@ -366,6 +376,11 @@ public class TestSimpleJson extends BaseJsonTest { + " business.`start_date` = DATE '2012-07-14'" ; runSQLAndVerifyCount(sql, 1); + + final String[] expectedPlan = {"condition=\\(start_date = \\{\"\\$dateDay\":\"2012-07-14\"\\}\\)"}; + final String[] excludedPlan = {}; + + PlanTestBase.testPlanMatchingPatterns(sql, expectedPlan, excludedPlan); } @Test @@ -379,7 +394,10 @@ public class TestSimpleJson extends BaseJsonTest { + " business.`last_update` = TIMESTAMP '2012-10-20 07:42:46'" ; runSQLAndVerifyCount(sql, 1); - } - */ + final String[] expectedPlan = {"condition=\\(last_update = \\{\"\\$date\":\"2012-10-20T07:42:46.000Z\"\\}\\)"}; + final String[] excludedPlan = {}; + + PlanTestBase.testPlanMatchingPatterns(sql, expectedPlan, excludedPlan); + } } http://git-wip-us.apache.org/repos/asf/drill/blob/c327f113/contrib/format-maprdb/src/test/resources/json/business.json ---------------------------------------------------------------------- diff --git a/contrib/format-maprdb/src/test/resources/json/business.json b/contrib/format-maprdb/src/test/resources/json/business.json index 0010ea1..6cc54df 100644 --- a/contrib/format-maprdb/src/test/resources/json/business.json +++ b/contrib/format-maprdb/src/test/resources/json/business.json @@ -1,10 +1,10 @@ -{"_version":{"$numberLong":0},"business_id":"1emggGHgoG6ipd_RMb-g","full_address":"3280 S Decatur Blvd\nWestside\nLas Vegas, NV 89102","zip":{"$numberLong":89102},"hours":{},"open":true,"categories":["Food","Convenience Stores"],"city":"Las Vegas","review_count":4,"name":"Sinclair","neighborhoods":["Westside"],"longitude":-115.2072382,"state":"NV","stars":4,"latitude":36.1305306,"attributes":{"Parking":{"garage":false,"street":false,"validated":false,"lot":true,"valet":false},"Accepts Credit Cards":true,"Price Range":1},"type":"business", "start_date":"2011-07-14"} -{"_version":{"$numberLong":0},"business_id":"4Pe8BZ6gj57VFL5mUE8g","full_address":"21001 North Tatum Blvd. #24\nPhoenix, AZ 85050","zip":{"$numberLong":85050},"hours":{},"open":true,"categories":["Shopping","Office Equipment"],"city":"Phoenix","review_count":5,"name":"Office Max","neighborhoods":[],"longitude":-111.9746066,"state":"AZ","stars":3,"latitude":33.678615,"attributes":{"Parking":{"garage":false,"street":false,"validated":false,"lot":false,"valet":false},"Accepts Credit Cards":true,"Price Range":3},"type":"business", "start_date":"2012-07-14"} -{"_version":{"$numberLong":0},"business_id":"5jkZ3-nUPZxUvtcbr8Uw","full_address":"1336 N Scottsdale Rd\nScottsdale, AZ 85257","zip":{"$numberLong":85257},"hours":{"Monday":{"close":{"$time":"21:00:00"},"open":{"$time":"11:00:00"}},"Tuesday":{"close":{"$time":"21:00:00"},"open":{"$time":"11:00:00"}},"Friday":{"close":{"$time":"21:00:00"},"open":{"$time":"11:00:00"}},"Wednesday":{"close":{"$time":"21:00:00"},"open":{"$time":"11:00:00"}},"Thursday":{"close":{"$time":"21:00:00"},"open":{"$time":"11:00:00"}},"Sunday":{"close":{"$time":"21:00:00"},"open":{"$time":"11:00:00"}},"Saturday":{"close":{"$time":"21:00:00"},"open":{"$time":"11:00:00"}}},"open":true,"categories":["Greek","Restaurants"],"city":"Scottsdale","review_count":42,"name":"Mika's Greek","neighborhoods":[],"longitude":-111.926908493042,"state":"AZ","stars":4.5,"latitude":33.4633733188117,"attributes":{"Take-out":true,"Wi-Fi":"no","Good For":{"dessert":false,"latenight":false,"lunch":true,"dinner":false,"breakfast":false,"b runch":false},"Caters":true,"Noise Level":"quiet","Takes Reservations":false,"Delivery":false,"Ambience":{"romantic":false,"intimate":false,"touristy":false,"hipster":false,"divey":false,"classy":false,"trendy":false,"upscale":false,"casual":true},"Parking":{"garage":false,"street":false,"validated":false,"lot":false,"valet":false},"Has TV":false,"Outdoor Seating":true,"Attire":"casual","Alcohol":"none","Waiter Service":false,"Accepts Credit Cards":true,"Good for Kids":true,"Good For Groups":true,"Price Range":1},"type":"business", "start_date":"2013-07-14"} -{"_version":{"$numberLong":0},"business_id":"BlvDO_RG2yElKu9XA1_g","full_address":"14870 N Northsight Blvd\nSte 103\nScottsdale, AZ 85260","zip":{"$numberLong":85260},"hours":{"Monday":{"close":{"$time":"21:00:00"},"open":{"$time":"10:30:00"}},"Tuesday":{"close":{"$time":"21:00:00"},"open":{"$time":"10:30:00"}},"Friday":{"close":{"$time":"21:00:00"},"open":{"$time":"10:30:00"}},"Wednesday":{"close":{"$time":"21:00:00"},"open":{"$time":"10:30:00"}},"Thursday":{"close":{"$time":"21:00:00"},"open":{"$time":"10:30:00"}},"Sunday":{"close":{"$time":"21:00:00"},"open":{"$time":"12:00:00"}},"Saturday":{"close":{"$time":"21:00:00"},"open":{"$time":"12:00:00"}}},"open":true,"categories":["Sushi Bars","Hawaiian","Chinese","Restaurants"],"city":"Scottsdale","review_count":65,"name":"Asian Island","neighborhoods":[],"longitude":-111.89783602953,"state":"AZ","stars":4,"latitude":33.6205679923296,"attributes":{"Take-out":true,"Wi-Fi":"free","Good For":{"dessert":false,"latenight":false,"lunch":tru e,"dinner":false,"breakfast":false,"brunch":false},"Caters":true,"Noise Level":"average","Takes Reservations":false,"Has TV":false,"Delivery":true,"Ambience":{"romantic":false,"intimate":false,"touristy":false,"hipster":false,"divey":false,"classy":false,"trendy":false,"upscale":false,"casual":true},"Parking":{"garage":false,"street":false,"validated":false,"lot":true,"valet":false},"Wheelchair Accessible":true,"Outdoor Seating":true,"Attire":"casual","Alcohol":"none","Waiter Service":true,"Accepts Credit Cards":true,"Good for Kids":true,"Good For Groups":true,"Price Range":1},"type":"business", "start_date":"2014-07-14"} -{"_version":{"$numberLong":0},"business_id":"Dl2rW_xO8GuYBomlg9zw","full_address":"4505 S Maryland Pkwy\nUniversity\nLas Vegas, NV 89119","zip":{"$numberLong":89119},"hours":{},"open":true,"categories":["Medical Centers","Health & Medical"],"city":"Las Vegas","review_count":6,"name":"UNLV Student Health Center","neighborhoods":["University"],"longitude":-115.1415145,"state":"NV","stars":4,"latitude":36.1109405,"attributes":{"By Appointment Only":true},"type":"business", "start_date":"2011-04-14"} -{"_version":{"$numberLong":0},"business_id":"Ol5mVSMaW8ExtmWRUmKA","full_address":"7110 E Thomas Rd\nSte D\nScottsdale, AZ 85251","zip":{"$numberLong":85251},"hours":{},"open":true,"categories":["Barbers","Beauty & Spas"],"city":"Scottsdale","review_count":3,"name":"Dave's Barber Shop","neighborhoods":[],"longitude":-111.9289668,"state":"AZ","stars":5,"latitude":33.48051,"attributes":{"By Appointment Only":false,"Parking":{"garage":false,"street":false,"validated":false,"lot":false,"valet":false},"Price Range":2},"type":"business", "start_date":"2013-02-15"} -{"_version":{"$numberLong":0},"business_id":"XBxRlD92RaV6TyUnP8Ow","full_address":"7510 W Thomas Rd Ste 108\nPhoenix, AZ 85033","zip":{"$numberLong":85033},"hours":{"Monday":{"close":{"$time":"19:00:00"},"open":{"$time":"11:00:00"}},"Tuesday":{"close":{"$time":"20:00:00"},"open":{"$time":"09:00:00"}},"Friday":{"close":{"$time":"20:00:00"},"open":{"$time":"09:00:00"}},"Wednesday":{"close":{"$time":"20:00:00"},"open":{"$time":"09:00:00"}},"Thursday":{"close":{"$time":"20:00:00"},"open":{"$time":"09:00:00"}},"Sunday":{"close":{"$time":"21:00:00"},"open":{"$time":"09:00:00"}},"Saturday":{"close":{"$time":"21:00:00"},"open":{"$time":"09:00:00"}}},"open":true,"categories":["Shopping","Mobile Phones"],"city":"Phoenix","review_count":3,"name":"Sprint","neighborhoods":[],"longitude":-112.221054,"state":"AZ","stars":3.5,"latitude":33.480679,"attributes":{},"type":"business", "start_date":"2013-01-21", "years":[2014,2015,2016]} -{"_version":{"$numberLong":0},"business_id":"Y_2lDOtVDioX5bwF6GIw","full_address":"115 State St\nCapitol\nMadison, WI 53703","zip":{"$numberLong":53703},"hours":{"Monday":{"close":{"$time":"02:00:00"},"open":{"$time":"11:00:00"}},"Tuesday":{"close":{"$time":"02:00:00"},"open":{"$time":"11:00:00"}},"Friday":{"close":{"$time":"02:00:00"},"open":{"$time":"11:00:00"}},"Wednesday":{"close":{"$time":"02:00:00"},"open":{"$time":"11:00:00"}},"Thursday":{"close":{"$time":"02:00:00"},"open":{"$time":"11:00:00"}},"Sunday":{"close":{"$time":"02:00:00"},"open":{"$time":"11:00:00"}},"Saturday":{"close":{"$time":"02:00:00"},"open":{"$time":"11:00:00"}}},"open":true,"categories":["Bars","Comfort Food","Nightlife","Restaurants"],"city":"Madison","review_count":21,"name":"Buck & Badger","neighborhoods":["Capitol"],"longitude":-89.3871119284652,"state":"WI","stars":3,"latitude":43.0747392865267,"attributes":{"Alcohol":"full_bar","Noise Level":"average","Has TV":true,"Attire":"casual","Ambience":{"roma ntic":false,"intimate":false,"touristy":false,"hipster":false,"divey":false,"classy":false,"trendy":false,"upscale":false,"casual":false},"Good for Kids":true,"Price Range":2,"Good For Dancing":false,"Delivery":false,"Coat Check":false,"Smoking":"no","Accepts Credit Cards":true,"Take-out":true,"Happy Hour":true,"Outdoor Seating":true,"Takes Reservations":true,"Waiter Service":true,"Wi-Fi":"no","Good For":{"dessert":false,"latenight":false,"lunch":false,"dinner":false,"brunch":false,"breakfast":false},"Parking":{"garage":false,"street":false,"validated":false,"lot":false,"valet":false},"Music":{"dj":false,"background_music":true,"jukebox":false,"live":false,"video":false,"karaoke":false},"Good For Groups":true},"type":"business", "start_date":"2015-03-21"} +{"_version":{"$numberLong":0},"business_id":"1emggGHgoG6ipd_RMb-g","full_address":"3280 S Decatur Blvd\nWestside\nLas Vegas, NV 89102","zip":{"$numberLong":89102},"hours":{},"open":true,"categories":["Food","Convenience Stores"],"city":"Las Vegas","review_count":4,"name":"Sinclair","neighborhoods":["Westside"],"longitude":-115.2072382,"state":"NV","stars":4,"latitude":36.1305306,"attributes":{"Parking":{"garage":false,"street":false,"validated":false,"lot":true,"valet":false},"Accepts Credit Cards":true,"Price Range":1},"type":"business", "start_date":{"$dateDay":"2011-07-14"}, "last_update":{"$date":"2012-10-20T07:42:46.000Z"}} +{"_version":{"$numberLong":0},"business_id":"4Pe8BZ6gj57VFL5mUE8g","full_address":"21001 North Tatum Blvd. #24\nPhoenix, AZ 85050","zip":{"$numberLong":85050},"hours":{},"open":true,"categories":["Shopping","Office Equipment"],"city":"Phoenix","review_count":5,"name":"Office Max","neighborhoods":[],"longitude":-111.9746066,"state":"AZ","stars":3,"latitude":33.678615,"attributes":{"Parking":{"garage":false,"street":false,"validated":false,"lot":false,"valet":false},"Accepts Credit Cards":true,"Price Range":3},"type":"business", "start_date":{"$dateDay":"2012-07-14"}} +{"_version":{"$numberLong":0},"business_id":"5jkZ3-nUPZxUvtcbr8Uw","full_address":"1336 N Scottsdale Rd\nScottsdale, AZ 85257","zip":{"$numberLong":85257},"hours":{"Monday":{"close":{"$time":"21:00:00"},"open":{"$time":"11:00:00"}},"Tuesday":{"close":{"$time":"21:00:00"},"open":{"$time":"11:00:00"}},"Friday":{"close":{"$time":"21:00:00"},"open":{"$time":"11:00:00"}},"Wednesday":{"close":{"$time":"21:00:00"},"open":{"$time":"11:00:00"}},"Thursday":{"close":{"$time":"21:00:00"},"open":{"$time":"11:00:00"}},"Sunday":{"close":{"$time":"21:00:00"},"open":{"$time":"11:00:00"}},"Saturday":{"close":{"$time":"21:00:00"},"open":{"$time":"11:00:00"}}},"open":true,"categories":["Greek","Restaurants"],"city":"Scottsdale","review_count":42,"name":"Mika's Greek","neighborhoods":[],"longitude":-111.926908493042,"state":"AZ","stars":4.5,"latitude":33.4633733188117,"attributes":{"Take-out":true,"Wi-Fi":"no","Good For":{"dessert":false,"latenight":false,"lunch":true,"dinner":false,"breakfast":false,"b runch":false},"Caters":true,"Noise Level":"quiet","Takes Reservations":false,"Delivery":false,"Ambience":{"romantic":false,"intimate":false,"touristy":false,"hipster":false,"divey":false,"classy":false,"trendy":false,"upscale":false,"casual":true},"Parking":{"garage":false,"street":false,"validated":false,"lot":false,"valet":false},"Has TV":false,"Outdoor Seating":true,"Attire":"casual","Alcohol":"none","Waiter Service":false,"Accepts Credit Cards":true,"Good for Kids":true,"Good For Groups":true,"Price Range":1},"type":"business", "start_date":{"$dateDay":"2013-07-14"}} +{"_version":{"$numberLong":0},"business_id":"BlvDO_RG2yElKu9XA1_g","full_address":"14870 N Northsight Blvd\nSte 103\nScottsdale, AZ 85260","zip":{"$numberLong":85260},"hours":{"Monday":{"close":{"$time":"21:00:00"},"open":{"$time":"10:30:00"}},"Tuesday":{"close":{"$time":"21:00:00"},"open":{"$time":"10:30:00"}},"Friday":{"close":{"$time":"21:00:00"},"open":{"$time":"10:30:00"}},"Wednesday":{"close":{"$time":"21:00:00"},"open":{"$time":"10:30:00"}},"Thursday":{"close":{"$time":"21:00:00"},"open":{"$time":"10:30:00"}},"Sunday":{"close":{"$time":"21:00:00"},"open":{"$time":"12:00:00"}},"Saturday":{"close":{"$time":"21:00:00"},"open":{"$time":"12:00:00"}}},"open":true,"categories":["Sushi Bars","Hawaiian","Chinese","Restaurants"],"city":"Scottsdale","review_count":65,"name":"Asian Island","neighborhoods":[],"longitude":-111.89783602953,"state":"AZ","stars":4,"latitude":33.6205679923296,"attributes":{"Take-out":true,"Wi-Fi":"free","Good For":{"dessert":false,"latenight":false,"lunch":tru e,"dinner":false,"breakfast":false,"brunch":false},"Caters":true,"Noise Level":"average","Takes Reservations":false,"Has TV":false,"Delivery":true,"Ambience":{"romantic":false,"intimate":false,"touristy":false,"hipster":false,"divey":false,"classy":false,"trendy":false,"upscale":false,"casual":true},"Parking":{"garage":false,"street":false,"validated":false,"lot":true,"valet":false},"Wheelchair Accessible":true,"Outdoor Seating":true,"Attire":"casual","Alcohol":"none","Waiter Service":true,"Accepts Credit Cards":true,"Good for Kids":true,"Good For Groups":true,"Price Range":1},"type":"business", "start_date":{"$dateDay":"2014-07-14"}} +{"_version":{"$numberLong":0},"business_id":"Dl2rW_xO8GuYBomlg9zw","full_address":"4505 S Maryland Pkwy\nUniversity\nLas Vegas, NV 89119","zip":{"$numberLong":89119},"hours":{},"open":true,"categories":["Medical Centers","Health & Medical"],"city":"Las Vegas","review_count":6,"name":"UNLV Student Health Center","neighborhoods":["University"],"longitude":-115.1415145,"state":"NV","stars":4,"latitude":36.1109405,"attributes":{"By Appointment Only":true},"type":"business", "start_date":{"$dateDay":"2011-04-14"}} +{"_version":{"$numberLong":0},"business_id":"Ol5mVSMaW8ExtmWRUmKA","full_address":"7110 E Thomas Rd\nSte D\nScottsdale, AZ 85251","zip":{"$numberLong":85251},"hours":{},"open":true,"categories":["Barbers","Beauty & Spas"],"city":"Scottsdale","review_count":3,"name":"Dave's Barber Shop","neighborhoods":[],"longitude":-111.9289668,"state":"AZ","stars":5,"latitude":33.48051,"attributes":{"By Appointment Only":false,"Parking":{"garage":false,"street":false,"validated":false,"lot":false,"valet":false},"Price Range":2},"type":"business", "start_date":{"$dateDay":"2013-02-15"}} +{"_version":{"$numberLong":0},"business_id":"XBxRlD92RaV6TyUnP8Ow","full_address":"7510 W Thomas Rd Ste 108\nPhoenix, AZ 85033","zip":{"$numberLong":85033},"hours":{"Monday":{"close":{"$time":"19:00:00"},"open":{"$time":"11:00:00"}},"Tuesday":{"close":{"$time":"20:00:00"},"open":{"$time":"09:00:00"}},"Friday":{"close":{"$time":"20:00:00"},"open":{"$time":"09:00:00"}},"Wednesday":{"close":{"$time":"20:00:00"},"open":{"$time":"09:00:00"}},"Thursday":{"close":{"$time":"20:00:00"},"open":{"$time":"09:00:00"}},"Sunday":{"close":{"$time":"21:00:00"},"open":{"$time":"09:00:00"}},"Saturday":{"close":{"$time":"21:00:00"},"open":{"$time":"09:00:00"}}},"open":true,"categories":["Shopping","Mobile Phones"],"city":"Phoenix","review_count":3,"name":"Sprint","neighborhoods":[],"longitude":-112.221054,"state":"AZ","stars":3.5,"latitude":33.480679,"attributes":{},"type":"business", "start_date":{"$dateDay":"2013-01-21"}, "years":[2014,2015,2016]} +{"_version":{"$numberLong":0},"business_id":"Y_2lDOtVDioX5bwF6GIw","full_address":"115 State St\nCapitol\nMadison, WI 53703","zip":{"$numberLong":53703},"hours":{"Monday":{"close":{"$time":"02:00:00"},"open":{"$time":"11:00:00"}},"Tuesday":{"close":{"$time":"02:00:00"},"open":{"$time":"11:00:00"}},"Friday":{"close":{"$time":"02:00:00"},"open":{"$time":"11:00:00"}},"Wednesday":{"close":{"$time":"02:00:00"},"open":{"$time":"11:00:00"}},"Thursday":{"close":{"$time":"02:00:00"},"open":{"$time":"11:00:00"}},"Sunday":{"close":{"$time":"14:00:00"},"open":{"$time":"11:00:00"}},"Saturday":{"close":{"$time":"02:00:00"},"open":{"$time":"11:00:00"}}},"open":true,"categories":["Bars","Comfort Food","Nightlife","Restaurants"],"city":"Madison","review_count":21,"name":"Buck & Badger","neighborhoods":["Capitol"],"longitude":-89.3871119284652,"state":"WI","stars":3,"latitude":43.0747392865267,"attributes":{"Alcohol":"full_bar","Noise Level":"average","Has TV":true,"Attire":"casual","Ambience":{"roma ntic":false,"intimate":false,"touristy":false,"hipster":false,"divey":false,"classy":false,"trendy":false,"upscale":false,"casual":false},"Good for Kids":true,"Price Range":2,"Good For Dancing":false,"Delivery":false,"Coat Check":false,"Smoking":"no","Accepts Credit Cards":true,"Take-out":true,"Happy Hour":true,"Outdoor Seating":true,"Takes Reservations":true,"Waiter Service":true,"Wi-Fi":"no","Good For":{"dessert":false,"latenight":false,"lunch":false,"dinner":false,"brunch":false,"breakfast":false},"Parking":{"garage":false,"street":false,"validated":false,"lot":false,"valet":false},"Music":{"dj":false,"background_music":true,"jukebox":false,"live":false,"video":false,"karaoke":false},"Good For Groups":true},"type":"business", "start_date":{"$dateDay":"2015-03-21"}} {"_version":{"$numberLong":0},"business_id":"jFTZmywe7StuZ2hEjxyA","full_address":"3991 Dean Martin Dr\nLas Vegas, NV 89103","zip":{"$numberLong":89103},"hours":{},"open":true,"categories":["Sandwiches","Restaurants"],"city":"Las Vegas","review_count":4,"name":"Subway","neighborhoods":[],"longitude":-115.18200516700699,"state":"NV","stars":4,"latitude":36.1188189268328,"attributes":{"Take-out":true,"Good For":{"dessert":false,"latenight":false,"lunch":false,"dinner":false,"brunch":false,"breakfast":false},"Takes Reservations":false,"Delivery":false,"Outdoor Seating":false,"Attire":"casual","Accepts Credit Cards":true,"Good for Kids":true,"Good For Groups":true,"Price Range":1},"type":"business", "start_date":"2014-02-13"} -{"_version":{"$numberLong":0},"business_id":"m1g9P1wxNblrLANfVqlA","full_address":"6 Waterloo Place\nEdinburgh EH1 3EG","hours":{},"open":true,"categories":["Bridal","Shopping"],"city":"Edinburgh","review_count":5,"name":"Caroline Castigliano","neighborhoods":[],"longitude":-3.1881974,"state":"EDH","stars":4,"latitude":55.9534049,"attributes":{"Parking":{"garage":false,"street":false,"validated":false,"lot":false,"valet":false},"Accepts Credit Cards":true,"Price Range":3},"type":"business", "start_date":"2014-02-17"} +{"_version":{"$numberLong":0},"business_id":"m1g9P1wxNblrLANfVqlA","full_address":"6 Waterloo Place\nEdinburgh EH1 3EG","hours":{},"open":true,"categories":["Bridal","Shopping"],"city":"Edinburgh","review_count":5,"name":"Caroline Castigliano","neighborhoods":[],"longitude":-3.1881974,"state":"EDH","stars":4,"latitude":55.9534049,"attributes":{"Parking":{"garage":false,"street":false,"validated":false,"lot":false,"valet":false},"Accepts Credit Cards":true,"Price Range":3},"type":"business", "start_date":{"$dateDay":"2014-02-17"}}