Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,13 @@ public List<JdbcStatement> extractTypeExtensions(List<Query> queries) {
public JdbcStatement addIndex(IndexDefinition index) {
var ddl =
new CreateIndexDDL(
index.getName(), index.getTableName(), index.getColumnNames(), index.getType());
return new GenericJdbcStatement(ddl.getIndexName(), Type.INDEX, ddl.getSql());
index.getName(),
index.getTableName(),
index.getColumnNames(),
index.getDirections(),
index.getType());

return new GenericJdbcStatement(ddl.indexName(), Type.INDEX, ddl.getSql());
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@
import com.google.common.base.Preconditions;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Value;
import java.util.stream.IntStream;
import org.apache.calcite.rel.RelFieldCollation.Direction;

@Value
public class CreateIndexDDL implements SqlDDLStatement {

String indexName;
String tableName;
List<String> columns;
IndexType type;
public record CreateIndexDDL(
String indexName,
String tableName,
List<String> columns,
List<Direction> directions,
IndexType type)
implements SqlDDLStatement {

@Override
public String getSql() {
Expand All @@ -41,7 +42,7 @@ public String getSql() {
"to_tsvector('english', %s )"
.formatted(
quoteIdentifier(columns).stream()
.map(col -> "coalesce(%s, '')".formatted(col))
.map("coalesce(%s, '')"::formatted)
.collect(Collectors.joining(" || ' ' || ")));
indexType = "GIN";
break;
Expand All @@ -59,7 +60,10 @@ public String getSql() {
indexType = "HNSW";
break;
default:
columnExpression = String.join(",", quoteIdentifier(columns));
columnExpression =
IntStream.range(0, columns.size())
.mapToObj(this::formatIndexColumn)
.collect(Collectors.joining(","));
indexType = type.name().toLowerCase();
}

Expand All @@ -69,4 +73,9 @@ public String getSql() {
quoteIdentifier(indexName), quoteIdentifier(tableName), indexType, columnExpression);
return sql;
}

private String formatIndexColumn(int index) {
var sortOrder = directions.get(index).isDescending() ? " DESC" : "";
return quoteIdentifier(columns.get(index)) + sortOrder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
*/
package com.datasqrl.plan.global;

import com.google.common.base.Preconditions;
import static com.google.common.base.Preconditions.checkArgument;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import lombok.Value;
import org.apache.calcite.rel.RelFieldCollation.Direction;

@Value
public class IndexDefinition implements Comparable<IndexDefinition> {
Expand All @@ -28,6 +31,7 @@ public class IndexDefinition implements Comparable<IndexDefinition> {
String tableName;
List<Integer> columns;
List<String> columnNames;
List<Direction> directions;
int partitionOffset;
IndexType type;

Expand All @@ -37,28 +41,59 @@ public IndexDefinition(
List<String> allFieldNames,
int partitionOffset,
IndexType type) {
Preconditions.checkArgument(
this(
tableName,
columns,
allFieldNames,
partitionOffset,
type,
columns.stream().map(column -> Direction.ASCENDING).toList());
}

public IndexDefinition(
String tableName,
List<Integer> columns,
List<String> allFieldNames,
int partitionOffset,
IndexType type,
List<Direction> directions) {

checkArgument(
type.isPartitioned() ^ partitionOffset < 0,
"Index must be partitioned XOR partition offset must be negative: %s | %s",
type,
partitionOffset);
Preconditions.checkArgument(

checkArgument(
partitionOffset <= columns.size(),
"Invalid partition offset: %s | %s",
partitionOffset,
columns.size());

checkArgument(
columns.size() == directions.size(),
"Number of index column directions must match number of columns: %s | %s",
columns.size(),
directions.size());

this.tableName = tableName;
this.columns = columns;
this.partitionOffset = partitionOffset;
this.columnNames = columns.stream().map(allFieldNames::get).collect(Collectors.toList());
this.type = type;
this.directions = directions;
}

private IndexDefinition(
String tableName, List<Integer> columns, List<String> columnNames, IndexType type) {
String tableName,
List<Integer> columns,
List<String> columnNames,
List<Direction> directions,
IndexType type) {
this.tableName = tableName;
this.columns = columns;
this.columnNames = columnNames;
this.directions = directions;
this.partitionOffset = -1;
this.type = type;
}
Expand All @@ -68,12 +103,19 @@ public String getName() {
+ "_"
+ type.name().toLowerCase()
+ "_"
+ columns.stream().map(i -> "c" + i).collect(Collectors.joining());
+ IntStream.range(0, columns.size())
.mapToObj(i -> "c" + columns.get(i) + (directions.get(i).isDescending() ? "d" : ""))
.collect(Collectors.joining());
}

public static IndexDefinition getPrimaryKeyIndex(
String tableId, List<Integer> primaryKeys, List<String> pkNames) {
return new IndexDefinition(tableId, primaryKeys, pkNames, IndexType.BTREE);
return new IndexDefinition(
tableId,
primaryKeys,
pkNames,
primaryKeys.stream().map(column -> Direction.ASCENDING).toList(),
IndexType.BTREE);
}

public int numEqualityColumnsRequired() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.EqualsAndHashCode.Include;
Expand Down Expand Up @@ -111,28 +110,32 @@ public Map<IndexDefinition, Double> optimizeIndexes(

public Optional<List<IndexDefinition>> getIndexHints(
String tableName, TableAnalysis tableAnalysis) {

var hints = tableAnalysis.getHints();
List<IndexHint> indexHints =
hints.getHints(IndexHint.class).collect(Collectors.toUnmodifiableList());
if (!indexHints.isEmpty()) {
return Optional.of(
indexHints.stream()
.filter(idxHint -> idxHint.getIndexType() != null) // filter out no-index hints
.filter(idxHint -> config.supportedIndexTypes().contains(idxHint.getIndexType()))
.map(
idxHint ->
new IndexDefinition(
tableName,
idxHint.getColumnIndexes(),
tableAnalysis.getRowType().getFieldNames(),
idxHint.getIndexType().isPartitioned()
? idxHint.getColumnNames().size()
: -1,
idxHint.getIndexType()))
.collect(Collectors.toUnmodifiableList()));
} else {
var indexHints = hints.getHints(IndexHint.class).toList();

if (indexHints.isEmpty()) {
return Optional.empty();
}

var indexDefinitions =
indexHints.stream()
.filter(idxHint -> idxHint.getIndexType() != null) // filter out no-index hints
.filter(idxHint -> config.supportedIndexTypes().contains(idxHint.getIndexType()))
.map(
idxHint ->
new IndexDefinition(
tableName,
idxHint.getColumnIndexes(),
tableAnalysis.getRowType().getFieldNames(),
idxHint.getIndexType().isPartitioned()
? idxHint.getColumnNames().size()
: -1,
idxHint.getIndexType(),
idxHint.getDirections()))
.toList();

return Optional.of(indexDefinitions);
}

private Map<IndexDefinition, Double> optimizeIndexes(
Expand Down Expand Up @@ -192,7 +195,7 @@ private Map<IndexDefinition, Double> optimizeIndexesWithCostMinimization(
// Determine all index candidates
Set<IndexDefinition> candidates = new LinkedHashSet<>();
indexes.forEach(idx -> candidates.addAll(generateIndexCandidates(idx)));
Function<QueryIndexSummary, Double> initialCost = idx -> idx.getBaseCost();
Function<QueryIndexSummary, Double> initialCost = QueryIndexSummary::getBaseCost;
if (config.hasPrimaryKeyIndex() && table.getAnalysis().getPrimaryKey().isDefined()) {
// The baseline cost is the cost of doing the lookup with the primary key index
// we need to use the primary key on the physical table (i.e. from the statement)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ public boolean requiresAllColumns() {
/**
* A general index covers comparison operators and can cover multiple columns. If it is not a
* general index, it is a function index that has a specific indexing method.
*
* @return
*/
public boolean isGeneralIndex() {
return this == HASH || this == BTREE || this == PBTREE;
Expand All @@ -43,6 +41,10 @@ public boolean isPartitioned() {
return this == PBTREE;
}

public boolean supportsSortOrder() {
return this == BTREE || this == PBTREE;
}

public static Optional<IndexType> fromName(String name) {
for (IndexType indexType : IndexType.values()) {
if (indexType.name().equalsIgnoreCase(name)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,13 @@ public class QueryIndexSummary {
private static final QueryIndexSummary EMPTY =
new QueryIndexSummary(null, Set.of(), Set.of(), Set.of(), 1.0);

public static final String INDEX_NAME = "_index_";

@Include NamedTable table;
@Include Set<Integer> equalityColumns;
@Include Set<Integer> inequalityColumns;
@Include Set<IndexableFunctionCall> functionCalls;

// TODO: add support for sort orders
// List<IndexableSort> sorts;

/** Keeps track of the relative frequency of query conjunctions as we reduce them */
double count = 1.0;
double count;

public static List<QueryIndexSummary> ofFilter(
@NonNull NamedTable table, RexNode filter, SqrlRexUtil rexUtil) {
Expand Down Expand Up @@ -104,16 +99,22 @@ public static List<QueryIndexSummary> ofFilter(
}

public static Optional<QueryIndexSummary> ofSort(@NonNull NamedTable table, RexNode node) {
if (node instanceof RexCall call) {
var idxFinder = new IndexableFinder();
call.accept(idxFinder);
if (idxFinder.isIndexable && idxFinder.idxCall != null) {
return Optional.of(
new QueryIndexSummary(
table, Set.of(), Set.of(), ImmutableSet.of(idxFinder.idxCall), 1.0));
}
if (node instanceof RexInputRef inputRef) {
return ofSort(table, inputRef.getIndex());
}

if (!(node instanceof RexCall call)) {
return Optional.empty();
}

var idxFinder = new IndexableFinder();
call.accept(idxFinder);
if (!idxFinder.isIndexable || idxFinder.idxCall == null) {
return Optional.empty();
}
return Optional.empty();

return Optional.of(
new QueryIndexSummary(table, Set.of(), Set.of(), ImmutableSet.of(idxFinder.idxCall), 1.0));
}

public static Optional<QueryIndexSummary> ofSort(@NonNull NamedTable table, int columnIndex) {
Expand Down Expand Up @@ -151,11 +152,11 @@ public double getCost(@NonNull IndexDefinition indexDef) {
// See which of the indexable function calls are covered
List<IndexableFunctionCall> coveredCalls = new ArrayList<>();
Set<Integer> indexCols = ImmutableSet.copyOf(indexDef.getColumns());
for (IndexableFunctionCall fcall : this.functionCalls) {
var function = fcall.function();
for (IndexableFunctionCall fnCall : this.functionCalls) {
var function = fnCall.function();
if (function.getSupportedIndexes().contains(indexType)
&& indexCols.containsAll(fcall.columnIndexes())) {
coveredCalls.add(fcall);
&& indexCols.containsAll(fnCall.columnIndexes())) {
coveredCalls.add(fnCall);
}
}
if (coveredCalls.isEmpty()) {
Expand Down
Loading