package org.rakam.plugin.user; import com.facebook.presto.sql.parser.SqlParser; import com.facebook.presto.sql.tree.Expression; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.node.ObjectNode; import io.netty.handler.codec.http.HttpResponseStatus; import org.rakam.collection.SchemaField; import org.rakam.report.realtime.AggregationType; import org.rakam.report.QueryResult; import org.rakam.util.RakamException; import java.time.Duration; import java.time.Instant; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import static java.lang.String.format; public interface UserStorage { String PRIMARY_KEY = "id"; Object create(String project, Object id, ObjectNode properties); List<Object> batchCreate(String project, List<User> users); default void batch(String project, List<? extends ISingleUserBatchOperation> operations) { for (ISingleUserBatchOperation operation : operations) { if (operation.getSetPropertiesOnce() != null) { setUserProperties(project, operation.getUser(), operation.getSetProperties()); } if (operation.getSetPropertiesOnce() != null) { setUserPropertiesOnce(project, operation.getUser(), operation.getSetPropertiesOnce()); } if (operation.getUnsetProperties() != null) { unsetProperties(project, operation.getUser(), operation.getUnsetProperties()); } if (operation.getIncrementProperties() != null) { for (Map.Entry<String, Double> entry : operation.getIncrementProperties().entrySet()) { incrementProperty(project, operation.getUser(), entry.getKey(), entry.getValue()); } } } } CompletableFuture<QueryResult> searchUsers(String project, List<String> columns, Expression filterExpression, List<EventFilter> eventFilter, Sorting sortColumn, long limit, String offset); void createSegment(String project, String name, String tableName, Expression filterExpression, List<EventFilter> eventFilter, Duration interval); List<SchemaField> getMetadata(String project); CompletableFuture<User> getUser(String project, Object userId); void setUserProperties(String project, Object user, ObjectNode properties); void setUserPropertiesOnce(String project, Object user, ObjectNode properties); default void createProjectIfNotExists(String project, boolean isNumeric) { } void incrementProperty(String project, Object user, String property, double value); void dropProjectIfExists(String project); void unsetProperties(String project, Object user, List<String> properties); default void applyOperations(String project, List<? extends ISingleUserBatchOperation> req) { for (ISingleUserBatchOperation data : req) { if (data.getSetProperties() != null) { setUserProperties(project, data.getUser(), data.getSetPropertiesOnce()); } if (data.getSetProperties() != null) { setUserPropertiesOnce(project, data.getUser(), data.getSetPropertiesOnce()); } if (data.getUnsetProperties() != null) { unsetProperties(project, data.getUser(), data.getUnsetProperties()); } if (data.getIncrementProperties() != null) { for (Map.Entry<String, Double> entry : data.getIncrementProperties().entrySet()) { incrementProperty(project, data.getUser(), entry.getKey(), entry.getValue()); } } } } class Sorting { public final String column; public final Ordering order; @JsonCreator public Sorting(@JsonProperty("column") String column, @JsonProperty("order") Ordering order) { this.column = column; this.order = order; } } enum Ordering { asc, desc } class EventFilterAggregation { public final AggregationType type; public final String field; public final Long minimum; public final Long maximum; @JsonCreator public EventFilterAggregation(@JsonProperty("aggregation") AggregationType type, @JsonProperty("field") String field, @JsonProperty("minimum") Long minimum, @JsonProperty("maximum") Long maximum) { this.type = type; this.field = field; this.minimum = minimum; this.maximum = maximum; } } class Timeframe { public final Instant start; public final Instant end; @JsonCreator public Timeframe(@JsonProperty("start") Instant start, @JsonProperty("end") Instant end) { this.start = start; this.end = end; } } class EventFilter { private static final SqlParser SQL_PARSER = new SqlParser(); public final String collection; public final String filterExpression; public final Timeframe timeframe; public final EventFilterAggregation aggregation; @JsonCreator public EventFilter(@JsonProperty("collection") String collection, @JsonProperty("filter") String filterExpression, @JsonProperty("timeframe") Timeframe timeframe, @JsonProperty("aggregation") EventFilterAggregation aggregation) { this.collection = collection; this.filterExpression = filterExpression; this.timeframe = timeframe; this.aggregation = aggregation; } @JsonIgnore public synchronized Expression getExpression() { try { return filterExpression != null ? SQL_PARSER.createExpression(filterExpression) : null; } catch (Exception e) { throw new RakamException(format("filter expression '%s' couldn't parsed", filterExpression), HttpResponseStatus.BAD_REQUEST); } } } }