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

io.permazen.kv.spanner.ReadOnlySpannerView Maven / Gradle / Ivy


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

package io.permazen.kv.spanner;

import com.google.cloud.ByteArray;
import com.google.cloud.spanner.Key;
import com.google.cloud.spanner.KeyRange;
import com.google.cloud.spanner.KeySet;
import com.google.cloud.spanner.Options;
import com.google.cloud.spanner.ReadContext;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.Struct;
import com.google.common.base.Preconditions;

import io.permazen.kv.AbstractKVStore;
import io.permazen.kv.CloseableKVStore;
import io.permazen.kv.KVPair;
import io.permazen.kv.KVStore;
import io.permazen.util.ByteUtil;
import io.permazen.util.CloseableIterator;

import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.function.Function;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Provides a (read-only) {@link KVStore} view of a {@link ReadContext}.
 *
 * 

* For best performance, consider wrapping an instance of this class in a {@link io.permazen.kv.caching.CachingKVStore}. * * @see ReadWriteSpannerView */ public class ReadOnlySpannerView extends AbstractKVStore implements CloseableKVStore { private static final byte[] TOP = new byte[] { (byte)0xff }; private static final Key TOP_KEY = Key.of(ByteArray.copyFrom(TOP)); private static final List V_COL = Arrays.asList("val"); private static final List KV_COL = Arrays.asList("key", "val"); protected final Logger log = LoggerFactory.getLogger(this.getClass()); protected final String tableName; protected final ReadContext context; protected final Function exceptionMapper; /** * Primary constructor. * * @param tableName name of the Spanner database table * @param context read context * @param exceptionMapper mapper for any thrown {@link SpannerException}s, or null for none * @throws IllegalArgumentException if {@code tableName} or {@code context} is null */ public ReadOnlySpannerView(String tableName, ReadContext context, Function exceptionMapper) { Preconditions.checkArgument(tableName != null); Preconditions.checkArgument(context != null); this.tableName = tableName; this.context = context; this.exceptionMapper = exceptionMapper; } /** * Convenience constructor. * *

* Equivalent to {@code ReadOnlySpannerView(tableName, context, null)}. * * @param tableName name of the Spanner database table * @param context read context * @throws IllegalArgumentException if {@code tableName} or {@code context} is null */ public ReadOnlySpannerView(String tableName, ReadContext context) { this(tableName, context, null); } @Override public byte[] get(byte[] key) { try { final Struct row = this.context.readRow(this.tableName, Key.of(ByteArray.copyFrom(key)), V_COL); final byte[] val = row != null ? row.getBytes(0).toByteArray() : null; if (this.log.isTraceEnabled()) this.log.trace("spanner: get(): {} -> {}", ByteUtil.toString(key), ByteUtil.toString(val)); return val; } catch (SpannerException e) { throw this.exceptionMapper != null ? this.exceptionMapper.apply(e) : e; } } @Override public KVPair getAtLeast(byte[] minKey, byte[] maxKey) { if (this.log.isTraceEnabled()) { this.log.trace("spanner: getAtLeast():\n minKey={}\n maxKey={}", ByteUtil.toString(minKey), ByteUtil.toString(maxKey)); } try (ResultSet resultSet = this.getPairs(minKey, maxKey, Options.limit(1))) { return resultSet.next() ? ReadOnlySpannerView.kv(resultSet.getCurrentRowAsStruct()) : null; } catch (SpannerException e) { throw this.exceptionMapper != null ? this.exceptionMapper.apply(e) : e; } } @Override public KVPair getAtMost(byte[] maxKey, byte[] minKey) { if (minKey != null && minKey.length == 0) minKey = null; try { // After this bug is fixed: https://github.com/GoogleCloudPlatform/google-cloud-java/issues/1632 // try (ResultSet resultSet = this.getPairs(minKey, maxKey, Options.limit(1), Options.reverseOrder())) { // return resultSet.next() ? ReadOnlySpannerView.kv(resultSet.getCurrentRowAsStruct()) : null; final Statement.Builder builder = Statement.newBuilder("SELECT key, val FROM " + this.tableName); this.addMaxKey(builder, maxKey, this.addMinKey(builder, minKey, false)); builder.append(" ORDER BY key DESC LIMIT 1"); if (this.log.isTraceEnabled()) { this.log.trace("spanner: getAtMost():\n maxKey={}\n minKey={}\n query={}", ByteUtil.toString(maxKey), ByteUtil.toString(minKey), builder.build()); } try (ResultSet resultSet = this.context.executeQuery(builder.build())) { return resultSet.next() ? ReadOnlySpannerView.kv(resultSet.getCurrentRowAsStruct()) : null; } } catch (SpannerException e) { throw this.exceptionMapper != null ? this.exceptionMapper.apply(e) : e; } } @Override public CloseableIterator getRange(byte[] minKey, byte[] maxKey, boolean reverse) { if (minKey != null && minKey.length == 0) minKey = null; try { // After this bug is fixed: https://github.com/GoogleCloudPlatform/google-cloud-java/issues/1632 // return new Iter(this.getPairs(minKey, maxKey, Options.reverseOrder())); if (!reverse) return new Iter(this.getPairs(minKey, maxKey)); final Statement.Builder builder = Statement.newBuilder("SELECT key, val FROM " + this.tableName); this.addMaxKey(builder, maxKey, this.addMinKey(builder, minKey, false)); builder.append(" ORDER BY key DESC"); if (this.log.isTraceEnabled()) { this.log.trace("spanner: getRange():\n minKey={}\n maxKey={}\n reverse={}\n query={}", ByteUtil.toString(minKey), ByteUtil.toString(maxKey), reverse, builder.build()); } return new Iter(this.context.executeQuery(builder.build())); } catch (SpannerException e) { throw this.exceptionMapper != null ? this.exceptionMapper.apply(e) : e; } } private ResultSet getPairs(byte[] minKey, byte[] maxKey, Options.ReadOption... options) { final Key min = Key.of(ByteArray.copyFrom(minKey != null ? minKey : ByteUtil.EMPTY)); final Key max = maxKey != null ? Key.of(ByteArray.copyFrom(maxKey)) : TOP_KEY; return this.context.read(this.tableName, KeySet.range(KeyRange.closedOpen(min, max)), KV_COL, options); } @Override public void put(byte[] key, byte[] value) { throw new UnsupportedOperationException("read-only view"); } @Override public void remove(byte[] key) { throw new UnsupportedOperationException("read-only view"); } @Override public void removeRange(byte[] minKey, byte[] maxKey) { throw new UnsupportedOperationException("read-only view"); } // Closeable /** * {@linkplain ReadContext#close Closes} the associated {@link ReadContext}. */ @Override public void close() { try { this.context.close(); } catch (Exception e) { if (this.log.isDebugEnabled()) this.log.debug("got exception closing {} (ignoring)", this.context, e); } } // Internal methods private boolean addMinKey(Statement.Builder builder, byte[] minKey, boolean where) { return this.addKey(builder, minKey, "minKey", ">=", where); } private boolean addMaxKey(Statement.Builder builder, byte[] maxKey, boolean where) { return this.addKey(builder, maxKey, "maxKey", "<", where); } private boolean addKey(Statement.Builder builder, byte[] key, String name, String op, boolean where) { if (key == null) return where; builder.append(where ? " AND " : " WHERE ").append("key ").append(op).append(" @").append(name) .bind(name).to(ByteArray.copyFrom(key)); return true; } protected static KVPair kv(Struct struct) { Preconditions.checkArgument(struct != null); return new KVPair(struct.getBytes(0).toByteArray(), struct.getBytes(1).toByteArray()); } // Iter private class Iter implements CloseableIterator { private final ResultSet resultSet; private Struct next; Iter(ResultSet resultSet) { this.resultSet = resultSet; } @Override public boolean hasNext() { try { if (this.next != null) return true; if (!this.resultSet.next()) return false; this.next = this.resultSet.getCurrentRowAsStruct(); return true; } catch (SpannerException e) { throw ReadOnlySpannerView.this.exceptionMapper != null ? ReadOnlySpannerView.this.exceptionMapper.apply(e) : e; } } @Override public KVPair next() { if (this.next == null && !this.hasNext()) throw new NoSuchElementException(); final KVPair kv = ReadOnlySpannerView.kv(this.next); this.next = null; return kv; } @Override public void remove() { throw new UnsupportedOperationException("read-only view"); } @Override public void close() { this.resultSet.close(); } @Override @SuppressWarnings("deprecation") protected void finalize() throws Throwable { try { this.close(); } finally { super.finalize(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy