io.ebean.platform.postgres.PostgresPlatform Maven / Gradle / Ivy
package io.ebean.platform.postgres;
import io.ebean.BackgroundExecutor;
import io.ebean.Query;
import io.ebean.annotation.PartitionMode;
import io.ebean.annotation.Platform;
import io.ebean.config.PlatformConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import io.ebean.util.SplitName;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
/**
* Postgres 10+ platform.
*
* Defaults to use "generated by default as identity".
*/
public class PostgresPlatform extends DatabasePlatform {
private static final String SKIP_LOCKED = " skip locked";
private static final String NO_WAIT = " nowait";
private static final String FOR_UPDATE = " for update";
private static final String FOR_NO_KEY_UPDATE = " for no key update";
private static final String FOR_SHARE = " for share";
private static final String FOR_KEY_SHARE = " for key share";
private boolean forUpdateNoKey;
public PostgresPlatform() {
super();
this.platform = Platform.POSTGRES;
this.supportsNativeIlike = true;
this.supportsDeleteTableAlias = true;
this.selectCountWithAlias = true;
this.blobDbType = Types.LONGVARBINARY;
this.clobDbType = Types.VARCHAR;
this.nativeUuidType = true;
this.truncateTable = "truncate table %s cascade";
this.dbEncrypt = new PostgresDbEncrypt();
this.historySupport = new PostgresHistorySupport();
// Use Identity and getGeneratedKeys
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
this.dbIdentity.setSupportsIdentity(true);
this.dbDefaultValue.setNow("current_timestamp");
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("55P03")
.addDuplicateKey("23505")
.addDataIntegrity("23000", "23502", "23503", "23514")
.addSerializableConflict("40001")
.build();
this.openQuote = "\"";
this.closeQuote = "\"";
DbPlatformType dbTypeText = new DbPlatformType("text", false);
DbPlatformType dbBytea = new DbPlatformType("bytea", false);
dbTypeMap.put(DbType.UUID, new DbPlatformType("uuid", false));
dbTypeMap.put(DbType.INET, new DbPlatformType("inet", false));
dbTypeMap.put(DbType.CIDR, new DbPlatformType("cidr", false));
dbTypeMap.put(DbType.HSTORE, new DbPlatformType("hstore", false));
dbTypeMap.put(DbType.JSON, new DbPlatformType("json", false));
dbTypeMap.put(DbType.JSONB, new DbPlatformType("jsonb", false));
dbTypeMap.put(DbType.INTEGER, new DbPlatformType("integer", false));
dbTypeMap.put(DbType.DOUBLE, new DbPlatformType("float"));
dbTypeMap.put(DbType.TINYINT, new DbPlatformType("smallint"));
dbTypeMap.put(DbType.TIMESTAMP, new DbPlatformType("timestamptz"));
dbTypeMap.put(DbType.LOCALDATETIME, new DbPlatformType("timestamp"));
dbTypeMap.put(DbType.BINARY, dbBytea);
dbTypeMap.put(DbType.VARBINARY, dbBytea);
dbTypeMap.put(DbType.BLOB, dbBytea);
dbTypeMap.put(DbType.CLOB, dbTypeText);
dbTypeMap.put(DbType.LONGVARBINARY, dbBytea);
dbTypeMap.put(DbType.LONGVARCHAR, dbTypeText);
}
@Override
public void configure(PlatformConfig config) {
super.configure(config);
forUpdateNoKey = config.isForUpdateNoKey();
}
@Override
protected void addGeoTypes(int srid) {
dbTypeMap.put(DbType.POINT, geoType("point", srid));
dbTypeMap.put(DbType.POLYGON, geoType("polygon", srid));
dbTypeMap.put(DbType.LINESTRING, geoType("linestring", srid));
dbTypeMap.put(DbType.MULTIPOINT, geoType("multipoint", srid));
dbTypeMap.put(DbType.MULTILINESTRING, geoType("multilinestring", srid));
dbTypeMap.put(DbType.MULTIPOLYGON, geoType("multipolygon", srid));
}
private DbPlatformType geoType(String type, int srid) {
return new DbPlatformType("geometry(" + type + "," + srid + ")");
}
/**
* So we can generate varchar[], int[], uuid[] column definitions and use the associated scalar types.
*/
@Override
public boolean isNativeArrayType() {
return true;
}
/**
* Create a Postgres specific sequence IdGenerator.
*/
@Override
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
return new PostgresSequenceIdGenerator(be, ds, seqName, sequenceBatchSize);
}
@Override
protected String withForUpdate(String sql, Query.LockWait lockWait, Query.LockType lockType) {
switch (lockWait) {
case SKIPLOCKED:
return sql + lock(lockType) + SKIP_LOCKED;
case NOWAIT:
return sql + lock(lockType) + NO_WAIT;
default:
return sql + lock(lockType);
}
}
private String lock(Query.LockType lockType) {
switch (lockType) {
case UPDATE: return FOR_UPDATE;
case NO_KEY_UPDATE: return FOR_NO_KEY_UPDATE;
case SHARE: return FOR_SHARE;
case KEY_SHARE: return FOR_KEY_SHARE;
case DEFAULT: return forUpdateNoKey ? FOR_NO_KEY_UPDATE : FOR_UPDATE;
}
return FOR_UPDATE;
}
@Override
public boolean tablePartitionsExist(Connection connection, String table) throws SQLException {
try (PreparedStatement statement = connection.prepareStatement("select count(*) from pg_inherits i WHERE i.inhparent = ?::regclass")) {
statement.setString(1, table);
try (ResultSet resultSet = statement.executeQuery()) {
return resultSet.next() && resultSet.getInt(1) > 0;
}
}
}
/**
* Return SQL using built in partition helper functions to create some initial partitions.
*
* Only use this if extra-ddl doesn't have some initial partitions defined (which it should).
*/
@Override
public String tablePartitionInit(String tableName, PartitionMode mode) {
// default partition required pg11 but this is only used for testing but bumped test docker container to pg14 by default
String[] schemaTable = SplitName.split(tableName);
String baseTable;
String plusSchema;
if (schemaTable[0] == null) {
plusSchema = "";
baseTable = tableName;
} else {
// table in an explicit schema
plusSchema = ",'" + schemaTable[0] + "'";
baseTable = schemaTable[1];
}
return
"create table " + tableName + "_default" + " partition of " + tableName + " default;\n" +
"select partition('" + mode.name().toLowerCase() + "','" + baseTable + "',1" + plusSchema + ");";
}
}