Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement ExecuteQuery API for SQL support #2280

Merged
merged 18 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
Prev Previous commit
Next Next commit
Implement ResultSetMetadata and use it everwhere we operate on metadata
Also fix AbstractProtoStructReaderTest which was using the wrong runner
and not executing any tests. Fixes a couple test bugs as well.

Change-Id: Ia59459f93dd38046e9ac22995191becaad6ea44e
  • Loading branch information
jackdingilian committed Jul 16, 2024
commit e40c924be06bb2551f906b82b47d0f2a602b4512
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package com.google.cloud.bigtable.data.v2.internal;

import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import com.google.bigtable.v2.Type;
import com.google.bigtable.v2.Value;
Expand All @@ -34,7 +33,6 @@
import org.threeten.bp.Instant;

@InternalApi
@BetaApi
public abstract class AbstractProtoStructReader implements StructReader {

abstract List<Value> values();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.cloud.bigtable.data.v2.internal;

import com.google.api.core.InternalApi;
import com.google.auto.value.AutoValue;
import com.google.bigtable.v2.Type;
import com.google.cloud.bigtable.data.v2.models.sql.ColumnMetadata;

/**
* Implementation of {@link ColumnMetadata} using AutoValue
*
* <p>This is considered an internal implementation detail and not meant to be used by applications.
*/
@InternalApi("For internal use only")
@AutoValue
public abstract class ColumnMetadataImpl implements ColumnMetadata {
public static ColumnMetadata create(String name, Type type) {
return new AutoValue_ColumnMetadataImpl(name, type);
}

static ColumnMetadata fromProto(com.google.bigtable.v2.ColumnMetadata proto) {
return create(proto.getName(), proto.getType());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.cloud.bigtable.data.v2.internal;

import com.google.api.core.InternalApi;
import com.google.bigtable.v2.ProtoSchema;
import com.google.bigtable.v2.ResultSetMetadata.SchemaCase;
import com.google.bigtable.v2.Type;
import com.google.cloud.bigtable.data.v2.models.sql.ColumnMetadata;
import com.google.cloud.bigtable.data.v2.models.sql.ResultSetMetadata;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;

/**
* Implementation of {@link ResultSetMetadata} using an underlying protobuf schema.
*
* <p>This is considered an internal implementation detail and not meant to be used by applications.
*/
@InternalApi
public class ProtoResultSetMetadata implements ResultSetMetadata {

// It is valid for a select query to return columns with the same name. This marker is used
// internally in the client to designate that getting a value by column name is invalid and will
// be converted into an exception.
private static final int AMBIGUOUS_FIELD_MARKER = -1;

private final List<ColumnMetadata> columns;
private final Map<String, Integer> columnNameMapping;

public static ResultSetMetadata create(List<ColumnMetadata> columns) {
return new ProtoResultSetMetadata(columns);
}

private ProtoResultSetMetadata(List<ColumnMetadata> columns) {
this.columns = ImmutableList.copyOf(columns);
// consider deferring building this until first get-by-column-name call
this.columnNameMapping = buildColumnNameMapping(columns);
}

@Override
public List<ColumnMetadata> getColumns() {
return columns;
}

@Override
public Type getColumnType(int columnIndex) {
return columns.get(columnIndex).type();
}

@Override
public Type getColumnType(String columnName) {
return getColumnType(getColumnIndex(columnName));
}

@Override
public int getColumnIndex(String columnName) {
Integer index = columnNameMapping.get(columnName);
if (index == null) {
throw new IllegalArgumentException("Column name not found: " + columnName);
}
int unboxedIndex = index;
if (unboxedIndex == AMBIGUOUS_FIELD_MARKER) {
throw new IllegalArgumentException(
"Ambiguous column name: " + columnName + ". Same name is used for multiple columns.");
}
return unboxedIndex;
}

@InternalApi
public static ResultSetMetadata fromProto(com.google.bigtable.v2.ResultSetMetadata proto) {
Preconditions.checkState(
proto.getSchemaCase().equals(SchemaCase.PROTO_SCHEMA),
"Unsupported schema type: %s",
proto.getSchemaCase().name());
ProtoSchema schema = proto.getProtoSchema();
validateSchema(schema);
ImmutableList.Builder<ColumnMetadata> columnsBuilder = ImmutableList.builder();
for (com.google.bigtable.v2.ColumnMetadata protoColumn : schema.getColumnsList()) {
columnsBuilder.add(ColumnMetadataImpl.fromProto(protoColumn));
}
return create(columnsBuilder.build());
}

private static void validateSchema(ProtoSchema schema) {
List<com.google.bigtable.v2.ColumnMetadata> columns = schema.getColumnsList();
Preconditions.checkState(!columns.isEmpty(), "columns cannot be empty");
for (com.google.bigtable.v2.ColumnMetadata column : columns) {
Preconditions.checkState(
column.getType().getKindCase() != Type.KindCase.KIND_NOT_SET,
"Column type cannot be empty");
}
}

private Map<String, Integer> buildColumnNameMapping(List<ColumnMetadata> columns) {
HashMap<String, Integer> mapping = new HashMap<>(columns.size());
for (int i = 0; i < columns.size(); i++) {
ColumnMetadata column = columns.get(i);
if (mapping.containsKey(column.name())) {
mapping.put(column.name(), AMBIGUOUS_FIELD_MARKER);
} else {
mapping.put(column.name(), i);
}
}
return ImmutableMap.copyOf(mapping);
}

@Override
public boolean equals(@Nullable Object other) {
if (other instanceof ProtoResultSetMetadata) {
ProtoResultSetMetadata o = (ProtoResultSetMetadata) other;
// columnNameMapping is derived from columns, so we only need to compare columns
return columns.equals(o.columns);
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,47 +15,36 @@
*/
package com.google.cloud.bigtable.data.v2.internal;

import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import com.google.auto.value.AutoValue;
import com.google.bigtable.v2.ColumnMetadata;
import com.google.bigtable.v2.Type;
import com.google.bigtable.v2.Value;
import com.google.cloud.bigtable.data.v2.models.sql.ResultSetMetadata;
import java.util.List;

@InternalApi
@BetaApi
@AutoValue
public abstract class ProtoSqlRow extends AbstractProtoStructReader implements SqlRow {
/**
* Creates a new SqlRow
*
* @param schema list of column metadata for each column
* @param metadata the {@link ResultSetMetadata} for the results
* @param values list of the values for each column
*/
public static ProtoSqlRow create(List<ColumnMetadata> schema, List<Value> values) {
return new AutoValue_ProtoSqlRow(values, schema);
public static ProtoSqlRow create(ResultSetMetadata metadata, List<Value> values) {
return new AutoValue_ProtoSqlRow(values, metadata);
}

/** List of {@link ColumnMetadata} describing the schema of the row. */
abstract List<ColumnMetadata> schema();
/** {@link ResultSetMetadata} describing the schema of the row. */
abstract ResultSetMetadata metadata();

// This is a temporary hack. The type/metadata wrappers will provide an efficient way to lookup
// fields and type by columnName
// TODO(jackdingilian): replace with efficient lookup
// TODO(jackdingilian): fail on requests for column that appears multiple times
@Override
public int getColumnIndex(String columnName) {
for (int i = 0; i < schema().size(); i++) {
if (schema().get(i).getName().equals(columnName)) {
return i;
}
}
throw new IllegalArgumentException("No field found with name: " + columnName);
return metadata().getColumnIndex(columnName);
}

@Override
public Type getColumnType(int columnIndex) {
return schema().get(columnIndex).getType();
return metadata().getColumnType(columnIndex);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package com.google.cloud.bigtable.data.v2.internal;

import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import com.google.auto.value.AutoValue;
import com.google.bigtable.v2.ArrayValue;
Expand All @@ -29,7 +28,6 @@
*
* <p>This is considered an internal implementation detail and not meant to be used by applications.
*/
@BetaApi
@InternalApi("For internal use only")
@AutoValue
public abstract class ProtoStruct extends AbstractProtoStructReader implements Struct {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@
*/
package com.google.cloud.bigtable.data.v2.internal;

import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import com.google.api.gax.rpc.ServerStream;
import com.google.bigtable.v2.ResultSetMetadata;
import com.google.cloud.Date;
import com.google.cloud.bigtable.data.v2.models.sql.ResultSet;
import com.google.cloud.bigtable.data.v2.models.sql.ResultSetMetadata;
import com.google.cloud.bigtable.data.v2.models.sql.Struct;
import com.google.cloud.bigtable.data.v2.models.sql.StructReader;
import com.google.common.base.Preconditions;
Expand All @@ -39,7 +38,6 @@
*
* <p>This is considered an internal implementation detail and not meant to be used by applications.
*/
@BetaApi
@InternalApi("For internal use only")
public class ResultSetImpl implements ResultSet, StructReader {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@
*/
package com.google.cloud.bigtable.data.v2.internal;

import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import com.google.cloud.bigtable.data.v2.models.sql.StructReader;
import java.io.Serializable;

/** Internal implementation detail that provides access to row data for SQL requests. */
@InternalApi
@BetaApi
public interface SqlRow extends StructReader, Serializable {}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import com.google.bigtable.v2.ExecuteQueryResponse;
import com.google.bigtable.v2.ResultSetMetadata;
import com.google.cloud.bigtable.data.v2.models.sql.ResultSetMetadata;
import com.google.cloud.bigtable.data.v2.stub.sql.SqlRowMerger;
import com.google.common.collect.ImmutableList;
import java.util.List;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.cloud.bigtable.data.v2.models.sql;

import com.google.api.core.BetaApi;
import com.google.bigtable.v2.Type;

/** Represents the metadata for a column in a {@link ResultSet} */
@BetaApi
public interface ColumnMetadata {
/** The name of the column. Returns Empty string if the column has no name */
String name();

/** The {@link Type} of the column */
// TODO(jackdingilian): replace with type wrapper
Type type();
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
package com.google.cloud.bigtable.data.v2.models.sql;

import com.google.bigtable.v2.ResultSetMetadata;
import com.google.api.core.BetaApi;
import java.util.concurrent.ExecutionException;

/**
Expand All @@ -39,6 +39,7 @@
* <p>{@code ResultSet} implementations are not required to be thread-safe: the thread that asked
* for a ResultSet must be the one that interacts with it.
*/
@BetaApi
public interface ResultSet extends StructReader, AutoCloseable {

/**
Expand All @@ -48,10 +49,9 @@ public interface ResultSet extends StructReader, AutoCloseable {
boolean next();

/**
* Returns the metadata for the ResultSet. Blocks until the underlying stream receives the
* metadata.
* Returns the {@link ResultSetMetadata} for the ResultSet. Blocks until the underlying request
* receives the metadata.
*/
// TODO replace with a wrapper around the protos (and update doc string)
ResultSetMetadata getMetadata() throws ExecutionException, InterruptedException;

/**
Expand Down
Loading