private IndexRequest prepareInsert( DocTableInfo tableInfo, ShardUpsertRequest request, ShardUpsertRequest.Item item) throws IOException { List<GeneratedReferenceInfo> generatedReferencesWithValue = new ArrayList<>(); BytesReference source; if (request.isRawSourceInsert()) { assert item.insertValues().length > 0 : "empty insert values array"; source = new BytesArray((BytesRef) item.insertValues()[0]); } else { XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); for (int i = 0; i < item.insertValues().length; i++) { Reference ref = request.insertColumns()[i]; if (ref.info().granularity() == RowGranularity.DOC) { // don't include values for partitions in the _source // ideally columns with partition granularity shouldn't be part of the request builder.field(ref.ident().columnIdent().fqn(), item.insertValues()[i]); if (ref.info() instanceof GeneratedReferenceInfo) { generatedReferencesWithValue.add((GeneratedReferenceInfo) ref.info()); } } } source = builder.bytes(); } int generatedColumnSize = 0; for (GeneratedReferenceInfo generatedReferenceInfo : tableInfo.generatedColumns()) { if (!tableInfo.partitionedByColumns().contains(generatedReferenceInfo)) { generatedColumnSize++; } } int numMissingGeneratedColumns = generatedColumnSize - generatedReferencesWithValue.size(); if (numMissingGeneratedColumns > 0 || (generatedReferencesWithValue.size() > 0 && request.validateGeneratedColumns())) { // we need to evaluate some generated column expressions Map<String, Object> sourceMap = processGeneratedColumnsOnInsert( tableInfo, request.insertColumns(), item.insertValues(), request.isRawSourceInsert(), request.validateGeneratedColumns()); source = XContentFactory.jsonBuilder().map(sourceMap).bytes(); } IndexRequest indexRequest = Requests.indexRequest(request.index()) .type(request.type()) .id(item.id()) .routing(request.routing()) .source(source) .create(!request.overwriteDuplicates()) .operationThreaded(false); if (logger.isTraceEnabled()) { logger.trace( "Inserting document with id {}, source: {}", item.id(), indexRequest.source().toUtf8()); } return indexRequest; }
public static PartitionName toPartitionName( DocTableInfo tableInfo, List<Assignment> partitionProperties, Object[] parameters) { Preconditions.checkArgument( tableInfo.isPartitioned(), "table '%s' is not partitioned", tableInfo.ident().fqn()); Preconditions.checkArgument( partitionProperties.size() == tableInfo.partitionedBy().size(), "The table \"%s\" is partitioned by %s columns but the PARTITION clause contains %s columns", tableInfo.ident().fqn(), tableInfo.partitionedBy().size(), partitionProperties.size()); Map<ColumnIdent, Object> properties = assignmentsToMap(partitionProperties, parameters); BytesRef[] values = new BytesRef[properties.size()]; for (Map.Entry<ColumnIdent, Object> entry : properties.entrySet()) { Object value = entry.getValue(); int idx = tableInfo.partitionedBy().indexOf(entry.getKey()); try { ReferenceInfo referenceInfo = tableInfo.partitionedByColumns().get(idx); Object converted = referenceInfo.type().value(value); values[idx] = converted == null ? null : DataTypes.STRING.value(converted); } catch (IndexOutOfBoundsException ex) { throw new IllegalArgumentException( String.format("\"%s\" is no known partition column", entry.getKey().sqlFqn())); } } return new PartitionName(tableInfo.ident(), Arrays.asList(values)); }
private void processGeneratedColumns( final DocTableInfo tableInfo, Map<String, Object> updatedColumns, Map<String, Object> updatedGeneratedColumns, boolean validateExpressionValue, @Nullable GetResult getResult) { SymbolToFieldExtractorContext ctx = new SymbolToFieldExtractorContext(functions, updatedColumns); for (GeneratedReferenceInfo referenceInfo : tableInfo.generatedColumns()) { // partitionedBy columns cannot be updated if (!tableInfo.partitionedByColumns().contains(referenceInfo)) { Object givenValue = updatedGeneratedColumns.get(referenceInfo.ident().columnIdent().fqn()); if ((givenValue != null && validateExpressionValue) || generatedExpressionEvaluationNeeded( referenceInfo.referencedReferenceInfos(), updatedColumns.keySet())) { // at least one referenced column was updated, need to evaluate expression and update // column FieldExtractor<GetResult> extractor = SYMBOL_TO_FIELD_EXTRACTOR.convert(referenceInfo.generatedExpression(), ctx); Object value = extractor.extract(getResult); if (givenValue == null) { // add column & value updatedColumns.put(referenceInfo.ident().columnIdent().fqn(), value); } else if (validateExpressionValue && !givenValue.equals(value)) { throw new IllegalArgumentException( String.format( Locale.ENGLISH, "Given value %s for generated column does not match defined generated expression value %s", givenValue, value)); } } } } }
/** * Prepares an update request by converting it into an index request. * * <p>TODO: detect a NOOP and return an update response if true */ @SuppressWarnings("unchecked") public IndexRequest prepareUpdate( DocTableInfo tableInfo, ShardUpsertRequest request, ShardUpsertRequest.Item item, ShardId shardId) throws ElasticsearchException { IndexService indexService = indicesService.indexServiceSafe(shardId.getIndex()); IndexShard indexShard = indexService.shardSafe(shardId.id()); final GetResult getResult = indexShard .getService() .get( request.type(), item.id(), new String[] {RoutingFieldMapper.NAME, ParentFieldMapper.NAME, TTLFieldMapper.NAME}, true, item.version(), VersionType.INTERNAL, FetchSourceContext.FETCH_SOURCE, false); if (!getResult.isExists()) { throw new DocumentMissingException( new ShardId(request.index(), request.shardId()), request.type(), item.id()); } if (getResult.internalSourceRef() == null) { // no source, we can't do nothing, through a failure... throw new DocumentSourceMissingException( new ShardId(request.index(), request.shardId()), request.type(), item.id()); } Tuple<XContentType, Map<String, Object>> sourceAndContent = XContentHelper.convertToMap(getResult.internalSourceRef(), true); final Map<String, Object> updatedSourceAsMap; final XContentType updateSourceContentType = sourceAndContent.v1(); String routing = getResult.getFields().containsKey(RoutingFieldMapper.NAME) ? getResult.field(RoutingFieldMapper.NAME).getValue().toString() : null; String parent = getResult.getFields().containsKey(ParentFieldMapper.NAME) ? getResult.field(ParentFieldMapper.NAME).getValue().toString() : null; updatedSourceAsMap = sourceAndContent.v2(); SymbolToFieldExtractorContext ctx = new SymbolToFieldExtractorContext(functions, item.insertValues()); Map<String, Object> pathsToUpdate = new LinkedHashMap<>(); Map<String, Object> updatedGeneratedColumns = new LinkedHashMap<>(); for (int i = 0; i < request.updateColumns().length; i++) { /** * NOTE: mapping isn't applied. So if an Insert was done using the ES Rest Endpoint the data * might be returned in the wrong format (date as string instead of long) */ String columnPath = request.updateColumns()[i]; Object value = SYMBOL_TO_FIELD_EXTRACTOR.convert(item.updateAssignments()[i], ctx).extract(getResult); ReferenceInfo referenceInfo = tableInfo.getReferenceInfo(ColumnIdent.fromPath(columnPath)); if (referenceInfo instanceof GeneratedReferenceInfo) { updatedGeneratedColumns.put(columnPath, value); } else { pathsToUpdate.put(columnPath, value); } } processGeneratedColumns( tableInfo, pathsToUpdate, updatedGeneratedColumns, request.validateGeneratedColumns(), getResult); updateSourceByPaths(updatedSourceAsMap, pathsToUpdate); final IndexRequest indexRequest = Requests.indexRequest(request.index()) .type(request.type()) .id(item.id()) .routing(routing) .parent(parent) .source(updatedSourceAsMap, updateSourceContentType) .version(getResult.getVersion()); indexRequest.operationThreaded(false); return indexRequest; }