ByteStringArgument.java

/*
 * Copyright 2018-2019 Providence Authors
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF 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.
 */
package net.morimekta.proto.jdbi.v3.util;

import com.google.protobuf.ByteString;
import net.morimekta.proto.jdbi.MorimektaJdbiOptions.SqlType;
import net.morimekta.proto.jdbi.ProtoJdbi;
import org.jdbi.v3.core.argument.Argument;
import org.jdbi.v3.core.result.ResultSetException;
import org.jdbi.v3.core.statement.StatementContext;

import java.io.StringReader;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Base64;
import java.util.Objects;

/**
 * Smart mapping of message fields to SQL bound argument. It will
 * map the type to whichever type is default or selected (if supported)
 * for most field types.
 */
public class ByteStringArgument implements Argument {
    private final ByteString value;
    private final SqlType    type;

    /**
     * Create a message field argument.
     *
     * @param value The argument value.
     * @param type  The SQL type. See {@link Types}.
     */
    public ByteStringArgument(ByteString value, SqlType type) {
        this.value = value;
        this.type = type;
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() +
               "{@type=" + type + "(" + ProtoJdbi.getColumnType(type) + "); " +
               Base64.getEncoder().encodeToString(value.toByteArray()) + "}";
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }
        if (!(o instanceof ByteStringArgument)) {
            return false;
        }
        ByteStringArgument other = (ByteStringArgument) o;
        return Objects.equals(value, other.value) &&
               type == other.type;
    }

    @Override
    public int hashCode() {
        return Objects.hash(getClass(), value, type);
    }

    @Override
    public void apply(int position, PreparedStatement statement, StatementContext ctx) throws SQLException {
        switch (type) {
            case BINARY:
            case VARBINARY:
            case LONG_VARBINARY:
                statement.setBytes(position, value.toByteArray());
                break;
            case BLOB: {
                statement.setBlob(position, value.newInput());
                break;
            }
            case CHAR:
            case NCHAR:
            case VARCHAR:
            case NVARCHAR:
            case LONG_VARCHAR:
            case LONG_NVARCHAR: {
                statement.setString(position, ENCODER.encodeToString(value.toByteArray()));
                break;
            }
            case CLOB: {
                var data = ENCODER.encodeToString(value.toByteArray());
                statement.setClob(position, new StringReader(data));
                break;
            }
            default:
                throw new ResultSetException(
                        "Unsupported type " + type + " for ByteString", null, ctx);
        }
    }

    private static final Base64.Encoder ENCODER = Base64.getEncoder();
}