Skip to content

Commit

Permalink
[CALCITE-1861] Spatial index, based on Hilbert space-filling curve
Browse files Browse the repository at this point in the history
Add SQL function Hilbert. It uses Google's uzaygezen library,
and our wraper code is copied from LocationTech's SFCurve
project.

Rewrite calls to ST_DWithin to use a range of on a Hilbert
column, plus the call to ST_DWithin for safety.

Implement function ST_MakeEnvelope.

Use constant reduction to recognize constant geometry
expressions before we apply SpatialRules.

Move interface Geom, and other classes, from GeoFunctions into
new utility class Geometries.

Geometry literals (not in the SQL parser (yet), but in
RexNode space).

Make Geom implement Comparable, so that it can be a literal
value.

Move SqlStdOperatorTables to new package, and rename to
SqlOperatorTables.

Add RelOptTestBase.Sql.withCatalogReaderFactory and
withConformance, to make it easier to run planner tests with
a different schema or SQL dialect.

Deprecate RelReferentialConstraint.getNumColumns().

In tests and examples, call ST_Point(longitude, latitude)
because conventionally x is longitude and y is latitude. (Yes,
there's a tension here. In map references and English
sentences latitude comes before longitude, but in Cartesian
geometry x comes before y.)

Close #2111
  • Loading branch information
