/*
* 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 org.rakam.postgresql.analysis;
import com.facebook.presto.sql.RakamSqlFormatter;
import com.google.common.base.Throwables;
import org.rakam.analysis.AbstractFunnelQueryExecutor;
import org.rakam.analysis.metadata.Metastore;
import org.rakam.config.ProjectConfig;
import org.rakam.postgresql.report.PostgresqlQueryExecutor;
import org.rakam.util.ValidationUtil;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static com.facebook.presto.sql.RakamExpressionFormatter.formatIdentifier;
import static java.lang.String.format;
import static org.rakam.util.ValidationUtil.checkTableColumn;
public class PostgresqlFunnelQueryExecutor
extends AbstractFunnelQueryExecutor
{
private final PostgresqlQueryExecutor executor;
@Inject
public PostgresqlFunnelQueryExecutor(ProjectConfig projectConfig, Metastore metastore, PostgresqlQueryExecutor executor)
{
super(projectConfig, metastore, executor);
this.executor = executor;
}
@PostConstruct
public void setup()
{
try (Connection conn = executor.getConnection()) {
conn.createStatement().execute("CREATE OR REPLACE FUNCTION get_funnel_step(arr int[]) RETURNS integer AS $$\n" +
"DECLARE next_step integer := 1; step integer;\n" +
" BEGIN \n" +
" FOREACH step IN ARRAY arr\n" +
" LOOP\n" +
" IF step = next_step THEN\n" +
" next_step = next_step + 1;\n" +
" END IF;\n" +
" END LOOP;\n" +
" return next_step - 1;\n" +
" END;\n" +
"$$ LANGUAGE plpgsql;");
}
catch (SQLException e) {
throw Throwables.propagate(e);
}
}
@Override
public String getTemplate(List<FunnelStep> steps, Optional<String> dimension, Optional<FunnelWindow> window)
{
return "select %s get_funnel_step(steps) step, count(*) total from (\n" +
"select %s array_agg(step order by " + checkTableColumn(projectConfig.getTimeColumn()) + ") as steps from (%s) t WHERE " + checkTableColumn(projectConfig.getTimeColumn()) + " between timestamp '%s' and timestamp '%s'\n" +
"group by %s %s\n" +
") t group by 1 %s order by 1";
}
public String convertFunnel(String project, String connectorField, int idx, FunnelStep funnelStep, Optional<String> dimension, LocalDate startDate, LocalDate endDate)
{
String table = project + "." + ValidationUtil.checkCollection(funnelStep.getCollection());
Optional<String> filterExp = funnelStep.getExpression().map(value -> RakamSqlFormatter.formatExpression(value,
name -> name.getParts().stream().map(e -> formatIdentifier(e, '"')).collect(Collectors.joining(".")),
name -> formatIdentifier("step" + idx, '"') + "." + name.getParts().stream()
.map(e -> formatIdentifier(e, '"')).collect(Collectors.joining(".")), '"'));
String format = format("SELECT %s %s as _user, %d as step, %s from %s %s %s",
dimension.map(ValidationUtil::checkTableColumn).map(v -> v + ",").orElse(""),
format(connectorField, "step" + idx),
idx + 1,
checkTableColumn(projectConfig.getTimeColumn()),
table,
"step" + idx,
filterExp.map(v -> "where " + v).orElse(""));
return format;
}
}