/* * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. Crate licenses * this file to you 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 * * http://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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial agreement. */ package io.crate.metadata; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import io.crate.types.StringType; import org.apache.commons.codec.binary.Base32; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; public class PartitionName { private static final Base32 BASE32 = new Base32(true); private static final Joiner DOT_JOINER = Joiner.on("."); private static final Splitter SPLITTER = Splitter.on(".").limit(5); private final TableIdent tableIdent; private List<BytesRef> values; private String indexName; private String ident; public static final String PARTITIONED_TABLE_PREFIX = ".partitioned"; public PartitionName(TableIdent tableIdent, List<BytesRef> values) { this.tableIdent = tableIdent; this.values = values; } public PartitionName(String tableName, List<BytesRef> values) { this(null, tableName, values); } public PartitionName(@Nullable String schemaName, String tableName, List<BytesRef> values) { this(new TableIdent(schemaName, tableName), values); } public static String indexName(TableIdent tableIdent, String ident) { if (tableIdent.schema().equalsIgnoreCase(Schemas.DEFAULT_SCHEMA_NAME)) { return DOT_JOINER.join(PARTITIONED_TABLE_PREFIX, tableIdent.name(), ident); } return DOT_JOINER.join(tableIdent.schema(), PARTITIONED_TABLE_PREFIX, tableIdent.name(), ident); } /** * decodes an encoded ident into it's values */ @Nullable public static List<BytesRef> decodeIdent(@Nullable String ident) { if (ident == null) { return ImmutableList.of(); } byte[] inputBytes = BASE32.decode(ident.toUpperCase(Locale.ROOT)); try (StreamInput in = StreamInput.wrap(inputBytes)) { int size = in.readVInt(); List<BytesRef> values = new ArrayList<>(size); for (int i = 0; i < size; i++) { values.add(StringType.INSTANCE.streamer().readValueFrom(in)); } return values; } catch (IOException e) { throw new IllegalArgumentException( String.format(Locale.ENGLISH, "Invalid partition ident: %s", ident), e); } } @Nullable public static String encodeIdent(Collection<? extends BytesRef> values) { if (values.size() == 0) { return null; } BytesStreamOutput streamOutput = new BytesStreamOutput(estimateSize(values)); try { streamOutput.writeVInt(values.size()); for (BytesRef value : values) { StringType.INSTANCE.streamer().writeValueTo(streamOutput, value); } } catch (IOException e) { throw Throwables.propagate(e); } byte[] bytes = BytesReference.toBytes(streamOutput.bytes()); String identBase32 = BASE32.encodeAsString(bytes).toLowerCase(Locale.ROOT); // decode doesn't need padding, remove it int idx = identBase32.indexOf('='); if (idx > -1) { return identBase32.substring(0, idx); } return identBase32; } /** * estimates the size the bytesRef values will take if written onto a StreamOutput using the String streamer */ private static int estimateSize(Iterable<? extends BytesRef> values) { int expectedEncodedSize = 0; for (BytesRef value : values) { // 5 bytes for the value of the length itself using vInt expectedEncodedSize += 5 + (value != null ? value.length : 0); } return expectedEncodedSize; } public String asIndexName() { if (indexName == null) { indexName = indexName(tableIdent, ident()); } return indexName; } /** * @return the encoded values of a partition */ @Nullable public String ident() { if (ident == null) { ident = encodeIdent(values); } return ident; } public List<BytesRef> values() { if (values == null) { if (ident == null) { return ImmutableList.of(); } else { values = decodeIdent(ident); } } return values; } public TableIdent tableIdent() { return tableIdent; } @Override public String toString() { return asIndexName(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PartitionName that = (PartitionName) o; if (!asIndexName().equals(that.asIndexName())) return false; return true; } @Override public int hashCode() { return asIndexName().hashCode(); } /** * creates a PartitionName from an index or template Name * <p> * an partition index has the format [<schema>.].partitioned.<table>.[<ident>] * a templateName has the same format but without the ident. */ public static PartitionName fromIndexOrTemplate(String indexOrTemplate) { assert indexOrTemplate != null : "indexOrTemplate must not be null"; List<String> parts = SPLITTER.splitToList(indexOrTemplate); String schema; String partitioned; String table; String ident; switch (parts.size()) { case 4: // ""."partitioned"."table_name". ["ident"] schema = null; partitioned = parts.get(1); table = parts.get(2); ident = parts.get(3); break; case 5: // "schema".""."partitioned"."table_name". ["ident"] schema = parts.get(0); partitioned = parts.get(2); table = parts.get(3); ident = parts.get(4); break; default: throw new IllegalArgumentException("Invalid partition name: " + indexOrTemplate); } if (!partitioned.equals("partitioned")) { throw new IllegalArgumentException("Invalid partition name: " + indexOrTemplate); } PartitionName partitionName = new PartitionName(schema, table, null); partitionName.ident = ident; return partitionName; } public static boolean isPartition(String index) { return index.length() > PARTITIONED_TABLE_PREFIX.length() + 1 && (index.startsWith(PARTITIONED_TABLE_PREFIX + ".") || index.contains("." + PARTITIONED_TABLE_PREFIX + ".")); } /** * compute the template name (used with partitioned tables) from a given schema and table name */ public static String templateName(@Nullable String schemaName, String tableName) { if (schemaName == null || schemaName.equals(Schemas.DEFAULT_SCHEMA_NAME)) { return DOT_JOINER.join(PARTITIONED_TABLE_PREFIX, tableName, ""); } else { return DOT_JOINER.join(schemaName, PARTITIONED_TABLE_PREFIX, tableName, ""); } } }