All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.permazen.jsck.cmd.JsckCommand Maven / Gradle / Ivy

Go to download

Permazen analog to UNIX fsck(8) command for checking the consistency of, and repairing any corruption in, a Permazen key/value database

There is a newer version: 5.1.0
Show newest version

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.jsck.cmd;

import io.permazen.Session;
import io.permazen.SessionMode;
import io.permazen.cli.CliSession;
import io.permazen.cli.cmd.AbstractCommand;
import io.permazen.core.FieldTypeRegistry;
import io.permazen.jsck.Jsck;
import io.permazen.jsck.JsckConfig;
import io.permazen.jsck.JsckLogger;
import io.permazen.kv.KVStore;
import io.permazen.parse.expr.Node;
import io.permazen.schema.SchemaModel;
import io.permazen.util.ParseContext;

import java.io.PrintWriter;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

public class JsckCommand extends AbstractCommand {

    public JsckCommand() {
        super("jsck"
         + " -repair:repair"
         + " -verbose:verbose"
         + " -weak:weak"
         + " -limit:limit:int"
         + " -gc:gc"
         + " -kv:kv:expr"
         + " -force-schemas:schema-map:expr"
         + " -force-format-version:format-version:int"
         + " -registry:registry:expr");
    }

    @Override
    public String getHelpSummary() {
        return "Check key/value database for inconsistencies and optionally repair them";
    }

    @Override
    public String getHelpDetail() {
        return "Options:\n"
          + "   -repair\n"
          + "       In addition to detecting inconsistencies, attempt to repair them; without this flag,\n"
          + "       inconsistencies are only reported and no changes are made.\n"
          + "   -limit\n"
          + "       Stop after encountering `limit' issues.\n"
          + "   -gc\n"
          + "       Garbage collect any unused schema versions at the end of inspection.\n"
          + "       Note: this garbage collection will occur even without `-repair'.\n"
          + "   -kv\n"
          + "       Specify a different KVStore to check (by default, the current transaction is checked).\n"
          + "   -registry\n"
          + "       Specify a custom field type registry. If this flag is not given, in Permazen and Core API modes,\n"
          + "       the configured registry will be used; in key/value database CLI mode, a default instances is used.\n"
          + "       The parameter must be a Java expression returning a FieldTypeRegistry.\n"
          + "   -force-schemas\n"
          + "       Forcibly override schema versions. The parameter must be a Java expression returning a\n"
          + "       Map. WARNING: only use this if you know what you are doing.\n"
          + "       This flag is ignored without `-repair'.\n"
          + "   -force-format-version\n"
          + "       Forcibly override format version. WARNING: only use this if you know what you are doing.\n"
          + "       This flag is ignored without `-repair'.\n"
          + "   -verbose\n"
          + "       Increase logging verbosity to show a high level of detail.\n"
          + "   -weak\n"
          + "       For certain key/value stores, use weaker consistency to reduce the chance of conflicts.\n"
          + "       This flag is incompatible with `-repair' and/or `-gc'.\n";
    }

    @Override
    public EnumSet getSessionModes() {
        return EnumSet.allOf(SessionMode.class);
    }

    @Override
    public CliSession.Action getAction(CliSession session, ParseContext ctx, boolean complete, Map params) {

        // Setup config (partially)
        final JsckConfig config = new JsckConfig();
        final boolean verbose = params.containsKey("verbose");
        config.setGarbageCollectSchemas(params.containsKey("gc"));
        config.setRepair(params.containsKey("repair"));
        final boolean weak = params.containsKey("weak");
        final Integer formatVersion = (Integer)params.get("format-version");
        if (formatVersion != null)
            config.setForceFormatVersion(formatVersion);
        final Integer limit = (Integer)params.get("limit");
        if (limit != null)
            config.setMaxIssues(limit);

        // Sanity check
        if (weak && (config.isGarbageCollectSchemas() || config.isRepair()))
            throw new RuntimeException("`-weak' flag requires read-only transaction (incompatible with `-gc' and `-repair')");

        // Done
        return new JsckAction(config,
          (Node)params.get("kv"), (Node)params.get("registry"), (Node)params.get("schema-map"), verbose, weak);
    }

    private class JsckAction implements CliSession.Action, Session.TransactionalAction, Session.HasTransactionOptions {

        private final JsckConfig config;
        private final Node kvNode;
        private final Node registryNode;
        private final Node schemasNode;
        private final boolean verbose;
        private final boolean weak;

        JsckAction(JsckConfig config, Node kvNode, Node registryNode, Node schemasNode, boolean verbose, boolean weak) {
            this.config = config;
            this.kvNode = kvNode;
            this.registryNode = registryNode;
            this.schemasNode = schemasNode;
            this.verbose = verbose;
            this.weak = weak;
        }

        @Override
        public void run(CliSession session) throws Exception {

            // Evaluate KVStore, if any
            final KVStore kv = this.kvNode != null ?
              JsckCommand.this.getExprParam(session, this.kvNode, "kv", KVStore.class) : session.getKVTransaction();

            // Evaluate registry, if any
            if (this.registryNode != null) {
                this.config.setFieldTypeRegistry(
                  JsckCommand.this.getExprParam(session, this.registryNode, "registry", FieldTypeRegistry.class));
            } else if (session.getDatabase() != null)
                config.setFieldTypeRegistry(session.getDatabase().getFieldTypeRegistry());

            // Evaluate forced schemas, if any
            if (this.schemasNode != null) {
                config.setForceSchemaVersions(JsckCommand.this.getExprParam(session, this.schemasNode, "force-schemas", obj -> {
                    if (!(obj instanceof Map))
                        throw new IllegalArgumentException("must be a Map");
                    final Map map = (Map)obj;
                    final HashMap versionMap = new HashMap<>(map.size());
                    for (Map.Entry entry : map.entrySet()) {
                        if (!(entry.getKey() instanceof Integer))
                            throw new IllegalArgumentException("must be a Map; found key " + entry.getKey());
                        if ((entry.getValue() != null && !(entry.getValue() instanceof SchemaModel))) {
                            throw new IllegalArgumentException("must be a Map; found value "
                              + entry.getValue());
                        }
                        versionMap.put((Integer)entry.getKey(), (SchemaModel)entry.getValue());
                    }
                    return versionMap;
                }));
            }

            // Configure logger to log to console
            final PrintWriter writer = session.getWriter();
            this.config.setJsckLogger(new JsckLogger() {
                @Override
                public boolean isDetailEnabled() {
                    return verbose;
                }
                @Override
                public void info(String message) {
                    writer.println("jsck: " + message);
                }
                @Override
                public void detail(String message) {
                    if (verbose)
                        writer.println("jsck: " + message);
                }
            });

            // Do scan
            final Jsck jsck = new Jsck(this.config);
            final AtomicInteger count = new AtomicInteger();
            final long numHandled = jsck.check(kv, issue -> {
                writer.println(String.format("[%05d] %s%s", count.incrementAndGet(),
                  issue, this.config.isRepair() ? " [FIXED]" : ""));
            });
            writer.println("jsck: " + (this.config.isRepair() ? "repaired" : "found") + " " + numHandled + " issue(s)");
        }

        // Use EVENTUAL_COMMITTED consistency for Raft key/value stores to avoid retries
        @Override
        public Map getTransactionOptions() {
            return this.weak && !this.config.isRepair() ? Collections.singletonMap("consistency", "EVENTUAL") : null;
        }
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy