Skip to content

Commit a69a525

Browse files
authored
Extended QPT to athena-docdb (#1796)
1 parent 834ce65 commit a69a525

File tree

4 files changed

+132
-9
lines changed

4 files changed

+132
-9
lines changed

athena-docdb/src/main/java/com/amazonaws/athena/connectors/docdb/DocDBMetadataHandler.java

+34-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import com.amazonaws.athena.connector.lambda.domain.TableName;
2727
import com.amazonaws.athena.connector.lambda.domain.spill.SpillLocation;
2828
import com.amazonaws.athena.connector.lambda.handlers.GlueMetadataHandler;
29+
import com.amazonaws.athena.connector.lambda.metadata.GetDataSourceCapabilitiesRequest;
30+
import com.amazonaws.athena.connector.lambda.metadata.GetDataSourceCapabilitiesResponse;
2931
import com.amazonaws.athena.connector.lambda.metadata.GetSplitsRequest;
3032
import com.amazonaws.athena.connector.lambda.metadata.GetSplitsResponse;
3133
import com.amazonaws.athena.connector.lambda.metadata.GetTableLayoutRequest;
@@ -37,13 +39,16 @@
3739
import com.amazonaws.athena.connector.lambda.metadata.ListTablesResponse;
3840
import com.amazonaws.athena.connector.lambda.metadata.MetadataRequest;
3941
import com.amazonaws.athena.connector.lambda.metadata.glue.GlueFieldLexer;
42+
import com.amazonaws.athena.connector.lambda.metadata.optimizations.OptimizationSubType;
4043
import com.amazonaws.athena.connector.lambda.security.EncryptionKeyFactory;
44+
import com.amazonaws.athena.connectors.docdb.qpt.DocDBQueryPassthrough;
4145
import com.amazonaws.services.athena.AmazonAthena;
4246
import com.amazonaws.services.glue.AWSGlue;
4347
import com.amazonaws.services.glue.model.Database;
4448
import com.amazonaws.services.glue.model.Table;
4549
import com.amazonaws.services.secretsmanager.AWSSecretsManager;
4650
import com.google.common.base.Strings;
51+
import com.google.common.collect.ImmutableMap;
4752
import com.mongodb.client.MongoClient;
4853
import com.mongodb.client.MongoCursor;
4954
import com.mongodb.client.MongoDatabase;
@@ -98,6 +103,7 @@ public class DocDBMetadataHandler
98103

99104
private final AWSGlue glue;
100105
private final DocDBConnectionFactory connectionFactory;
106+
private final DocDBQueryPassthrough queryPassthrough = new DocDBQueryPassthrough();
101107

102108
public DocDBMetadataHandler(java.util.Map<String, String> configOptions)
103109
{
@@ -143,6 +149,15 @@ private String getConnStr(MetadataRequest request)
143149
return conStr;
144150
}
145151

152+
@Override
153+
public GetDataSourceCapabilitiesResponse doGetDataSourceCapabilities(BlockAllocator allocator, GetDataSourceCapabilitiesRequest request)
154+
{
155+
ImmutableMap.Builder<String, List<OptimizationSubType>> capabilities = ImmutableMap.builder();
156+
queryPassthrough.addQueryPassthroughCapabilityIfEnabled(capabilities, configOptions);
157+
158+
return new GetDataSourceCapabilitiesResponse(request.getCatalogName(), capabilities.build());
159+
}
160+
146161
/**
147162
* List databases in your DocumentDB instance treating each as a 'schema' (aka database)
148163
*
@@ -241,7 +256,6 @@ public ListTablesResponse doListTables(BlockAllocator blockAllocator, ListTables
241256
private Stream<String> doListTablesWithCommand(MongoClient client, ListTablesRequest request)
242257
{
243258
logger.debug("doListTablesWithCommand Start");
244-
List<String> tables = new ArrayList<>();
245259
Document queryDocument = new Document("listCollections", 1).append("nameOnly", true).append("authorizedCollections", true);
246260
Document document = client.getDatabase(request.getSchemaName()).runCommand(queryDocument);
247261

@@ -262,8 +276,19 @@ public GetTableResponse doGetTable(BlockAllocator blockAllocator, GetTableReques
262276
throws Exception
263277
{
264278
logger.info("doGetTable: enter", request.getTableName());
265-
String schemaNameInput = request.getTableName().getSchemaName();
266-
String tableNameInput = request.getTableName().getTableName();
279+
String schemaNameInput;
280+
String tableNameInput;
281+
282+
if (request.isQueryPassthrough()) {
283+
queryPassthrough.verify(request.getQueryPassthroughArguments());
284+
schemaNameInput = request.getQueryPassthroughArguments().get(DocDBQueryPassthrough.DATABASE);
285+
tableNameInput = request.getQueryPassthroughArguments().get(DocDBQueryPassthrough.COLLECTION);
286+
}
287+
else {
288+
schemaNameInput = request.getTableName().getSchemaName();
289+
tableNameInput = request.getTableName().getTableName();
290+
}
291+
267292
TableName tableName = new TableName(schemaNameInput, tableNameInput);
268293
Schema schema = null;
269294
try {
@@ -292,6 +317,12 @@ public GetTableResponse doGetTable(BlockAllocator blockAllocator, GetTableReques
292317
return new GetTableResponse(request.getCatalogName(), tableName, schema);
293318
}
294319

320+
@Override
321+
public GetTableResponse doGetQueryPassthroughSchema(BlockAllocator allocator, GetTableRequest request) throws Exception
322+
{
323+
return doGetTable(allocator, request);
324+
}
325+
295326
/**
296327
* Our table doesn't support complex layouts or partitioning so we simply make this method a NoOp.
297328
*

athena-docdb/src/main/java/com/amazonaws/athena/connectors/docdb/DocDBRecordHandler.java

+19-6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.amazonaws.athena.connector.lambda.domain.predicate.ValueSet;
2828
import com.amazonaws.athena.connector.lambda.handlers.RecordHandler;
2929
import com.amazonaws.athena.connector.lambda.records.ReadRecordsRequest;
30+
import com.amazonaws.athena.connectors.docdb.qpt.DocDBQueryPassthrough;
3031
import com.amazonaws.services.athena.AmazonAthena;
3132
import com.amazonaws.services.athena.AmazonAthenaClientBuilder;
3233
import com.amazonaws.services.s3.AmazonS3;
@@ -75,6 +76,8 @@ public class DocDBRecordHandler
7576

7677
private final DocDBConnectionFactory connectionFactory;
7778

79+
private final DocDBQueryPassthrough queryPassthrough = new DocDBQueryPassthrough();
80+
7881
public DocDBRecordHandler(java.util.Map<String, String> configOptions)
7982
{
8083
this(
@@ -138,14 +141,25 @@ protected void readWithConstraint(BlockSpiller spiller, ReadRecordsRequest recor
138141
SOURCE_TABLE_PROPERTY, tableNameObj.getTableName());
139142

140143
logger.info("Resolved tableName to: {}", tableName);
141-
142144
Map<String, ValueSet> constraintSummary = recordsRequest.getConstraints().getSummary();
143145

144146
MongoClient client = getOrCreateConn(recordsRequest.getSplit());
145-
MongoDatabase db = client.getDatabase(schemaName);
146-
MongoCollection<Document> table = db.getCollection(tableName);
147-
148-
Document query = QueryUtils.makeQuery(recordsRequest.getSchema(), constraintSummary);
147+
MongoDatabase db;
148+
MongoCollection<Document> table;
149+
Document query;
150+
151+
if (recordsRequest.getConstraints().isQueryPassThrough()) {
152+
Map<String, String> qptArguments = recordsRequest.getConstraints().getQueryPassthroughArguments();
153+
queryPassthrough.verify(qptArguments);
154+
db = client.getDatabase(qptArguments.get(DocDBQueryPassthrough.DATABASE));
155+
table = db.getCollection(qptArguments.get(DocDBQueryPassthrough.COLLECTION));
156+
query = QueryUtils.parseFilter(qptArguments.get(DocDBQueryPassthrough.FILTER));
157+
}
158+
else {
159+
db = client.getDatabase(schemaName);
160+
table = db.getCollection(tableName);
161+
query = QueryUtils.makeQuery(recordsRequest.getSchema(), constraintSummary);
162+
}
149163

150164
String disableProjectionAndCasingEnvValue = configOptions.getOrDefault(DISABLE_PROJECTION_AND_CASING_ENV, "false").toLowerCase();
151165
boolean disableProjectionAndCasing = disableProjectionAndCasingEnvValue.equals("true");
@@ -157,7 +171,6 @@ protected void readWithConstraint(BlockSpiller spiller, ReadRecordsRequest recor
157171
// Once AWS DocumentDB supports collation, then projections do not have to be disabled anymore because case
158172
// insensitive indexes allows for case insensitive projections.
159173
Document projection = disableProjectionAndCasing ? null : QueryUtils.makeProjection(recordsRequest.getSchema());
160-
161174
logger.info("readWithConstraint: query[{}] projection[{}]", query, projection);
162175

163176
final MongoCursor<Document> iterable = table

athena-docdb/src/main/java/com/amazonaws/athena/connectors/docdb/QueryUtils.java

+16
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.apache.arrow.vector.types.pojo.Schema;
4545
import org.apache.arrow.vector.util.Text;
4646
import org.bson.Document;
47+
import org.bson.json.JsonParseException;
4748

4849
import java.util.ArrayList;
4950
import java.util.List;
@@ -214,6 +215,21 @@ else if (singleValues.size() > 1) {
214215
.collect(toList()));
215216
}
216217

218+
/**
219+
* Parses DocDB/MongoDB Json Filter/Projection to confirm its valid and convert it to Doc
220+
* @param filter json's based filter
221+
* @return Document
222+
*/
223+
public static Document parseFilter(String filter)
224+
{
225+
try {
226+
return Document.parse(filter);
227+
}
228+
catch (JsonParseException e) {
229+
throw new IllegalArgumentException("Can't parse 'filter' argument as json", e);
230+
}
231+
}
232+
217233
private static Document documentOf(String key, Object value)
218234
{
219235
return new Document(key, value);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*-
2+
* #%L
3+
* athena-docdb
4+
* %%
5+
* Copyright (C) 2019 - 2024 Amazon Web Services
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package com.amazonaws.athena.connectors.docdb.qpt;
21+
22+
import com.amazonaws.athena.connector.lambda.metadata.optimizations.querypassthrough.QueryPassthroughSignature;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
25+
26+
import java.util.Arrays;
27+
import java.util.List;
28+
29+
public final class DocDBQueryPassthrough implements QueryPassthroughSignature
30+
{
31+
private static final String SCHEMA_NAME = "system";
32+
private static final String NAME = "query";
33+
34+
// List of arguments for the query, statically initialized as it always contains the same value.
35+
public static final String DATABASE = "DATABASE";
36+
public static final String COLLECTION = "COLLECTION";
37+
public static final String FILTER = "FILTER";
38+
private static final Logger LOGGER = LoggerFactory.getLogger(DocDBQueryPassthrough.class);
39+
40+
@Override
41+
public String getFunctionSchema()
42+
{
43+
return SCHEMA_NAME;
44+
}
45+
46+
@Override
47+
public String getFunctionName()
48+
{
49+
return NAME;
50+
}
51+
52+
@Override
53+
public List<String> getFunctionArguments()
54+
{
55+
return Arrays.asList(DATABASE, COLLECTION, FILTER);
56+
}
57+
58+
@Override
59+
public Logger getLogger()
60+
{
61+
return LOGGER;
62+
}
63+
}

0 commit comments

Comments
 (0)