/*
* Copyright 2017 requery.io
*
* 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 io.requery.sql.platform;
import io.requery.meta.Attribute;
import io.requery.meta.Type;
import io.requery.query.Expression;
import io.requery.sql.BaseType;
import io.requery.sql.GeneratedColumnDefinition;
import io.requery.sql.Mapping;
import io.requery.sql.QueryBuilder;
import io.requery.sql.VersionColumnDefinition;
import io.requery.sql.gen.Generator;
import io.requery.sql.gen.LimitGenerator;
import io.requery.sql.gen.Output;
import io.requery.sql.type.VarCharType;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Map;
import java.util.UUID;
import static io.requery.sql.Keyword.CONFLICT;
import static io.requery.sql.Keyword.DO;
import static io.requery.sql.Keyword.INSERT;
import static io.requery.sql.Keyword.INTO;
import static io.requery.sql.Keyword.ON;
import static io.requery.sql.Keyword.SET;
import static io.requery.sql.Keyword.UPDATE;
import static io.requery.sql.Keyword.VALUES;
/**
* PostgresSQL PL/pgSQL (9+)
*/
public class PostgresSQL extends Generic {
private final SerialColumnDefinition serialColumnDefinition;
private final VersionColumnDefinition versionColumnDefinition;
public PostgresSQL() {
serialColumnDefinition = new SerialColumnDefinition();
versionColumnDefinition = new SystemVersionColumnDefinition();
}
@Override
public boolean supportsInlineForeignKeyReference() {
return true;
}
@Override
public boolean supportsGeneratedKeysInBatchUpdate() {
return true;
}
@Override
public GeneratedColumnDefinition generatedColumnDefinition() {
return serialColumnDefinition;
}
@Override
public void addMappings(Mapping mapping) {
super.addMappings(mapping);
mapping.replaceType(Types.BINARY, new ByteArrayType(Types.BINARY));
mapping.replaceType(Types.VARBINARY, new ByteArrayType(Types.VARBINARY));
mapping.replaceType(Types.NVARCHAR, new VarCharType());
mapping.putType(UUID.class, new UUIDType());
}
@Override
public LimitGenerator limitGenerator() {
return new LimitGenerator();
}
@Override
public VersionColumnDefinition versionColumnDefinition() {
return versionColumnDefinition;
}
@Override
public Generator<Map<Expression<?>, Object>> upsertGenerator() {
return new UpsertOnConflictDoUpdate();
}
private static class ByteArrayType extends BaseType<byte[]> {
ByteArrayType(int jdbcType) {
super(byte[].class, jdbcType);
}
@Override
public String getIdentifier() {
return "bytea";
}
@Override
public byte[] read(ResultSet results, int column) throws SQLException {
byte[] value = results.getBytes(column);
return results.wasNull() ? null : value;
}
}
private static class UUIDType extends BaseType<UUID> {
UUIDType() {
super(UUID.class, Types.JAVA_OBJECT);
}
@Override
public String getIdentifier() {
return "uuid";
}
@Override
public void write(PreparedStatement statement, int index, UUID value)
throws SQLException {
statement.setObject(index, value);
}
}
private static class SerialColumnDefinition implements GeneratedColumnDefinition {
@Override
public boolean skipTypeIdentifier() {
return true;
}
@Override
public boolean postFixPrimaryKey() {
return false;
}
@Override
public void appendGeneratedSequence(QueryBuilder qb, Attribute attribute) {
qb.append("serial");
}
}
private static class SystemVersionColumnDefinition implements VersionColumnDefinition {
@Override
public boolean createColumn() {
return false;
}
@Override
public String columnName() {
return "xmin";
}
}
/**
* Performs an upsert (insert/update) using insert on conflict do update.
*/
private static class UpsertOnConflictDoUpdate implements Generator<Map<Expression<?>, Object>> {
@Override
public void write(final Output output, final Map<Expression<?>, Object> values) {
QueryBuilder qb = output.builder();
Type<?> type = ((Attribute)values.keySet().iterator().next()).getDeclaringType();
// insert into <table> (<columns>) values (<values)
// on conflict do update (<column>=EXCLUDED.<value>...
qb.keyword(INSERT, INTO)
.tableNames(values.keySet())
.openParenthesis()
.commaSeparatedExpressions(values.keySet())
.closeParenthesis().space()
.keyword(VALUES)
.openParenthesis()
.commaSeparated(values.keySet(), new QueryBuilder.Appender<Expression<?>>() {
@Override
public void append(QueryBuilder qb, Expression expression) {
qb.append("?");
output.parameters().add(expression, values.get(expression));
}
})
.closeParenthesis().space()
.keyword(ON, CONFLICT)
.openParenthesis()
.commaSeparatedAttributes(type.getKeyAttributes())
.closeParenthesis().space()
.keyword(DO, UPDATE, SET)
.commaSeparated(values.keySet(), new QueryBuilder.Appender<Expression<?>>() {
@Override
public void append(QueryBuilder qb, Expression<?> value) {
qb.attribute((Attribute) value);
qb.append("= EXCLUDED." + value.getName());
}
});
}
}
}