/* * ToroDB * Copyright © 2014 8Kdata Technology (www.8kdata.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.torodb.backend.postgresql; import com.google.common.base.Preconditions; import com.torodb.backend.AbstractStructureInterface; import com.torodb.backend.ErrorHandler.Context; import com.torodb.backend.InternalField; import com.torodb.backend.SqlBuilder; import com.torodb.backend.SqlHelper; import com.torodb.backend.converters.jooq.DataTypeForKv; import com.torodb.core.backend.IdentifierConstraints; import com.torodb.core.transaction.metainf.MetaCollection; import com.torodb.core.transaction.metainf.MetaDatabase; import com.torodb.core.transaction.metainf.MetaDocPart; import com.torodb.core.transaction.metainf.MetaSnapshot; import org.jooq.DSLContext; import org.jooq.lambda.tuple.Tuple2; import java.util.Collection; import java.util.List; import java.util.function.Function; import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.inject.Inject; import javax.inject.Singleton; @Singleton public class PostgreSqlStructureInterface extends AbstractStructureInterface { private SqlHelper sqlHelper; @Inject public PostgreSqlStructureInterface(PostgreSqlDbBackend dbBackend, PostgreSqlMetaDataReadInterface metaDataReadInterface, SqlHelper sqlHelper, IdentifierConstraints identifierConstraints) { super(dbBackend, metaDataReadInterface, sqlHelper, identifierConstraints); this.sqlHelper = sqlHelper; } @Override public void dropDatabase(@Nonnull DSLContext dsl, @Nonnull MetaDatabase metaDatabase) { dropDatabase(dsl, metaDatabase.getIdentifier()); } @Override protected void dropDatabase(DSLContext dsl, String dbIdentifier) { String statement = getDropSchemaStatement(dbIdentifier); sqlHelper.executeUpdate(dsl, statement, Context.DROP_SCHEMA); } @Override protected String getDropTableStatement(String schemaName, String tableName) { return "DROP TABLE \"" + schemaName + "\".\"" + tableName + "\""; } @Override protected String getRenameTableStatement(String fromSchemaName, String fromTableName, String toTableName) { return "ALTER TABLE \"" + fromSchemaName + "\".\"" + fromTableName + "\" RENAME TO \"" + toTableName + "\""; } @Override protected String getRenameIndexStatement(String fromSchemaName, String fromIndexName, String toIndexName) { return "ALTER INDEX \"" + fromSchemaName + "\".\"" + fromIndexName + "\" RENAME TO \"" + toIndexName + "\""; } @Override protected String getSetTableSchemaStatement(String fromSchemaName, String fromTableName, String toSchemaName) { return "ALTER TABLE \"" + fromSchemaName + "\".\"" + fromTableName + "\" SET SCHEMA = \"" + toSchemaName + "\""; } @Override protected String getDropSchemaStatement(String schemaName) { return "DROP SCHEMA \"" + schemaName + "\" CASCADE"; } @Override protected String getCreateIndexStatement(String indexName, String schemaName, String tableName, List<Tuple2<String, Boolean>> columnList, boolean unique) { StringBuilder sb = new StringBuilder() .append(unique ? "CREATE UNIQUE INDEX " : "CREATE INDEX ") .append("\"").append(indexName).append("\"") .append(" ON ") .append("\"").append(schemaName).append("\"") .append(".") .append("\"").append(tableName).append("\"") .append(" ("); for (Tuple2<String, Boolean> columnEntry : columnList) { sb.append("\"").append(columnEntry.v1()).append("\" ") .append(columnEntry.v2() ? "ASC," : "DESC,"); } sb.setCharAt(sb.length() - 1, ')'); String statement = sb.toString(); return statement; } @Override protected String getDropIndexStatement(String schemaName, String indexName) { StringBuilder sb = new StringBuilder() .append("DROP INDEX \"") .append(schemaName) .append("\".\"") .append(indexName) .append("\" CASCADE"); String statement = sb.toString(); return statement; } @Override protected String getCreateSchemaStatement(String schemaName) { return "CREATE SCHEMA IF NOT EXISTS \"" + schemaName + "\""; } @Override protected String getCreateDocPartTableStatement(String schemaName, String tableName, Collection<InternalField<?>> fields) { SqlBuilder sb = new SqlBuilder("CREATE TABLE "); sb.table(schemaName, tableName) .append(" ("); if (!fields.isEmpty()) { for (InternalField<?> field : fields) { sb.quote(field.getName()).append(' ') .append(field.getDataType().getCastTypeName()); if (!field.isNullable()) { sb.append(" NOT NULL"); } sb.append(','); } sb.setLastChar(')'); } else { sb.append(')'); } return sb.toString(); } @Override protected String getAddDocPartTablePrimaryKeyStatement(String schemaName, String tableName, Collection<InternalField<?>> primaryKeyFields) { SqlBuilder sb = new SqlBuilder("ALTER TABLE "); sb.table(schemaName, tableName) .append(" ADD PRIMARY KEY ("); for (InternalField<?> field : primaryKeyFields) { sb.quote(field.getName()).append(','); } sb.setLastChar(')'); return sb.toString(); } @Override protected String getAddDocPartTableForeignKeyStatement(String schemaName, String tableName, Collection<InternalField<?>> referenceFields, String foreignTableName, Collection<InternalField<?>> foreignFields) { Preconditions.checkArgument(referenceFields.size() == foreignFields.size()); SqlBuilder sb = new SqlBuilder("ALTER TABLE "); sb.table(schemaName, tableName) .append(" ADD FOREIGN KEY ("); for (InternalField<?> field : referenceFields) { sb.quote(field.getName()).append(','); } sb.setLastChar(')') .append(" REFERENCES ") .table(schemaName, foreignTableName) .append(" ("); for (InternalField<?> field : foreignFields) { sb.quote(field.getName()).append(','); } sb.setLastChar(')'); return sb.toString(); } @Override protected String getCreateDocPartTableIndexStatement(String schemaName, String tableName, Collection<InternalField<?>> indexFields) { Preconditions.checkArgument(!indexFields.isEmpty()); SqlBuilder sb = new SqlBuilder("CREATE INDEX ON "); sb.table(schemaName, tableName) .append(" ("); for (InternalField<?> field : indexFields) { sb.quote(field.getName()).append(','); } sb.setLastChar(')'); return sb.toString(); } @Override protected String getAddColumnToDocPartTableStatement(String schemaName, String tableName, String columnName, DataTypeForKv<?> dataType) { SqlBuilder sb = new SqlBuilder("ALTER TABLE ") .table(schemaName, tableName) .append(" ADD COLUMN ") .quote(columnName) .append(" ") .append(dataType.getCastTypeName()); return sb.toString(); } @Override public Stream<Function<DSLContext, String>> streamDataInsertFinishTasks(MetaSnapshot snapshot) { return snapshot.streamMetaDatabases().flatMap( db -> db.streamMetaCollections().flatMap( col -> col.streamContainedMetaDocParts().map( docPart -> createAnalyzeConsumer(db, col, docPart) ) ) ); } private Function<DSLContext, String> createAnalyzeConsumer(MetaDatabase db, MetaCollection col, MetaDocPart docPart) { return dsl -> { SqlBuilder sb = new SqlBuilder("ANALYZE ") .table(db.getIdentifier(), docPart.getIdentifier()); dsl.execute(sb.toString()); return "analyze table " + docPart.getIdentifier(); }; } }