/*
 * Copyright 2017-present Facebook, Inc.
 *
 * 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
 *
 *     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.
 */

package com.facebook.buck.rules;

import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.google.common.base.Charsets;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import org.rocksdb.Options;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
import org.rocksdb.RocksIterator;
import org.rocksdb.WriteBatch;
import org.rocksdb.WriteOptions;

public class RocksDBBuildInfoStore implements BuildInfoStore {
  private static final char KEY_SEP = '\0';

  private final RocksDB db;

  public RocksDBBuildInfoStore(ProjectFilesystem filesystem) throws IOException {
    RocksDB.loadLibrary();
    Path pathToDB =
        filesystem
            .getRootPath()
            .resolve(filesystem.getBuckPaths().getScratchDir().resolve("metadata"));
    try (Options options = new Options()) {
      options.setCreateIfMissing(true);
      db = RocksDB.open(options, pathToDB.toString());
    } catch (RocksDBException e) {
      throw new IOException(e);
    }
  }

  @Override
  public void close() {
    db.close();
  }

  @Override
  public Optional<String> readMetadata(BuildTarget buildTarget, String key) {
    try {
      byte[] result = db.get(makeKey(buildTarget, key));
      if (result == null) {
        return Optional.empty();
      }
      return Optional.of(new String(result, Charsets.UTF_8));
    } catch (RocksDBException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public void updateMetadata(BuildTarget buildTarget, Map<String, String> metadata)
      throws IOException {
    try (WriteOptions options = new WriteOptions();
        WriteBatch wb = new WriteBatch()) {
      for (Map.Entry<String, String> entry : metadata.entrySet()) {
        wb.put(makeKey(buildTarget, entry.getKey()), entry.getValue().getBytes(Charsets.UTF_8));
      }
      db.write(options, wb);
    } catch (RocksDBException e) {
      throw new IOException(e);
    }
  }

  @Override
  public void deleteMetadata(BuildTarget buildTarget) throws IOException {
    try (WriteOptions options = new WriteOptions();
        WriteBatch wb = new WriteBatch();
        RocksIterator it = db.newIterator()) {
      byte[] keyPrefix = keyPrefix(buildTarget);
      it.seek(keyPrefix);
      while (it.isValid() && startsWith(it.key(), keyPrefix)) {
        wb.remove(it.key());
        it.next();
      }
      db.write(options, wb);
    } catch (RocksDBException e) {
      throw new IOException(e);
    }
  }

  private boolean startsWith(byte[] src, byte[] cmp) {
    if (src.length < cmp.length) {
      return false;
    }
    for (int i = 0; i < cmp.length; i++) {
      if (src[i] != cmp[i]) {
        return false;
      }
    }
    return true;
  }

  private static byte[] makeKey(BuildTarget buildTarget, String key) {
    return (buildTarget.getFullyQualifiedName() + KEY_SEP + key).getBytes(Charsets.UTF_8);
  }

  private static byte[] keyPrefix(BuildTarget buildTarget) {
    return (buildTarget.getFullyQualifiedName() + KEY_SEP).getBytes(Charsets.UTF_8);
  }
}

@@ -1164,17 +1164,17 @@
 SEP = '%5C
-0
+t
 ';%0A%0A  pr

Use tab to separate target and key in metadata DB