001package io.ebean.annotation;
002
003import java.lang.annotation.ElementType;
004import java.lang.annotation.Repeatable;
005import java.lang.annotation.Retention;
006import java.lang.annotation.RetentionPolicy;
007import java.lang.annotation.Target;
008
009/**
010 * Assign to a property to be based on a SQL formula.
011 * <p>
012 * This is typically a SQL Literal value, SQL case statement, SQL function or
013 * similar.
014 * </p>
015 * <p>
016 * Any property based on a formula becomes a read only property.
017 * </p>
018 * <p>
019 * You may also put use the Transient annotation with the Formula annotation.
020 * The effect of the Transient annotation in this case is that the formula will
021 * <b>NOT</b> be included in queries by default - you have to explicitly include
022 * it via <code>Query.select()</code> or <code>Query.fetch()</code>.
023 * You may want to do this if the Formula is relatively expensive and only want
024 * it included in the query when you explicitly state it.
025 * </p>
026 * <p>
027 * <pre>{@code
028 * // On the Order "master" bean
029 * // ... a formula using the Order details
030 * // ... sum(order_qty*unit_price)
031 * @Transient
032 * @Formula(select = "_b${ta}.total_amount", join = "join (select order_id, sum(order_qty*unit_price) as total_amount from o_order_detail group by order_id) as _b${ta} on _b${ta}.order_id = ${ta}.id")
033 * Double totalAmount;
034 *
035 * }</pre>
036 * <p>
037 * As the totalAmount formula is also Transient it is not included by default in
038 * queries - it needs to be explicitly included.
039 * </p>
040 * <pre>{@code
041 *
042 * // find by Id
043 * Order o1 = Ebean.find(Order.class)
044 *     .select("id, totalAmount")
045 *     .setId(1).findUnique();
046 *
047 * // find list ... using totalAmount in the where clause
048 * List<Order> list = Ebean.find(Order.class)
049 *     .select("id, totalAmount")
050 *     .where()
051 *     .eq("status", Order.Status.NEW)
052 *     .gt("totalAmount", 10)
053 *     .findList();
054 *
055 * // as a join from customer
056 * List<Customer> l0 = Ebean.find(Customer.class)
057 *     .select("id, name")
058 *     .fetch("orders", "status, totalAmount")
059 *     .where()
060 *     .gt("id", 0)
061 *     .gt("orders.totalAmount", 10)
062 *     .findList();
063 *
064 * }</pre>
065 */
066@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
067@Retention(RetentionPolicy.RUNTIME)
068@Repeatable(Formula.List.class)
069public @interface Formula {
070
071  /**
072   * The SQL to be used in the SELECT part of the SQL to populate a property.
073   */
074  String select();
075
076  /**
077   * OPTIONAL - the SQL to be used in the JOIN part of the SQL to support the
078   * formula.
079   * <p>
080   * This is commonly used to join a 'dynamic view' to support aggregation such
081   * as count, sum etc.
082   * </p>
083   * <p>
084   * The join string should start with either "left join" or "join".
085   * </p>
086   * <p>
087   * You will almost certainly use the "${ta}" as a place holder for the table
088   * alias of the table you are joining back to (the "base table" of the entity
089   * bean).
090   * </p>
091   * <p>
092   * The example below is used to support a total count of topics created by a
093   * user.
094   * </p>
095   * <p>
096   * <pre>{@code
097   *
098   * join (select user_id, count(*) as topic_count from f_topic group by user_id) as _tc on _tc.user_id = ${ta}.id
099   *
100   * }</pre>
101   */
102  String join() default "";
103
104  Platform[] platforms() default {};
105
106  /**
107   * Repeatable support for {@link Formula}.
108   */
109  @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
110  @Retention(RetentionPolicy.RUNTIME)
111  @interface List {
112
113    Formula[] value() default {};
114  }
115
116}