julianhyde committed Aug 21, 2020
1 parent 39cf82b commit eab043f
Show file tree
Hide file tree
Showing 43 changed files with 1,900 additions and 419 deletions.
1 change: 1 addition & 0 deletions bom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ dependencies {
apiv("com.google.code.findbugs:jsr305", "findbugs.jsr305")
apiv("com.google.guava:guava")
apiv("com.google.protobuf:protobuf-java", "protobuf")
apiv("com.google.uzaygezen:uzaygezen-core", "uzaygezen")
apiv("com.h2database:h2")
apiv("com.jayway.jsonpath:json-path")
apiv("com.joestelmach:natty")
Expand Down
1 change: 1 addition & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dependencies {
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml")
implementation("com.google.code.findbugs:jsr305"/* optional*/)
implementation("com.google.guava:guava")
implementation("com.google.uzaygezen:uzaygezen-core")
implementation("com.jayway.jsonpath:json-path")
implementation("com.yahoo.datasketches:sketches-core")
implementation("commons-codec:commons-codec")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
import org.apache.calcite.rex.RexSubQuery;
import org.apache.calcite.rex.RexTableInputRef;
import org.apache.calcite.rex.RexVisitor;
import org.apache.calcite.runtime.GeoFunctions;
import org.apache.calcite.runtime.Geometries;
import org.apache.calcite.runtime.SqlFunctions;
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlOperator;
Expand Down Expand Up @@ -732,6 +734,11 @@ public static Expression translateLiteral(
Expressions.constant(
literal.getValueAs(byte[].class),
byte[].class));
case GEOMETRY:
final Geometries.Geom geom = literal.getValueAs(Geometries.Geom.class);
final String wkt = GeoFunctions.ST_AsWKT(geom);
return Expressions.call(null, BuiltInMethod.ST_GEOM_FROM_TEXT.method,
Expressions.constant(wkt));
case SYMBOL:
value2 = literal.getValueAs(Enum.class);
javaClass = value2.getClass();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.rel.type.RelRecordType;
import org.apache.calcite.runtime.GeoFunctions;
import org.apache.calcite.runtime.Geometries;
import org.apache.calcite.runtime.Unit;
import org.apache.calcite.sql.type.BasicSqlType;
import org.apache.calcite.sql.type.IntervalSqlType;
Expand Down Expand Up @@ -209,7 +209,7 @@ public Type getJavaClass(RelDataType type) {
case VARBINARY:
return ByteString.class;
case GEOMETRY:
return GeoFunctions.Geom.class;
return Geometries.Geom.class;
case SYMBOL:
return Enum.class;
case ANY:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.apache.calcite.config.CalciteConnectionConfig;
import org.apache.calcite.jdbc.CalciteSchema;
import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
import org.apache.calcite.linq4j.function.Hints;
import org.apache.calcite.model.ModelHandler;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.rel.type.RelDataType;
Expand Down Expand Up @@ -333,7 +334,7 @@ private static SqlOperator toOp(SqlIdentifier name,
i -> function.getParameters().get(i).getName(),
i -> function.getParameters().get(i).isOptional());

final SqlKind kind = SqlKind.OTHER_FUNCTION;
final SqlKind kind = kind(function);
if (function instanceof ScalarFunction) {
final SqlReturnTypeInference returnTypeInference =
infer((ScalarFunction) function);
Expand All @@ -357,6 +358,23 @@ private static SqlOperator toOp(SqlIdentifier name,
}
}

/** Deduces the {@link org.apache.calcite.sql.SqlKind} of a user-defined
* function based on a {@link Hints} annotation, if present. */
private static SqlKind kind(org.apache.calcite.schema.Function function) {
if (function instanceof ScalarFunctionImpl) {
Hints hints =
((ScalarFunctionImpl) function).method.getAnnotation(Hints.class);
if (hints != null) {
for (String hint : hints.value()) {
if (hint.startsWith("SqlKind:")) {
return SqlKind.valueOf(hint.substring("SqlKind:".length()));
}
}
}
}
return SqlKind.OTHER_FUNCTION;
}

private static SqlReturnTypeInference infer(final ScalarFunction function) {
return opBinding -> {
final RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
Expand Down Expand Up @@ -392,18 +410,18 @@ private static RelDataType toSql(RelDataTypeFactory typeFactory,
}

public List<SqlOperator> getOperatorList() {
final ImmutableList.Builder<SqlOperator> b = ImmutableList.builder();
final ImmutableList.Builder<SqlOperator> builder = ImmutableList.builder();
for (List<String> schemaPath : schemaPaths) {
CalciteSchema schema =
SqlValidatorUtil.getSchema(rootSchema, schemaPath, nameMatcher);
if (schema != null) {
for (String name : schema.getFunctionNames()) {
schema.getFunctions(name, true).forEach(f ->
b.add(toOp(new SqlIdentifier(name, SqlParserPos.ZERO), f)));
builder.add(toOp(new SqlIdentifier(name, SqlParserPos.ZERO), f)));
}
}
}
return b.build();
return builder.build();
}

public CalciteSchema getRootSchema() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
import org.apache.calcite.sql.parser.impl.SqlParserImpl;
import org.apache.calcite.sql.type.ExtraSqlTypes;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.util.ChainedSqlOperatorTable;
import org.apache.calcite.sql.util.SqlOperatorTables;
import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql2rel.SqlRexConvertletTable;
Expand Down Expand Up @@ -704,8 +704,10 @@ private SqlValidator createSqlValidator(Context context,
final SqlOperatorTable opTab0 =
context.config().fun(SqlOperatorTable.class,
SqlStdOperatorTable.instance());
final SqlOperatorTable opTab =
ChainedSqlOperatorTable.of(opTab0, catalogReader);
final List<SqlOperatorTable> list = new ArrayList<>();
list.add(opTab0);
list.add(catalogReader);
final SqlOperatorTable opTab = SqlOperatorTables.chain(list);
final JavaTypeFactory typeFactory = context.getTypeFactory();
final CalciteConnectionConfig connectionConfig = context.config();
final SqlValidator.Config config = SqlValidator.Config.DEFAULT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
import org.apache.calcite.sql.SqlOperatorTable;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.util.ChainedSqlOperatorTable;
import org.apache.calcite.sql.util.SqlOperatorTables;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql2rel.RelDecorrelator;
import org.apache.calcite.sql2rel.SqlRexConvertletTable;
Expand Down Expand Up @@ -321,7 +321,7 @@ private CalciteCatalogReader createCatalogReader() {

private SqlValidator createSqlValidator(CalciteCatalogReader catalogReader) {
final SqlOperatorTable opTab =
ChainedSqlOperatorTable.of(operatorTable, catalogReader);
SqlOperatorTables.chain(operatorTable, catalogReader);
return new CalciteSqlValidator(opTab,
catalogReader,
typeFactory,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,12 @@ public RelOptPredicateList getAllPredicates(RelSubset rel,
/**
* Extract predicates for a table scan.
*/
public RelOptPredicateList getAllPredicates(TableScan table, RelMetadataQuery mq) {
public RelOptPredicateList getAllPredicates(TableScan scan, RelMetadataQuery mq) {
final BuiltInMetadata.AllPredicates.Handler handler =
scan.getTable().unwrap(BuiltInMetadata.AllPredicates.Handler.class);
if (handler != null) {
return handler.getAllPredicates(scan, mq);
}
return RelOptPredicateList.EMPTY;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -702,50 +702,7 @@ public SqlNode toSql(RexProgram program, RexNode rex) {
}

case LITERAL:
final RexLiteral literal = (RexLiteral) rex;
if (literal.getTypeName() == SqlTypeName.SYMBOL) {
final Enum symbol = (Enum) literal.getValue();
return SqlLiteral.createSymbol(symbol, POS);
}
switch (literal.getTypeName().getFamily()) {
case CHARACTER:
return SqlLiteral.createCharString((String) literal.getValue2(), POS);
case NUMERIC:
case EXACT_NUMERIC:
return SqlLiteral.createExactNumeric(
literal.getValueAs(BigDecimal.class).toPlainString(), POS);
case APPROXIMATE_NUMERIC:
return SqlLiteral.createApproxNumeric(
literal.getValueAs(BigDecimal.class).toPlainString(), POS);
case BOOLEAN:
return SqlLiteral.createBoolean(literal.getValueAs(Boolean.class),
POS);
case INTERVAL_YEAR_MONTH:
case INTERVAL_DAY_TIME:
final boolean negative = literal.getValueAs(Boolean.class);
return SqlLiteral.createInterval(negative ? -1 : 1,
literal.getValueAs(String.class),
literal.getType().getIntervalQualifier(), POS);
case DATE:
return SqlLiteral.createDate(literal.getValueAs(DateString.class),
POS);
case TIME:
return SqlLiteral.createTime(literal.getValueAs(TimeString.class),
literal.getType().getPrecision(), POS);
case TIMESTAMP:
return SqlLiteral.createTimestamp(
literal.getValueAs(TimestampString.class),
literal.getType().getPrecision(), POS);
case ANY:
case NULL:
switch (literal.getTypeName()) {
case NULL:
return SqlLiteral.createNull(POS);
// fall through
}
default:
throw new AssertionError(literal + ": " + literal.getTypeName());
}
return SqlImplementor.toSql((RexLiteral) rex);

case CASE:
final RexCall caseCall = (RexCall) rex;
Expand Down Expand Up @@ -1203,6 +1160,53 @@ public SqlImplementor implementor() {
}
}

/** Converts a {@link RexLiteral} to a {@link SqlLiteral}. */
public static SqlLiteral toSql(RexLiteral literal) {
if (literal.getTypeName() == SqlTypeName.SYMBOL) {
final Enum symbol = (Enum) literal.getValue();
return SqlLiteral.createSymbol(symbol, POS);
}
switch (literal.getTypeName().getFamily()) {
case CHARACTER:
return SqlLiteral.createCharString((String) literal.getValue2(), POS);
case NUMERIC:
case EXACT_NUMERIC:
return SqlLiteral.createExactNumeric(
literal.getValueAs(BigDecimal.class).toPlainString(), POS);
case APPROXIMATE_NUMERIC:
return SqlLiteral.createApproxNumeric(
literal.getValueAs(BigDecimal.class).toPlainString(), POS);
case BOOLEAN:
return SqlLiteral.createBoolean(literal.getValueAs(Boolean.class),
POS);
case INTERVAL_YEAR_MONTH:
case INTERVAL_DAY_TIME:
final boolean negative = literal.getValueAs(Boolean.class);
return SqlLiteral.createInterval(negative ? -1 : 1,
literal.getValueAs(String.class),
literal.getType().getIntervalQualifier(), POS);
case DATE:
return SqlLiteral.createDate(literal.getValueAs(DateString.class),
POS);
case TIME:
return SqlLiteral.createTime(literal.getValueAs(TimeString.class),
literal.getType().getPrecision(), POS);
case TIMESTAMP:
return SqlLiteral.createTimestamp(
literal.getValueAs(TimestampString.class),
literal.getType().getPrecision(), POS);
case ANY:
case NULL:
switch (literal.getTypeName()) {
case NULL:
return SqlLiteral.createNull(POS);
// fall through
}
default:
throw new AssertionError(literal + ": " + literal.getTypeName());
}
}

/** Simple implementation of {@link Context} that cannot handle sub-queries
* or correlations. Because it is so simple, you do not need to create a
* {@link SqlImplementor} or {@link org.apache.calcite.tools.RelBuilder}
Expand Down

0 comments on commit eab043f

Please sign in to comment.