/* * Copyright (C) 2015 Stichting Akvo (Akvo Foundation) * * This file is part of Akvo FLOW. * * Akvo FLOW is free software: you can redistribute it and modify it under the terms of * the GNU Affero General Public License (AGPL) as published by the Free Software Foundation, * either version 3 of the License or any later version. * * Akvo FLOW 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 included below for more details. * * The full license text can also be seen at <http://www.gnu.org/licenses/agpl.html>. */ package org.akvo.gae.remoteapi; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.akvo.flow.events.EventUtils.Kind; import org.codehaus.jackson.map.ObjectMapper; import org.postgresql.util.PGobject; import com.google.appengine.api.datastore.DatastoreService; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.FetchOptions; import com.google.appengine.api.datastore.Query; /* * Exports GAE datastore data to PostgreSQL * Notes: * PG version 9.4+, with local connections set to 'trust' * The database must be created upfront */ public class ExportDataToPG implements Process { private static final int BATCH_SIZE = 2000; private static final String[] KINDS = { Kind.SURVEY_GROUP, Kind.FORM, Kind.QUESTION_GROUP, Kind.QUESTION, Kind.DATA_POINT, Kind.FORM_INSTANCE, Kind.DEVICE_FILE, Kind.ANSWER }; private static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS %s (id bigint, created_at bigint, payload jsonb)"; private static final String INSERT = "INSERT INTO %s VALUES (?, ?, ?)"; private static final Map<String, String> TABLE_NAME = new HashMap<>(); static { TABLE_NAME.put(Kind.ANSWER, "answer"); TABLE_NAME.put(Kind.DATA_POINT, "data_point"); TABLE_NAME.put(Kind.DEVICE_FILE, "device_file"); TABLE_NAME.put(Kind.FORM, "form"); TABLE_NAME.put(Kind.FORM_INSTANCE, "form_instance"); TABLE_NAME.put(Kind.QUESTION, "question"); TABLE_NAME.put(Kind.QUESTION_GROUP, "question_group"); TABLE_NAME.put(Kind.SURVEY_GROUP, "survey_group"); } @Override public void execute(DatastoreService ds, String[] args) throws Exception { Class.forName("org.postgresql.Driver"); String dbName = args[0].replaceAll("-", "_"); String url = String.format("jdbc:postgresql://localhost/%s?user=postgres", dbName); Connection conn = DriverManager.getConnection(url); conn.setAutoCommit(false); long t0 = System.currentTimeMillis(); ObjectMapper om = new ObjectMapper(); for (String kind : KINDS) { String tableName = TABLE_NAME.get(kind); PreparedStatement ps0 = conn.prepareStatement(String.format(CREATE_TABLE, tableName)); ps0.execute(); conn.commit(); ps0.close(); Query q = new Query(kind); PreparedStatement ps = conn .prepareStatement(String.format(INSERT, tableName)); long i = 0; long t1 = System.currentTimeMillis(); for (Entity e : ds.prepare(q) .asIterable(FetchOptions.Builder.withChunkSize(BATCH_SIZE))) { Date createdDateTime = (Date) e.getProperty("createdDateTime"); long createdAt = createdDateTime == null ? 0 : createdDateTime.getTime(); String val = om.writeValueAsString(e).replaceAll("\\\\u0000", ""); PGobject payload = new PGobject(); payload.setType("jsonb"); payload.setValue(val); Long entityId = e.getKey().getId(); Long parentId = e.getKey().getParent() == null ? 0 : e.getKey().getParent().getId(); ps.setLong(1, entityId + parentId); ps.setLong(2, createdAt); ps.setObject(3, payload); ps.executeUpdate(); i++; if (i % BATCH_SIZE == 0) { System.out.print("."); conn.commit(); } } ps.close(); conn.commit(); System.out.println("\n"); System.out.println(kind + " " + (System.currentTimeMillis() - t1) + " ms"); } conn.close(); System.out.println("Total " + (System.currentTimeMillis() - t0) + " ms"); } }