mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
303
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
335c5fb69b | ||
|
|
94ed568812 | ||
|
|
1c23720605 | ||
|
|
ccba5d08ef | ||
|
|
641ada001e | ||
|
|
fca80ac649 | ||
|
|
379cc39e38 | ||
|
|
386d3a37f1 | ||
|
|
37612c6f83 | ||
|
|
7984553d96 | ||
|
|
d592e7d35f | ||
|
|
5a0b65b4f2 | ||
|
|
aaefaa5dcd | ||
|
|
f3e66c459a | ||
|
|
16d5e04809 | ||
|
|
b01df515bd | ||
|
|
7fda911931 | ||
|
|
e332aace16 | ||
|
|
c79231c846 | ||
|
|
efad23d482 | ||
|
|
fe8a32ea18 | ||
|
|
31099f6dda | ||
|
|
58f4f791f6 | ||
|
|
b27989f541 | ||
|
|
7bc1a902ed | ||
|
|
358720776e | ||
|
|
417ee4061e | ||
|
|
394cb3854a | ||
|
|
547fa342c4 | ||
|
|
f8b8a41bce | ||
|
|
ba39897b18 | ||
|
|
1437843f08 | ||
|
|
7c705e2baa | ||
|
|
2caeb4fff1 | ||
|
|
84190f3c2a | ||
|
|
46b158a0a0 | ||
|
|
b172a32710 | ||
|
|
f8ef87f7a8 | ||
|
|
ea54d1b794 | ||
|
|
b1780948a8 | ||
|
|
1a0f3a6614 | ||
|
|
580b51381e | ||
|
|
75e9770d5e | ||
|
|
10937de24b | ||
|
|
18aeedc153 | ||
|
|
9ca1a87f8b | ||
|
|
111354f391 | ||
|
|
79879fb326 | ||
|
|
9f6ccdee66 | ||
|
|
bbf469bd54 | ||
|
|
ad97ac2be9 | ||
|
|
9d83017374 | ||
|
|
98ec00a2d6 | ||
|
|
24971bcc86 | ||
|
|
c1c45139a0 | ||
|
|
116425b460 | ||
|
|
48acb6294b | ||
|
|
61f5057528 | ||
|
|
b91ef46243 | ||
|
|
0eddcc30bb | ||
|
|
24ec0fefba | ||
|
|
5ac28cb3fb | ||
|
|
d4635fb615 | ||
|
|
b9774e64e9 | ||
|
|
f29ba8383e | ||
|
|
a15dd33380 | ||
|
|
f22563b36a | ||
|
|
fe17ec5dbb | ||
|
|
85fdf8646d | ||
|
|
bd9acab335 | ||
|
|
0a6941bda5 | ||
|
|
215417a43c | ||
|
|
60c1a5b111 | ||
|
|
5166fbed96 | ||
|
|
851f40e7b0 | ||
|
|
845faf74ff | ||
|
|
c0d4dac2b3 | ||
|
|
f6c3279e86 | ||
|
|
48f0da9343 | ||
|
|
ce0d73cf23 | ||
|
|
1585bd7cd0 | ||
|
|
1603c69156 | ||
|
|
7cd9dc0c8d | ||
|
|
e4304494e1 | ||
|
|
0612d89e9a | ||
|
|
e6f779268a | ||
|
|
815d846695 | ||
|
|
4b74062d80 | ||
|
|
1dd0841f00 | ||
|
|
3a9668fe6c | ||
|
|
93f802f7fe | ||
|
|
8cde0da419 | ||
|
|
14f984ea3e | ||
|
|
253bebb9bb | ||
|
|
483538bbc1 | ||
|
|
c0ef6d8b34 | ||
|
|
7f06863b19 | ||
|
|
8be7ee1b7d | ||
|
|
15e2fde864 | ||
|
|
4e5140c877 | ||
|
|
13ed298ee8 | ||
|
|
e83a6f05bc | ||
|
|
a0c312e5a4 | ||
|
|
1eabb7fdb4 | ||
|
|
9e170b40f6 | ||
|
|
efd5e16511 | ||
|
|
c4e0d7aedc | ||
|
|
d1584bed40 | ||
|
|
689d201b04 | ||
|
|
22407506ff | ||
|
|
009f91b949 | ||
|
|
3bd281bea0 | ||
|
|
30c3a435d6 | ||
|
|
0fe321b0ef | ||
|
|
8f12442ac7 | ||
|
|
40ab3c81dc | ||
|
|
d5a8bf38ad | ||
|
|
0e793036fb | ||
|
|
19b7637d46 | ||
|
|
5609bc6d02 | ||
|
|
fceeeacf88 | ||
|
|
4327108ddf | ||
|
|
cb77f33fc8 | ||
|
|
b687305be2 | ||
|
|
192305c61f | ||
|
|
03faf29bcf | ||
|
|
d389f8b5ac | ||
|
|
56348938fb | ||
|
|
9b0aafbfa8 | ||
|
|
9239d61c74 | ||
|
|
58a110e109 | ||
|
|
b255102780 | ||
|
|
3a14b4c87f | ||
|
|
e1d6bf5961 | ||
|
|
8e546bde0b | ||
|
|
29a97d84a1 | ||
|
|
c526b3ef57 | ||
|
|
032721d246 | ||
|
|
0731084ec0 | ||
|
|
11a3e49daa | ||
|
|
68481844ee | ||
|
|
96a9def513 | ||
|
|
98a4f046ee | ||
|
|
9db8c941dc | ||
|
|
fd4a6906bd | ||
|
|
da65256208 | ||
|
|
0d2615522f | ||
|
|
6e5e5fb666 | ||
|
|
8005ef8432 | ||
|
|
49fe97bbe4 | ||
|
|
95ab97df73 | ||
|
|
3be9833ee0 | ||
|
|
b82223dc1d | ||
|
|
17d97623d5 | ||
|
|
1fca5ba008 | ||
|
|
5bb97ee42f | ||
|
|
ae9a03e58c | ||
|
|
ca12761fb2 | ||
|
|
18a61416fd | ||
|
|
37ff2c7227 | ||
|
|
af4018403b | ||
|
|
d3b9bde5c2 | ||
|
|
d704fef294 | ||
|
|
bb4bb64795 | ||
|
|
402ab092d8 | ||
|
|
9769e4b574 | ||
|
|
e19fe5ccd3 | ||
|
|
11963e1ff7 | ||
|
|
ec0d4fbd07 | ||
|
|
4e66e348fc | ||
|
|
eb6fce5a72 | ||
|
|
21b33bb9cd | ||
|
|
430b3cff96 | ||
|
|
334a8b66bd | ||
|
|
204b9ca6e5 | ||
|
|
93f13113e6 | ||
|
|
ca1f95df7a | ||
|
|
0c9bcca584 | ||
|
|
a27433dc11 | ||
|
|
dbd789f4be | ||
|
|
7efbffb46d | ||
|
|
559383a810 | ||
|
|
1897ac67a0 | ||
|
|
4f2d669897 | ||
|
|
f50b18556e | ||
|
|
1dc8a7f314 | ||
|
|
b526d07eed | ||
|
|
97cd0909e8 | ||
|
|
39cb85fda6 | ||
|
|
498f567e37 | ||
|
|
99cecd6756 | ||
|
|
7ab374b4ee | ||
|
|
22d36bd1c4 | ||
|
|
ebc4d9fdc5 | ||
|
|
b1c41a58f2 | ||
|
|
40710f788d | ||
|
|
5513c46f78 | ||
|
|
8879bd2be8 | ||
|
|
4ab8ced4a3 | ||
|
|
5bb9e1b188 | ||
|
|
db97d4576f | ||
|
|
c581fa27ab | ||
|
|
bef4e476d7 | ||
|
|
e30e35bf4f | ||
|
|
2e1f02163c | ||
|
|
1ce84ec343 | ||
|
|
6dc21ce69b | ||
|
|
8d5621248f | ||
|
|
b73cc378b5 | ||
|
|
564c7b8317 | ||
|
|
f4a61cddaf | ||
|
|
456f5c1d6a | ||
|
|
ae0404b1a7 | ||
|
|
31f91e571d | ||
|
|
20acd56bb9 | ||
|
|
a764006523 | ||
|
|
84b8777c2b | ||
|
|
c0fc7d69a6 | ||
|
|
4807ca32be | ||
|
|
4c841a8f9f | ||
|
|
6babebfec5 | ||
|
|
660666a28b | ||
|
|
34550dd778 | ||
|
|
64bc0636d7 | ||
|
|
aa199da57d | ||
|
|
b265ffd7d6 | ||
|
|
00f4aee80a | ||
|
|
d6973ed88d | ||
|
|
2ada43f6ba | ||
|
|
e800a18b31 | ||
|
|
ea5ceb61ca | ||
|
|
972609281f | ||
|
|
4ea68ebce4 | ||
|
|
d01c6262b6 | ||
|
|
19e3d82d98 | ||
|
|
f06c1b83a9 | ||
|
|
ea504a93cf | ||
|
|
9fe47194ff | ||
|
|
ba3b1fea6f | ||
|
|
58088b2090 | ||
|
|
cca7a505a3 | ||
|
|
ece7466117 | ||
|
|
3451280679 | ||
|
|
bef2894949 | ||
|
|
06b6638926 | ||
|
|
53cf6c162c | ||
|
|
069b83b78d | ||
|
|
c0c56c0040 | ||
|
|
92c298b848 | ||
|
|
831d2a6179 | ||
|
|
90886193c0 | ||
|
|
deed0bb01d | ||
|
|
7f2c705b94 | ||
|
|
6c0401b191 | ||
|
|
331d28b512 | ||
|
|
9fd936c521 | ||
|
|
4a13fc307b | ||
|
|
adef15ce2b | ||
|
|
88340618af | ||
|
|
49b97887b0 | ||
|
|
6843c72149 | ||
|
|
f78c217d83 | ||
|
|
ede91ecf78 | ||
|
|
efe4e69ed1 | ||
|
|
8a3d9edd34 | ||
|
|
b9aa76c367 | ||
|
|
a49b99156b | ||
|
|
54f3dbf35a | ||
|
|
27847424b3 | ||
|
|
a5e94f6ae2 | ||
|
|
9f8b3799eb | ||
|
|
7734919010 | ||
|
|
19f40cb2e5 | ||
|
|
3d4bd15dac | ||
|
|
a3c81c3a31 | ||
|
|
3549f88232 | ||
|
|
59d009bc94 | ||
|
|
a6edafbf2d | ||
|
|
5fab66204c | ||
|
|
2fa7c57357 | ||
|
|
890e924eb0 | ||
|
|
1c6592a6ec | ||
|
|
554c5a7bfc | ||
|
|
7a32c50894 | ||
|
|
3fa8f04a4c | ||
|
|
962d7f601d | ||
|
|
f2aa2b5b8b | ||
|
|
87b485cfa4 | ||
|
|
5a55b5235d | ||
|
|
97adb76eac | ||
|
|
d0d5ab474d | ||
|
|
d396f09b3a | ||
|
|
3baa5bca46 | ||
|
|
c00484026c | ||
|
|
71798afadd | ||
|
|
1ec2f74df9 | ||
|
|
61a0651439 | ||
|
|
eae06059c9 | ||
|
|
a1ff9034b6 | ||
|
|
1085a8d608 | ||
|
|
3a547bf487 | ||
|
|
82489636d5 | ||
|
|
3cafb37a73 |
@@ -9,6 +9,7 @@ logs/
|
||||
log/
|
||||
/db
|
||||
/mydb.db
|
||||
!src/test/ddl-review/*.sql
|
||||
|
||||
|
||||
# Intellij project files
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>6.1.1</version>
|
||||
<version>6.5.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>avaje-ebeanorm</name>
|
||||
@@ -163,31 +163,11 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-integration</artifactId>
|
||||
<version>1.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>1.9.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.0.9</version>
|
||||
<groupId>org.avaje.composite</groupId>
|
||||
<artifactId>avaje-composite-testing</artifactId>
|
||||
<version>1.1</version>
|
||||
<scope>test</scope>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -33,6 +33,11 @@ public interface BeanState {
|
||||
*/
|
||||
boolean isDirty();
|
||||
|
||||
/**
|
||||
* This can be called with true to disable lazy loading on the bean.
|
||||
*/
|
||||
void setDisableLazyLoad(boolean disableLazyLoading);
|
||||
|
||||
/**
|
||||
* For partially populated beans returns the properties that are loaded on the
|
||||
* bean.
|
||||
|
||||
@@ -80,7 +80,7 @@ public interface CallableSql {
|
||||
/**
|
||||
* Return the label that is put into the transaction log.
|
||||
*/
|
||||
public String getLabel();
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
* Set the label that is put in the transaction log.
|
||||
|
||||
@@ -80,17 +80,16 @@ final class DRawSqlColumnsParser {
|
||||
return new ColumnMapping.Column(indexPos++, sb.toString(), split[split.length - 1]);
|
||||
}
|
||||
|
||||
private int nextComma() {
|
||||
private void nextComma() {
|
||||
boolean inQuote = false;
|
||||
while (pos < end) {
|
||||
char c = sqlSelect.charAt(pos);
|
||||
if (c == '\'') {
|
||||
inQuote = !inQuote;
|
||||
} else if (!inQuote && c == ',') {
|
||||
return pos;
|
||||
return;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class DRawSqlParser {
|
||||
private int placeHolderAndWhere;
|
||||
private int placeHolderHaving;
|
||||
private int placeHolderAndHaving;
|
||||
private boolean hasPlaceHolders;
|
||||
private final boolean hasPlaceHolders;
|
||||
|
||||
private int selectPos = -1;
|
||||
private int distinctPos = -1;
|
||||
@@ -98,19 +98,7 @@ class DRawSqlParser {
|
||||
}
|
||||
|
||||
private boolean hasPlaceHolders() {
|
||||
if (placeHolderWhere > -1) {
|
||||
return true;
|
||||
}
|
||||
if (placeHolderAndWhere > -1) {
|
||||
return true;
|
||||
}
|
||||
if (placeHolderHaving > -1) {
|
||||
return true;
|
||||
}
|
||||
if (placeHolderAndHaving > -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return placeHolderWhere > -1 || placeHolderAndWhere > -1 || placeHolderHaving > -1 || placeHolderAndHaving > -1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -168,9 +168,9 @@ public final class Ebean {
|
||||
defaultServer = getWithCreate(defaultName.trim());
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
} catch (Throwable e) {
|
||||
logger.error("Error trying to create the default EbeanServer", e);
|
||||
throw e;
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import com.avaje.ebean.annotation.CacheStrategy;
|
||||
import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.meta.MetaInfoManager;
|
||||
import com.avaje.ebean.plugin.SpiServer;
|
||||
import com.avaje.ebean.text.csv.CsvReader;
|
||||
import com.avaje.ebean.text.json.JsonContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Provides the API for fetching and saving beans to a particular DataSource.
|
||||
* <p>
|
||||
@@ -128,6 +128,11 @@ public interface EbeanServer {
|
||||
*/
|
||||
MetaInfoManager getMetaInfoManager();
|
||||
|
||||
/**
|
||||
* Return the extended API intended for use by plugins.
|
||||
*/
|
||||
SpiServer getPluginApi();
|
||||
|
||||
/**
|
||||
* Return the BeanState for a given entity bean.
|
||||
* <p>
|
||||
@@ -148,7 +153,7 @@ public interface EbeanServer {
|
||||
* difference comparison.
|
||||
* </p>
|
||||
*/
|
||||
Map<String, ValuePair> diff(Object a, Object b);
|
||||
Map<String, ValuePair> diff(Object newBean, Object oldBean);
|
||||
|
||||
/**
|
||||
* Create a new instance of T that is an EntityBean.
|
||||
@@ -904,6 +909,15 @@ public interface EbeanServer {
|
||||
*/
|
||||
<T> void findEachWhile(Query<T> query, QueryEachWhileConsumer<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute a query returning a list of beans.
|
||||
* <p>
|
||||
@@ -1099,6 +1113,21 @@ public interface EbeanServer {
|
||||
@Nullable
|
||||
<T> T findUnique(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
* <p>
|
||||
* Note that if the query includes joins then the generated delete statement may not be
|
||||
* optimal depending on the database platform.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query used for the delete
|
||||
* @param transaction the transaction to use (can be null)
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @return the number of beans/rows that were deleted
|
||||
*/
|
||||
<T> int delete(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a list of MapBean.
|
||||
* <p>
|
||||
@@ -1196,7 +1225,6 @@ public interface EbeanServer {
|
||||
* public class Order { ...
|
||||
*
|
||||
* @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
|
||||
* @JoinColumn(name="order_id")
|
||||
* List<OrderDetail> details;
|
||||
* ...
|
||||
* }
|
||||
@@ -1226,9 +1254,9 @@ public interface EbeanServer {
|
||||
void delete(Object bean) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Delete all the beans in the collection.
|
||||
* Delete the bean with an explicit transaction.
|
||||
*/
|
||||
int deleteAll(Collection<?> beans) throws OptimisticLockException;
|
||||
void delete(Object bean, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Delete the bean given its type and id.
|
||||
@@ -1240,6 +1268,16 @@ public interface EbeanServer {
|
||||
*/
|
||||
int delete(Class<?> beanType, Object id, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Delete all the beans in the collection.
|
||||
*/
|
||||
int deleteAll(Collection<?> beans) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Delete all the beans in the collection using an explicit transaction.
|
||||
*/
|
||||
int deleteAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Delete several beans given their type and id values.
|
||||
*/
|
||||
@@ -1251,11 +1289,6 @@ public interface EbeanServer {
|
||||
*/
|
||||
void deleteAll(Class<?> beanType, Collection<?> ids, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Delete the bean with an explicit transaction.
|
||||
*/
|
||||
void delete(Object bean, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Execute a Sql Update Delete or Insert statement. This returns the number of
|
||||
* rows that where updated, deleted or inserted. If is executed in batch then
|
||||
|
||||
@@ -169,7 +169,22 @@ public interface ExpressionFactory {
|
||||
* In - property has a value in the collection of values.
|
||||
*/
|
||||
Expression in(String propertyName, Collection<?> values);
|
||||
|
||||
|
||||
/**
|
||||
* Not In - property has a value in the array of values.
|
||||
*/
|
||||
Expression notIn(String propertyName, Object[] values);
|
||||
|
||||
/**
|
||||
* Not In - property has a value in the collection of values.
|
||||
*/
|
||||
Expression notIn(String propertyName, Collection<?> values);
|
||||
|
||||
/**
|
||||
* Not In - using a subQuery.
|
||||
*/
|
||||
Expression notIn(String propertyName, Query<?> subQuery);
|
||||
|
||||
/**
|
||||
* Exists expression
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.avaje.ebean.text.PathProperties;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -95,6 +96,29 @@ public interface ExpressionList<T> extends Serializable {
|
||||
*/
|
||||
Query<T> apply(PathProperties pathProperties);
|
||||
|
||||
/**
|
||||
* Perform an 'As of' query using history tables to return the object graph
|
||||
* as of a time in the past.
|
||||
* <p>
|
||||
* To perform this query the DB must have underlying history tables.
|
||||
* </p>
|
||||
*
|
||||
* @param asOf the date time in the past at which you want to view the data
|
||||
*/
|
||||
Query<T> asOf(Timestamp asOf);
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
* <p>
|
||||
* Note that if the query includes joins then the generated delete statement may not be
|
||||
* optimal depending on the database platform.
|
||||
* </p>
|
||||
*
|
||||
* @return the number of beans/rows that were deleted.
|
||||
*/
|
||||
int delete();
|
||||
|
||||
/**
|
||||
* Execute the query iterating over the results.
|
||||
*
|
||||
@@ -223,6 +247,24 @@ public interface ExpressionList<T> extends Serializable {
|
||||
*/
|
||||
PagedList<T> findPagedList(int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
List<Version<T>> findVersions();
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean between the 2 timestamps.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end);
|
||||
|
||||
/**
|
||||
* Add some filter predicate expressions to the many property.
|
||||
*/
|
||||
@@ -236,6 +278,24 @@ public interface ExpressionList<T> extends Serializable {
|
||||
*/
|
||||
Query<T> select(String properties);
|
||||
|
||||
/**
|
||||
* Set whether this query uses DISTINCT.
|
||||
* <p>
|
||||
* The select() clause MUST be specified when setDistinct(true) is set. The reason for this is that
|
||||
* generally ORM queries include the "id" property and this doesn't make sense for distinct queries.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<Customer> customers =
|
||||
* Ebean.find(Customer.class)
|
||||
* .setDistinct(true)
|
||||
* .select("name") // only select the customer name
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
Query<T> setDistinct(boolean distinct);
|
||||
|
||||
/**
|
||||
* Set the first row to fetch.
|
||||
*
|
||||
@@ -467,7 +527,22 @@ public interface ExpressionList<T> extends Serializable {
|
||||
* In - property has a value in the collection of values.
|
||||
*/
|
||||
ExpressionList<T> in(String propertyName, Collection<?> values);
|
||||
|
||||
|
||||
/**
|
||||
* Not In - property has a value in the array of values.
|
||||
*/
|
||||
ExpressionList<T> notIn(String propertyName, Object... values);
|
||||
|
||||
/**
|
||||
* Not In - property has a value in the collection of values.
|
||||
*/
|
||||
ExpressionList<T> notIn(String propertyName, Collection<?> values);
|
||||
|
||||
/**
|
||||
* Not In - using a subQuery.
|
||||
*/
|
||||
ExpressionList<T> notIn(String propertyName, Query<?> subQuery);
|
||||
|
||||
/**
|
||||
* Exists expression
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Intended to be used as a base class for 'Finder' implementations that can then
|
||||
* be injected or used as public static fields on the associated entity bean.
|
||||
* <p>
|
||||
* These 'finders' are a place to organise all the finder methods for that bean type
|
||||
* and specific finder methods are expected to be added (find by unique properties etc).
|
||||
* </p>
|
||||
*
|
||||
* <h3>Testing</h3>
|
||||
* <p>
|
||||
* For testing the mocki-ebean project has the ability to replace the finder implementation
|
||||
*
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* public class CustomerFinder extends Finder<Customer> {
|
||||
*
|
||||
* public CustomerFinder() {
|
||||
* super(Customer.class);
|
||||
* }
|
||||
*
|
||||
* // Add your customer finder methods ...
|
||||
*
|
||||
* public Customer byName(String name) {
|
||||
* return query().eq("name", name).findUnique();
|
||||
* }
|
||||
*
|
||||
* public List<Customer> findNew() {
|
||||
* return query()
|
||||
* .eq("status", Customer.Status.NEW)
|
||||
* .orderBy("name")
|
||||
* .findList()
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @Entity
|
||||
* public class Customer extends BaseModel {
|
||||
*
|
||||
* public static final CustomerFinder find = new CustomerFinder();
|
||||
* ...
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public class Finder<I, T> {
|
||||
|
||||
/**
|
||||
* The entity bean type.
|
||||
*/
|
||||
private final Class<T> type;
|
||||
|
||||
/**
|
||||
* The name of the EbeanServer, null for the default server.
|
||||
*/
|
||||
private final String serverName;
|
||||
|
||||
/**
|
||||
* Create with the type of the entity bean.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* public class CustomerFinder extends Finder<Customer> {
|
||||
*
|
||||
* public CustomerFinder() {
|
||||
* super(Customer.class);
|
||||
* }
|
||||
*
|
||||
* // ... add extra customer specific finder methods
|
||||
* }
|
||||
*
|
||||
* @Entity
|
||||
* public class Customer extends BaseModel {
|
||||
*
|
||||
* public static final CustomerFinder find = new CustomerFinder();
|
||||
* ...
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public Finder(Class<T> type) {
|
||||
this.type = type;
|
||||
this.serverName = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with the type of the entity bean and specific server name.
|
||||
*/
|
||||
public Finder(Class<T> type, String serverName) {
|
||||
this.type = type;
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying 'default' EbeanServer.
|
||||
*
|
||||
* <p>
|
||||
* This provides full access to the API such as explicit transaction demarcation etc.
|
||||
*
|
||||
*/
|
||||
public EbeanServer db() {
|
||||
return Ebean.getServer(serverName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return typically a different EbeanServer to the default.
|
||||
* <p>
|
||||
* This is equivalent to {@link Ebean#getServer(String)}
|
||||
*
|
||||
* @param server
|
||||
* The name of the EbeanServer. If this is null then the default EbeanServer is
|
||||
* returned.
|
||||
*/
|
||||
public EbeanServer db(String server) {
|
||||
return Ebean.getServer(server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an entity reference for this ID.
|
||||
*
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#getReference(Class, Object)}
|
||||
*/
|
||||
public T ref(I id) {
|
||||
return db().getReference(type, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an entity by ID.
|
||||
*
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#find(Class, Object)}
|
||||
*/
|
||||
@Nullable
|
||||
public T byId(I id) {
|
||||
return db().find(type, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a bean by Id.
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#delete(Class, Object)}
|
||||
*/
|
||||
public void deleteById(I id) {
|
||||
db().delete(type, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all entities of the given type.
|
||||
*/
|
||||
public List<T> all() {
|
||||
return query().findList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query.
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#find(Class)}
|
||||
*/
|
||||
public Query<T> query() {
|
||||
return db().find(type);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package com.avaje.ebean;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
@@ -441,6 +441,7 @@ public abstract class Model {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Find() {
|
||||
this.serverName = null;
|
||||
this.type = (Class<T>)ClassUtil.getSecondArgumentType(getClass());
|
||||
|
||||
@@ -159,6 +159,11 @@ public interface PagedList<T> {
|
||||
*/
|
||||
int getPageIndex();
|
||||
|
||||
/**
|
||||
* Return the page size used for this query.
|
||||
*/
|
||||
int getPageSize();
|
||||
|
||||
/**
|
||||
* Return true if there is a next page.
|
||||
* <p>
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.avaje.ebean.text.PathProperties;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -285,6 +286,17 @@ public interface Query<T> extends Serializable {
|
||||
*/
|
||||
Query<T> setRawSql(RawSql rawSql);
|
||||
|
||||
/**
|
||||
* Perform an 'As of' query using history tables to return the object graph
|
||||
* as of a time in the past.
|
||||
* <p>
|
||||
* To perform this query the DB must have underlying history tables.
|
||||
* </p>
|
||||
*
|
||||
* @param asOf the date time in the past at which you want to view the data
|
||||
*/
|
||||
Query<T> asOf(Timestamp asOf);
|
||||
|
||||
/**
|
||||
* Cancel the query execution if supported by the underlying database and
|
||||
* driver.
|
||||
@@ -708,6 +720,41 @@ public interface Query<T> extends Serializable {
|
||||
@Nullable
|
||||
T findUnique();
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
* Note that this query will work against view based history implementations
|
||||
* but not sql2011 standards based implementations that require a start and
|
||||
* end timestamp to be specified.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
List<Version<T>> findVersions();
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean between the 2 timestamps.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end);
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
* <p>
|
||||
* Note that if the query includes joins then the generated delete statement may not be
|
||||
* optimal depending on the database platform.
|
||||
* </p>
|
||||
*
|
||||
* @return the number of beans/rows that were deleted.
|
||||
*/
|
||||
int delete();
|
||||
|
||||
/**
|
||||
* Return the count of entities this query should return.
|
||||
* <p>
|
||||
@@ -1108,6 +1155,19 @@ public interface Query<T> extends Serializable {
|
||||
|
||||
/**
|
||||
* Set whether this query uses DISTINCT.
|
||||
* <p>
|
||||
* The select() clause MUST be specified when setDistinct(true) is set. The reason for this is that
|
||||
* generally ORM queries include the "id" property and this doesn't make sense for distinct queries.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<Customer> customers =
|
||||
* Ebean.find(Customer.class)
|
||||
* .setDistinct(true)
|
||||
* .select("name")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
Query<T> setDistinct(boolean isDistinct);
|
||||
|
||||
@@ -1236,4 +1296,11 @@ public interface Query<T> extends Serializable {
|
||||
*/
|
||||
Class<T> getBeanType();
|
||||
|
||||
/**
|
||||
* Set true if you want to disable lazy loading.
|
||||
* <p>
|
||||
* That is, once the object graph is returned further lazy loading is disabled.
|
||||
* </p>
|
||||
*/
|
||||
Query<T> setDisableLazyLoading(boolean disableLazyLoading);
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ public interface SqlRow extends Serializable, Map<String, Object> {
|
||||
/**
|
||||
* Put all the values from t into this map.
|
||||
*/
|
||||
void putAll(Map<? extends String, ? extends Object> t);
|
||||
void putAll(Map<? extends String, ?> t);
|
||||
|
||||
/**
|
||||
* Return the size of the map.
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.avaje.ebean;
|
||||
|
||||
import com.avaje.ebean.config.PersistBatch;
|
||||
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.RollbackException;
|
||||
import java.io.Closeable;
|
||||
@@ -97,6 +96,17 @@ public interface Transaction extends Closeable {
|
||||
*/
|
||||
void setPersistCascade(boolean persistCascade);
|
||||
|
||||
/**
|
||||
* Set to true when you want all loaded properties to be included in the update
|
||||
* (rather than just the changed properties).
|
||||
* <p>
|
||||
* You might set this when using JDBC batch in order to get multiple updates
|
||||
* with slightly different sets of changed properties into the same statement
|
||||
* and hence better JDBC batch performance.
|
||||
* </p>
|
||||
*/
|
||||
void setUpdateAllLoadedProperties(boolean updateAllLoadedProperties);
|
||||
|
||||
/**
|
||||
* Turn on or off statement batching. Statement batching can be transparent
|
||||
* for drivers and databases that support getGeneratedKeys. Otherwise you may
|
||||
@@ -279,7 +289,7 @@ public interface Transaction extends Closeable {
|
||||
* <li>Transaction commit occurs</li>
|
||||
* </ul>
|
||||
*/
|
||||
void flushBatch() throws PersistenceException, OptimisticLockException;
|
||||
void flushBatch() throws PersistenceException;
|
||||
|
||||
/**
|
||||
* Return the underlying Connection object.
|
||||
|
||||
@@ -46,5 +46,5 @@ public enum TxType {
|
||||
* If there is an existing transaction throws an Exception. Method runs
|
||||
* without a transaction.
|
||||
*/
|
||||
NEVER;
|
||||
NEVER
|
||||
}
|
||||
|
||||
@@ -5,10 +5,19 @@ package com.avaje.ebean;
|
||||
*/
|
||||
public class ValuePair {
|
||||
|
||||
private final Object newValue;
|
||||
protected Object newValue;
|
||||
|
||||
private final Object oldValue;
|
||||
protected Object oldValue;
|
||||
|
||||
/**
|
||||
* Default constructor for JSON tools.
|
||||
*/
|
||||
public ValuePair() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct with the pair of new and old values.
|
||||
*/
|
||||
public ValuePair(Object newValue, Object oldValue) {
|
||||
this.newValue = newValue;
|
||||
this.oldValue = oldValue;
|
||||
@@ -27,21 +36,19 @@ public class ValuePair {
|
||||
public Object getOldValue() {
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the new value.
|
||||
* Set the new value.
|
||||
*/
|
||||
@Deprecated
|
||||
public Object getValue1() {
|
||||
return newValue;
|
||||
public void setNewValue(Object newValue) {
|
||||
this.newValue = newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the old value.
|
||||
* Set the old value.
|
||||
*/
|
||||
@Deprecated
|
||||
public Object getValue2() {
|
||||
return oldValue;
|
||||
public void setOldValue(Object oldValue) {
|
||||
this.oldValue = oldValue;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Wraps a version of a @History bean.
|
||||
*/
|
||||
public class Version<T> {
|
||||
|
||||
/**
|
||||
* The version of the bean.
|
||||
*/
|
||||
protected T bean;
|
||||
|
||||
/**
|
||||
* The effective start date time of this version.
|
||||
*/
|
||||
protected Timestamp start;
|
||||
|
||||
/**
|
||||
* The effective end date time of this version.
|
||||
*/
|
||||
protected Timestamp end;
|
||||
|
||||
/**
|
||||
* The map of changed properties.
|
||||
*/
|
||||
protected Map<String, ValuePair> diff;
|
||||
|
||||
/**
|
||||
* Construct with bean and an effective date time range.
|
||||
*/
|
||||
public Version(T bean, Timestamp start, Timestamp end) {
|
||||
this.bean = bean;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor - useful for JSON tools such as Jackson.
|
||||
*/
|
||||
public Version() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean instance for this version.
|
||||
*/
|
||||
public T getBean() {
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean instance for this version.
|
||||
*/
|
||||
public void setBean(T bean) {
|
||||
this.bean = bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the effective start date time of this version.
|
||||
*/
|
||||
public Timestamp getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the effective start date time of this version.
|
||||
*/
|
||||
public void setStart(Timestamp start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the effective end date time of this version.
|
||||
*/
|
||||
public Timestamp getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the effective end date time of this version.
|
||||
*/
|
||||
public void setEnd(Timestamp end) {
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the map of differences from this bean to the prior version.
|
||||
*/
|
||||
public void setDiff(Map<String, ValuePair> diff) {
|
||||
this.diff = diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the map of differences from this bean to the prior version.
|
||||
*/
|
||||
public Map<String, ValuePair> getDiff() {
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks an entity bean as being included in the change logging.
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ChangeLog {
|
||||
|
||||
/**
|
||||
* Specify if inserts should be explicitly Included or Excluded.
|
||||
* <p>
|
||||
* If not defined explicitly then the server default behaviour defined
|
||||
* on ServerConfig is used.
|
||||
* </p>
|
||||
*/
|
||||
ChangeLogInsertMode inserts() default ChangeLogInsertMode.DEFAULT;
|
||||
|
||||
/**
|
||||
* When specified only include update requests that have at least one
|
||||
* of the given properties as a dirty property.
|
||||
* <p>
|
||||
* This provides a way to filter requests to include in the change log such that
|
||||
* only updates that include at least one of the given properties is included
|
||||
* in the change log.
|
||||
* </p>
|
||||
*/
|
||||
String[] updatesThatInclude() default {};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
/**
|
||||
* The mode used to determine if inserts should be included or not for a given bean type.
|
||||
*/
|
||||
public enum ChangeLogInsertMode {
|
||||
|
||||
/**
|
||||
* Use the default behaviour as defined on ServerConfig.
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
/**
|
||||
* Include inserts in the change log.
|
||||
*/
|
||||
INCLUDE,
|
||||
|
||||
/**
|
||||
* Exclude inserts in the change log.
|
||||
*/
|
||||
EXCLUDE
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks an entity bean as having history support.
|
||||
* <p>
|
||||
* In Postgres for example this means there is an associated history table which is
|
||||
* typically automatically populated via database triggers.
|
||||
* </p>
|
||||
*/
|
||||
@Target({ ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface History {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks a property as being excluded from history.
|
||||
* <p>
|
||||
* This means the property values are not maintained in the history table. Typically this
|
||||
* would be placed on relatively large properties (Clobs, Blobs, large varchar columns etc)
|
||||
* that are considered not interesting enough to maintain history on excluding them reduces
|
||||
* underlying database costs.
|
||||
* </p>
|
||||
* <p>
|
||||
* When placed on a ManyToMany this means that the intersection table does not have history
|
||||
* support.
|
||||
* </p>
|
||||
*/
|
||||
@Target({ ElementType.FIELD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface HistoryExclude {
|
||||
|
||||
}
|
||||
@@ -10,15 +10,24 @@ import java.lang.annotation.Target;
|
||||
*
|
||||
* @author rvbiljouw
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Target({ElementType.TYPE, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Index {
|
||||
|
||||
/**
|
||||
* Name of the index
|
||||
*
|
||||
* @return index name
|
||||
* Name of the index. If left blank a name is derived using the built in naming convention.
|
||||
*/
|
||||
String value() default "";
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* If set true indicates this is a unique index.
|
||||
*/
|
||||
boolean unique() default false;
|
||||
|
||||
/**
|
||||
* When placed on the class (rather than field) you can specify the columns
|
||||
* to include in the index in order.
|
||||
*/
|
||||
String[] columnNames() default {};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* For a timestamp property that is set to the datetime when the entity is
|
||||
* created/inserted.
|
||||
* <p>
|
||||
* This is effectively an alias for @CreatedTimestamp and added to hint
|
||||
* towards a better naming convention (WhenCreated, WhenModified).
|
||||
* </p>
|
||||
* <p>
|
||||
* An alternative to using this annotation would be to use insertable=false,
|
||||
* updateable=false with @Column and have the DB insert the current time
|
||||
* (default value on the DB column is SYSTIME etc).
|
||||
* </p>
|
||||
* <p>
|
||||
* The downside to this approach is that the inserted entity does not have the
|
||||
* timestamp value after the insert has occurred. You need to fetch the entity
|
||||
* back to get the inserted timestamp if you want to used it.
|
||||
* </p>
|
||||
*
|
||||
* <h3>Example:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* @WhenCreated
|
||||
* Timestamp whenCreated;
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface WhenCreated {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* For a timestamp property that is set to the datetime when the entity was last modified.
|
||||
* <p>
|
||||
* This is effectively an alias for @UpdatedTimestamp and added to hint
|
||||
* towards a better naming convention (WhenCreated, WhenModified).
|
||||
* </p>
|
||||
* </p>
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface WhenModified {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Mapped onto a entity bean property that represents the user id of
|
||||
* who created the entity>
|
||||
* <p>
|
||||
* To use this annotation you need to implement CurrentUserProvider.
|
||||
* The type of the bean property should match the type returned by
|
||||
* CurrentUserProvider.
|
||||
*</p>
|
||||
|
||||
* <h3>Example:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* @WhoCreated
|
||||
* String whoCreated;
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface WhoCreated {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Mapped onto a entity bean property that represents the user id of
|
||||
* who last modified the entity>
|
||||
* <p>
|
||||
* To use this annotation you need to implement CurrentUserProvider.
|
||||
* The type of the bean property should match the type returned by
|
||||
* CurrentUserProvider.
|
||||
* </p>
|
||||
*
|
||||
* <h3>Example:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* @WhoModified
|
||||
* String whoModified;
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface WhoModified {
|
||||
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
/**
|
||||
* The actual entity bean that 'owns' this intercept.
|
||||
*/
|
||||
private EntityBean owner;
|
||||
private final EntityBean owner;
|
||||
|
||||
private EntityBean embeddedOwner;
|
||||
private int embeddedOwnerIndex;
|
||||
@@ -72,7 +72,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
/**
|
||||
* Used when a bean is partially filled.
|
||||
*/
|
||||
private boolean[] loadedProps;
|
||||
private final boolean[] loadedProps;
|
||||
|
||||
private boolean fullyLoadedBean;
|
||||
|
||||
@@ -581,7 +581,29 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if any of the given property names are dirty.
|
||||
*/
|
||||
public boolean hasDirtyProperty(Set<String> propertyNames) {
|
||||
|
||||
String[] names = owner._ebean_getPropertyNames();
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
// the property has been changed on this bean
|
||||
if (propertyNames.contains(names[i])) {
|
||||
return true;
|
||||
}
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
if (propertyNames.contains(names[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of dirty properties with their new and old values.
|
||||
*/
|
||||
@@ -608,7 +630,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
// an embedded property has been changed - recurse
|
||||
EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i);
|
||||
embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i)+".");
|
||||
embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i) + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -638,6 +660,20 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a loaded property hash.
|
||||
*/
|
||||
public int getLoadedPropertyHash() {
|
||||
int hash = 37;
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (isLoadedProperty(i)) {
|
||||
hash = hash * 31 + (i+1);
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of property names for changed properties.
|
||||
*/
|
||||
@@ -877,7 +913,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -892,7 +928,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Integer.valueOf(oldValue), Integer.valueOf(newValue));
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -908,7 +944,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Long.valueOf(oldValue), Long.valueOf(newValue));
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -923,7 +959,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Double.valueOf(oldValue), Double.valueOf(newValue));
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -938,7 +974,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Float.valueOf(oldValue), Float.valueOf(newValue));
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -953,7 +989,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Short.valueOf(oldValue), Short.valueOf(newValue));
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -968,7 +1004,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Character.valueOf(oldValue), Character.valueOf(newValue));
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -983,7 +1019,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), Byte.valueOf(oldValue), Byte.valueOf(newValue));
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -82,6 +82,7 @@ public final class ObjectGraphNode implements Serializable {
|
||||
}
|
||||
|
||||
ObjectGraphNode e = (ObjectGraphNode) obj;
|
||||
//noinspection StringEquality
|
||||
return ((e.path == path) || (e.path != null && e.path.equals(path)))
|
||||
&& e.originQueryPoint.equals(originQueryPoint);
|
||||
}
|
||||
|
||||
@@ -74,12 +74,12 @@ public interface PersistenceContext {
|
||||
* The bean was previously deleted from this persistence context (can't hit
|
||||
* L2 cache).
|
||||
*/
|
||||
public static WithOption DELETED = new WithOption(true);
|
||||
public static final WithOption DELETED = new WithOption();
|
||||
|
||||
private final boolean deleted;
|
||||
private final Object bean;
|
||||
|
||||
private WithOption(boolean deleted) {
|
||||
private WithOption() {
|
||||
this.deleted = true;
|
||||
this.bean = null;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ public class ServerCacheStatistics {
|
||||
protected long evictByLRU;
|
||||
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
//noinspection StringBufferReplaceableByString
|
||||
StringBuilder sb = new StringBuilder(80);
|
||||
sb.append(cacheName);
|
||||
sb.append(" maxSize:").append(maxSize);
|
||||
sb.append(" size:").append(size);
|
||||
|
||||
@@ -343,41 +343,41 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
return list.remove(o);
|
||||
}
|
||||
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
public boolean removeAll(Collection<?> beans) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyRemoveListening) {
|
||||
boolean changed = false;
|
||||
Iterator<?> it = c.iterator();
|
||||
while (it.hasNext()) {
|
||||
Object o = it.next();
|
||||
if (list.remove(o)) {
|
||||
modifyRemoval(o);
|
||||
for (Object bean : beans) {
|
||||
if (list.remove(bean)) {
|
||||
// register this bean as having been removed
|
||||
modifyRemoval(bean);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
return list.removeAll(c);
|
||||
return list.removeAll(beans);
|
||||
}
|
||||
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
public boolean retainAll(Collection<?> retainBeans) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyRemoveListening) {
|
||||
boolean changed = false;
|
||||
Iterator<E> it = list.iterator();
|
||||
while (it.hasNext()) {
|
||||
Object o = it.next();
|
||||
if (!c.contains(o)) {
|
||||
Object bean = it.next();
|
||||
if (!retainBeans.contains(bean)) {
|
||||
// removing this bean
|
||||
it.remove();
|
||||
modifyRemoval(o);
|
||||
modifyRemoval(bean);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
return list.retainAll(c);
|
||||
return list.retainAll(retainBeans);
|
||||
}
|
||||
|
||||
public E set(int index, E element) {
|
||||
@@ -415,6 +415,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
|
||||
public <T> T[] toArray(T[] a) {
|
||||
init();
|
||||
//noinspection SuspiciousToArrayCall
|
||||
return list.toArray(a);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package com.avaje.ebean.common;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.avaje.ebean.bean.BeanCollectionLoader;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Map capable of lazy loading.
|
||||
*/
|
||||
@@ -148,8 +147,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
public Collection<E> getActualDetails() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the map entrySet.
|
||||
* <p>
|
||||
@@ -159,7 +157,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
*/
|
||||
public Collection<?> getActualEntries() {
|
||||
return map.entrySet();
|
||||
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
@@ -195,9 +192,9 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
checkReadOnly();
|
||||
initClear();
|
||||
if (modifyRemoveListening) {
|
||||
for (K key : map.keySet()) {
|
||||
E o = map.remove(key);
|
||||
modifyRemoval(o);
|
||||
// add all beans to the removal list
|
||||
for (E bean : map.values()) {
|
||||
modifyRemoval(bean);
|
||||
}
|
||||
}
|
||||
map.clear();
|
||||
@@ -249,27 +246,30 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyListening) {
|
||||
Object o = map.put(key, value);
|
||||
modifyAddition(value);
|
||||
modifyRemoval(o);
|
||||
Object oldBean = map.put(key, value);
|
||||
if (value != oldBean) {
|
||||
// register the add of the new and the removal of the old
|
||||
modifyAddition(value);
|
||||
modifyRemoval(oldBean);
|
||||
}
|
||||
}
|
||||
return map.put(key, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void putAll(Map<? extends K, ? extends E> t) {
|
||||
public void putAll(Map<? extends K, ? extends E> puts) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyListening) {
|
||||
Iterator it = t.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry entry = (Map.Entry) it.next();
|
||||
Object o = map.put((K) entry.getKey(), (E) entry.getValue());
|
||||
modifyAddition((E) entry.getValue());
|
||||
modifyRemoval(o);
|
||||
for (Entry<? extends K, ? extends E> entry : puts.entrySet()) {
|
||||
Object oldBean = map.put(entry.getKey(), entry.getValue());
|
||||
if (entry.getValue() != oldBean) {
|
||||
modifyAddition(entry.getValue());
|
||||
modifyRemoval(oldBean);
|
||||
}
|
||||
}
|
||||
}
|
||||
map.putAll(t);
|
||||
map.putAll(puts);
|
||||
}
|
||||
|
||||
public E remove(Object key) {
|
||||
|
||||
@@ -192,32 +192,29 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
return set.add(o);
|
||||
}
|
||||
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
public boolean addAll(Collection<? extends E> addCollection) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyAddListening) {
|
||||
boolean changed = false;
|
||||
Iterator<? extends E> it = c.iterator();
|
||||
while (it.hasNext()) {
|
||||
E o = it.next();
|
||||
if (set.add(o)) {
|
||||
modifyAddition(o);
|
||||
for (E bean : addCollection) {
|
||||
if (set.add(bean)) {
|
||||
// register the addition of the bean
|
||||
modifyAddition(bean);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
return set.addAll(c);
|
||||
return set.addAll(addCollection);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
checkReadOnly();
|
||||
initClear();
|
||||
if (modifyRemoveListening) {
|
||||
Iterator<E> it = set.iterator();
|
||||
while (it.hasNext()) {
|
||||
E e = it.next();
|
||||
modifyRemoval(e);
|
||||
for (E bean : set) {
|
||||
modifyRemoval(bean);
|
||||
}
|
||||
}
|
||||
set.clear();
|
||||
@@ -262,41 +259,40 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
return set.remove(o);
|
||||
}
|
||||
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
public boolean removeAll(Collection<?> beans) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyRemoveListening) {
|
||||
boolean changed = false;
|
||||
Iterator<?> it = c.iterator();
|
||||
while (it.hasNext()) {
|
||||
Object o = it.next();
|
||||
if (set.remove(o)) {
|
||||
modifyRemoval(o);
|
||||
for (Object bean : beans) {
|
||||
if (set.remove(bean)) {
|
||||
modifyRemoval(bean);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
return set.removeAll(c);
|
||||
return set.removeAll(beans);
|
||||
}
|
||||
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
public boolean retainAll(Collection<?> beans) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyRemoveListening) {
|
||||
boolean changed = false;
|
||||
Iterator<?> it = set.iterator();
|
||||
while (it.hasNext()) {
|
||||
Object o = it.next();
|
||||
if (!c.contains(o)) {
|
||||
Object bean = it.next();
|
||||
if (!beans.contains(bean)) {
|
||||
// not retaining this bean so add it to the removal list
|
||||
it.remove();
|
||||
modifyRemoval(o);
|
||||
modifyRemoval(bean);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
return set.retainAll(c);
|
||||
return set.retainAll(beans);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
@@ -311,6 +307,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
|
||||
public <T> T[] toArray(T[] a) {
|
||||
init();
|
||||
//noinspection SuspiciousToArrayCall
|
||||
return set.toArray(a);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,9 +40,7 @@ class ModifyCollection<E> implements Collection<E> {
|
||||
|
||||
public boolean addAll(Collection<? extends E> collection) {
|
||||
boolean changed = false;
|
||||
Iterator<? extends E> it = collection.iterator();
|
||||
while (it.hasNext()) {
|
||||
E o = it.next();
|
||||
for (E o : collection) {
|
||||
if (c.add(o)) {
|
||||
owner.modifyAddition(o);
|
||||
changed = true;
|
||||
@@ -51,27 +49,33 @@ class ModifyCollection<E> implements Collection<E> {
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
c.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return c.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> collection) {
|
||||
return c.containsAll(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return c.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
Iterator<E> it = c.iterator();
|
||||
return new ModifyIterator<E>(owner, it);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
if (c.remove(o)) {
|
||||
owner.modifyRemoval(o);
|
||||
@@ -80,42 +84,47 @@ class ModifyCollection<E> implements Collection<E> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> collection) {
|
||||
boolean changed = false;
|
||||
Iterator<?> it = collection.iterator();
|
||||
while (it.hasNext()) {
|
||||
Object o = (Object) it.next();
|
||||
if (c.remove(o)) {
|
||||
owner.modifyRemoval(o);
|
||||
for (Object bean : collection) {
|
||||
if (c.remove(bean)) {
|
||||
owner.modifyRemoval(bean);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> collection) {
|
||||
boolean changed = false;
|
||||
Iterator<?> it = c.iterator();
|
||||
while (it.hasNext()) {
|
||||
Object o = (Object) it.next();
|
||||
if (!collection.contains(o)) {
|
||||
Object bean = it.next();
|
||||
if (!collection.contains(bean)) {
|
||||
// not retaining this bean so add to removals
|
||||
it.remove();
|
||||
owner.modifyRemoval(o);
|
||||
owner.modifyRemoval(bean);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return c.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return c.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
//noinspection SuspiciousToArrayCall
|
||||
return c.toArray(a);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ class ModifyHolder<E> implements Serializable {
|
||||
void modifyRemoval(Object bean) {
|
||||
if (bean != null) {
|
||||
// If it is to be added then just remove the addition
|
||||
if (!modifyAdditions.remove((E) bean)) {
|
||||
if (!modifyAdditions.remove(bean)) {
|
||||
modifyDeletions.add((E) bean);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.avaje.ebean.common;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
@@ -39,12 +38,10 @@ class ModifyList<E> extends ModifyCollection<E> implements List<E> {
|
||||
owner.modifyAddition(element);
|
||||
}
|
||||
|
||||
public boolean addAll(int index, Collection<? extends E> co) {
|
||||
if (list.addAll(index, co)) {
|
||||
Iterator<? extends E> it = co.iterator();
|
||||
while (it.hasNext()) {
|
||||
E o = it.next();
|
||||
owner.modifyAddition(o);
|
||||
public boolean addAll(int index, Collection<? extends E> addCollection) {
|
||||
if (list.addAll(index, addCollection)) {
|
||||
for (E bean : addCollection) {
|
||||
owner.modifyAddition(bean);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -67,9 +67,7 @@ class ModifyListIterator<E> implements ListIterator<E> {
|
||||
}
|
||||
|
||||
public void set(E o) {
|
||||
if (last == null) {
|
||||
// in theory this is not allowed
|
||||
} else {
|
||||
if (last != null) {
|
||||
owner.modifyRemoval(last);
|
||||
owner.modifyAddition(o);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package com.avaje.ebean.config;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
|
||||
import javax.persistence.Inheritance;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Provides some base implementation for NamingConventions.
|
||||
*
|
||||
@@ -14,31 +12,39 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
|
||||
/** The Constant logger. */
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractNamingConvention.class);
|
||||
|
||||
/** The Constant DEFAULT_SEQ_FORMAT. */
|
||||
/**
|
||||
* The Constant DEFAULT_SEQ_FORMAT.
|
||||
*/
|
||||
public static final String DEFAULT_SEQ_FORMAT = "{table}_seq";
|
||||
|
||||
/** Sequence Format that includes the Primary Key column */
|
||||
/**
|
||||
* Sequence Format that includes the Primary Key column
|
||||
*/
|
||||
public static final String TABLE_PKCOLUMN_SEQ_FORMAT = "{table}_{column}_seq";
|
||||
|
||||
/** The catalog. */
|
||||
/**
|
||||
* The catalog.
|
||||
*/
|
||||
private String catalog;
|
||||
|
||||
/** The schema. */
|
||||
/**
|
||||
* The schema.
|
||||
*/
|
||||
private String schema;
|
||||
|
||||
/** The sequence format. */
|
||||
/**
|
||||
* The sequence format.
|
||||
*/
|
||||
private String sequenceFormat;
|
||||
|
||||
/** The database platform. */
|
||||
/**
|
||||
* The database platform.
|
||||
*/
|
||||
protected DatabasePlatform databasePlatform;
|
||||
|
||||
/** The max length of constraint names. */
|
||||
protected int maxConstraintNameLength;
|
||||
|
||||
/** Used to trim off extra prefix for M2M. */
|
||||
/**
|
||||
* Used to trim off extra prefix for M2M.
|
||||
*/
|
||||
protected int rhsPrefixLength = 3;
|
||||
|
||||
protected boolean useForeignKeyPrefix;
|
||||
@@ -62,8 +68,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct with the default sequence format ("{table}_seq") and
|
||||
* useForeignKeyPrefix as true.
|
||||
* Construct with the default sequence format ("{table}_seq") and useForeignKeyPrefix as true.
|
||||
*/
|
||||
public AbstractNamingConvention() {
|
||||
this(DEFAULT_SEQ_FORMAT);
|
||||
@@ -71,9 +76,6 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
|
||||
public void setDatabasePlatform(DatabasePlatform databasePlatform) {
|
||||
this.databasePlatform = databasePlatform;
|
||||
this.maxConstraintNameLength = databasePlatform.getDbDdlSyntax().getMaxConstraintNameLength();
|
||||
|
||||
logger.trace("Using maxConstraintNameLength of " + maxConstraintNameLength);
|
||||
}
|
||||
|
||||
public String getSequenceName(String tableName, String pkColumn) {
|
||||
@@ -218,11 +220,11 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
}
|
||||
buffer.append(rhsTableName);
|
||||
|
||||
int maxConstraintNameLength = databasePlatform.getDbDdlSyntax().getMaxConstraintNameLength();
|
||||
int maxTableNameLength = databasePlatform.getMaxTableNameLength();
|
||||
|
||||
// maxConstraintNameLength is used as the max table name length.
|
||||
if (buffer.length() > maxConstraintNameLength) {
|
||||
buffer.setLength(maxConstraintNameLength);
|
||||
if (buffer.length() > maxTableNameLength) {
|
||||
buffer.setLength(maxTableNameLength);
|
||||
}
|
||||
|
||||
return new TableName(lhsTable.getCatalog(), lhsTable.getSchema(), buffer.toString());
|
||||
@@ -239,7 +241,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
if (t != null && !isEmpty(t.name())) {
|
||||
// Note: empty catalog and schema are converted to null
|
||||
// Only need to convert quoted identifiers from annotations
|
||||
return new TableName(quoteIdentifiers(t.catalog()), quoteIdentifiers(t.schema()), quoteIdentifiers(t.name()));
|
||||
return new TableName(quoteIdentifiers(t.catalog()), quoteIdentifiers(t.schema()), quoteIdentifiers(t.name()));
|
||||
}
|
||||
|
||||
// No annotation
|
||||
@@ -272,10 +274,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
* Checks string is null or empty .
|
||||
*/
|
||||
protected boolean isEmpty(String s) {
|
||||
if (s == null || s.trim().length() == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return s == null || s.trim().length() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.avaje.ebean.config;
|
||||
|
||||
/**
|
||||
* Provides the current user in order to support 'Who created', 'Who modified' and other audit features.
|
||||
*/
|
||||
public interface CurrentUserProvider {
|
||||
|
||||
/**
|
||||
* Return the current user id.
|
||||
* <p>
|
||||
* The type returned should match the type of the properties annotated
|
||||
* with @WhoCreated and @WhoModified. These are typically String, Long or UUID.
|
||||
* </p>
|
||||
*/
|
||||
Object currentUser();
|
||||
}
|
||||
@@ -507,8 +507,7 @@ public class DataSourceConfig {
|
||||
|
||||
String customProperties = properties.get("customProperties", null);
|
||||
if (customProperties != null && customProperties.length() > 0) {
|
||||
Map<String, String> custProps = StringHelper.delimitedToMap(customProperties, ";", "=");
|
||||
this.customProperties = custProps;
|
||||
this.customProperties = StringHelper.delimitedToMap(customProperties, ";", "=");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
package com.avaje.ebean.config;
|
||||
|
||||
/**
|
||||
* Naming convention used for constraint names.
|
||||
* <p>
|
||||
* Note that these constraint names are trimmed in the PlatformDdl which can be overridden
|
||||
* but provides a decent default implementation.
|
||||
* </p>
|
||||
*/
|
||||
public class DbConstraintNaming {
|
||||
|
||||
/**
|
||||
* Defines how constraint names are shortened if required based on platform limitations.
|
||||
*/
|
||||
public interface MaxLength {
|
||||
|
||||
/**
|
||||
* Truncate or shorten the constraint name to support DB platform limitations.
|
||||
* <p>
|
||||
* There is a default implementation of this which is used if an implementation is
|
||||
* not specified.
|
||||
* </p>
|
||||
*/
|
||||
String maxLength(String constraintName, int count);
|
||||
}
|
||||
|
||||
protected String pkPrefix = "pk_";
|
||||
protected String pkSuffix = "";
|
||||
|
||||
protected String fkPrefix = "fk_";
|
||||
protected String fkMiddle = "_";
|
||||
protected String fkSuffix = "";
|
||||
|
||||
protected String fkIndexPrefix = "ix_";
|
||||
protected String fkIndexMiddle = "_";
|
||||
protected String fkIndexSuffix = "";
|
||||
|
||||
protected String indexPrefix = "ix_";
|
||||
protected String indexMiddle = "_";
|
||||
protected String indexSuffix = "";
|
||||
|
||||
protected String uqPrefix = "uq_";
|
||||
protected String uqSuffix = "";
|
||||
|
||||
protected String ckPrefix = "ck_";
|
||||
protected String ckSuffix = "";
|
||||
|
||||
protected boolean lowerCaseNames = true;
|
||||
|
||||
protected MaxLength maxLength;
|
||||
|
||||
protected DbConstraintNormalise normalise = new DbConstraintNormalise();
|
||||
|
||||
public DbConstraintNaming() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the MaxLength implementation used to truncate/shorten db constraint names as necessary.
|
||||
*/
|
||||
public MaxLength getMaxLength() {
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the MaxLength implementation used to truncate/shorten db constraint names as necessary.
|
||||
*/
|
||||
public void setMaxLength(MaxLength maxLength) {
|
||||
this.maxLength = maxLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the primary key constraint name.
|
||||
*/
|
||||
public String primaryKeyName(String tableName) {
|
||||
|
||||
return pkPrefix + normaliseTable(tableName) + pkSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the foreign key constraint name given a single column foreign key.
|
||||
*/
|
||||
public String foreignKeyConstraintName(String tableName, String columnName) {
|
||||
return fkPrefix + normaliseTable(tableName) + fkMiddle + normaliseColumn(columnName) + fkSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index name associated with a foreign key constraint given multiple columns.
|
||||
*/
|
||||
public String foreignKeyIndexName(String tableName, String[] columns) {
|
||||
|
||||
String colPart = joinColumnNames(columns);
|
||||
return fkIndexPrefix + normaliseTable(tableName) + fkIndexMiddle + colPart + fkIndexSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index name associated with a foreign key constraint given a single column foreign key.
|
||||
*/
|
||||
public String foreignKeyIndexName(String tableName, String column) {
|
||||
|
||||
String colPart = normaliseTable(column);
|
||||
return fkIndexPrefix + normaliseTable(tableName) + fkIndexMiddle + colPart + fkIndexSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index name for a general index (not associated with a foreign key).
|
||||
*/
|
||||
public String indexName(String tableName, String column) {
|
||||
|
||||
String colPart = normaliseTable(column);
|
||||
return indexPrefix + normaliseTable(tableName) + indexMiddle + colPart + indexSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index name for a general index (not associated with a foreign key).
|
||||
*/
|
||||
public String indexName(String tableName, String[] columns) {
|
||||
|
||||
String colPart = joinColumnNames(columns);
|
||||
return indexPrefix + normaliseTable(tableName) + indexMiddle + colPart + indexSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the column names together with underscores.
|
||||
*/
|
||||
protected String joinColumnNames(String[] columns) {
|
||||
|
||||
if (columns.length == 1) {
|
||||
return normaliseColumn(columns[0]);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append("_");
|
||||
}
|
||||
sb.append(normaliseColumn(columns[i]));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the unique constraint name.
|
||||
*/
|
||||
public String uniqueConstraintName(String tableName, String columnName) {
|
||||
|
||||
return uqPrefix + normaliseTable(tableName) + "_" + normaliseColumn(columnName) + uqSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the unique constraint name.
|
||||
*/
|
||||
public String uniqueConstraintName(String tableName, String[] columns) {
|
||||
|
||||
String colPart = joinColumnNames(columns);
|
||||
return uqPrefix + normaliseTable(tableName) + "_" + colPart + uqSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the check constraint name.
|
||||
*/
|
||||
public String checkConstraintName(String tableName, String columnName) {
|
||||
|
||||
return ckPrefix + normaliseTable(tableName) + "_" + normaliseColumn(columnName) + ckSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise the table name by trimming catalog and schema and removing any
|
||||
* quoted identifier characters (",',[,] etc).
|
||||
*/
|
||||
public String normaliseTable(String tableName) {
|
||||
|
||||
return normalise.normaliseTable(tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise the column name by removing any quoted identifier characters (",',[,] etc).
|
||||
*/
|
||||
public String normaliseColumn(String tableName) {
|
||||
|
||||
return normalise.normaliseColumn(tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lower case the table or column name checking for quoted identifiers.
|
||||
*/
|
||||
public String lowerName(String tableName) {
|
||||
if (lowerCaseNames && normalise.notQuoted(tableName)) {
|
||||
return tableName.toLowerCase();
|
||||
}
|
||||
return tableName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.avaje.ebean.config;
|
||||
|
||||
/**
|
||||
* Used to normalise table and column names which means stripping out
|
||||
* quoted identifier characters and any catalog or schema prefix.
|
||||
*/
|
||||
public class DbConstraintNormalise {
|
||||
|
||||
protected final String[] quotedIdentifiers;
|
||||
|
||||
protected boolean lowerCaseTables = true;
|
||||
|
||||
protected boolean lowerCaseColumns = true;
|
||||
|
||||
public DbConstraintNormalise() {
|
||||
this.quotedIdentifiers = new String[]{"\"", "'", "[", "]", "`"};
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise the table name by trimming catalog and schema and removing any
|
||||
* quoted identifier characters (",',[,] etc).
|
||||
*/
|
||||
public String normaliseTable(String tableName) {
|
||||
|
||||
tableName = trimQuotes(tableName);
|
||||
int lastPeriod = tableName.lastIndexOf('.');
|
||||
if (lastPeriod > -1) {
|
||||
// trim off catalog and schema prefix
|
||||
tableName = tableName.substring(lastPeriod + 1);
|
||||
}
|
||||
if (lowerCaseTables) {
|
||||
tableName = tableName.toLowerCase();
|
||||
}
|
||||
return tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise the column name by removing any quoted identifier characters.
|
||||
*/
|
||||
public String normaliseColumn(String columnName) {
|
||||
|
||||
columnName = trimQuotes(columnName);
|
||||
if (lowerCaseColumns) {
|
||||
columnName = columnName.toLowerCase();
|
||||
}
|
||||
return columnName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim off the platform quoted identifier quotes like [ ' and ".
|
||||
*/
|
||||
public boolean notQuoted(String tableName) {
|
||||
|
||||
// remove quoted identifier characters
|
||||
for (int i = 0; i < quotedIdentifiers.length; i++) {
|
||||
if (tableName.contains(quotedIdentifiers[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim off the platform quoted identifier quotes like [ ' and ".
|
||||
*/
|
||||
public String trimQuotes(String tableName) {
|
||||
|
||||
if (tableName == null) {
|
||||
return "";
|
||||
}
|
||||
// remove quoted identifier characters
|
||||
for (int i = 0; i < quotedIdentifiers.length; i++) {
|
||||
tableName = tableName.replace(quotedIdentifiers[i], "");
|
||||
}
|
||||
return tableName;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.avaje.ebean.config;
|
||||
|
||||
/**
|
||||
* Configuration for the DB migration processing.
|
||||
*/
|
||||
public class DbMigrationConfig {
|
||||
|
||||
/**
|
||||
* Resource path for the migration xml and sql.
|
||||
* Typically you would change 'app' to be a better/more unique.
|
||||
*/
|
||||
private String resourcePath = "dbmigration/app";
|
||||
|
||||
/**
|
||||
* Return the resource path for db migrations.
|
||||
*/
|
||||
public String getResourcePath() {
|
||||
return resourcePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the resource path for db migrations.
|
||||
* <p>
|
||||
* Typically this would be something like "dbmigration/myapp" where myapp gives it a
|
||||
* unique resource path in the case there are multiple EbeanServer applications in the
|
||||
* single classpath.
|
||||
* </p>
|
||||
*/
|
||||
public void setResourcePath(String resourcePath) {
|
||||
this.resourcePath = resourcePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the settings from the PropertiesWrapper.
|
||||
*/
|
||||
public void loadSettings(PropertiesWrapper properties) {
|
||||
resourcePath = properties.get("migration.resourcePath", resourcePath);
|
||||
}
|
||||
}
|
||||
@@ -82,8 +82,7 @@ public interface NamingConvention {
|
||||
String getPropertyFromColumn(Class<?> beanClass, String dbColumnName);
|
||||
|
||||
/**
|
||||
* Return the sequence name given the table name (for DB's that use
|
||||
* sequences).
|
||||
* Return the sequence name given the table name (for DB's that use sequences).
|
||||
* <p>
|
||||
* Typically you might append "_seq" to the table name as an example.
|
||||
* </p>
|
||||
@@ -99,14 +98,14 @@ public interface NamingConvention {
|
||||
* Return true if a prefix should be used building a foreign key name.
|
||||
* <p>
|
||||
* This by default is true and this works well when the primary key column
|
||||
* names are simply "ID". In this case a prefix (such as "ORDER" and
|
||||
* "CUSTOMER" etc) is added to the foreign key column producing "ORDER_ID" and
|
||||
* "CUSTOMER_ID".
|
||||
* names are simply "id". In this case a prefix (such as "order" and
|
||||
* "customer" etc) is added to the foreign key column producing "order_id" and
|
||||
* "customer_id".
|
||||
* </p>
|
||||
* <p>
|
||||
* This should return false when your primary key columns are the same as the
|
||||
* foreign key columns. For example, when the primary key columns are
|
||||
* "ORDER_ID", "CUST_ID" etc ... and they are the same as the foreign key
|
||||
* "order_id", "cust_id" etc ... and they are the same as the foreign key
|
||||
* column names.
|
||||
* </p>
|
||||
*/
|
||||
|
||||
@@ -36,7 +36,7 @@ public enum PersistBatch {
|
||||
INHERIT(false);
|
||||
|
||||
|
||||
boolean forInsert;
|
||||
final boolean forInsert;
|
||||
|
||||
PersistBatch(boolean forInsert) {
|
||||
this.forInsert = forInsert;
|
||||
|
||||
@@ -27,12 +27,12 @@ final class PropertyExpression {
|
||||
/**
|
||||
* Used to detect the start of an expression.
|
||||
*/
|
||||
private static String START = "${";
|
||||
private static final String START = "${";
|
||||
|
||||
/**
|
||||
* Used to detect the end of an expression.
|
||||
*/
|
||||
private static String END = "}";
|
||||
private static final String END = "}";
|
||||
|
||||
/**
|
||||
* Specify the PropertyHolder.
|
||||
@@ -144,13 +144,7 @@ final class PropertyExpression {
|
||||
}
|
||||
|
||||
private static boolean isJndiExpression(String exp) {
|
||||
if (exp.startsWith("JNDI:")) {
|
||||
return true;
|
||||
}
|
||||
if (exp.startsWith("jndi:")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return exp.startsWith("JNDI:") || exp.startsWith("jndi:");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@ public final class PropertyMap implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
|
||||
private final LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
|
||||
|
||||
public static Properties defaultProperties() {
|
||||
PropertyMap propertyMap = PropertyMapLoader.loadGlobalProperties();
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.avaje.ebean.config;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
|
||||
/**
|
||||
* Unwrap the PreparedStatement to get the specific underlying implementation.
|
||||
* <p>
|
||||
* This is used to handle specific JDBC driver issues. Typically this means
|
||||
* getting the OraclePreparedStatement to handle Oracle specific issues etc.
|
||||
* </p>
|
||||
*
|
||||
* @author rbygrave
|
||||
*/
|
||||
public interface PstmtDelegate {
|
||||
|
||||
/**
|
||||
* Unwrap the PreparedStatement to get the specific underlying implementation.
|
||||
*
|
||||
* @param pstmt
|
||||
* the PreparedStatement coming out of the connection pool
|
||||
* @return the underlying PreparedStatement
|
||||
*/
|
||||
PreparedStatement unwrap(PreparedStatement pstmt);
|
||||
}
|
||||
@@ -8,6 +8,9 @@ import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebean.config.dbplatform.DbEncrypt;
|
||||
import com.avaje.ebean.event.*;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogListener;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogRegister;
|
||||
import com.avaje.ebean.meta.MetaInfoManager;
|
||||
import com.avaje.ebean.util.ClassUtil;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
@@ -70,6 +73,11 @@ public class ServerConfig {
|
||||
|
||||
private ContainerConfig containerConfig;
|
||||
|
||||
/**
|
||||
* The underlying properties that were used during configuration.
|
||||
*/
|
||||
private Properties properties;
|
||||
|
||||
/**
|
||||
* The resource directory.
|
||||
*/
|
||||
@@ -113,6 +121,12 @@ public class ServerConfig {
|
||||
*/
|
||||
private String classPathReaderClassName;
|
||||
|
||||
/**
|
||||
* This is used to populate @WhoCreated, @WhoModified and
|
||||
* support other audit features (who executed a query etc).
|
||||
*/
|
||||
private CurrentUserProvider currentUserProvider;
|
||||
|
||||
/**
|
||||
* Config controlling the autofetch behaviour.
|
||||
*/
|
||||
@@ -144,6 +158,24 @@ public class ServerConfig {
|
||||
*/
|
||||
private int databaseSequenceBatchSize = 20;
|
||||
|
||||
/**
|
||||
* Suffix appended to the base table to derive the view that contains the union
|
||||
* of the base table and the history table in order to support asOf queries.
|
||||
*/
|
||||
private String asOfViewSuffix = "_with_history";
|
||||
|
||||
/**
|
||||
* Column used to support history and 'As of' queries. This column is a timestamp range
|
||||
* or equivalent.
|
||||
*/
|
||||
private String asOfSysPeriod = "sys_period";
|
||||
|
||||
/**
|
||||
* Suffix appended to the base table to derive the view that contains the union
|
||||
* of the base table and the history table in order to support asOf queries.
|
||||
*/
|
||||
private String historyTableSuffix = "_history";
|
||||
|
||||
/**
|
||||
* Use for transaction scoped batch mode.
|
||||
*/
|
||||
@@ -180,11 +212,6 @@ public class ServerConfig {
|
||||
private ExternalTransactionManager externalTransactionManager;
|
||||
|
||||
/**
|
||||
* Used to unwrap PreparedStatements to perform JDBC Driver specific functions
|
||||
*/
|
||||
private PstmtDelegate pstmtDelegate;
|
||||
|
||||
/**
|
||||
* The data source (if programmatically provided).
|
||||
*/
|
||||
private DataSource dataSource;
|
||||
@@ -194,6 +221,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private DataSourceConfig dataSourceConfig = new DataSourceConfig();
|
||||
|
||||
/**
|
||||
* The db migration config (migration resource path etc).
|
||||
*/
|
||||
private DbMigrationConfig migrationConfig = new DbMigrationConfig();
|
||||
|
||||
/**
|
||||
* Set to true if the DataSource uses autoCommit.
|
||||
* <p>
|
||||
@@ -221,11 +253,21 @@ public class ServerConfig {
|
||||
*/
|
||||
private NamingConvention namingConvention = new UnderscoreNamingConvention();
|
||||
|
||||
/**
|
||||
* Naming convention used in DDL generation for primary keys, foreign keys etc.
|
||||
*/
|
||||
private DbConstraintNaming constraintNaming = new DbConstraintNaming();
|
||||
|
||||
/**
|
||||
* Behaviour of update to include on the change properties.
|
||||
*/
|
||||
private boolean updateChangesOnly = true;
|
||||
|
||||
|
||||
/**
|
||||
* Behaviour of updates in JDBC batch to by default include all properties.
|
||||
*/
|
||||
private boolean updateAllPropertiesInBatch = true;
|
||||
|
||||
/**
|
||||
* Default behaviour for updates when cascade save on a O2M or M2M to delete any missing children.
|
||||
*/
|
||||
@@ -239,12 +281,24 @@ public class ServerConfig {
|
||||
|
||||
private List<BeanFindController> findControllers = new ArrayList<BeanFindController>();
|
||||
private List<BeanPersistController> persistControllers = new ArrayList<BeanPersistController>();
|
||||
private List<BeanPostLoad> postLoaders = new ArrayList<BeanPostLoad>();
|
||||
private List<BeanPersistListener> persistListeners = new ArrayList<BeanPersistListener>();
|
||||
private List<BeanQueryAdapter> queryAdapters = new ArrayList<BeanQueryAdapter>();
|
||||
private List<BulkTableEventListener> bulkTableEventListeners = new ArrayList<BulkTableEventListener>();
|
||||
private List<ServerConfigStartup> configStartupListeners = new ArrayList<ServerConfigStartup>();
|
||||
private List<TransactionEventListener> transactionEventListeners = new ArrayList<TransactionEventListener>();
|
||||
|
||||
/**
|
||||
* By default inserts are included in the change log.
|
||||
*/
|
||||
private boolean changeLogIncludeInserts = true;
|
||||
|
||||
private ChangeLogPrepare changeLogPrepare;
|
||||
|
||||
private ChangeLogListener changeLogListener;
|
||||
|
||||
private ChangeLogRegister changeLogRegister;
|
||||
|
||||
private EncryptKeyManager encryptKeyManager;
|
||||
|
||||
private EncryptDeployManager encryptDeployManager;
|
||||
@@ -299,6 +353,12 @@ public class ServerConfig {
|
||||
private int queryCacheMaxIdleTime = 600;
|
||||
private int queryCacheMaxTimeToLive = 60*60*6;
|
||||
private Object objectMapper;
|
||||
private boolean diffFlatMode = true;
|
||||
|
||||
/**
|
||||
* Set to true if you want eq("someProperty", null) to generate 1=1 rather than "is null" sql expression.
|
||||
*/
|
||||
private boolean expressionEqualsWithNullAsNoop;
|
||||
|
||||
/**
|
||||
* Construct a Server Configuration for programmatically creating an EbeanServer.
|
||||
@@ -434,6 +494,22 @@ public class ServerConfig {
|
||||
this.defaultServer = defaultServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CurrentUserProvider. This is used to populate @WhoCreated, @WhoModified and
|
||||
* support other audit features (who executed a query etc).
|
||||
*/
|
||||
public CurrentUserProvider getCurrentUserProvider() {
|
||||
return currentUserProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CurrentUserProvider. This is used to populate @WhoCreated, @WhoModified and
|
||||
* support other audit features (who executed a query etc).
|
||||
*/
|
||||
public void setCurrentUserProvider(CurrentUserProvider currentUserProvider) {
|
||||
this.currentUserProvider = currentUserProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the PersistBatch mode to use by default at the transaction level.
|
||||
* <p>
|
||||
@@ -461,7 +537,7 @@ public class ServerConfig {
|
||||
/**
|
||||
* Return the JDBC batch mode to use per save(), delete(), insert() or update() request.
|
||||
* <p>
|
||||
* This makes sense when a save() or delete() etc cascades and executes multiple child statements. The best caase
|
||||
* This makes sense when a save() or delete() cascades and executes multiple child statements. The best case
|
||||
* for this is when saving a master/parent bean this cascade inserts many detail/child beans.
|
||||
* </p>
|
||||
* <p>
|
||||
@@ -579,6 +655,136 @@ public class ServerConfig {
|
||||
this.databaseSequenceBatchSize = databaseSequenceBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ChangeLogPrepare.
|
||||
* <p>
|
||||
* This is used to set user context information to the ChangeSet in the
|
||||
* foreground thread prior to the logging occurring in a background thread.
|
||||
* </p>
|
||||
*/
|
||||
public ChangeLogPrepare getChangeLogPrepare() {
|
||||
return changeLogPrepare;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ChangeLogPrepare.
|
||||
* <p>
|
||||
* This is used to set user context information to the ChangeSet in the
|
||||
* foreground thread prior to the logging occurring in a background thread.
|
||||
* </p>
|
||||
*/
|
||||
public void setChangeLogPrepare(ChangeLogPrepare changeLogPrepare) {
|
||||
this.changeLogPrepare = changeLogPrepare;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ChangeLogListener which actually performs the logging of change sets
|
||||
* in the background.
|
||||
*/
|
||||
public ChangeLogListener getChangeLogListener() {
|
||||
return changeLogListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ChangeLogListener which actually performs the logging of change sets
|
||||
* in the background.
|
||||
*/
|
||||
public void setChangeLogListener(ChangeLogListener changeLogListener) {
|
||||
this.changeLogListener = changeLogListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ChangeLogRegister which controls which ChangeLogFilter is used for each
|
||||
* bean type and in this way provide fine grained control over which persist requests
|
||||
* are included in the change log.
|
||||
*/
|
||||
public ChangeLogRegister getChangeLogRegister() {
|
||||
return changeLogRegister;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ChangeLogRegister which controls which ChangeLogFilter is used for each
|
||||
* bean type and in this way provide fine grained control over which persist requests
|
||||
* are included in the change log.
|
||||
*/
|
||||
public void setChangeLogRegister(ChangeLogRegister changeLogRegister) {
|
||||
this.changeLogRegister = changeLogRegister;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if inserts should be included in the change log by default.
|
||||
*/
|
||||
public boolean isChangeLogIncludeInserts() {
|
||||
return changeLogIncludeInserts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if inserts should be included in the change log by default.
|
||||
*/
|
||||
public void setChangeLogIncludeInserts(boolean changeLogIncludeInserts) {
|
||||
this.changeLogIncludeInserts = changeLogIncludeInserts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB migration configuration.
|
||||
*/
|
||||
public DbMigrationConfig getMigrationConfig() {
|
||||
return migrationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB migration configuration.
|
||||
*/
|
||||
public void setMigrationConfig(DbMigrationConfig migrationConfig) {
|
||||
this.migrationConfig = migrationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the suffix appended to the base table to derive the view that contains the union
|
||||
* of the base table and the history table in order to support asOf queries.
|
||||
*/
|
||||
public String getAsOfViewSuffix() {
|
||||
return asOfViewSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the suffix appended to the base table to derive the view that contains the union
|
||||
* of the base table and the history table in order to support asOf queries.
|
||||
*/
|
||||
public void setAsOfViewSuffix(String asOfViewSuffix) {
|
||||
this.asOfViewSuffix = asOfViewSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the database column used to support history and 'As of' queries. This column is a timestamp range
|
||||
* or equivalent.
|
||||
*/
|
||||
public String getAsOfSysPeriod() {
|
||||
return asOfSysPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the database column used to support history and 'As of' queries. This column is a timestamp range
|
||||
* or equivalent.
|
||||
*/
|
||||
public void setAsOfSysPeriod(String asOfSysPeriod) {
|
||||
this.asOfSysPeriod = asOfSysPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the history table suffix (defaults to _history).
|
||||
*/
|
||||
public String getHistoryTableSuffix() {
|
||||
return historyTableSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the history table suffix.
|
||||
*/
|
||||
public void setHistoryTableSuffix(String historyTableSuffix) {
|
||||
this.historyTableSuffix = historyTableSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we are running in a JTA Transaction manager.
|
||||
*/
|
||||
@@ -883,6 +1089,20 @@ public class ServerConfig {
|
||||
this.namingConvention = namingConvention;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the constraint naming convention used in DDL generation.
|
||||
*/
|
||||
public DbConstraintNaming getConstraintNaming() {
|
||||
return constraintNaming;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the constraint naming convention used in DDL generation.
|
||||
*/
|
||||
public void setConstraintNaming(DbConstraintNaming constraintNaming) {
|
||||
this.constraintNaming = constraintNaming;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configuration for the Autofetch feature.
|
||||
*/
|
||||
@@ -897,24 +1117,6 @@ public class ServerConfig {
|
||||
this.autofetchConfig = autofetchConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the PreparedStatementDelegate.
|
||||
*/
|
||||
public PstmtDelegate getPstmtDelegate() {
|
||||
return pstmtDelegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the PstmtDelegate which can be used to support JDBC driver specific
|
||||
* features.
|
||||
* <p>
|
||||
* Typically this means Oracle JDBC driver specific workarounds.
|
||||
* </p>
|
||||
*/
|
||||
public void setPstmtDelegate(PstmtDelegate pstmtDelegate) {
|
||||
this.pstmtDelegate = pstmtDelegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DataSource.
|
||||
*/
|
||||
@@ -1449,7 +1651,26 @@ public class ServerConfig {
|
||||
public void setUpdateChangesOnly(boolean updateChangesOnly) {
|
||||
this.updateChangesOnly = updateChangesOnly;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if updates in JDBC batch default to include all properties by default.
|
||||
*/
|
||||
public boolean isUpdateAllPropertiesInBatch() {
|
||||
return updateAllPropertiesInBatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to false if by default updates in JDBC batch should not include all properties.
|
||||
* <p>
|
||||
* This mode can be explicitly set per transaction.
|
||||
* </p>
|
||||
*
|
||||
* @see com.avaje.ebean.Transaction#setUpdateAllLoadedProperties(boolean)
|
||||
*/
|
||||
public void setUpdateAllPropertiesInBatch(boolean updateAllPropertiesInBatch) {
|
||||
this.updateAllPropertiesInBatch = updateAllPropertiesInBatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if updates by default delete missing children when cascading save to a OneToMany or
|
||||
* ManyToMany. When not set this defaults to true.
|
||||
@@ -1564,14 +1785,45 @@ public class ServerConfig {
|
||||
persistControllers.add(beanPersistController);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a BeanPostLoad instance.
|
||||
* <p>
|
||||
* Note alternatively you can use {@link #setPostLoaders(List)} to set
|
||||
* all the BeanPostLoad instances.
|
||||
* </p>
|
||||
*/
|
||||
public void add(BeanPostLoad postLoad) {
|
||||
postLoaders.add(postLoad);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of BeanFindController instances.
|
||||
*/
|
||||
public List<BeanFindController> getFindControllers() {
|
||||
return findControllers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of BeanFindController instances.
|
||||
*/
|
||||
public void setFindControllers(List<BeanFindController> findControllers) {
|
||||
this.findControllers = findControllers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of BeanPostLoader instances.
|
||||
*/
|
||||
public List<BeanPostLoad> getPostLoaders() {
|
||||
return postLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of BeanPostLoader instances.
|
||||
*/
|
||||
public void setPostLoaders(List<BeanPostLoad> postLoaders) {
|
||||
this.postLoaders = postLoaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the BeanPersistController instances.
|
||||
*/
|
||||
@@ -1716,10 +1968,18 @@ public class ServerConfig {
|
||||
* Load the settings from the given properties
|
||||
*/
|
||||
public void loadFromProperties(Properties properties) {
|
||||
// keep the properties used for configuration so that these are available for plugins
|
||||
this.properties = properties;
|
||||
PropertiesWrapper p = new PropertiesWrapper("ebean", name, properties);
|
||||
loadSettings(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the properties that we used for configuration and were set via a call to loadFromProperties().
|
||||
*/
|
||||
public Properties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T createInstance(PropertiesWrapper p, Class<T> pluginType, String key) {
|
||||
@@ -1751,6 +2011,8 @@ public class ServerConfig {
|
||||
*/
|
||||
protected void loadSettings(PropertiesWrapper p) {
|
||||
|
||||
migrationConfig.loadSettings(p);
|
||||
|
||||
namingConvention = createNamingConvention(p, namingConvention);
|
||||
if (namingConvention != null) {
|
||||
namingConvention.loadFromProperties(p);
|
||||
@@ -1768,6 +2030,7 @@ public class ServerConfig {
|
||||
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
|
||||
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
|
||||
|
||||
currentUserProvider = createInstance(p, CurrentUserProvider.class, "currentUserProvider");
|
||||
disableClasspathSearch = p.getBoolean("disableClasspathSearch", disableClasspathSearch);
|
||||
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform");
|
||||
encryptKeyManager = createInstance(p, EncryptKeyManager.class, "encryptKeyManager");
|
||||
@@ -1792,6 +2055,7 @@ public class ServerConfig {
|
||||
collectQueryStatsByNode = p.getBoolean("collectQueryStatsByNode", collectQueryStatsByNode);
|
||||
collectQueryOrigins = p.getBoolean("collectQueryOrigins", collectQueryOrigins);
|
||||
|
||||
updateAllPropertiesInBatch = p.getBoolean("updateAllPropertiesInBatch", updateAllPropertiesInBatch);
|
||||
updateChangesOnly = p.getBoolean("updateChangesOnly", updateChangesOnly);
|
||||
|
||||
boolean defaultDeleteMissingChildren = p.getBoolean("defaultDeleteMissingChildren", updatesDeleteMissingChildren);
|
||||
@@ -1809,6 +2073,12 @@ public class ServerConfig {
|
||||
|
||||
persistenceContextScope = PersistenceContextScope.valueOf(p.get("persistenceContextScope", "TRANSACTION"));
|
||||
|
||||
changeLogIncludeInserts = p.getBoolean("changeLogIncludeInserts", changeLogIncludeInserts);
|
||||
expressionEqualsWithNullAsNoop = p.getBoolean("expressionEqualsWithNullAsNoop", expressionEqualsWithNullAsNoop);
|
||||
diffFlatMode = p.getBoolean("diffFlatMode", diffFlatMode);
|
||||
asOfViewSuffix = p.get("asOfViewSuffix", asOfViewSuffix);
|
||||
asOfSysPeriod = p.get("asOfSysPeriod", asOfSysPeriod);
|
||||
historyTableSuffix = p.get("historyTableSuffix", historyTableSuffix);
|
||||
dataSourceJndiName = p.get("dataSourceJndiName", dataSourceJndiName);
|
||||
databaseSequenceBatchSize = p.getInt("databaseSequenceBatchSize", databaseSequenceBatchSize);
|
||||
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
|
||||
@@ -1898,11 +2168,58 @@ public class ServerConfig {
|
||||
return databasePlatform.isDisallowBatchOnCascade() ? PersistBatch.NONE : persistBatchOnCascade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Jackson ObjectMapper.
|
||||
* <p>
|
||||
* Note that this is not strongly typed as Jackson ObjectMapper is an optional dependency.
|
||||
* </p>
|
||||
*/
|
||||
public Object getObjectMapper() {
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Jackson ObjectMapper.
|
||||
* <p>
|
||||
* Note that this is not strongly typed as Jackson ObjectMapper is an optional dependency.
|
||||
* </p>
|
||||
*/
|
||||
public void setObjectMapper(Object objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if diff should return flat properties with dot notation rather than
|
||||
* embedded beans or reference beans (when the associated bean id is different).
|
||||
*/
|
||||
public boolean isDiffFlatMode() {
|
||||
return diffFlatMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if diff should return flat properties with dot notation rather than
|
||||
* embedded beans or reference beans (when the associated bean id is different).
|
||||
*/
|
||||
public void setDiffFlatMode(boolean diffFlatMode) {
|
||||
this.diffFlatMode = diffFlatMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if eq("someProperty", null) should to generate "1=1" rather than "is null" sql expression.
|
||||
*/
|
||||
public boolean isExpressionEqualsWithNullAsNoop() {
|
||||
return expressionEqualsWithNullAsNoop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if you want eq("someProperty", null) to generate "1=1" rather than "is null" sql expression.
|
||||
* <p>
|
||||
* Setting this to true has the effect that eq(propertyName, value), ieq(propertyName, value) and
|
||||
* ne(propertyName, value) have no effect when the value is null. The expression factory adds a NoopExpression
|
||||
* which will add "1=1" into the SQL rather than "is null".
|
||||
* </p>
|
||||
*/
|
||||
public void setExpressionEqualsWithNullAsNoop(boolean expressionEqualsWithNullAsNoop) {
|
||||
this.expressionEqualsWithNullAsNoop = expressionEqualsWithNullAsNoop;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,26 @@ package com.avaje.ebean.config;
|
||||
/**
|
||||
* Converts between Camel Case and Underscore based names for both table and
|
||||
* column names (and is the default naming convention in Ebean).
|
||||
*
|
||||
*
|
||||
* @author emcgreal
|
||||
* @author rbygrave
|
||||
*/
|
||||
public class UnderscoreNamingConvention extends AbstractNamingConvention {
|
||||
|
||||
/** Force toUnderscore to return in upper case. */
|
||||
/**
|
||||
* Force toUnderscore to return in upper case.
|
||||
*/
|
||||
private boolean forceUpperCase = false;
|
||||
|
||||
/** The digits compressed. */
|
||||
/**
|
||||
* The digits compressed.
|
||||
*/
|
||||
private boolean digitsCompressed = true;
|
||||
|
||||
/**
|
||||
* Create with a given sequence format.
|
||||
*
|
||||
* @param sequenceFormat
|
||||
* the sequence format
|
||||
*
|
||||
* @param sequenceFormat the sequence format
|
||||
*/
|
||||
public UnderscoreNamingConvention(String sequenceFormat) {
|
||||
super(sequenceFormat);
|
||||
@@ -34,39 +37,30 @@ public class UnderscoreNamingConvention extends AbstractNamingConvention {
|
||||
|
||||
/**
|
||||
* Returns the last part of the class name.
|
||||
*
|
||||
* @param beanClass
|
||||
* the bean class
|
||||
*
|
||||
*
|
||||
* @param beanClass the bean class
|
||||
* @return the table name from class
|
||||
*/
|
||||
public TableName getTableNameByConvention(Class<?> beanClass) {
|
||||
|
||||
return new TableName(getCatalog(),
|
||||
getSchema(),
|
||||
toUnderscoreFromCamel(beanClass.getSimpleName()));
|
||||
return new TableName(getCatalog(), getSchema(), toUnderscoreFromCamel(beanClass.getSimpleName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Camel case property name to underscore based column name.
|
||||
*
|
||||
*
|
||||
* @return the column from property
|
||||
*/
|
||||
public String getColumnFromProperty(Class<?> beanClass, String propertyName) {// Field
|
||||
// field)
|
||||
// {
|
||||
public String getColumnFromProperty(Class<?> beanClass, String propertyName) {
|
||||
|
||||
return toUnderscoreFromCamel(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts underscore based column name to Camel case property name.
|
||||
*
|
||||
* @param beanClass
|
||||
* the bean class
|
||||
* @param dbColumnName
|
||||
* the db column name
|
||||
*
|
||||
*
|
||||
* @param beanClass the bean class
|
||||
* @param dbColumnName the db column name
|
||||
* @return the property from column
|
||||
*/
|
||||
public String getPropertyFromColumn(Class<?> beanClass, String dbColumnName) {
|
||||
@@ -110,7 +104,7 @@ public class UnderscoreNamingConvention extends AbstractNamingConvention {
|
||||
protected String toUnderscoreFromCamel(String camelCase) {
|
||||
|
||||
int lastUpper = -1;
|
||||
StringBuffer sb = new StringBuffer();
|
||||
StringBuilder sb = new StringBuilder(camelCase.length()+4);
|
||||
for (int i = 0; i < camelCase.length(); i++) {
|
||||
char c = camelCase.charAt(i);
|
||||
|
||||
@@ -144,16 +138,11 @@ public class UnderscoreNamingConvention extends AbstractNamingConvention {
|
||||
}
|
||||
|
||||
/**
|
||||
* To camel from underscore.
|
||||
*
|
||||
* @param underscore
|
||||
* the underscore
|
||||
*
|
||||
* @return the string
|
||||
* Convert and return the from string from underscore to camel case.
|
||||
*/
|
||||
protected String toCamelFromUnderscore(String underscore) {
|
||||
|
||||
StringBuffer result = new StringBuffer();
|
||||
StringBuilder result = new StringBuilder(underscore.length());
|
||||
String[] vals = underscore.split("_");
|
||||
|
||||
for (int i = 0; i < vals.length; i++) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.sql.Types;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.DB2Ddl;
|
||||
|
||||
/**
|
||||
* DB2 specific platform.
|
||||
@@ -14,10 +15,11 @@ public class DB2Platform extends DatabasePlatform {
|
||||
public DB2Platform() {
|
||||
super();
|
||||
this.name = "db2";
|
||||
|
||||
// only support getGeneratedKeys with non-batch JDBC
|
||||
// so generally use SEQUENCE instead for H2
|
||||
this.maxTableNameLength = 18;
|
||||
this.maxConstraintNameLength = 18;
|
||||
this.sqlLimiter = new Db2SqlLimiter();
|
||||
this.platformDdl = new DB2Ddl(dbTypeMap, dbIdentity);
|
||||
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
|
||||
@@ -25,8 +27,6 @@ public class DB2Platform extends DatabasePlatform {
|
||||
dbTypeMap.put(Types.REAL, new DbType("real"));
|
||||
dbTypeMap.put(Types.TINYINT, new DbType("smallint"));
|
||||
dbTypeMap.put(Types.DECIMAL, new DbType("decimal", 15));
|
||||
|
||||
this.dbDdlSyntax.setIdentity("generated by default as identity");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,9 @@ import javax.sql.DataSource;
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.Query;
|
||||
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlHandler;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.PlatformDdl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -17,6 +20,7 @@ public class DatabasePlatform {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabasePlatform.class);
|
||||
|
||||
|
||||
/**
|
||||
* Behavior used when ending a query only transaction (at read committed isolation level).
|
||||
*/
|
||||
@@ -70,16 +74,16 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected DbTypeMap dbTypeMap = new DbTypeMap();
|
||||
|
||||
/**
|
||||
* DB specific DDL syntax.
|
||||
*/
|
||||
protected DbDdlSyntax dbDdlSyntax = new DbDdlSyntax();
|
||||
|
||||
/**
|
||||
/**
|
||||
* Defines DB identity/sequence features.
|
||||
*/
|
||||
protected DbIdentity dbIdentity = new DbIdentity();
|
||||
|
||||
/**
|
||||
* The history support for this database platform.
|
||||
*/
|
||||
protected DbHistorySupport historySupport;
|
||||
|
||||
/**
|
||||
* The JDBC type to map booleans to (by default).
|
||||
*/
|
||||
@@ -140,6 +144,20 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected boolean disallowBatchOnCascade;
|
||||
|
||||
protected PlatformDdl platformDdl;
|
||||
|
||||
/**
|
||||
* The maximum length of table names - used specifically when derived
|
||||
* default table names for intersection tables.
|
||||
*/
|
||||
protected int maxTableNameLength = 60;
|
||||
|
||||
/**
|
||||
* A value of 60 is a reasonable default for all databases except
|
||||
* Oracle (limited to 30) and DB2 (limited to 18).
|
||||
*/
|
||||
protected int maxConstraintNameLength = 60;
|
||||
|
||||
/**
|
||||
* Instantiates a new database platform.
|
||||
*/
|
||||
@@ -152,13 +170,42 @@ public class DatabasePlatform {
|
||||
* "generic" is returned when no specific database platform has been set or
|
||||
* found.
|
||||
* </p>
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum table name length.
|
||||
* <p>
|
||||
* This is used when deriving names of intersection tables.
|
||||
* </p>
|
||||
*/
|
||||
public int getMaxTableNameLength() {
|
||||
return maxTableNameLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum constraint name allowed for the platform.
|
||||
*/
|
||||
public int getMaxConstraintNameLength() {
|
||||
return maxConstraintNameLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the platform specific DDL.
|
||||
*/
|
||||
public PlatformDdl getPlatformDdl() {
|
||||
return platformDdl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a DDL handler for generating DDL scripts.
|
||||
*/
|
||||
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
|
||||
return platformDdl.createDdlHandler(serverConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the JDBC driver does not allow additional queries to execute
|
||||
* when a resultSet is being 'streamed' as is the case with findEach() etc.
|
||||
@@ -215,6 +262,20 @@ public class DatabasePlatform {
|
||||
this.dbEncrypt = dbEncrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the history support for this database platform.
|
||||
*/
|
||||
public DbHistorySupport getHistorySupport() {
|
||||
return historySupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the history support for this database platform.
|
||||
*/
|
||||
public void setHistorySupport(DbHistorySupport historySupport) {
|
||||
this.historySupport = historySupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapping of JDBC to DB types.
|
||||
*
|
||||
@@ -224,15 +285,6 @@ public class DatabasePlatform {
|
||||
return dbTypeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DDL syntax for this platform.
|
||||
*
|
||||
* @return the db ddl syntax
|
||||
*/
|
||||
public DbDdlSyntax getDbDdlSyntax() {
|
||||
return dbDdlSyntax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column alias prefix.
|
||||
*/
|
||||
@@ -442,22 +494,4 @@ public class DatabasePlatform {
|
||||
return disallowBatchOnCascade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and return the create sequence DDL.
|
||||
*/
|
||||
public String ddlCreateSequence(String sequenceName, int initialValue, int allocationSize) {
|
||||
|
||||
StringBuilder sb = new StringBuilder("create sequence ");
|
||||
sb.append(sequenceName);
|
||||
if (initialValue > 1) {
|
||||
sb.append(" start with ").append(initialValue);
|
||||
}
|
||||
if (allocationSize > 0 && allocationSize != 50) {
|
||||
// at this stage ignoring allocationSize 50 as this is the 'default' and
|
||||
// not consistent with the way Ebean batch fetches sequence values
|
||||
sb.append(" increment by ").append(allocationSize);
|
||||
}
|
||||
sb.append(";");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,333 +0,0 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.config.TableName;
|
||||
|
||||
/**
|
||||
* Used to support DB specific syntax for DDL generation.
|
||||
*/
|
||||
public class DbDdlSyntax {
|
||||
|
||||
protected boolean renderIndexForFkey = true;
|
||||
|
||||
protected boolean inlinePrimaryKeyConstraint = false;
|
||||
|
||||
protected boolean addOneToOneUniqueContraint = true;
|
||||
|
||||
protected int maxConstraintNameLength = 32;
|
||||
|
||||
protected int columnNameWidth = 25;
|
||||
|
||||
/**
|
||||
* Flag set when we want to disable constraints on each table (rather than globally).
|
||||
*/
|
||||
protected boolean dropKeyConstraints;
|
||||
|
||||
protected String dropTableCascade;
|
||||
protected String dropIfExists;
|
||||
|
||||
protected String newLine = "\n";
|
||||
|
||||
protected String identity = "auto_increment";
|
||||
protected String identitySuffix = "";
|
||||
|
||||
protected String pkPrefix = "pk_";
|
||||
|
||||
protected String disableReferentialIntegrity;
|
||||
protected String enableReferentialIntegrity;
|
||||
|
||||
protected String foreignKeySuffix;
|
||||
|
||||
/**
|
||||
* Return the primary key name for a given bean descriptor.
|
||||
*/
|
||||
public String getPrimaryKeyName(String tableName) {
|
||||
|
||||
String pk = pkPrefix + tableName;
|
||||
if (pk.length() > maxConstraintNameLength) {
|
||||
// need to trim the primary key name
|
||||
pk = pk.substring(0, maxConstraintNameLength);
|
||||
}
|
||||
return pk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column definition for an identity column.
|
||||
*/
|
||||
public String getIdentityColumnDefn(String columnDefn) {
|
||||
String identity = getIdentity();
|
||||
if (identity != null && identity.length() > 0) {
|
||||
return columnDefn+" "+identity;
|
||||
}
|
||||
return columnDefn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the identity clause for DB's that have identities.
|
||||
*/
|
||||
public String getIdentity() {
|
||||
return identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the identity clause.
|
||||
*/
|
||||
public void setIdentity(String identity) {
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Typically returns empty string but for SQLite and perhaps others
|
||||
* the Identity/AutoIncrement clause comes after the primary key clause.
|
||||
*/
|
||||
public String getIdentitySuffix() {
|
||||
return identitySuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the identity clause that would appear after the primary key clause.
|
||||
* <p>
|
||||
* Only used for SQLite at this stage.
|
||||
* </p>
|
||||
*/
|
||||
public void setIdentitySuffix(String identitySuffix) {
|
||||
this.identitySuffix = identitySuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width for padding whitespace after column names.
|
||||
*/
|
||||
public int getColumnNameWidth() {
|
||||
return columnNameWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the amount of padding to write after the column name.
|
||||
*/
|
||||
public void setColumnNameWidth(int columnNameWidth) {
|
||||
this.columnNameWidth = columnNameWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the new line character.
|
||||
*/
|
||||
public String getNewLine() {
|
||||
return newLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the new line character.
|
||||
*/
|
||||
public void setNewLine(String newLine) {
|
||||
this.newLine = newLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the prefix used in naming primary keys.
|
||||
*/
|
||||
public String getPkPrefix() {
|
||||
return pkPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the prefix used in naming primary keys.
|
||||
*/
|
||||
public void setPkPrefix(String pkPrefix) {
|
||||
this.pkPrefix = pkPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB specific command to disable referential integrity
|
||||
*/
|
||||
public String getDisableReferentialIntegrity() {
|
||||
return disableReferentialIntegrity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB specific command to disable referential integrity
|
||||
*/
|
||||
public void setDisableReferentialIntegrity(String disableReferentialIntegrity) {
|
||||
this.disableReferentialIntegrity = disableReferentialIntegrity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB specific command to enable referential integrity
|
||||
*/
|
||||
public String getEnableReferentialIntegrity() {
|
||||
return enableReferentialIntegrity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB specific command to enable referential integrity
|
||||
*/
|
||||
public void setEnableReferentialIntegrity(String enableReferentialIntegrity) {
|
||||
this.enableReferentialIntegrity = enableReferentialIntegrity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true to is constraints are disabled on each table.
|
||||
*/
|
||||
public boolean isDropKeyConstraints() {
|
||||
return dropKeyConstraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return some DDL to disable constraints on the given table.
|
||||
*/
|
||||
public String dropKeyConstraintPrefix(String tableName, String fkName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if indexes should be created for the foreign keys.
|
||||
*/
|
||||
public boolean isRenderIndexForFkey() {
|
||||
return renderIndexForFkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether indexes should be created for the foreign keys.
|
||||
*/
|
||||
public void setRenderIndexForFkey(boolean renderIndexForFkey) {
|
||||
this.renderIndexForFkey = renderIndexForFkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Typically returns IF EXISTS (if that is supported by the database platform)
|
||||
* or null.
|
||||
*/
|
||||
public String getDropIfExists() {
|
||||
return dropIfExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IF EXISTS clause for dropping tables if that is supported by the
|
||||
* database platform.
|
||||
*/
|
||||
public void setDropIfExists(String dropIfExists) {
|
||||
this.dropIfExists = dropIfExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return prefix text that goes before drop table.
|
||||
*/
|
||||
public String dropTablePrefix(String tableName) {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cascade option for the drop table command.
|
||||
*/
|
||||
public String getDropTableCascade() {
|
||||
return dropTableCascade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cascade option for the drop table command.
|
||||
*/
|
||||
public void setDropTableCascade(String dropTableCascade) {
|
||||
this.dropTableCascade = dropTableCascade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the foreign key suffix.
|
||||
*/
|
||||
public String getForeignKeySuffix() {
|
||||
return foreignKeySuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the foreign key suffix.
|
||||
*/
|
||||
public void setForeignKeySuffix(String foreignKeySuffix) {
|
||||
this.foreignKeySuffix = foreignKeySuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum length that constraint names can be for this database.
|
||||
*/
|
||||
public int getMaxConstraintNameLength() {
|
||||
return maxConstraintNameLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum length that constraint names can be for this database.
|
||||
*/
|
||||
public void setMaxConstraintNameLength(int maxFkeyLength) {
|
||||
this.maxConstraintNameLength = maxFkeyLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if imported side of OneToOne's should have unique constraints
|
||||
* generated.
|
||||
*/
|
||||
public boolean isAddOneToOneUniqueContraint() {
|
||||
return addOneToOneUniqueContraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to false for DB's that don't want both a unique and index on the
|
||||
* imported OneToOne.
|
||||
*/
|
||||
public void setAddOneToOneUniqueContraint(boolean addOneToOneUniqueContraint) {
|
||||
this.addOneToOneUniqueContraint = addOneToOneUniqueContraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if primary key constraints should be inlined when they are a
|
||||
* single column.
|
||||
*/
|
||||
public boolean isInlinePrimaryKeyConstraint() {
|
||||
return inlinePrimaryKeyConstraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to inline primary key constraints.
|
||||
*/
|
||||
public void setInlinePrimaryKeyConstraint(boolean inlinePrimaryKeyConstraint) {
|
||||
this.inlinePrimaryKeyConstraint = inlinePrimaryKeyConstraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns a fully index name.
|
||||
*/
|
||||
public String getIndexName(String table, String propName, int ixCount) {
|
||||
|
||||
|
||||
StringBuilder buffer = new StringBuilder(30);
|
||||
buffer.append("ix_").append(TableName.parse(table));
|
||||
buffer.append("_").append(propName);
|
||||
|
||||
addSuffix(buffer, ixCount);
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns a fully qualified foreign key constraint name.
|
||||
*/
|
||||
public String getForeignKeyName(String table, String propName, int fkCount) {
|
||||
|
||||
StringBuilder buffer = new StringBuilder(30);
|
||||
buffer.append("fk_").append(TableName.parse(table));
|
||||
buffer.append("_").append(propName);
|
||||
|
||||
addSuffix(buffer, fkCount);
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the suffix.
|
||||
*/
|
||||
protected void addSuffix(StringBuilder buffer, int count) {
|
||||
String suffixNr = Integer.toString(count);
|
||||
int suffixLen = suffixNr.length() + 1;
|
||||
|
||||
if (buffer.length() + suffixLen > maxConstraintNameLength) {
|
||||
buffer.setLength(maxConstraintNameLength - suffixLen);
|
||||
}
|
||||
buffer.append("_");
|
||||
buffer.append(suffixNr);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
/**
|
||||
* History support for the database platform.
|
||||
*/
|
||||
public interface DbHistorySupport {
|
||||
|
||||
/**
|
||||
* Return true if the 'As of' predicate is part of the from clause
|
||||
* (more standard sql2011). So true for Oracle total recall and false
|
||||
* for Postgres and MySql (where we use views and history tables).
|
||||
*/
|
||||
boolean isBindWithFromClause();
|
||||
|
||||
/**
|
||||
* Return the number of columns bound in a 'As Of' predicate.
|
||||
* <p>
|
||||
* This is 1 for more standard sql2011 style and Postgres which has the
|
||||
* special range type and 2 for view based solutions with 2 columns such as
|
||||
* MySql.
|
||||
* </p>
|
||||
*/
|
||||
int getBindCount();
|
||||
|
||||
/**
|
||||
* For sql2011 style this ignores the passed in view suffix and returns something
|
||||
* like the ' as of timestamp ?' clause to be appended after the base table name.
|
||||
*
|
||||
* @param asOfViewSuffix the configured view suffix (typically "_with_history").
|
||||
* @return The suffix appended after the base table name in the from and join clauses.
|
||||
*/
|
||||
String getAsOfViewSuffix(String asOfViewSuffix);
|
||||
|
||||
/**
|
||||
* Return the 'versions between timestamp' suffix.
|
||||
*/
|
||||
String getVersionsBetweenSuffix(String asOfViewSuffix);
|
||||
|
||||
/**
|
||||
* Return the 'as of' predicate added for the given table alias.
|
||||
*
|
||||
* @param tableAlias The table alias this predicate is added for
|
||||
* @param sysPeriod The name of the 'sys_period' column used for effective date time range.
|
||||
* @return The predicate containing a single ? bind parameter which will be bound to the 'as at' timestamp value
|
||||
*/
|
||||
String getAsOfPredicate(String tableAlias, String sysPeriod);
|
||||
|
||||
/**
|
||||
* Return the column for the system period lower bound that will be included in findVersions() queries.
|
||||
*
|
||||
* @param tableAlias the table alias which will typically be 't0'
|
||||
* @param sysPeriod the name of the sys_period column
|
||||
*/
|
||||
String getSysPeriodLower(String tableAlias, String sysPeriod);
|
||||
|
||||
/**
|
||||
* Return the column for the system period upper bound that will be included in findVersions() queries.
|
||||
*
|
||||
* @param tableAlias the table alias which will typically be 't0'
|
||||
* @param sysPeriod the name of the sys_period column
|
||||
*/
|
||||
String getSysPeriodUpper(String tableAlias, String sysPeriod);
|
||||
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.dbmigration.migration.IdentityType;
|
||||
|
||||
/**
|
||||
* Defines the identity/sequence behaviour for the database.
|
||||
*/
|
||||
@@ -114,4 +116,28 @@ public class DbIdentity {
|
||||
this.idType = idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the id type to use based on requested identityType and
|
||||
* the support for that in the database platform.
|
||||
*/
|
||||
public IdType useIdentityType(IdentityType identityType) {
|
||||
|
||||
if (identityType == null) {
|
||||
// use the default
|
||||
return idType;
|
||||
}
|
||||
switch (identityType) {
|
||||
case GENERATOR:
|
||||
return IdType.GENERATOR;
|
||||
case EXTERNAL:
|
||||
return IdType.EXTERNAL;
|
||||
case SEQUENCE:
|
||||
return supportsSequence ? IdType.SEQUENCE : idType;
|
||||
case IDENTITY:
|
||||
return supportsIdentity ? IdType.IDENTITY : idType;
|
||||
}
|
||||
|
||||
// use the default
|
||||
return idType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
/**
|
||||
* Built in supported platforms.
|
||||
*/
|
||||
public enum DbPlatformName {
|
||||
|
||||
/**
|
||||
* Generic base platform configured via properties or code.
|
||||
*/
|
||||
GENERIC,
|
||||
|
||||
/**
|
||||
* H2.
|
||||
*/
|
||||
H2,
|
||||
|
||||
/**
|
||||
* Postgres.
|
||||
*/
|
||||
POSTGRES,
|
||||
|
||||
/**
|
||||
* MySql.
|
||||
*/
|
||||
MYSQL,
|
||||
|
||||
/**
|
||||
* Oracle.
|
||||
*/
|
||||
ORACLE,
|
||||
|
||||
/**
|
||||
* Microsoft SQL Server.
|
||||
*/
|
||||
SQLSERVER,
|
||||
|
||||
/**
|
||||
* DB2.
|
||||
*/
|
||||
DB2,
|
||||
|
||||
/**
|
||||
* SQLite.
|
||||
*/
|
||||
SQLITE
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
/**
|
||||
* SQL2011 based history support using 'as of timestamp' type clause appended as part of the the from or join clause.
|
||||
*/
|
||||
public abstract class DbStandardHistorySupport implements DbHistorySupport {
|
||||
|
||||
/**
|
||||
* Return true as with sql2011 the 'as of timestamp' clause included in from or join clause.
|
||||
*/
|
||||
@Override
|
||||
public boolean isBindWithFromClause() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return 1 as the bind count (not 2 for effective start and effective end columns).
|
||||
*/
|
||||
@Override
|
||||
public int getBindCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return null - not used for sql2011 based history.
|
||||
*/
|
||||
@Override
|
||||
public String getAsOfPredicate(String tableAlias, String sysPeriod) {
|
||||
// not used for sql2011 based history
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -9,16 +9,80 @@ import java.util.Map;
|
||||
*/
|
||||
public class DbTypeMap {
|
||||
|
||||
private static final DbType JSON_CLOB_PLACEHOLDER = new DbType("jsonClobPlaceholder");
|
||||
private static final DbType JSON_BLOB_PLACEHOLDER = new DbType("jsonBlobPlaceholder");
|
||||
private static final DbType JSON_VARCHAR_PLACEHOLDER = new DbType("jsonVarcharPlaceholder");
|
||||
|
||||
/**
|
||||
* A map to reverse lookup the type by name.
|
||||
* <p>
|
||||
* Used when converting from logical types to platform types which we
|
||||
* want to do with 2 phase DDL generation.
|
||||
*/
|
||||
static Map<String, Integer> lookup = new HashMap<String, Integer>();
|
||||
|
||||
static {
|
||||
lookup.put("BOOLEAN", Types.BOOLEAN);
|
||||
lookup.put("BIT", Types.BIT);
|
||||
lookup.put("INTEGER", Types.INTEGER);
|
||||
lookup.put("BIGINT", Types.BIGINT);
|
||||
lookup.put("REAL", Types.REAL);
|
||||
// Float is most common REAL mapping to have that as well
|
||||
lookup.put("FLOAT", Types.REAL);
|
||||
|
||||
lookup.put("DOUBLE", Types.DOUBLE);
|
||||
lookup.put("SMALLINT", Types.SMALLINT);
|
||||
lookup.put("TINYINT", Types.TINYINT);
|
||||
lookup.put("DECIMAL", Types.DECIMAL);
|
||||
lookup.put("VARCHAR", Types.VARCHAR);
|
||||
// VARCHAR2 - extra for Oracle specific column definition
|
||||
lookup.put("VARCHAR2", Types.VARCHAR);
|
||||
lookup.put("CHAR", Types.CHAR);
|
||||
lookup.put("BLOB", Types.BLOB);
|
||||
lookup.put("CLOB", Types.CLOB);
|
||||
|
||||
lookup.put("LONGVARBINARY", Types.LONGVARBINARY);
|
||||
lookup.put("LONGVARCHAR", Types.LONGVARCHAR);
|
||||
lookup.put("VARBINARY", Types.VARBINARY);
|
||||
lookup.put("BINARY", Types.BINARY);
|
||||
lookup.put("DATE", Types.DATE);
|
||||
lookup.put("TIME", Types.TIME);
|
||||
lookup.put("TIMESTAMP", Types.TIMESTAMP);
|
||||
|
||||
// Not standard java.sql.Types
|
||||
// logical JSON storage types
|
||||
lookup.put("JSON", DbType.JSON);
|
||||
lookup.put("JSONB", DbType.JSONB);
|
||||
lookup.put("JSONCLOB", DbType.JSONClob);
|
||||
lookup.put("JSONBLOB", DbType.JSONBlob);
|
||||
lookup.put("JSONVARCHAR", DbType.JSONVarchar);
|
||||
}
|
||||
|
||||
|
||||
private final Map<Integer, DbType> typeMap = new HashMap<Integer, DbType>();
|
||||
|
||||
/**
|
||||
* Return the DbTypeMap with standard (not platform specific) types.
|
||||
*
|
||||
* This has some extended JSON types (JSON, JSONB, JSONVarchar, JSONClob, JSONBlob).
|
||||
* These types get translated to specific database platform types during DDL generation.
|
||||
*/
|
||||
public static DbTypeMap logicalTypes() {
|
||||
return new DbTypeMap(true);
|
||||
}
|
||||
|
||||
public DbTypeMap() {
|
||||
loadDefaults();
|
||||
loadDefaults(false);
|
||||
}
|
||||
|
||||
private DbTypeMap(boolean logicalTypes) {
|
||||
loadDefaults(logicalTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the standard types. These can be overridden by DB specific platform.
|
||||
*/
|
||||
private void loadDefaults() {
|
||||
private void loadDefaults(boolean logicalTypes) {
|
||||
|
||||
put(Types.BOOLEAN, new DbType("boolean"));
|
||||
put(Types.BIT, new DbType("bit"));
|
||||
@@ -37,11 +101,22 @@ public class DbTypeMap {
|
||||
put(Types.BLOB, new DbType("blob"));
|
||||
put(Types.CLOB, new DbType("clob"));
|
||||
|
||||
put(DbType.JSON, new DbType("clob")); // Postgres maps this to JSON
|
||||
put(DbType.JSONB, new DbType("clob")); // Postgres maps this to JSONB
|
||||
put(DbType.JSONClob, new DbType("clob"));
|
||||
put(DbType.JSONBlob, new DbType("blob"));
|
||||
put(DbType.JSONVarchar, new DbType("varchar", 1000));
|
||||
if (logicalTypes) {
|
||||
// keep it logical for 2 layer DDL generation
|
||||
put(DbType.HSTORE, new DbType("hstore"));
|
||||
put(DbType.JSON, new DbType("json"));
|
||||
put(DbType.JSONB, new DbType("jsonb"));
|
||||
put(DbType.JSONClob, new DbType("jsonclob"));
|
||||
put(DbType.JSONBlob, new DbType("jsonblob"));
|
||||
put(DbType.JSONVarchar, new DbType("jsonvarchar", 1000));
|
||||
|
||||
} else {
|
||||
put(DbType.JSON, JSON_CLOB_PLACEHOLDER); // Postgres maps this to JSON
|
||||
put(DbType.JSONB, JSON_CLOB_PLACEHOLDER); // Postgres maps this to JSONB
|
||||
put(DbType.JSONClob, JSON_CLOB_PLACEHOLDER);
|
||||
put(DbType.JSONBlob, JSON_BLOB_PLACEHOLDER);
|
||||
put(DbType.JSONVarchar, JSON_VARCHAR_PLACEHOLDER);
|
||||
}
|
||||
|
||||
put(Types.LONGVARBINARY, new DbType("longvarbinary"));
|
||||
put(Types.LONGVARCHAR, new DbType("lonvarchar"));
|
||||
@@ -54,24 +129,58 @@ public class DbTypeMap {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the platform specific DbType given the standard sql type name.
|
||||
*/
|
||||
public DbType lookup(String name) {
|
||||
name = name.trim().toUpperCase();
|
||||
Integer typeKey = lookup.get(name);
|
||||
if (typeKey == null) {
|
||||
throw new IllegalArgumentException("Unknown type [" + name + "] - not standard sql type");
|
||||
}
|
||||
// handle JSON types mapped to clob, blob and varchar
|
||||
switch (typeKey) {
|
||||
case DbType.JSONBlob:
|
||||
return get(Types.BLOB);
|
||||
case DbType.JSONClob:
|
||||
return get(Types.CLOB);
|
||||
case DbType.JSONVarchar:
|
||||
return get(Types.VARCHAR);
|
||||
case DbType.JSON:
|
||||
return getJsonType(DbType.JSON);
|
||||
case DbType.JSONB:
|
||||
return getJsonType(DbType.JSONB);
|
||||
default:
|
||||
return get(typeKey);
|
||||
}
|
||||
}
|
||||
|
||||
private DbType getJsonType(int type) {
|
||||
DbType dbType = get(type);
|
||||
if (dbType == JSON_CLOB_PLACEHOLDER) {
|
||||
return get(Types.CLOB);
|
||||
}
|
||||
if (dbType == JSON_BLOB_PLACEHOLDER) {
|
||||
return get(Types.BLOB);
|
||||
}
|
||||
if (dbType == JSON_VARCHAR_PLACEHOLDER) {
|
||||
return get(Types.VARCHAR);
|
||||
}
|
||||
// Postgres has specific type
|
||||
return get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the type for a given JDBC type.
|
||||
*/
|
||||
public void put(int jdbcType, DbType dbType) {
|
||||
typeMap.put(Integer.valueOf(jdbcType), dbType);
|
||||
typeMap.put(jdbcType, dbType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type for a given jdbc type.
|
||||
*/
|
||||
public DbType get(int jdbcType) {
|
||||
|
||||
DbType dbType = typeMap.get(Integer.valueOf(jdbcType));
|
||||
if (dbType == null) {
|
||||
String m = "No DB type for JDBC type " + jdbcType;
|
||||
throw new RuntimeException(m);
|
||||
}
|
||||
|
||||
return dbType;
|
||||
return typeMap.get(jdbcType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
/**
|
||||
* Database view based implementation of DbHistorySupport.
|
||||
* <p>
|
||||
* These implementations have explicit history tables, a view to union the
|
||||
* base table and history table and triggers to maintain the history.
|
||||
* </p>
|
||||
*/
|
||||
public abstract class DbViewHistorySupport implements DbHistorySupport {
|
||||
|
||||
/**
|
||||
* Return false for view based implementations where we append extra 'as of' predicates to the end.
|
||||
*/
|
||||
@Override
|
||||
public boolean isBindWithFromClause() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured view suffix.
|
||||
*
|
||||
* @param asOfViewSuffix the configured view suffix (typically "_with_history").
|
||||
*/
|
||||
@Override
|
||||
public String getAsOfViewSuffix(String asOfViewSuffix) {
|
||||
// just return the configured suffix
|
||||
return asOfViewSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured view suffix (same as getAsOfViewSuffix()).
|
||||
*
|
||||
* @param asOfViewSuffix the configured view suffix (typically "_with_history").
|
||||
*/
|
||||
@Override
|
||||
public String getVersionsBetweenSuffix(String asOfViewSuffix) {
|
||||
// just return the configured asOfViewSuffix (using the view for versions between query)
|
||||
return asOfViewSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return 2 if we have effective start and effective end as 2 columns.
|
||||
* Note that for postgres we can use a single range type so that returns 1.
|
||||
*/
|
||||
@Override
|
||||
public int getBindCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 'as of' predicate clause appended to the end of the normal query predicates.
|
||||
*/
|
||||
@Override
|
||||
public String getAsOfPredicate(String asOfTableAlias, String asOfSysPeriod) {
|
||||
|
||||
// (sys_period_start < ? and (sys_period_end is null or sys_period_end > ?));
|
||||
return "(" + asOfTableAlias + "." + asOfSysPeriod + "_start" + " < ? and (" + asOfTableAlias + "." + asOfSysPeriod + "_end" + " is null or " + asOfTableAlias + "." + asOfSysPeriod + "_end" + " > ?))";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the lower bound column prepended with the table alias.
|
||||
*
|
||||
* @param tableAlias the table alias
|
||||
* @param sysPeriod the name of the sys_period column
|
||||
*/
|
||||
@Override
|
||||
public String getSysPeriodLower(String tableAlias, String sysPeriod) {
|
||||
return tableAlias + "." + sysPeriod + "_start";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the upper bound column prepended with the table alias.
|
||||
*
|
||||
* @param tableAlias the table alias
|
||||
* @param sysPeriod the name of the sys_period column
|
||||
*/
|
||||
@Override
|
||||
public String getSysPeriodUpper(String tableAlias, String sysPeriod) {
|
||||
return tableAlias + "." + sysPeriod + "_end";
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.H2Ddl;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -13,9 +14,7 @@ public class H2Platform extends DatabasePlatform {
|
||||
super();
|
||||
this.name = "h2";
|
||||
this.dbEncrypt = new H2DbEncrypt();
|
||||
// like ? escape'' not working in the latest version H2 so just using no
|
||||
// escape clause for now noting that backslash is an escape char for like in H2
|
||||
this.likeClause = "like ?";
|
||||
this.platformDdl = new H2Ddl(this.dbTypeMap, dbIdentity);
|
||||
|
||||
// only support getGeneratedKeys with non-batch JDBC
|
||||
// so generally use SEQUENCE instead of IDENTITY for H2
|
||||
@@ -24,16 +23,12 @@ public class H2Platform extends DatabasePlatform {
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
// like ? escape'' not working in the latest version H2 so just using no
|
||||
// escape clause for now noting that backslash is an escape char for like in H2
|
||||
this.likeClause = "like ?";
|
||||
|
||||
// H2 data types match default JDBC types
|
||||
// so no changes to dbTypeMap required
|
||||
|
||||
this.dbDdlSyntax.setDropIfExists("if exists");
|
||||
this.dbDdlSyntax.setDisableReferentialIntegrity("SET REFERENTIAL_INTEGRITY FALSE");
|
||||
this.dbDdlSyntax.setEnableReferentialIntegrity("SET REFERENTIAL_INTEGRITY TRUE");
|
||||
this.dbDdlSyntax.setForeignKeySuffix("on delete restrict on update restrict");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.HsqldbDdl;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
@@ -14,32 +15,18 @@ public class HsqldbPlatform extends DatabasePlatform {
|
||||
super();
|
||||
this.name = "hsqldb";
|
||||
this.dbEncrypt = new H2DbEncrypt();
|
||||
this.platformDdl = new HsqldbDdl(dbTypeMap, dbIdentity);
|
||||
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
// H2 data types match default JDBC types
|
||||
// so no changes to dbTypeMap required
|
||||
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
|
||||
|
||||
this.dbDdlSyntax.setDropIfExists("if exists");
|
||||
this.dbDdlSyntax.setDisableReferentialIntegrity("SET DATABASE REFERENTIAL INTEGRITY FALSE");
|
||||
this.dbDdlSyntax.setEnableReferentialIntegrity("SET DATABASE REFERENTIAL INTEGRITY TRUE");
|
||||
this.dbDdlSyntax.setForeignKeySuffix("on delete restrict on update restrict");
|
||||
this.dbDdlSyntax.setIdentity("GENERATED BY DEFAULT AS IDENTITY (START WITH 1) ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a H2 specific sequence IdGenerator that supports batch fetching
|
||||
* sequence values.
|
||||
*/
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be,
|
||||
DataSource ds, String seqName, int batchSize) {
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
|
||||
return new H2SequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ public interface IdGenerator {
|
||||
/**
|
||||
* The name of the default UUID generator.
|
||||
*/
|
||||
static final String AUTO_UUID = "auto.uuid";
|
||||
String AUTO_UUID = "auto.uuid";
|
||||
|
||||
/**
|
||||
* Return the name of the IdGenerator. For sequences this is the sequence
|
||||
|
||||
@@ -26,6 +26,15 @@ public enum IdType {
|
||||
* id properties of type UUID.
|
||||
* </p>
|
||||
*/
|
||||
GENERATOR;
|
||||
GENERATOR,
|
||||
|
||||
/**
|
||||
* Expected that the identity is externally set (for example a ISO code for
|
||||
* country or currency or a user defined code for lookup tables).
|
||||
* <p>
|
||||
* Used when the key is a compound key or lookup table code.
|
||||
* </p>
|
||||
*/
|
||||
EXTERNAL
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.MsSqlServerDdl;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
@@ -21,8 +23,9 @@ public class MsSqlServer2005Platform extends DatabasePlatform {
|
||||
// due to lack of support for getGeneratedKeys in batch mode
|
||||
this.disallowBatchOnCascade = true;
|
||||
this.idInExpandedForm = true;
|
||||
this.selectCountWithAlias = true;
|
||||
this.sqlLimiter = new MsSqlServer2005SqlLimiter();
|
||||
this.dbDdlSyntax = new MsDdlSyntax();
|
||||
this.platformDdl = new MsSqlServerDdl(dbTypeMap, dbIdentity);
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
@@ -44,35 +47,9 @@ public class MsSqlServer2005Platform extends DatabasePlatform {
|
||||
dbTypeMap.put(Types.LONGVARBINARY, new DbType("image"));
|
||||
dbTypeMap.put(Types.LONGVARCHAR, new DbType("text"));
|
||||
|
||||
dbTypeMap.put(Types.DATE, new DbType("datetime"));
|
||||
dbTypeMap.put(Types.TIME, new DbType("datetime"));
|
||||
dbTypeMap.put(Types.TIMESTAMP, new DbType("datetime"));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* MS SQL Server specific DDL Syntax.
|
||||
*/
|
||||
public class MsDdlSyntax extends DbDdlSyntax {
|
||||
|
||||
MsDdlSyntax() {
|
||||
this.identity = "identity(1,1)";
|
||||
this.dropKeyConstraints = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return some DDL to disable constraints on the given table.
|
||||
*/
|
||||
public String dropKeyConstraintPrefix(String tableName, String fkName) {
|
||||
return "IF OBJECT_ID('"+fkName+"', 'F') IS NOT NULL";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return prefix text that goes before drop table.
|
||||
*/
|
||||
public String dropTablePrefix(String tableName) {
|
||||
return "IF OBJECT_ID('"+tableName+"', 'U') IS NOT NULL ";
|
||||
}
|
||||
dbTypeMap.put(Types.DATE, new DbType("date"));
|
||||
dbTypeMap.put(Types.TIME, new DbType("time"));
|
||||
dbTypeMap.put(Types.TIMESTAMP, new DbType("datetime2"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,11 @@ public class MsSqlServer2005SqlLimiter implements SqlLimiter {
|
||||
|
||||
if (firstRow < 1) {
|
||||
// just use top n
|
||||
sb.append(" select top ").append(lastRow).append(" ");
|
||||
sb.append(" select ");
|
||||
if (request.isDistinct()) {
|
||||
sb.append("distinct ");
|
||||
}
|
||||
sb.append(" top ").append(lastRow).append(" ");
|
||||
sb.append(request.getDbSql());
|
||||
return new SqlLimitResponse(sb.toString(), false);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
/**
|
||||
* Runtime support for @History with MySql.
|
||||
*/
|
||||
public class MySqlHistorySupport extends DbViewHistorySupport {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.MySqlDdl;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
@@ -24,6 +25,8 @@ public class MySqlPlatform extends DatabasePlatform {
|
||||
this.likeClause = "like ? escape''";
|
||||
this.selectCountWithAlias = true;
|
||||
this.dbEncrypt = new MySqlDbEncrypt();
|
||||
this.platformDdl = new MySqlDdl(this.dbTypeMap, this.dbIdentity);
|
||||
this.historySupport = new MySqlHistorySupport();
|
||||
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
@@ -43,12 +46,6 @@ public class MySqlPlatform extends DatabasePlatform {
|
||||
dbTypeMap.put(Types.BLOB, new MySqlBlob());
|
||||
dbTypeMap.put(Types.BINARY, new DbType("binary", 255));
|
||||
dbTypeMap.put(Types.VARBINARY, new DbType("varbinary", 255));
|
||||
|
||||
dbDdlSyntax.setMaxConstraintNameLength(64);
|
||||
dbDdlSyntax.setDisableReferentialIntegrity("SET FOREIGN_KEY_CHECKS=0");
|
||||
dbDdlSyntax.setEnableReferentialIntegrity("SET FOREIGN_KEY_CHECKS=1");
|
||||
dbDdlSyntax.setForeignKeySuffix("on delete restrict on update restrict");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
/**
|
||||
* Since Ebean v2.2.0 Oracle9 is no different from Oracle10.
|
||||
* <p>
|
||||
* This will be removed in the future.
|
||||
* </p>
|
||||
*/
|
||||
public class Oracle9Platform extends Oracle10Platform {
|
||||
|
||||
public Oracle9Platform() {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
||||
+7
-7
@@ -43,25 +43,25 @@ package com.avaje.ebean.config.dbplatform;
|
||||
*
|
||||
* @author rbygrave
|
||||
*/
|
||||
public class Oracle10DbEncrypt extends AbstractDbEncrypt {
|
||||
public class OracleDbEncrypt extends AbstractDbEncrypt {
|
||||
|
||||
/**
|
||||
* Constructs the Oracle10DbEncrypt with default encrypt and decrypt stored procedures.
|
||||
*/
|
||||
public Oracle10DbEncrypt() {
|
||||
public OracleDbEncrypt() {
|
||||
this("eb_encrypt", "eb_decrypt");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the Oracle10DbEncrypt specifying encrypt and decrypt stored procedures.
|
||||
*
|
||||
* @param encryptfunction the encrypt stored procedure
|
||||
* @param decryptfunction the decrypt stored procedure
|
||||
* @param encryptFunction the encrypt stored procedure
|
||||
* @param decryptFunction the decrypt stored procedure
|
||||
*/
|
||||
public Oracle10DbEncrypt(String encryptfunction, String decryptfunction) {
|
||||
public OracleDbEncrypt(String encryptFunction, String decryptFunction) {
|
||||
|
||||
this.varcharEncryptFunction = new OraVarcharFunction(encryptfunction, decryptfunction);
|
||||
this.dateEncryptFunction = new OraDateFunction(encryptfunction, decryptfunction);
|
||||
this.varcharEncryptFunction = new OraVarcharFunction(encryptFunction, decryptFunction);
|
||||
this.dateEncryptFunction = new OraDateFunction(encryptFunction, decryptFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
/**
|
||||
* Oracle Total recall based history support.
|
||||
*/
|
||||
public class OracleDbHistorySupport extends DbStandardHistorySupport {
|
||||
|
||||
/**
|
||||
* Return the ' as of timestamp ?' clause appended after the table name.
|
||||
*/
|
||||
@Override
|
||||
public String getAsOfViewSuffix(String asOfViewSuffix) {
|
||||
return " as of TIMESTAMP ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersionsBetweenSuffix(String asOfViewSuffix) {
|
||||
return " versions between timestamp ? and ?";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Oracle specific effective start column.
|
||||
*/
|
||||
@Override
|
||||
public String getSysPeriodLower(String tableAlias, String sysPeriod) {
|
||||
return "versions_starttime";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Oracle specific effective end column.
|
||||
*/
|
||||
@Override
|
||||
public String getSysPeriodUpper(String tableAlias, String sysPeriod) {
|
||||
return "versions_endtime";
|
||||
}
|
||||
|
||||
}
|
||||
+9
-9
@@ -1,6 +1,7 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.Oracle10Ddl;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
@@ -8,15 +9,19 @@ import java.sql.Types;
|
||||
/**
|
||||
* Oracle10 and greater specific platform.
|
||||
*/
|
||||
public class Oracle10Platform extends DatabasePlatform {
|
||||
public class OraclePlatform extends DatabasePlatform {
|
||||
|
||||
public Oracle10Platform() {
|
||||
public OraclePlatform() {
|
||||
super();
|
||||
this.name = "oracle";
|
||||
this.maxTableNameLength = 30;
|
||||
this.maxConstraintNameLength = 30;
|
||||
// OnQueryOnly.CLOSE as a performance optimisation on Oracle
|
||||
this.onQueryOnly = OnQueryOnly.CLOSE;
|
||||
this.dbEncrypt = new Oracle10DbEncrypt();
|
||||
this.dbEncrypt = new OracleDbEncrypt();
|
||||
this.sqlLimiter = new RownumSqlLimiter();
|
||||
this.platformDdl = new Oracle10Ddl(this.dbTypeMap, this.dbIdentity);
|
||||
this.historySupport = new OracleDbHistorySupport();
|
||||
|
||||
// Not using getGeneratedKeys as instead we will
|
||||
// batch load sequences which enables JDBC batch execution
|
||||
@@ -48,15 +53,10 @@ public class Oracle10Platform extends DatabasePlatform {
|
||||
dbTypeMap.put(Types.BINARY, new DbType("raw", 255));
|
||||
|
||||
dbTypeMap.put(Types.TIME, new DbType("timestamp"));
|
||||
|
||||
dbDdlSyntax.setDropTableCascade("cascade constraints purge");
|
||||
dbDdlSyntax.setIdentity(null);
|
||||
dbDdlSyntax.setMaxConstraintNameLength(30);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
|
||||
return new OracleSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
}
|
||||
@@ -1,67 +1,20 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Postgres v8.3 specific platform.
|
||||
* <p>
|
||||
* No support for getGeneratedKeys.
|
||||
* </p>
|
||||
*/
|
||||
public class Postgres8Platform extends DatabasePlatform {
|
||||
public class Postgres8Platform extends PostgresPlatform {
|
||||
|
||||
public Postgres8Platform() {
|
||||
super();
|
||||
this.name = "postgres";
|
||||
this.selectCountWithAlias = true;
|
||||
this.blobDbType = Types.LONGVARBINARY;
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
this.dbIdentity.setIdType(IdType.SEQUENCE);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
this.columnAliasPrefix = "as c";
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
// dbTypeMap.put(Types.BOOLEAN, new DbType("bit default 0"));
|
||||
|
||||
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
|
||||
dbTypeMap.put(Types.DOUBLE, new DbType("float"));
|
||||
dbTypeMap.put(Types.TINYINT, new DbType("smallint"));
|
||||
dbTypeMap.put(Types.DECIMAL, new DbType("decimal", 38));
|
||||
|
||||
dbTypeMap.put(Types.BINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.VARBINARY, new DbType("bytea", false));
|
||||
|
||||
dbTypeMap.put(Types.BLOB, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.CLOB, new DbType("text"));
|
||||
dbTypeMap.put(Types.LONGVARBINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.LONGVARCHAR, new DbType("text"));
|
||||
|
||||
dbDdlSyntax.setDropTableCascade("cascade");
|
||||
dbDdlSyntax.setDropIfExists("if exists");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Postgres specific sequence IdGenerator.
|
||||
*/
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
|
||||
return new PostgresSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String withForUpdate(String sql) {
|
||||
return sql + " for update";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
public class PostgresDdlSyntax extends DbDdlSyntax {
|
||||
|
||||
/**
|
||||
* Map bigint, integer and smallint into their equivilent serial types.
|
||||
*/
|
||||
@Override
|
||||
public String getIdentityColumnDefn(String columnDefn) {
|
||||
|
||||
//smallserial, serial and bigserial
|
||||
if ("bigint".equalsIgnoreCase(columnDefn)) {
|
||||
return "bigserial";
|
||||
}
|
||||
if ("integer".equalsIgnoreCase(columnDefn)) {
|
||||
return "serial";
|
||||
}
|
||||
if ("smallint".equalsIgnoreCase(columnDefn)) {
|
||||
return "smallserial";
|
||||
}
|
||||
|
||||
return columnDefn;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
/**
|
||||
* Postgres support for history features.
|
||||
*/
|
||||
public class PostgresHistorySupport extends DbViewHistorySupport {
|
||||
|
||||
/**
|
||||
* Return 1 as we are using the postgres range type and hence don't need 2 bind variables.
|
||||
*/
|
||||
@Override
|
||||
public int getBindCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return the 'as of' predicate for a given table alias.
|
||||
* <p>
|
||||
* Each @History entity involved in the query has this predicate added using the related table alias.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public String getAsOfPredicate(String asOfTableAlias, String asOfSysPeriod) {
|
||||
|
||||
// for Postgres we are using the 'timestamp with timezone range' data type
|
||||
// as our sys_period column so hence the predicate below
|
||||
return asOfTableAlias + "." + asOfSysPeriod + " @> ?::timestamptz";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSysPeriodLower(String tableAlias, String sysPeriod) {
|
||||
return "lower(" + tableAlias + "." + sysPeriod + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSysPeriodUpper(String tableAlias, String sysPeriod) {
|
||||
return "upper(" + tableAlias + "." + sysPeriod + ")";
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlHandler;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.PostgresDdl;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
@@ -16,28 +19,32 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
public PostgresPlatform() {
|
||||
super();
|
||||
this.name = "postgres";
|
||||
|
||||
// OnQueryOnly.CLOSE as a performance optimisation on Postgres
|
||||
this.onQueryOnly = OnQueryOnly.CLOSE;
|
||||
this.likeClause = "like ? escape''";
|
||||
|
||||
this.dbDdlSyntax = new PostgresDdlSyntax();
|
||||
|
||||
|
||||
this.selectCountWithAlias = true;
|
||||
this.blobDbType = Types.LONGVARBINARY;
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
this.historySupport = new PostgresHistorySupport();
|
||||
this.platformDdl = new PostgresDdl(this.dbTypeMap, this.dbIdentity);
|
||||
|
||||
// Use Identity and getGeneratedKeys
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
|
||||
this.columnAliasPrefix = "as c";
|
||||
//this.columnAliasPrefix = "as c";
|
||||
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
DbType dbTypeText = new DbType("text");
|
||||
DbType dbBytea = new DbType("bytea", false);
|
||||
|
||||
dbTypeMap.put(DbType.HSTORE, new DbType("hstore"));
|
||||
dbTypeMap.put(DbType.JSON, new DbType("json"));
|
||||
dbTypeMap.put(DbType.JSONB, new DbType("jsonb"));
|
||||
@@ -47,25 +54,28 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
dbTypeMap.put(Types.TINYINT, new DbType("smallint"));
|
||||
dbTypeMap.put(Types.DECIMAL, new DbType("decimal", 38));
|
||||
|
||||
dbTypeMap.put(Types.BINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.VARBINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.BINARY, dbBytea);
|
||||
dbTypeMap.put(Types.VARBINARY, dbBytea);
|
||||
|
||||
dbTypeMap.put(Types.BLOB, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.CLOB, new DbType("text"));
|
||||
dbTypeMap.put(Types.LONGVARBINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.LONGVARCHAR, new DbType("text"));
|
||||
dbTypeMap.put(Types.BLOB, dbBytea);
|
||||
dbTypeMap.put(Types.CLOB, dbTypeText);
|
||||
dbTypeMap.put(Types.LONGVARBINARY, dbBytea);
|
||||
dbTypeMap.put(Types.LONGVARCHAR, dbTypeText);
|
||||
|
||||
dbDdlSyntax.setDropTableCascade("cascade");
|
||||
dbDdlSyntax.setDropIfExists("if exists");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a DdlHandler instance for generating DDL for the specific platform.
|
||||
*/
|
||||
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
|
||||
return this.platformDdl.createDdlHandler(serverConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Postgres specific sequence IdGenerator.
|
||||
*/
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
|
||||
return new PostgresSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.SQLiteDdl;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
|
||||
public class SQLitePlatform extends DatabasePlatform {
|
||||
|
||||
public SQLitePlatform() {
|
||||
super();
|
||||
this.name = "sqlite";
|
||||
this.platformDdl = new SQLiteDdl(dbTypeMap, dbIdentity);
|
||||
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
this.dbIdentity.setSupportsSequence(false);
|
||||
this.dbIdentity.setSelectLastInsertedIdTemplate("select last_insert_rowid()");
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
this.booleanDbType = Types.INTEGER;
|
||||
|
||||
@@ -24,25 +22,7 @@ public class SQLitePlatform extends DatabasePlatform {
|
||||
dbTypeMap.put(Types.BOOLEAN, new DbType("int default 0"));
|
||||
dbTypeMap.put(Types.BIGINT, new DbType("integer"));
|
||||
dbTypeMap.put(Types.SMALLINT, new DbType("integer"));
|
||||
|
||||
dbDdlSyntax.setInlinePrimaryKeyConstraint(true);
|
||||
|
||||
// AutoIncrement goes the primary key declaration
|
||||
dbDdlSyntax.setIdentity("");
|
||||
dbDdlSyntax.setIdentitySuffix("AUTOINCREMENT");
|
||||
|
||||
dbDdlSyntax.setDisableReferentialIntegrity("PRAGMA foreign_keys = OFF");
|
||||
dbDdlSyntax.setEnableReferentialIntegrity("PRAGMA foreign_keys = ON");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return null in case there is a sequence annotation.
|
||||
*/
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be,
|
||||
DataSource ds, String seqName, int batchSize) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public abstract class SequenceIdGenerator implements IdGenerator {
|
||||
|
||||
protected final ArrayList<Long> idList = new ArrayList<Long>(50);
|
||||
|
||||
protected int batchSize;
|
||||
protected final int batchSize;
|
||||
|
||||
protected int currentlyBackgroundLoading;
|
||||
|
||||
|
||||
@@ -61,8 +61,7 @@ public class SimpleSequenceIdGenerator implements IdGenerator {
|
||||
pstmt = c.prepareStatement(sql);
|
||||
rset = pstmt.executeQuery();
|
||||
if (rset.next()) {
|
||||
int val = rset.getInt(1);
|
||||
return Integer.valueOf(val);
|
||||
return rset.getInt(1);
|
||||
} else {
|
||||
String m = "Always expecting 1 row from " + sql;
|
||||
throw new PersistenceException(m);
|
||||
|
||||
@@ -11,7 +11,6 @@ import java.sql.Types;
|
||||
* </ul>
|
||||
* </p>
|
||||
*/
|
||||
|
||||
public class SqlAnywherePlatform extends DatabasePlatform {
|
||||
|
||||
public SqlAnywherePlatform() {
|
||||
|
||||
@@ -11,12 +11,7 @@ public interface SqlLimiter {
|
||||
* Note that this is removed for logging sql to the transaction log.
|
||||
* </p>
|
||||
*/
|
||||
static final char NEW_LINE = '\n';
|
||||
|
||||
/**
|
||||
* The carriage return character.
|
||||
*/
|
||||
static final char CARRIAGE_RETURN = '\r';
|
||||
char NEW_LINE = '\n';
|
||||
|
||||
/**
|
||||
* Add the SQL limiting statements around the query.
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
package com.avaje.ebean.dbmigration;
|
||||
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.config.DbConstraintNaming;
|
||||
import com.avaje.ebean.config.DbMigrationConfig;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.DB2Platform;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebean.config.dbplatform.DbPlatformName;
|
||||
import com.avaje.ebean.config.dbplatform.H2Platform;
|
||||
import com.avaje.ebean.config.dbplatform.MsSqlServer2005Platform;
|
||||
import com.avaje.ebean.config.dbplatform.MySqlPlatform;
|
||||
import com.avaje.ebean.config.dbplatform.OraclePlatform;
|
||||
import com.avaje.ebean.config.dbplatform.PostgresPlatform;
|
||||
import com.avaje.ebean.config.dbplatform.SQLitePlatform;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlWrite;
|
||||
import com.avaje.ebean.dbmigration.migration.Migration;
|
||||
import com.avaje.ebean.dbmigration.migrationreader.MigrationXmlWriter;
|
||||
import com.avaje.ebean.dbmigration.model.CurrentModel;
|
||||
import com.avaje.ebean.dbmigration.model.MConfiguration;
|
||||
import com.avaje.ebean.dbmigration.model.MigrationModel;
|
||||
import com.avaje.ebean.dbmigration.model.ModelContainer;
|
||||
import com.avaje.ebean.dbmigration.model.PlatformDdlWriter;
|
||||
import com.avaje.ebean.dbmigration.model.ModelDiff;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Generates DB Migration xml and sql scripts.
|
||||
* <p>
|
||||
* Reads the prior migrations and compares with the current model of the EbeanServer
|
||||
* and generates a migration 'diff' in the form of xml document with the logical schema
|
||||
* changes and a series of sql scripts to apply, rollback the applied changes if necessary
|
||||
* and drop objects (drop tables, drop columns).
|
||||
* </p>
|
||||
* <p>
|
||||
* This does not run the migration or ddl scripts but just generates them.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* DbMigration migration = new DbMigration();
|
||||
* migration.setPathToResources("src/main/resources");
|
||||
* migration.setPlatform(DbPlatformName.ORACLE);
|
||||
*
|
||||
* migration.generateMigration();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public class DbMigration {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DbMigration.class);
|
||||
|
||||
protected SpiEbeanServer server;
|
||||
|
||||
protected DbMigrationConfig migrationConfig;
|
||||
|
||||
protected String pathToResources = "src/main/resources";
|
||||
|
||||
protected DatabasePlatform databasePlatform;
|
||||
|
||||
protected List<Pair> platforms = new ArrayList<Pair>();
|
||||
|
||||
protected ServerConfig serverConfig;
|
||||
|
||||
protected DbConstraintNaming constraintNaming;
|
||||
|
||||
public DbMigration() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path from the current working directory to the application resources.
|
||||
*
|
||||
* This defaults to maven style 'src/main/resources'.
|
||||
*/
|
||||
public void setPathToResources(String pathToResources) {
|
||||
this.pathToResources = pathToResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the server to use to determine the current model.
|
||||
* Typically this is not called explicitly.
|
||||
*/
|
||||
public void setServer(EbeanServer ebeanServer) {
|
||||
this.server = (SpiEbeanServer) ebeanServer;
|
||||
setServerConfig(server.getServerConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the serverConfig to use. Typically this is not called explicitly.
|
||||
*/
|
||||
public void setServerConfig(ServerConfig config) {
|
||||
if (this.serverConfig == null) {
|
||||
this.serverConfig = config;
|
||||
}
|
||||
if (migrationConfig == null) {
|
||||
this.migrationConfig = serverConfig.getMigrationConfig();
|
||||
}
|
||||
if (constraintNaming == null) {
|
||||
this.constraintNaming = serverConfig.getConstraintNaming();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the specific platform to generate DDL for.
|
||||
* <p>
|
||||
* If not set this defaults to the platform of the default server.
|
||||
* </p>
|
||||
*/
|
||||
public void setPlatform(DbPlatformName platform) {
|
||||
setPlatform(getPlatform(platform));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the specific platform to generate DDL for.
|
||||
* <p>
|
||||
* If not set this defaults to the platform of the default server.
|
||||
* </p>
|
||||
*/
|
||||
public void setPlatform(DatabasePlatform databasePlatform) {
|
||||
this.databasePlatform = databasePlatform;
|
||||
DbOffline.setPlatform(databasePlatform.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an additional platform to write the migration DDL.
|
||||
* <p>
|
||||
* Use this when you want to generate sql scripts for multiple database platforms
|
||||
* from the migration (e.g. generate migration sql for MySql, Postgres and Oracle).
|
||||
* </p>
|
||||
*/
|
||||
public void addPlatform(DbPlatformName platform, String prefix) {
|
||||
if (!prefix.endsWith("-")) {
|
||||
prefix += "-";
|
||||
}
|
||||
platforms.add(new Pair(getPlatform(platform), prefix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the next migration xml file and associated apply and rollback sql scripts.
|
||||
* <p>
|
||||
* This does not run the migration or ddl scripts but just generates them.
|
||||
* </p>
|
||||
* <h3>Example: Run for a single specific platform</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* DbMigration migration = new DbMigration();
|
||||
* migration.setPathToResources("src/main/resources");
|
||||
* migration.setPlatform(DbPlatformName.ORACLE);
|
||||
*
|
||||
* migration.generateMigration();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Example: Run migration generating DDL for multiple platforms</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* DbMigration migration = new DbMigration();
|
||||
* migration.setPathToResources("src/main/resources");
|
||||
*
|
||||
* migration.addPlatform(DbPlatformName.POSTGRES, "pg");
|
||||
* migration.addPlatform(DbPlatformName.MYSQL, "mysql");
|
||||
* migration.addPlatform(DbPlatformName.ORACLE, "mysql");
|
||||
*
|
||||
* migration.generateMigration();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public void generateMigration() throws IOException {
|
||||
|
||||
// use this flag to stop other plugins like full DDL generation
|
||||
DbOffline.setRunningMigration();
|
||||
|
||||
setDefaults();
|
||||
|
||||
try {
|
||||
MigrationModel migrationModel = new MigrationModel(migrationConfig.getResourcePath());
|
||||
ModelContainer migrated = migrationModel.read();
|
||||
int nextMajorVersion = migrationModel.getNextMajorVersion();
|
||||
|
||||
logger.info("next migration version {}", nextMajorVersion);
|
||||
|
||||
CurrentModel currentModel = new CurrentModel(server, constraintNaming);
|
||||
ModelContainer current = currentModel.read();
|
||||
|
||||
ModelDiff diff = new ModelDiff(migrated);
|
||||
diff.compareTo(current);
|
||||
|
||||
if (diff.isEmpty()) {
|
||||
logger.info("no changes detected - no migration written");
|
||||
return;
|
||||
}
|
||||
|
||||
// there were actually changes to write
|
||||
Migration dbMigration = diff.getMigration();
|
||||
|
||||
File writePath = getWritePath();
|
||||
logger.info("migration writing version {} to {}", nextMajorVersion, writePath.getAbsolutePath());
|
||||
writeMigrationXml(dbMigration, writePath, nextMajorVersion);
|
||||
|
||||
if (databasePlatform != null) {
|
||||
// writer needs the current model to provide table/column details for
|
||||
// history ddl generation (triggers, history tables etc)
|
||||
DdlWrite write = new DdlWrite(new MConfiguration(), currentModel.read());
|
||||
PlatformDdlWriter writer = new PlatformDdlWriter(databasePlatform, serverConfig);
|
||||
writer.processMigration(dbMigration, write, writePath, nextMajorVersion);
|
||||
}
|
||||
|
||||
writeExtraPlatformDdl(nextMajorVersion, currentModel, dbMigration, writePath);
|
||||
|
||||
} finally {
|
||||
DbOffline.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write any extra platform ddl.
|
||||
*/
|
||||
protected void writeExtraPlatformDdl(int nextMajorVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
|
||||
|
||||
for (Pair pair : platforms) {
|
||||
DdlWrite platformBuffer = new DdlWrite(new MConfiguration(), currentModel.read());
|
||||
|
||||
PlatformDdlWriter platformWriter = new PlatformDdlWriter(pair.platform, serverConfig, pair.prefix);
|
||||
platformWriter.processMigration(dbMigration, platformBuffer, writePath, nextMajorVersion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the migration xml.
|
||||
*/
|
||||
protected void writeMigrationXml(Migration dbMigration, File resourcePath, int migrationVersion) {
|
||||
|
||||
File file = new File(resourcePath, "v"+migrationVersion+".0.xml");
|
||||
MigrationXmlWriter xmlWriter = new MigrationXmlWriter();
|
||||
xmlWriter.write(dbMigration, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default server and platform if necessary.
|
||||
*/
|
||||
protected void setDefaults() {
|
||||
if (server == null) {
|
||||
setServer(Ebean.getDefaultServer());
|
||||
}
|
||||
if (databasePlatform == null && platforms.isEmpty()) {
|
||||
// not explicitly set not set a list of platforms so
|
||||
// default to the platform of the default server
|
||||
databasePlatform = server.getDatabasePlatform();
|
||||
logger.debug("set platform to {}", databasePlatform.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file path to write the xml and sql to.
|
||||
*/
|
||||
protected File getWritePath() {
|
||||
|
||||
// path to src/main/resources in typical maven project
|
||||
File resourceRootDir = new File(pathToResources);
|
||||
|
||||
String resourcePath = migrationConfig.getResourcePath();
|
||||
|
||||
// expect to be a path to something like - src/main/resources/dbmigration/myapp
|
||||
File path = new File(resourceRootDir, resourcePath);
|
||||
if (!path.exists()) {
|
||||
if (!path.mkdirs()) {
|
||||
logger.debug("Unable to ensure migration directory exists at {}", path.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DatabasePlatform given the platform key.
|
||||
*/
|
||||
protected DatabasePlatform getPlatform(DbPlatformName platform) {
|
||||
switch (platform) {
|
||||
case H2:
|
||||
return new H2Platform();
|
||||
case POSTGRES:
|
||||
return new PostgresPlatform();
|
||||
case MYSQL:
|
||||
return new MySqlPlatform();
|
||||
case ORACLE:
|
||||
return new OraclePlatform();
|
||||
case SQLSERVER:
|
||||
return new MsSqlServer2005Platform();
|
||||
case DB2:
|
||||
return new DB2Platform();
|
||||
case SQLITE:
|
||||
return new SQLitePlatform();
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Platform missing? " + platform);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds a platform and prefix. Used to generate multiple platform specific DDL
|
||||
* for a single migration.
|
||||
*/
|
||||
public static class Pair {
|
||||
|
||||
/**
|
||||
* The platform to generate the DDL for.
|
||||
*/
|
||||
public final DatabasePlatform platform;
|
||||
|
||||
/**
|
||||
* A prefix included into the file/resource names indicating the platform.
|
||||
*/
|
||||
public final String prefix;
|
||||
|
||||
public Pair(DatabasePlatform platform, String prefix) {
|
||||
this.platform = platform;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.avaje.ebean.dbmigration;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbPlatformName;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Helper to indicate that an EbeanServer should come up offline
|
||||
* typically for DDL generation purposes.
|
||||
*/
|
||||
public class DbOffline {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DbOffline.class);
|
||||
|
||||
private static final String KEY = "ebean.dboffline";
|
||||
|
||||
private static boolean runningMigration;
|
||||
|
||||
/**
|
||||
* Set the platform to use when creating the next EbeanServer instance.
|
||||
*/
|
||||
public static void setPlatform(DbPlatformName dbPlatform) {
|
||||
System.setProperty(KEY, dbPlatform.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the platform to use when creating the next EbeanServer instance.
|
||||
*/
|
||||
public static void setPlatform(String platformName) {
|
||||
System.setProperty(KEY, platformName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the platform to use when creating the next EbeanServer instance.
|
||||
*/
|
||||
public static String getPlatform() {
|
||||
return System.getProperty(KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bring up the next EbeanServer instance using the H2 platform.
|
||||
*/
|
||||
public static void asH2() {
|
||||
setPlatform(DbPlatformName.H2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the offline platform has been set.
|
||||
*/
|
||||
public static boolean isSet() {
|
||||
return getPlatform() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the migration is running. This typically means don't run the
|
||||
* plugins like full DDL generation.
|
||||
*/
|
||||
public static boolean isRunningMigration() {
|
||||
return runningMigration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the migration is running is order to stop other plugins
|
||||
* like the full DDL generation from executing.
|
||||
*/
|
||||
public static void setRunningMigration() {
|
||||
runningMigration = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the offline platform and runningMigration flag.
|
||||
*/
|
||||
public static void reset() {
|
||||
runningMigration = false;
|
||||
System.clearProperty(KEY);
|
||||
logger.debug("reset");
|
||||
}
|
||||
|
||||
}
|
||||
+37
-71
@@ -1,5 +1,14 @@
|
||||
package com.avaje.ebeaninternal.server.ddl;
|
||||
package com.avaje.ebean.dbmigration;
|
||||
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.dbmigration.model.CurrentModel;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanPlugin;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
@@ -13,18 +22,6 @@ import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebean.config.NamingConvention;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanPlugin;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
|
||||
/**
|
||||
* Controls the generation of DDL and potentially runs the resulting scripts.
|
||||
*/
|
||||
@@ -34,24 +31,17 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
|
||||
private SpiEbeanServer server;
|
||||
|
||||
private DatabasePlatform dbPlatform;
|
||||
|
||||
private int summaryLength = 80;
|
||||
|
||||
private boolean generateDdl;
|
||||
private boolean runDdl;
|
||||
|
||||
private CurrentModel currentModel;
|
||||
private String dropContent;
|
||||
private String createContent;
|
||||
|
||||
private NamingConvention namingConvention;
|
||||
|
||||
public void setup(SpiEbeanServer server, DatabasePlatform dbPlatform, ServerConfig serverConfig) {
|
||||
public void setup(SpiEbeanServer server, ServerConfig serverConfig) {
|
||||
this.server = server;
|
||||
this.dbPlatform = dbPlatform;
|
||||
this.generateDdl = serverConfig.isDdlGenerate();
|
||||
this.runDdl = serverConfig.isDdlRun();
|
||||
this.namingConvention = serverConfig.getNamingConvention();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,10 +93,8 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
try {
|
||||
String c = generateDropDdl();
|
||||
writeFile(dropFile, c);
|
||||
|
||||
} catch (IOException e) {
|
||||
String msg = "Error generating Drop DDL";
|
||||
throw new PersistenceException(msg, e);
|
||||
throw new PersistenceException("Error generating Drop DDL", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,65 +103,44 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
try {
|
||||
String c = generateCreateDdl();
|
||||
writeFile(createFile, c);
|
||||
|
||||
} catch (IOException e) {
|
||||
String msg = "Error generating Create DDL";
|
||||
throw new PersistenceException(msg, e);
|
||||
throw new PersistenceException("Error generating Create DDL", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String generateDropDdl() {
|
||||
|
||||
DdlGenContext ctx = createContext();
|
||||
|
||||
if (ctx.getDdlSyntax().isDropKeyConstraints()) {
|
||||
// generate drop foreign key constraint statements (sql server joy)
|
||||
AddForeignKeysVisitor fkeys = new AddForeignKeysVisitor(false, ctx);
|
||||
VisitorUtil.visit(server, fkeys);
|
||||
ctx.writeNewLine();
|
||||
try {
|
||||
dropContent = currentModel().getDropDdl();
|
||||
return dropContent;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
DropTableVisitor drop = new DropTableVisitor(ctx);
|
||||
VisitorUtil.visit(server, drop);
|
||||
|
||||
DropSequenceVisitor dropSequence = new DropSequenceVisitor(ctx);
|
||||
VisitorUtil.visit(server, dropSequence);
|
||||
|
||||
ctx.flush();
|
||||
dropContent = ctx.getContent();
|
||||
return dropContent;
|
||||
}
|
||||
|
||||
public String generateCreateDdl() {
|
||||
|
||||
DdlGenContext ctx = createContext();
|
||||
CreateTableVisitor create = new CreateTableVisitor(ctx);
|
||||
VisitorUtil.visit(server, create);
|
||||
|
||||
CreateSequenceVisitor createSequence = new CreateSequenceVisitor(ctx);
|
||||
VisitorUtil.visit(server, createSequence);
|
||||
|
||||
AddForeignKeysVisitor fkeys = new AddForeignKeysVisitor(true, ctx);
|
||||
VisitorUtil.visit(server, fkeys);
|
||||
|
||||
CreateIndexVisitor indexes = new CreateIndexVisitor(ctx);
|
||||
VisitorUtil.visit(server, indexes);
|
||||
|
||||
ctx.flush();
|
||||
createContent = ctx.getContent();
|
||||
return createContent;
|
||||
try {
|
||||
createContent = currentModel().getCreateDdl();
|
||||
return createContent;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getDropFileName() {
|
||||
return server.getName() + "-drop.sql";
|
||||
return server.getName() + "-drop-all.sql";
|
||||
}
|
||||
|
||||
protected String getCreateFileName() {
|
||||
return server.getName() + "-create.sql";
|
||||
return server.getName() + "-create-all.sql";
|
||||
}
|
||||
|
||||
protected DdlGenContext createContext() {
|
||||
return new DdlGenContext(dbPlatform, namingConvention);
|
||||
protected CurrentModel currentModel() {
|
||||
if (currentModel == null) {
|
||||
currentModel = new CurrentModel(server);
|
||||
}
|
||||
return currentModel;
|
||||
}
|
||||
|
||||
protected void writeFile(String fileName, String fileContent) throws IOException {
|
||||
@@ -201,7 +168,7 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
FileReader fr = new FileReader(f);
|
||||
LineNumberReader lr = new LineNumberReader(fr);
|
||||
try {
|
||||
String s = null;
|
||||
String s;
|
||||
while ((s = lr.readLine()) != null) {
|
||||
buf.append(s).append("\n");
|
||||
}
|
||||
@@ -233,8 +200,7 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
t.commit();
|
||||
|
||||
} catch (Exception e) {
|
||||
String msg = "Error: " + e.getMessage();
|
||||
throw new PersistenceException(msg, e);
|
||||
throw new PersistenceException("Error: " + e.getMessage(), e);
|
||||
} finally {
|
||||
t.end();
|
||||
}
|
||||
@@ -274,7 +240,7 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
stmt = stmt.substring(0, stmt.length() - 1);
|
||||
}
|
||||
|
||||
logger.trace("executing " + oneOf + " " + getSummary(stmt));
|
||||
logger.info("executing " + oneOf + " " + getSummary(stmt));
|
||||
|
||||
pstmt = c.prepareStatement(stmt);
|
||||
pstmt.execute();
|
||||
@@ -340,8 +306,8 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
}
|
||||
|
||||
private String getSummary(String s) {
|
||||
if (s.length() > summaryLength) {
|
||||
return s.substring(0, summaryLength).trim() + "...";
|
||||
if (s.length() > 80) {
|
||||
return s.substring(0, 80).trim() + "...";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration;
|
||||
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.BaseTableDdl;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.PlatformDdl;
|
||||
import com.avaje.ebean.dbmigration.migration.AddColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.AddHistoryTable;
|
||||
import com.avaje.ebean.dbmigration.migration.AlterColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.ChangeSet;
|
||||
import com.avaje.ebean.dbmigration.migration.CreateIndex;
|
||||
import com.avaje.ebean.dbmigration.migration.CreateTable;
|
||||
import com.avaje.ebean.dbmigration.migration.DropColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.DropHistoryTable;
|
||||
import com.avaje.ebean.dbmigration.migration.DropIndex;
|
||||
import com.avaje.ebean.dbmigration.migration.DropTable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class BaseDdlHandler implements DdlHandler {
|
||||
|
||||
protected final TableDdl tableDdl;
|
||||
|
||||
public BaseDdlHandler(ServerConfig serverConfig, PlatformDdl platformDdl) {
|
||||
this.tableDdl = new BaseTableDdl(serverConfig, platformDdl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, ChangeSet changeSet) throws IOException {
|
||||
|
||||
List<Object> changeSetChildren = changeSet.getChangeSetChildren();
|
||||
for (Object change : changeSetChildren) {
|
||||
if (change instanceof CreateTable) {
|
||||
generate(writer, (CreateTable) change);
|
||||
} else if (change instanceof DropTable) {
|
||||
generate(writer, (DropTable) change);
|
||||
} else if (change instanceof CreateIndex) {
|
||||
generate(writer, (CreateIndex) change);
|
||||
} else if (change instanceof DropIndex) {
|
||||
generate(writer, (DropIndex) change);
|
||||
} else if (change instanceof AddColumn) {
|
||||
generate(writer, (AddColumn) change);
|
||||
} else if (change instanceof DropColumn) {
|
||||
generate(writer, (DropColumn) change);
|
||||
} else if (change instanceof AlterColumn) {
|
||||
generate(writer, (AlterColumn) change);
|
||||
} else if (change instanceof AddHistoryTable) {
|
||||
generate(writer, (AddHistoryTable) change);
|
||||
} else if (change instanceof DropHistoryTable) {
|
||||
generate(writer, (DropHistoryTable) change);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateExtra(DdlWrite write) throws IOException {
|
||||
tableDdl.generateExtra(write);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, CreateTable createTable) throws IOException {
|
||||
tableDdl.generate(writer, createTable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, DropTable dropTable) throws IOException {
|
||||
tableDdl.generate(writer, dropTable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AddColumn addColumn) throws IOException {
|
||||
tableDdl.generate(writer, addColumn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, DropColumn dropColumn) throws IOException {
|
||||
tableDdl.generate(writer, dropColumn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AlterColumn alterColumn) throws IOException {
|
||||
tableDdl.generate(writer, alterColumn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AddHistoryTable addHistoryTable) throws IOException {
|
||||
tableDdl.generate(writer, addHistoryTable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
|
||||
tableDdl.generate(writer, dropHistoryTable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, CreateIndex createIndex) throws IOException {
|
||||
tableDdl.generate(writer, createIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, DropIndex dropIndex) throws IOException {
|
||||
tableDdl.generate(writer, dropIndex);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration;
|
||||
|
||||
import com.avaje.ebean.dbmigration.migration.AddColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.AlterColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.DropColumn;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Write DDL for AddColumn , DropColumn or AlterColumn.
|
||||
*/
|
||||
public interface ColumnDdl {
|
||||
|
||||
/**
|
||||
* Write the add column change.
|
||||
*/
|
||||
void generate(DdlWrite writer, AddColumn addColumn) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the drop column change.
|
||||
*/
|
||||
void generate(DdlWrite writer, DropColumn dropColumn) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the alter column changes.
|
||||
*/
|
||||
void generate(DdlWrite writer, AlterColumn alterColumn) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration;
|
||||
|
||||
import com.avaje.ebean.dbmigration.model.MConfiguration;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Buffer to append generated DDL to.
|
||||
*/
|
||||
public interface DdlBuffer {
|
||||
|
||||
/**
|
||||
* Return the configuration (default tablespaces etc).
|
||||
*/
|
||||
MConfiguration getConfiguration();
|
||||
|
||||
/**
|
||||
* Return true if the buffer is empty.
|
||||
*/
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Append DDL content to the buffer.
|
||||
*/
|
||||
DdlBuffer append(String content) throws IOException;
|
||||
|
||||
/**
|
||||
* Append DDL content to the buffer with space padding.
|
||||
*/
|
||||
DdlBuffer append(String type, int space) throws IOException;
|
||||
|
||||
/**
|
||||
* Append a value that is potentially null or empty and proceed it with a space if so.
|
||||
*/
|
||||
DdlBuffer appendWithSpace(String foreignKeyRestrict) throws IOException;
|
||||
|
||||
/**
|
||||
* Append new line character to the buffer.
|
||||
*/
|
||||
DdlBuffer newLine() throws IOException;
|
||||
|
||||
/**
|
||||
* Append the end of statement content.
|
||||
*/
|
||||
DdlBuffer endOfStatement() throws IOException;
|
||||
|
||||
/**
|
||||
* End of a change - add some whitespace.
|
||||
*/
|
||||
DdlBuffer end() throws IOException;
|
||||
|
||||
/**
|
||||
* Return the buffer content.
|
||||
*/
|
||||
String getBuffer();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration;
|
||||
|
||||
import com.avaje.ebean.dbmigration.migration.AddColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.AddHistoryTable;
|
||||
import com.avaje.ebean.dbmigration.migration.AlterColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.ChangeSet;
|
||||
import com.avaje.ebean.dbmigration.migration.CreateIndex;
|
||||
import com.avaje.ebean.dbmigration.migration.CreateTable;
|
||||
import com.avaje.ebean.dbmigration.migration.DropColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.DropHistoryTable;
|
||||
import com.avaje.ebean.dbmigration.migration.DropIndex;
|
||||
import com.avaje.ebean.dbmigration.migration.DropTable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*/
|
||||
public interface DdlHandler {
|
||||
|
||||
void generate(DdlWrite writer, ChangeSet changeSet) throws IOException;
|
||||
|
||||
void generate(DdlWrite writer, CreateTable createTable) throws IOException;
|
||||
|
||||
void generate(DdlWrite writer, DropTable dropTable) throws IOException;
|
||||
|
||||
void generate(DdlWrite writer, AddColumn addColumn) throws IOException;
|
||||
|
||||
void generate(DdlWrite writer, DropColumn dropColumn) throws IOException;
|
||||
|
||||
void generate(DdlWrite writer, AlterColumn alterColumn) throws IOException;
|
||||
|
||||
void generate(DdlWrite writer, AddHistoryTable addHistoryTable) throws IOException;
|
||||
|
||||
void generate(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException;
|
||||
|
||||
void generate(DdlWrite writer, CreateIndex createIndex) throws IOException;
|
||||
|
||||
void generate(DdlWrite writer, DropIndex dropIndex) throws IOException;
|
||||
|
||||
void generateExtra(DdlWrite write) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration;
|
||||
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.BaseDdlBuffer;
|
||||
import com.avaje.ebean.dbmigration.model.MConfiguration;
|
||||
import com.avaje.ebean.dbmigration.model.MTable;
|
||||
import com.avaje.ebean.dbmigration.model.ModelContainer;
|
||||
|
||||
/**
|
||||
* Write context holding the buffers for both apply and rollback DDL.
|
||||
*/
|
||||
public class DdlWrite {
|
||||
|
||||
private final ModelContainer currentModel;
|
||||
|
||||
private final DdlBuffer apply;
|
||||
|
||||
private final DdlBuffer applyForeignKeys;
|
||||
|
||||
private final DdlBuffer applyHistory;
|
||||
|
||||
private final DdlBuffer rollbackForeignKeys;
|
||||
|
||||
private final DdlBuffer rollback;
|
||||
|
||||
/**
|
||||
* For DDL that drops tables and columns etc.
|
||||
*
|
||||
* This DDL typically can not run automatically in production as there is most commonly
|
||||
* existing servers running the application using these tables and columns. Typically
|
||||
* these drop statements may be executed AFTER all the servers in the application have
|
||||
* migrated onto new code.
|
||||
*/
|
||||
private final DdlBuffer drop;
|
||||
|
||||
/**
|
||||
* For use when History is turned off for a base table or history is no longer
|
||||
* desired on specific columns. This DDL should typically execute manually after review
|
||||
* by DBA's.
|
||||
*/
|
||||
private final DdlBuffer dropHistory;
|
||||
|
||||
/**
|
||||
* Create without any configuration or current model (no history support).
|
||||
*/
|
||||
public DdlWrite() {
|
||||
this(new MConfiguration(), new ModelContainer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with a configuration.
|
||||
*/
|
||||
public DdlWrite(MConfiguration configuration, ModelContainer currentModel) {
|
||||
this.currentModel = currentModel;
|
||||
this.apply = new BaseDdlBuffer(configuration);
|
||||
this.applyForeignKeys = new BaseDdlBuffer(configuration);
|
||||
this.applyHistory = new BaseDdlBuffer(configuration);
|
||||
this.rollbackForeignKeys = new BaseDdlBuffer(configuration);
|
||||
this.rollback = new BaseDdlBuffer(configuration);
|
||||
this.drop = new BaseDdlBuffer(configuration);
|
||||
this.dropHistory = new BaseDdlBuffer(configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Table information from the current model.
|
||||
* <p>
|
||||
* This is typically required for the history support (used to determine the list of columns
|
||||
* included in the history when creating or recreating the associated trigger/stored procedure).
|
||||
* </p>
|
||||
*/
|
||||
public MTable getTable(String tableName) {
|
||||
return currentModel.getTable(tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the apply buffers are all empty.
|
||||
*/
|
||||
public boolean isApplyEmpty() {
|
||||
return apply.getBuffer().isEmpty()
|
||||
&& applyForeignKeys.getBuffer().isEmpty()
|
||||
&& applyHistory.getBuffer().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the apply rollback buffers are all empty.
|
||||
*/
|
||||
public boolean isApplyRollbackEmpty() {
|
||||
return rollback.getBuffer().isEmpty()
|
||||
&& rollbackForeignKeys.getBuffer().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true the drop buffers are empty.
|
||||
*/
|
||||
public boolean isDropEmpty() {
|
||||
return drop.getBuffer().isEmpty() && dropHistory.getBuffer().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the buffer that APPLY DDL is written to.
|
||||
*/
|
||||
public DdlBuffer apply() {
|
||||
return apply;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the buffer that APPLY DDL is written to for foreign keys and their associated indexes.
|
||||
* <p>
|
||||
* Statements added to this buffer are executed after all the normal apply statements and
|
||||
* typically 'add foreign key' is added to this buffer.
|
||||
*/
|
||||
public DdlBuffer applyForeignKeys() {
|
||||
return applyForeignKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the buffer that apply history DDL is written to.
|
||||
*/
|
||||
public DdlBuffer applyHistory() {
|
||||
return applyHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the buffer that ROLLBACK DDL is written to for foreign keys and associated indexes.
|
||||
*/
|
||||
public DdlBuffer rollbackForeignKeys() {
|
||||
return rollbackForeignKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the buffer that ROLLBACK DDL is written to which is considered safe to run when
|
||||
* apply changes fail to execute. This will reverse the apply changes typically dropping
|
||||
* newly created tables, foreign keys etc.
|
||||
* <p>
|
||||
* When apply changes are made against DB's that support transactional DDL you could argue
|
||||
* that these rollback statements are not necessary.
|
||||
* <p>
|
||||
* Note that statements added to this rollback buffer are executed after foreign key rollback
|
||||
* has been executed.
|
||||
*/
|
||||
public DdlBuffer rollback() {
|
||||
return rollback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the buffer that destructive changes are written to. This is typically drop table and
|
||||
* drop column.
|
||||
*/
|
||||
public DdlBuffer drop() {
|
||||
return drop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the buffer that is used when history is no longer required on a table or specific columns.
|
||||
*/
|
||||
public DdlBuffer dropHistory() {
|
||||
return dropHistory;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration;
|
||||
|
||||
import com.avaje.ebean.dbmigration.migration.AddColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.AddHistoryTable;
|
||||
import com.avaje.ebean.dbmigration.migration.AlterColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.CreateIndex;
|
||||
import com.avaje.ebean.dbmigration.migration.CreateTable;
|
||||
import com.avaje.ebean.dbmigration.migration.DropColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.DropHistoryTable;
|
||||
import com.avaje.ebean.dbmigration.migration.DropIndex;
|
||||
import com.avaje.ebean.dbmigration.migration.DropTable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Write table DDL.
|
||||
*/
|
||||
public interface TableDdl {
|
||||
|
||||
/**
|
||||
* Generate the create table change.
|
||||
*/
|
||||
void generate(DdlWrite writer, CreateTable createTable) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the drop column change.
|
||||
*/
|
||||
void generate(DdlWrite writer, DropTable dropTable) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the add column change.
|
||||
*/
|
||||
void generate(DdlWrite writer, AddColumn addColumn) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the alter column changes.
|
||||
*/
|
||||
void generate(DdlWrite writer, AlterColumn alterColumn) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the drop column change.
|
||||
*/
|
||||
void generate(DdlWrite writer, DropColumn dropColumn) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the AddHistoryTable change.
|
||||
*/
|
||||
void generate(DdlWrite writer, AddHistoryTable addHistoryTable) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the DropHistoryTable change.
|
||||
*/
|
||||
void generate(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException;
|
||||
|
||||
/**
|
||||
* Generate the create index change.
|
||||
*/
|
||||
void generate(DdlWrite writer, CreateIndex createIndex) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the drop index change.
|
||||
*/
|
||||
void generate(DdlWrite writer, DropIndex dropIndex) throws IOException;
|
||||
|
||||
/**
|
||||
* Generate any extra DDL such as regeneration of history triggers.
|
||||
*/
|
||||
void generateExtra(DdlWrite write) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import com.avaje.ebean.dbmigration.model.MConfiguration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* Base implementation of DdlBuffer using an underlying writer.
|
||||
*/
|
||||
public class BaseDdlBuffer implements DdlBuffer {
|
||||
|
||||
protected final StringWriter writer;
|
||||
|
||||
protected final MConfiguration configuration;
|
||||
|
||||
public BaseDdlBuffer(MConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
this.writer = new StringWriter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MConfiguration getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return writer.getBuffer().length() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DdlBuffer appendWithSpace(String foreignKeyRestrict) throws IOException {
|
||||
if (foreignKeyRestrict != null && !foreignKeyRestrict.isEmpty()) {
|
||||
writer.append(" ").append(foreignKeyRestrict);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DdlBuffer append(String content) throws IOException {
|
||||
writer.append(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DdlBuffer append(String content, int space) throws IOException {
|
||||
writer.append(content);
|
||||
appendSpace(space, content);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected void appendSpace(int max, String content) throws IOException {
|
||||
int space = max - content.length();
|
||||
if (space > 0) {
|
||||
for (int i = 0; i < space; i++) {
|
||||
append(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DdlBuffer endOfStatement() throws IOException {
|
||||
writer.append(";\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to demarcate the end of a series of statements.
|
||||
* This should be just whitespace or a sql comment.
|
||||
*/
|
||||
@Override
|
||||
public DdlBuffer end() throws IOException {
|
||||
writer.append("\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DdlBuffer newLine() throws IOException {
|
||||
writer.append("\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getBuffer() {
|
||||
return writer.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,780 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.config.DbConstraintNaming;
|
||||
import com.avaje.ebean.config.NamingConvention;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.IdType;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlWrite;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.TableDdl;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.util.IndexSet;
|
||||
import com.avaje.ebean.dbmigration.migration.AddColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.AddHistoryTable;
|
||||
import com.avaje.ebean.dbmigration.migration.AlterColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.Column;
|
||||
import com.avaje.ebean.dbmigration.migration.CreateIndex;
|
||||
import com.avaje.ebean.dbmigration.migration.CreateTable;
|
||||
import com.avaje.ebean.dbmigration.migration.DropColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.DropHistoryTable;
|
||||
import com.avaje.ebean.dbmigration.migration.DropIndex;
|
||||
import com.avaje.ebean.dbmigration.migration.DropTable;
|
||||
import com.avaje.ebean.dbmigration.migration.ForeignKey;
|
||||
import com.avaje.ebean.dbmigration.migration.UniqueConstraint;
|
||||
import com.avaje.ebean.dbmigration.model.MTable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Base implementation for 'create table' and 'alter table' statements.
|
||||
*/
|
||||
public class BaseTableDdl implements TableDdl {
|
||||
|
||||
protected final DbConstraintNaming naming;
|
||||
|
||||
protected final NamingConvention namingConvention;
|
||||
|
||||
protected final PlatformDdl platformDdl;
|
||||
|
||||
protected final String historyTableSuffix;
|
||||
|
||||
/**
|
||||
* Used to check that indexes on foreign keys should be skipped as a unique index on the columns
|
||||
* already exists.
|
||||
*/
|
||||
protected IndexSet indexSet = new IndexSet();
|
||||
|
||||
/**
|
||||
* Used when unique constraints specifically for OneToOne can't be created normally (MsSqlServer).
|
||||
*/
|
||||
protected List<Column> externalUnique = new ArrayList<Column>();
|
||||
|
||||
// counters used when constraint names are truncated due to maximum length
|
||||
// and these counters are used to keep the constraint name unique
|
||||
protected int countCheck;
|
||||
protected int countUnique;
|
||||
protected int countForeignKey;
|
||||
protected int countIndex;
|
||||
|
||||
/**
|
||||
* Base tables that have associated history tables that need their triggers/functions regenerated as
|
||||
* columns have been added, removed, included or excluded.
|
||||
*/
|
||||
protected Map<String, HistoryTableUpdate> regenerateHistoryTriggers = new LinkedHashMap<String, HistoryTableUpdate>();
|
||||
|
||||
/**
|
||||
* Construct with a naming convention and platform specific DDL.
|
||||
*/
|
||||
public BaseTableDdl(ServerConfig serverConfig, PlatformDdl platformDdl) {
|
||||
this.namingConvention = serverConfig.getNamingConvention();
|
||||
this.naming = serverConfig.getConstraintNaming();
|
||||
this.historyTableSuffix = serverConfig.getHistoryTableSuffix();
|
||||
this.platformDdl = platformDdl;
|
||||
this.platformDdl.configure(serverConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset counters and index set for each table processed.
|
||||
*/
|
||||
protected void reset() {
|
||||
indexSet.clear();
|
||||
externalUnique.clear();
|
||||
countCheck = 0;
|
||||
countUnique = 0;
|
||||
countForeignKey = 0;
|
||||
countIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the appropriate 'create table' and matching 'drop table' statements
|
||||
* and add them to the appropriate 'apply' and 'rollback' buffers.
|
||||
*/
|
||||
@Override
|
||||
public void generate(DdlWrite writer, CreateTable createTable) throws IOException {
|
||||
|
||||
reset();
|
||||
|
||||
String tableName = lowerName(createTable.getName());
|
||||
List<Column> columns = createTable.getColumn();
|
||||
List<Column> pk = determinePrimaryKeyColumns(columns);
|
||||
|
||||
boolean singleColumnPrimaryKey = (pk.size() == 1);
|
||||
boolean useIdentity = false;
|
||||
boolean useSequence = false;
|
||||
|
||||
if (singleColumnPrimaryKey) {
|
||||
IdType useDbIdentityType = platformDdl.useIdentityType(createTable.getIdentityType());
|
||||
useIdentity = (IdType.IDENTITY == useDbIdentityType);
|
||||
useSequence = (IdType.SEQUENCE == useDbIdentityType);
|
||||
}
|
||||
|
||||
DdlBuffer apply = writer.apply();
|
||||
apply.append("create table ").append(tableName).append(" (");
|
||||
writeTableColumns(apply, columns, useIdentity);
|
||||
writeCheckConstraints(apply, createTable);
|
||||
writeUniqueConstraints(apply, createTable);
|
||||
writeCompoundUniqueConstraints(apply, createTable);
|
||||
if (!pk.isEmpty()) {
|
||||
// defined on the columns
|
||||
writePrimaryKeyConstraint(apply, createTable.getPkName(), toColumnNames(pk));
|
||||
}
|
||||
|
||||
apply.newLine().append(")").endOfStatement();
|
||||
|
||||
writeUniqueOneToOneConstraints(writer, createTable);
|
||||
|
||||
if (isTrue(createTable.isWithHistory())) {
|
||||
// create history with rollback before the
|
||||
// associated drop table is written to rollback
|
||||
createWithHistory(writer, createTable.getName());
|
||||
}
|
||||
|
||||
// add drop table to the rollback buffer - do this before
|
||||
// we drop the related sequence (if sequences are used)
|
||||
dropTable(writer.rollback(), tableName);
|
||||
|
||||
if (useSequence) {
|
||||
String pkCol = pk.get(0).getName();
|
||||
writeSequence(writer, createTable, pkCol);
|
||||
}
|
||||
|
||||
// add blank line for a bit of whitespace between tables
|
||||
apply.end();
|
||||
writer.rollback().end();
|
||||
|
||||
writeAddForeignKeys(writer, createTable);
|
||||
|
||||
}
|
||||
|
||||
private void writeTableColumns(DdlBuffer apply, List<Column> columns, boolean useIdentity) throws IOException {
|
||||
platformDdl.writeTableColumns(apply, columns, useIdentity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific handling of OneToOne unique constraints for MsSqlServer.
|
||||
* For all other DB platforms these unique constraints are done inline as per normal.
|
||||
*/
|
||||
protected void writeUniqueOneToOneConstraints(DdlWrite write, CreateTable createTable) throws IOException {
|
||||
|
||||
String tableName = createTable.getName();
|
||||
for (Column col : externalUnique) {
|
||||
String uqName = col.getUniqueOneToOne();
|
||||
String[] columnNames = {col.getName()};
|
||||
write.apply()
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames))
|
||||
.endOfStatement();
|
||||
|
||||
write.rollbackForeignKeys()
|
||||
.append(platformDdl.dropIndex(uqName, tableName))
|
||||
.endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeSequence(DdlWrite writer, CreateTable createTable, String pk) throws IOException {
|
||||
|
||||
// explicit sequence use or platform decides
|
||||
String explicitSequenceName = createTable.getSequenceName();
|
||||
int initial = toInt(createTable.getSequenceInitial());
|
||||
int allocate = toInt(createTable.getSequenceAllocate());
|
||||
|
||||
String seqName = explicitSequenceName;
|
||||
if (seqName == null) {
|
||||
seqName = namingConvention.getSequenceName(createTable.getName(), pk);
|
||||
}
|
||||
|
||||
String createSeq = platformDdl.createSequence(seqName, initial, allocate);
|
||||
if (createSeq != null) {
|
||||
writer.apply().append(createSeq).newLine();
|
||||
writer.rollback().append(platformDdl.dropSequence(seqName)).endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
protected void createWithHistory(DdlWrite writer, String name) throws IOException {
|
||||
|
||||
MTable table = writer.getTable(name);
|
||||
platformDdl.createWithHistory(writer, table);
|
||||
}
|
||||
|
||||
protected void writeAddForeignKeys(DdlWrite write, CreateTable createTable) throws IOException {
|
||||
|
||||
String tableName = createTable.getName();
|
||||
List<Column> columns = createTable.getColumn();
|
||||
for (Column column : columns) {
|
||||
String references = column.getReferences();
|
||||
if (hasValue(references)) {
|
||||
writeForeignKey(write, tableName, column);
|
||||
}
|
||||
}
|
||||
|
||||
writeAddCompoundForeignKeys(write, createTable);
|
||||
}
|
||||
|
||||
protected void writeAddCompoundForeignKeys(DdlWrite write, CreateTable createTable) throws IOException {
|
||||
|
||||
String tableName = createTable.getName();
|
||||
|
||||
List<ForeignKey> foreignKey = createTable.getForeignKey();
|
||||
for (ForeignKey key : foreignKey) {
|
||||
|
||||
String refTableName = key.getRefTableName();
|
||||
String fkName = key.getName();
|
||||
String[] cols = toColumnNamesSplit(key.getColumnNames());
|
||||
String[] refColumns = toColumnNamesSplit(key.getRefColumnNames());
|
||||
|
||||
writeForeignKey(write, fkName, tableName, cols, refTableName, refColumns, key.getIndexName());
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeForeignKey(DdlWrite write, String tableName, Column column) throws IOException {
|
||||
|
||||
String fkName = column.getForeignKeyName();
|
||||
String references = column.getReferences();
|
||||
int pos = references.lastIndexOf('.');
|
||||
if (pos == -1) {
|
||||
throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]");
|
||||
}
|
||||
String refTableName = references.substring(0, pos);
|
||||
String refColumnName = references.substring(pos + 1);
|
||||
|
||||
String[] cols = {column.getName()};
|
||||
String[] refCols = {refColumnName};
|
||||
|
||||
writeForeignKey(write, fkName, tableName, cols, refTableName, refCols, column.getForeignKeyIndex());
|
||||
}
|
||||
|
||||
protected void writeForeignKey(DdlWrite write, String fkName, String tableName, String[] columns, String refTable, String[] refColumns, String indexName) throws IOException {
|
||||
|
||||
tableName = lowerName(tableName);
|
||||
DdlBuffer fkeyBuffer = write.applyForeignKeys();
|
||||
alterTableAddForeignKey(fkeyBuffer, fkName, tableName, columns, refTable, refColumns);
|
||||
|
||||
if (indexName != null) {
|
||||
// no matching unique constraint so add the index
|
||||
fkeyBuffer.append(platformDdl.createIndex(indexName, tableName, columns)).endOfStatement();
|
||||
}
|
||||
|
||||
fkeyBuffer.end();
|
||||
|
||||
write.rollbackForeignKeys()
|
||||
.append(platformDdl.alterTableDropForeignKey(tableName, fkName)).endOfStatement();
|
||||
|
||||
if (indexName != null) {
|
||||
write.rollbackForeignKeys()
|
||||
.append(platformDdl.dropIndex(indexName, tableName)).endOfStatement();
|
||||
}
|
||||
|
||||
write.rollbackForeignKeys().end();
|
||||
|
||||
}
|
||||
|
||||
protected void alterTableAddForeignKey(DdlBuffer buffer, String fkName, String tableName, String[] columns, String refTable, String[] refColumns) throws IOException {
|
||||
|
||||
buffer.append(platformDdl.alterTableAddForeignKey(tableName, fkName, columns, refTable, refColumns)).endOfStatement();
|
||||
}
|
||||
|
||||
protected void appendColumns(String[] columns, DdlBuffer buffer) throws IOException {
|
||||
buffer.append(" (");
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
if (i > 0) {
|
||||
buffer.append(",");
|
||||
}
|
||||
buffer.append(lowerName(columns[i].trim()));
|
||||
}
|
||||
buffer.append(")");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add 'drop table' statement to the buffer.
|
||||
*/
|
||||
protected void dropTable(DdlBuffer buffer, String tableName) throws IOException {
|
||||
|
||||
buffer.append(platformDdl.dropTable(tableName)).endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write all the check constraints.
|
||||
*/
|
||||
protected void writeCheckConstraints(DdlBuffer apply, CreateTable createTable) throws IOException {
|
||||
|
||||
List<Column> columns = createTable.getColumn();
|
||||
for (Column column : columns) {
|
||||
String checkConstraint = column.getCheckConstraint();
|
||||
if (hasValue(checkConstraint)) {
|
||||
writeCheckConstraint(apply, column, checkConstraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a check constraint.
|
||||
*/
|
||||
protected void writeCheckConstraint(DdlBuffer buffer, Column column, String checkConstraint) throws IOException {
|
||||
|
||||
String ckName = column.getCheckConstraintName();
|
||||
|
||||
buffer.append(",").newLine();
|
||||
buffer.append(" constraint ").append(ckName);
|
||||
buffer.append(" ").append(checkConstraint);
|
||||
}
|
||||
|
||||
protected void writeCompoundUniqueConstraints(DdlBuffer apply, CreateTable createTable) throws IOException {
|
||||
|
||||
List<UniqueConstraint> uniqueConstraints = createTable.getUniqueConstraint();
|
||||
for (UniqueConstraint uniqueConstraint : uniqueConstraints) {
|
||||
String uqName = uniqueConstraint.getName();
|
||||
String[] columns = toColumnNamesSplit(uniqueConstraint.getColumnNames());
|
||||
apply.append(",").newLine();
|
||||
apply.append(" constraint ").append(uqName).append(" unique");
|
||||
appendColumns(columns, apply);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the unique constraints inline with the create table statement.
|
||||
*/
|
||||
protected void writeUniqueConstraints(DdlBuffer apply, CreateTable createTable) throws IOException {
|
||||
|
||||
boolean inlineUniqueOneToOne = platformDdl.isInlineUniqueOneToOne();
|
||||
|
||||
List<Column> columns = createTable.getColumn();
|
||||
for (Column column : columns) {
|
||||
if (hasValue(column.getUnique()) || (inlineUniqueOneToOne && hasValue(column.getUniqueOneToOne()))) {
|
||||
// normal mechanism for adding unique constraint
|
||||
inlineUniqueConstraintSingle(apply, column);
|
||||
|
||||
} else if (!inlineUniqueOneToOne && hasValue(column.getUniqueOneToOne())) {
|
||||
// MsSqlServer specific mechanism for adding unique constraints (that allow nulls)
|
||||
externalUnique.add(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the unique constraint inline with the create table statement.
|
||||
*/
|
||||
protected void inlineUniqueConstraintSingle(DdlBuffer buffer, Column column) throws IOException {
|
||||
|
||||
String uqName = column.getUnique();
|
||||
if (uqName == null) {
|
||||
uqName = column.getUniqueOneToOne();
|
||||
}
|
||||
|
||||
buffer.append(",").newLine();
|
||||
buffer.append(" constraint ").append(uqName).append(" unique ");
|
||||
buffer.append("(");
|
||||
buffer.append(lowerName(column.getName()));
|
||||
buffer.append(")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the primary key constraint inline with the create table statement.
|
||||
*/
|
||||
protected void writePrimaryKeyConstraint(DdlBuffer buffer, String pkName, String[] pkColumns) throws IOException {
|
||||
|
||||
buffer.append(",").newLine();
|
||||
buffer.append(" constraint ").append(pkName).append(" primary key");
|
||||
appendColumns(pkColumns, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return as an array of string column names.
|
||||
*/
|
||||
protected String[] toColumnNames(List<Column> columns) {
|
||||
|
||||
String[] cols = new String[columns.size()];
|
||||
for (int i = 0; i < cols.length; i++) {
|
||||
cols[i] = columns.get(i).getName();
|
||||
}
|
||||
return cols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return as an array of string column names.
|
||||
*/
|
||||
protected String[] toColumnNamesSplit(String columns) {
|
||||
return columns.split(",");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the table or column name to lower case.
|
||||
* <p>
|
||||
* This is passed up to the platformDdl to override as desired.
|
||||
* Generally lower case with underscore is a good cross database
|
||||
* choice for column/table names.
|
||||
*/
|
||||
protected String lowerName(String name) {
|
||||
return naming.lowerName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of columns that make the primary key.
|
||||
*/
|
||||
protected List<Column> determinePrimaryKeyColumns(List<Column> columns) {
|
||||
List<Column> pk = new ArrayList<Column>(3);
|
||||
for (Column column : columns) {
|
||||
if (isTrue(column.isPrimaryKey())) {
|
||||
pk.add(column);
|
||||
}
|
||||
}
|
||||
return pk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, CreateIndex createIndex) throws IOException {
|
||||
|
||||
String[] cols = toColumnNamesSplit(createIndex.getColumns());
|
||||
writer.apply()
|
||||
.append(platformDdl.createIndex(createIndex.getIndexName(), createIndex.getTableName(), cols))
|
||||
.endOfStatement();
|
||||
|
||||
writer.rollback()
|
||||
.append(platformDdl.dropIndex(createIndex.getIndexName(), createIndex.getTableName()))
|
||||
.endOfStatement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, DropIndex dropIndex) throws IOException {
|
||||
|
||||
writer.apply()
|
||||
.append(platformDdl.dropIndex(dropIndex.getIndexName(), dropIndex.getTableName()))
|
||||
.endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add add history table DDL.
|
||||
*/
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AddHistoryTable addHistoryTable) throws IOException {
|
||||
platformDdl.addHistoryTable(writer, addHistoryTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add drop history table DDL.
|
||||
*/
|
||||
@Override
|
||||
public void generate(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
|
||||
platformDdl.dropHistoryTable(writer, dropHistoryTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the end to generate additional ddl such as regenerate history triggers.
|
||||
*/
|
||||
@Override
|
||||
public void generateExtra(DdlWrite write) throws IOException {
|
||||
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
|
||||
platformDdl.regenerateHistoryTriggers(write, update);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add add column DDL.
|
||||
*/
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AddColumn addColumn) throws IOException {
|
||||
|
||||
String tableName = addColumn.getTableName();
|
||||
List<Column> columns = addColumn.getColumn();
|
||||
for (Column column : columns) {
|
||||
alterTableAddColumn(writer.apply(), tableName, column, false);
|
||||
alterTableDropColumn(writer.rollback(), tableName, column.getName());
|
||||
}
|
||||
|
||||
if (isTrue(addColumn.isWithHistory())) {
|
||||
// make same changes to the history table
|
||||
String historyTable = historyTable(tableName);
|
||||
for (Column column : columns) {
|
||||
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.ADD, column.getName());
|
||||
alterTableAddColumn(writer.apply(), historyTable, column, true);
|
||||
alterTableDropColumn(writer.rollback(), historyTable, column.getName());
|
||||
}
|
||||
}
|
||||
|
||||
// add a bit of whitespace
|
||||
writer.apply().end();
|
||||
writer.rollback().end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add drop table DDL.
|
||||
*/
|
||||
@Override
|
||||
public void generate(DdlWrite writer, DropTable dropTable) throws IOException {
|
||||
|
||||
dropTable(writer.drop(), dropTable.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add drop column DDL.
|
||||
*/
|
||||
@Override
|
||||
public void generate(DdlWrite writer, DropColumn dropColumn) throws IOException {
|
||||
|
||||
String tableName = dropColumn.getTableName();
|
||||
|
||||
alterTableDropColumn(writer.drop(), tableName, dropColumn.getColumnName());
|
||||
if (isTrue(dropColumn.isWithHistory())) {
|
||||
// also drop from the history table
|
||||
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.DROP, dropColumn.getColumnName());
|
||||
alterTableDropColumn(writer.drop(), historyTable(tableName), dropColumn.getColumnName());
|
||||
}
|
||||
|
||||
writer.drop().end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the appropriate changes based on the column changes.
|
||||
*/
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AlterColumn alterColumn) throws IOException {
|
||||
|
||||
if (isTrue(alterColumn.isHistoryExclude())) {
|
||||
regenerateHistoryTriggers(alterColumn.getTableName(), HistoryTableUpdate.Change.EXCLUDE, alterColumn.getColumnName());
|
||||
} else if (isFalse(alterColumn.isHistoryExclude())) {
|
||||
regenerateHistoryTriggers(alterColumn.getTableName(), HistoryTableUpdate.Change.INCLUDE, alterColumn.getColumnName());
|
||||
}
|
||||
|
||||
if (hasValue(alterColumn.getDropForeignKey())) {
|
||||
alterColumnDropForeignKey(writer, alterColumn);
|
||||
}
|
||||
if (hasValue(alterColumn.getReferences())) {
|
||||
alterColumnAddForeignKey(writer, alterColumn);
|
||||
}
|
||||
|
||||
if (hasValue(alterColumn.getDropUnique())) {
|
||||
alterColumnDropUniqueConstraint(writer, alterColumn);
|
||||
}
|
||||
if (hasValue(alterColumn.getUnique())) {
|
||||
alterColumnAddUniqueConstraint(writer, alterColumn);
|
||||
}
|
||||
if (hasValue(alterColumn.getUniqueOneToOne())) {
|
||||
alterColumnAddUniqueOneToOneConstraint(writer, alterColumn);
|
||||
}
|
||||
|
||||
boolean alterBaseAttributes = false;
|
||||
if (hasValue(alterColumn.getType())) {
|
||||
alterColumnType(writer, alterColumn);
|
||||
alterBaseAttributes = true;
|
||||
}
|
||||
if (hasValue(alterColumn.getDefaultValue())) {
|
||||
alterColumnDefaultValue(writer, alterColumn);
|
||||
alterBaseAttributes = true;
|
||||
}
|
||||
if (alterColumn.isNotnull() != null) {
|
||||
alterColumnNotnull(writer, alterColumn);
|
||||
alterBaseAttributes = true;
|
||||
}
|
||||
|
||||
if (alterBaseAttributes) {
|
||||
alterColumnBaseAttributes(writer, alterColumn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the history table given the base table name.
|
||||
*/
|
||||
protected String historyTable(String baseTable) {
|
||||
return baseTable + historyTableSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the base table that we need to regenerate the history triggers on.
|
||||
*/
|
||||
protected void regenerateHistoryTriggers(String baseTableName, HistoryTableUpdate.Change change, String column) {
|
||||
|
||||
HistoryTableUpdate update = regenerateHistoryTriggers.get(baseTableName);
|
||||
if (update == null) {
|
||||
update = new HistoryTableUpdate(baseTableName);
|
||||
regenerateHistoryTriggers.put(baseTableName, update);
|
||||
}
|
||||
update.add(change, column);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is mysql specific - alter all the base attributes of the column together.
|
||||
*/
|
||||
protected void alterColumnBaseAttributes(DdlWrite writer, AlterColumn alter) throws IOException {
|
||||
|
||||
String ddl = platformDdl.alterColumnBaseAttributes(alter);
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
|
||||
// reverse and generate the rollback statement
|
||||
String currentType = alter.getCurrentType();
|
||||
String type = alter.getType();
|
||||
Boolean currentNotnull = alter.isCurrentNotnull();
|
||||
Boolean notnull = alter.isNotnull();
|
||||
|
||||
alter.setCurrentType(type);
|
||||
alter.setType(currentType);
|
||||
alter.setNotnull(currentNotnull);
|
||||
alter.setCurrentNotnull(notnull);
|
||||
|
||||
// write the rollback
|
||||
ddl = platformDdl.alterColumnBaseAttributes(alter);
|
||||
writer.rollback().append(ddl).endOfStatement();
|
||||
|
||||
if (isTrue(alter.isWithHistory()) && alter.getType() != null) {
|
||||
// mysql and sql server column type change allowing nulls in the history table column
|
||||
AlterColumn alterHistoryColumn = new AlterColumn();
|
||||
alterHistoryColumn.setTableName(historyTable(alter.getTableName()));
|
||||
alterHistoryColumn.setColumnName(alter.getColumnName());
|
||||
alterHistoryColumn.setType(alter.getType());
|
||||
String histColumnDdl = platformDdl.alterColumnBaseAttributes(alterHistoryColumn);
|
||||
|
||||
// write the apply to history table
|
||||
writer.apply().append(histColumnDdl).endOfStatement();
|
||||
|
||||
// write the rollback from history table
|
||||
alterHistoryColumn.setType(currentType);
|
||||
histColumnDdl = platformDdl.alterColumnBaseAttributes(alterHistoryColumn);
|
||||
writer.rollback().append(histColumnDdl).endOfStatement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void alterColumnDefaultValue(DdlWrite writer, AlterColumn alter) throws IOException {
|
||||
|
||||
String ddl = platformDdl.alterColumnDefaultValue(alter.getTableName(), alter.getColumnName(), alter.getDefaultValue());
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
protected void alterColumnNotnull(DdlWrite writer, AlterColumn alter) throws IOException {
|
||||
|
||||
String ddl = platformDdl.alterColumnNotnull(alter.getTableName(), alter.getColumnName(), alter.isNotnull());
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
ddl = platformDdl.alterColumnNotnull(alter.getTableName(), alter.getColumnName(), alter.isCurrentNotnull());
|
||||
writer.rollback().append(ddl).endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
protected void alterColumnType(DdlWrite writer, AlterColumn alter) throws IOException {
|
||||
|
||||
String ddl = platformDdl.alterColumnType(alter.getTableName(), alter.getColumnName(), alter.getType());
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
ddl = platformDdl.alterColumnType(alter.getTableName(), alter.getColumnName(), alter.getCurrentType());
|
||||
writer.rollback().append(ddl).endOfStatement();
|
||||
if (isTrue(alter.isWithHistory())) {
|
||||
// apply same type change to matching column in the history table
|
||||
ddl = platformDdl.alterColumnType(historyTable(alter.getTableName()), alter.getColumnName(), alter.getType());
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
ddl = platformDdl.alterColumnType(historyTable(alter.getTableName()), alter.getColumnName(), alter.getCurrentType());
|
||||
writer.rollback().append(ddl).endOfStatement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void alterColumnAddForeignKey(DdlWrite writer, AlterColumn alterColumn) throws IOException {
|
||||
|
||||
String tableName = alterColumn.getTableName();
|
||||
String fkName = alterColumn.getForeignKeyName();
|
||||
String[] cols = {alterColumn.getColumnName()};
|
||||
String references = alterColumn.getReferences();
|
||||
int pos = references.lastIndexOf('.');
|
||||
if (pos == -1) {
|
||||
throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]");
|
||||
}
|
||||
String refTableName = references.substring(0, pos);
|
||||
String refColumnName = references.substring(pos + 1);
|
||||
String[] refCols = {refColumnName};
|
||||
|
||||
alterTableAddForeignKey(writer.apply(), fkName, tableName, cols, refTableName, refCols);
|
||||
}
|
||||
|
||||
protected void alterColumnDropForeignKey(DdlWrite writer, AlterColumn alter) throws IOException {
|
||||
|
||||
writer.apply()
|
||||
.append(platformDdl.alterTableDropForeignKey(alter.getTableName(), alter.getDropForeignKey()))
|
||||
.endOfStatement();
|
||||
}
|
||||
|
||||
|
||||
protected void alterColumnDropUniqueConstraint(DdlWrite writer, AlterColumn alter) throws IOException {
|
||||
|
||||
writer.apply()
|
||||
.append(platformDdl.alterTableDropUniqueConstraint(alter.getTableName(), alter.getDropUnique()))
|
||||
.endOfStatement();
|
||||
}
|
||||
|
||||
protected void alterColumnAddUniqueOneToOneConstraint(DdlWrite writer, AlterColumn alter) throws IOException {
|
||||
|
||||
addUniqueConstraint(writer, alter, alter.getUniqueOneToOne());
|
||||
}
|
||||
|
||||
protected void alterColumnAddUniqueConstraint(DdlWrite writer, AlterColumn alter) throws IOException {
|
||||
|
||||
addUniqueConstraint(writer, alter, alter.getUnique());
|
||||
}
|
||||
|
||||
protected void addUniqueConstraint(DdlWrite writer, AlterColumn alter, String uqName) throws IOException {
|
||||
|
||||
String[] cols = {alter.getColumnName()};
|
||||
|
||||
writer.apply()
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols))
|
||||
.endOfStatement();
|
||||
|
||||
writer.rollbackForeignKeys()
|
||||
.append(platformDdl.dropIndex(uqName, alter.getTableName()))
|
||||
.endOfStatement();
|
||||
}
|
||||
|
||||
|
||||
protected void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
|
||||
|
||||
buffer.append("alter table ").append(tableName).append(" drop column ").append(columnName)
|
||||
.endOfStatement();
|
||||
}
|
||||
|
||||
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable) throws IOException {
|
||||
|
||||
buffer.append("alter table ").append(tableName)
|
||||
.append(" add column ").append(column.getName())
|
||||
.append(" ").append(column.getType());
|
||||
|
||||
if (!onHistoryTable) {
|
||||
if (isTrue(column.isNotnull())) {
|
||||
buffer.append(" not null");
|
||||
}
|
||||
if (hasValue(column.getCheckConstraint())) {
|
||||
buffer.append(" ").append(column.getCheckConstraint());
|
||||
}
|
||||
}
|
||||
buffer.endOfStatement();
|
||||
}
|
||||
|
||||
protected boolean isFalse(Boolean value) {
|
||||
return value != null && !value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if null or trimmed string is empty.
|
||||
*/
|
||||
protected boolean hasValue(String value) {
|
||||
return value != null && !value.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Null safe Boolean true test.
|
||||
*/
|
||||
protected boolean isTrue(Boolean value) {
|
||||
return Boolean.TRUE.equals(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return as an int value with 0 when it is null.
|
||||
*/
|
||||
protected int toInt(BigInteger value) {
|
||||
return (value == null) ? 0 : value.intValue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbIdentity;
|
||||
import com.avaje.ebean.config.dbplatform.DbTypeMap;
|
||||
|
||||
/**
|
||||
* DB2 platform specific DDL.
|
||||
*/
|
||||
public class DB2Ddl extends PlatformDdl {
|
||||
|
||||
public DB2Ddl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
|
||||
super(platformTypes, dbIdentity);
|
||||
this.identitySuffix = " generated by default as identity";
|
||||
}
|
||||
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.config.DbConstraintNaming;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlWrite;
|
||||
import com.avaje.ebean.dbmigration.migration.AddHistoryTable;
|
||||
import com.avaje.ebean.dbmigration.migration.DropHistoryTable;
|
||||
import com.avaje.ebean.dbmigration.model.MColumn;
|
||||
import com.avaje.ebean.dbmigration.model.MTable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Uses DB triggers to maintain a history table.
|
||||
*/
|
||||
public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
|
||||
protected DbConstraintNaming constraintNaming;
|
||||
|
||||
protected PlatformDdl platformDdl;
|
||||
|
||||
protected String sysPeriod;
|
||||
protected String sysPeriodStart;
|
||||
protected String sysPeriodEnd;
|
||||
|
||||
protected String viewSuffix;
|
||||
protected String historySuffix;
|
||||
|
||||
|
||||
protected String currentTimestamp = "now(6)";
|
||||
protected String sysPeriodType = "datetime(6)";
|
||||
|
||||
public DbTriggerBasedHistoryDdl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(ServerConfig serverConfig, PlatformDdl platformDdl) {
|
||||
this.platformDdl = platformDdl;
|
||||
this.sysPeriod = serverConfig.getAsOfSysPeriod();
|
||||
this.viewSuffix = serverConfig.getAsOfViewSuffix();
|
||||
this.historySuffix = serverConfig.getHistoryTableSuffix();
|
||||
this.constraintNaming = serverConfig.getConstraintNaming();
|
||||
|
||||
this.sysPeriodStart = sysPeriod+"_start";
|
||||
this.sysPeriodEnd = sysPeriod+"_end";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void regenerateHistoryTriggers(DdlWrite writer, HistoryTableUpdate update) throws IOException {
|
||||
|
||||
MTable table = writer.getTable(update.getBaseTable());
|
||||
if (table == null) {
|
||||
throw new IllegalStateException("MTable "+update.getBaseTable()+" not found in writer? (required for history DDL)");
|
||||
}
|
||||
regenerateHistoryTriggers(writer, table, update);
|
||||
}
|
||||
|
||||
protected abstract void regenerateHistoryTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException;
|
||||
|
||||
@Override
|
||||
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
|
||||
|
||||
String baseTable = dropHistoryTable.getBaseTable();
|
||||
|
||||
// drop in appropriate order
|
||||
dropTriggers(writer.dropHistory(), baseTable);
|
||||
dropHistoryTableEtc(writer.dropHistory(), baseTable);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addHistoryTable(DdlWrite writer, AddHistoryTable addHistoryTable) throws IOException {
|
||||
|
||||
String baseTable = addHistoryTable.getBaseTable();
|
||||
MTable table = writer.getTable(baseTable);
|
||||
if (table == null) {
|
||||
throw new IllegalStateException("MTable "+baseTable+" not found in writer? (required for history DDL)");
|
||||
}
|
||||
|
||||
createWithHistory(writer, table);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createWithHistory(DdlWrite writer, MTable table) throws IOException {
|
||||
|
||||
String baseTable = table.getName();
|
||||
String whenCreatedColumn = table.getWhenCreatedColumn();
|
||||
|
||||
// rollback changes in appropriate order
|
||||
dropTriggers(writer.rollback(), baseTable);
|
||||
dropHistoryTableEtc(writer.rollback(), baseTable);
|
||||
|
||||
addHistoryTable(writer, table, whenCreatedColumn);
|
||||
addStoredFunction(writer, table, null);
|
||||
createTriggers(writer, table);
|
||||
}
|
||||
|
||||
protected abstract void createTriggers(DdlWrite writer, MTable table) throws IOException;
|
||||
|
||||
protected abstract void dropTriggers(DdlBuffer buffer, String baseTable) throws IOException;
|
||||
|
||||
protected void addStoredFunction(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
protected String normalise(String tableName) {
|
||||
return constraintNaming.normaliseTable(tableName);
|
||||
}
|
||||
|
||||
protected String historyTableName(String baseTableName) {
|
||||
return normalise(baseTableName) + historySuffix;
|
||||
}
|
||||
|
||||
protected String procedureName(String baseTableName) {
|
||||
return normalise(baseTableName) + "_history_version";
|
||||
}
|
||||
|
||||
protected String triggerName(String baseTableName) {
|
||||
return normalise(baseTableName) + "_history_upd";
|
||||
}
|
||||
|
||||
protected String updateTriggerName(String baseTableName) {
|
||||
return normalise(baseTableName) + "_history_upd";
|
||||
}
|
||||
|
||||
protected String deleteTriggerName(String baseTableName) {
|
||||
return normalise(baseTableName) + "_history_del";
|
||||
}
|
||||
|
||||
protected void addHistoryTable(DdlWrite writer, MTable table, String whenCreatedColumn) throws IOException {
|
||||
|
||||
String baseTableName = table.getName();
|
||||
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
|
||||
addSysPeriodColumns(apply, baseTableName, whenCreatedColumn);
|
||||
createHistoryTable(apply, table);
|
||||
createWithHistoryView(apply, baseTableName);
|
||||
}
|
||||
|
||||
protected void addSysPeriodColumns(DdlBuffer apply, String baseTableName, String whenCreatedColumn) throws IOException {
|
||||
|
||||
apply.append("alter table ").append(baseTableName).append(" add column ")
|
||||
.append(sysPeriodStart).append(" ").append(sysPeriodType).append(" default ").append(currentTimestamp).endOfStatement();
|
||||
apply.append("alter table ").append(baseTableName).append(" add column ")
|
||||
.append(sysPeriodEnd).append(" ").append(sysPeriodType).endOfStatement();
|
||||
|
||||
if (whenCreatedColumn != null) {
|
||||
apply.append("update ").append(baseTableName).append(" set ").append(sysPeriodStart).append(" = ").append(whenCreatedColumn).endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
protected void createHistoryTable(DdlBuffer apply, MTable table) throws IOException {
|
||||
|
||||
apply.append("create table ").append(table.getName()).append(historySuffix).append("(").newLine();
|
||||
|
||||
Collection<MColumn> cols = table.getColumns().values();
|
||||
for (MColumn column : cols) {
|
||||
writeColumnDefinition(apply, column.getName(), column.getType());
|
||||
apply.append(",").newLine();
|
||||
}
|
||||
writeColumnDefinition(apply, sysPeriodStart, sysPeriodType);
|
||||
apply.append(",").newLine();
|
||||
writeColumnDefinition(apply, sysPeriodEnd, sysPeriodType);
|
||||
apply.newLine().append(")").endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the column definition to the create table statement.
|
||||
*/
|
||||
protected void writeColumnDefinition(DdlBuffer buffer, String columnName, String type) throws IOException {
|
||||
|
||||
String platformType = platformDdl.convert(type, false);
|
||||
buffer.append(" ");
|
||||
buffer.append(platformDdl.lowerName(columnName), 30);
|
||||
buffer.append(platformType);
|
||||
}
|
||||
|
||||
protected void createWithHistoryView(DdlBuffer apply, String baseTableName) throws IOException {
|
||||
|
||||
apply
|
||||
.append("create view ").append(baseTableName).append(viewSuffix)
|
||||
.append(" as select * from ").append(baseTableName)
|
||||
.append(" union all select * from ").append(baseTableName).append(historySuffix)
|
||||
.endOfStatement().end();
|
||||
}
|
||||
|
||||
protected void dropHistoryTableEtc(DdlBuffer buffer, String baseTableName) throws IOException {
|
||||
|
||||
buffer.append("drop view ").append(baseTableName).append(viewSuffix).endOfStatement();
|
||||
dropSysPeriodColumns(buffer, baseTableName);
|
||||
buffer.append("drop table ").append(baseTableName).append(historySuffix).endOfStatement().end();
|
||||
}
|
||||
|
||||
protected void dropSysPeriodColumns(DdlBuffer buffer, String baseTableName) throws IOException {
|
||||
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriodStart).endOfStatement();
|
||||
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriodEnd).endOfStatement();
|
||||
}
|
||||
|
||||
//protected abstract void addFunction(DdlBuffer apply, String procedureName, String historyTable, List<String> includedColumns) throws IOException;
|
||||
|
||||
protected void appendInsertIntoHistory(DdlBuffer buffer, String historyTable, List<String> columns) throws IOException {
|
||||
|
||||
buffer.append(" insert into ").append(historyTable).append(" (").append(sysPeriodStart).append(",").append(sysPeriodEnd).append(",");
|
||||
appendColumnNames(buffer, columns, "");
|
||||
buffer.append(") values (OLD.").append(sysPeriodStart).append(", ").append(currentTimestamp).append(",");
|
||||
appendColumnNames(buffer, columns, "OLD.");
|
||||
buffer.append(");").newLine();
|
||||
}
|
||||
|
||||
protected void appendColumnNames(DdlBuffer buffer, List<String> columns, String columnPrefix) throws IOException {
|
||||
|
||||
for (int i=0; i< columns.size(); i++) {
|
||||
if (i > 0) {
|
||||
buffer.append(", ");
|
||||
}
|
||||
buffer.append(columnPrefix);
|
||||
buffer.append(columns.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of included columns in order.
|
||||
*/
|
||||
protected List<String> includedColumnNames(MTable table) throws IOException {
|
||||
|
||||
Collection<MColumn> columns = table.getColumns().values();
|
||||
List<String> includedColumns = new ArrayList<String>(columns.size());
|
||||
|
||||
for (MColumn column : columns) {
|
||||
if (!column.isHistoryExclude()) {
|
||||
includedColumns.add(column.getName());
|
||||
}
|
||||
}
|
||||
return includedColumns;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.config.DbConstraintNaming;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.util.VowelRemover;
|
||||
|
||||
/**
|
||||
* Default implementation used to truncate or shorten db constraint names as required.
|
||||
*/
|
||||
public class DefaultConstraintMaxLength implements DbConstraintNaming.MaxLength {
|
||||
|
||||
private final int maxConstraintNameLength;
|
||||
|
||||
public DefaultConstraintMaxLength(int maxConstraintNameLength) {
|
||||
this.maxConstraintNameLength = maxConstraintNameLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a maximum length to the constraint name.
|
||||
* <p>
|
||||
* This implementation should work well apart from perhaps DB2 where the limit is 18.
|
||||
*/
|
||||
public String maxLength(String constraintName, int count) {
|
||||
if (constraintName.length() < maxConstraintNameLength) {
|
||||
return constraintName;
|
||||
}
|
||||
if (maxConstraintNameLength < 60) {
|
||||
// trim out vowels for Oracle / DB2 with short max lengths
|
||||
constraintName = VowelRemover.trim(constraintName, 4);
|
||||
if (constraintName.length() < maxConstraintNameLength) {
|
||||
return constraintName;
|
||||
}
|
||||
}
|
||||
// add the count to ensure the constraint name is unique
|
||||
// (relying on the prefix having the table name to be globally unique)
|
||||
return constraintName.substring(0, maxConstraintNameLength - 3) + "_" + count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbIdentity;
|
||||
import com.avaje.ebean.config.dbplatform.DbTypeMap;
|
||||
|
||||
/**
|
||||
* H2 platform specific DDL.
|
||||
*/
|
||||
public class H2Ddl extends PlatformDdl {
|
||||
|
||||
public H2Ddl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
|
||||
super(platformTypes, dbIdentity);
|
||||
}
|
||||
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Bean holding comments relating to a history table that needs to have it's
|
||||
* associated triggers/function updated due to columns added/dropped/included
|
||||
* or excluded.
|
||||
*/
|
||||
public class HistoryTableUpdate {
|
||||
|
||||
|
||||
/**
|
||||
* Column change type.
|
||||
*/
|
||||
public enum Change {
|
||||
ADD,
|
||||
DROP,
|
||||
INCLUDE,
|
||||
EXCLUDE
|
||||
}
|
||||
|
||||
public static class Column {
|
||||
|
||||
public final Change change;
|
||||
public final String column;
|
||||
public Column(Change change, String column) {
|
||||
this.change = change;
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
public String description() {
|
||||
return change.name().toLowerCase()+" "+column;
|
||||
}
|
||||
|
||||
public void apply(List<String> includedColumns) {
|
||||
switch (change) {
|
||||
case ADD:
|
||||
case INCLUDE: {
|
||||
includedColumns.remove(column);
|
||||
break;
|
||||
}
|
||||
case DROP:
|
||||
case EXCLUDE: {
|
||||
includedColumns.add(column);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected change "+change);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final String baseTable;
|
||||
|
||||
private final List<Column> columnChanges = new ArrayList<Column>();
|
||||
|
||||
/**
|
||||
* Construct with a given base table name.
|
||||
*/
|
||||
public HistoryTableUpdate(String baseTable) {
|
||||
this.baseTable = baseTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description of the changes that cause the history trigger/function
|
||||
* to be regenerated (added, dropped, included or excluded columns).
|
||||
*/
|
||||
public String description() {
|
||||
StringBuilder sb = new StringBuilder(90);
|
||||
for (int i = 0; i < columnChanges.size(); i++) {
|
||||
if (i > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(columnChanges.get(i).description());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void toRevertedColumns(List<String> includedColumns) {
|
||||
|
||||
for (Column columnChange : columnChanges) {
|
||||
columnChange.apply(includedColumns);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a comment for column added, dropped, included or excluded.
|
||||
*/
|
||||
public void add(Change change, String column) {
|
||||
columnChanges.add(new Column(change, column));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base table name.
|
||||
*/
|
||||
public String getBaseTable() {
|
||||
return baseTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the comments.
|
||||
*/
|
||||
public List<Column> getColumnChanges() {
|
||||
return columnChanges;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbIdentity;
|
||||
import com.avaje.ebean.config.dbplatform.DbTypeMap;
|
||||
|
||||
/**
|
||||
* Hsqldb platform specific DDL.
|
||||
*/
|
||||
public class HsqldbDdl extends PlatformDdl {
|
||||
|
||||
public HsqldbDdl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
|
||||
super(platformTypes, dbIdentity);
|
||||
this.identitySuffix = " generated by default as identity (start with 1) ";
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user