提交 0bedabe6 authored 作者: james.moger@gmail.com's avatar james.moger@gmail.com

JaQu annotations, model generation, model validation, extended syntax (issue #286)

上级 e0228642
......@@ -65,7 +65,9 @@ import org.h2.test.db.TestView;
import org.h2.test.db.TestViewAlterTable;
import org.h2.test.db.TestViewDropView;
import org.h2.test.jaqu.AliasMapTest;
import org.h2.test.jaqu.AnnotationsTest;
import org.h2.test.jaqu.ClobTest;
import org.h2.test.jaqu.ModelsTest;
import org.h2.test.jaqu.SamplesTest;
import org.h2.test.jaqu.UpdateTest;
import org.h2.test.jdbc.TestBatchUpdates;
......@@ -588,6 +590,8 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new ClobTest().runTest(this);
new SamplesTest().runTest(this);
new UpdateTest().runTest(this);
new AnnotationsTest().runTest(this);
new ModelsTest().runTest(this);
// jdbc
new TestBatchUpdates().runTest(this);
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.test.jaqu;
import java.util.List;
import org.h2.constant.ErrorCode;
import org.h2.jaqu.Db;
import org.h2.jdbc.JdbcSQLException;
import org.h2.test.TestBase;
public class AnnotationsTest extends TestBase {
/**
* This object represents a database (actually a connection to the database).
*/
//## Java 1.5 begin ##
Db db;
//## Java 1.5 end ##
/**
* This method is called when executing this application from the command
* line.
*
* @param args the command line parameters
*/
public static void main(String... args) {
new AnnotationsTest().test();
}
public void test() {
//## Java 1.5 begin ##
// EventLogger.activateConsoleLogger();
db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.insertAll(Product.getList());
db.insertAll(ProductAnnotationOnly.getList());
db.insertAll(ProductMixedAnnotation.getList());
testProductAnnotationOnly();
testProductMixedAnnotation();
testTrimStringAnnotation();
testCreateTableIfRequiredAnnotation();
testColumnInheritanceAnnotation();
db.close();
// EventLogger.deactivateConsoleLogger();
//## Java 1.5 end ##
}
private void testProductAnnotationOnly() {
ProductAnnotationOnly p = new ProductAnnotationOnly();
assertEquals(10, db.from(p).selectCount());
// Test JQColumn.name="cat"
assertEquals(2, db.from(p).where(p.category).is("Beverages").selectCount());
// Test JQTable.annotationsOnly=true
// public String unmappedField is ignored by JaQu
assertEquals(0, db.from(p).where(p.unmappedField).is("unmapped").selectCount());
// Test JQColumn.autoIncrement=true
// 10 objects, 10 autoIncremented unique values
assertEquals(10, db.from(p).selectDistinct(p.autoIncrement).size());
// Test JQTable.primaryKey=id
int errorCode = 0;
try {
db.insertAll(ProductAnnotationOnly.getList());
} catch (Throwable t) {
if (t.getCause() instanceof JdbcSQLException) {
JdbcSQLException s = (JdbcSQLException) t.getCause();
errorCode = s.getErrorCode();
}
}
assertEquals(errorCode, ErrorCode.DUPLICATE_KEY_1);
}
private void testProductMixedAnnotation() {
ProductMixedAnnotation p = new ProductMixedAnnotation();
// Test JQColumn.name="cat"
assertEquals(2, db.from(p).where(p.category).is("Beverages").selectCount());
// Test JQTable.annotationsOnly=false
// public String mappedField is reflectively mapped by JaQu
assertEquals(10, db.from(p).where(p.mappedField).is("mapped").selectCount());
// Test JQColumn.primaryKey=true
int errorCode = 0;
try {
db.insertAll(ProductMixedAnnotation.getList());
} catch (Throwable t) {
if (t.getCause() instanceof JdbcSQLException) {
JdbcSQLException s = (JdbcSQLException) t.getCause();
errorCode = s.getErrorCode();
}
}
assertEquals(errorCode, ErrorCode.DUPLICATE_KEY_1);
}
private void testTrimStringAnnotation() {
ProductAnnotationOnly p = new ProductAnnotationOnly();
ProductAnnotationOnly prod = db.from(p).selectFirst();
String oldValue = prod.category;
String newValue = "01234567890123456789";
prod.category = newValue; // 2 chars exceeds field max
db.update(prod);
ProductAnnotationOnly newProd = db.from(p)
.where(p.productId)
.is(prod.productId)
.selectFirst();
assertEquals(newValue.substring(0, 15), newProd.category);
newProd.category = oldValue;
db.update(newProd);
}
private void testColumnInheritanceAnnotation() {
ProductInheritedAnnotation table = new ProductInheritedAnnotation();
Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
List<ProductInheritedAnnotation> inserted = ProductInheritedAnnotation.getData();
db.insertAll(inserted);
List<ProductInheritedAnnotation> retrieved = db.from(table).select();
for (int j = 0; j < retrieved.size(); j++) {
ProductInheritedAnnotation i = inserted.get(j);
ProductInheritedAnnotation r = retrieved.get(j);
assertEquals(i.category, r.category);
assertEquals(i.mappedField, r.mappedField);
assertEquals(i.unitsInStock, r.unitsInStock);
assertEquals(i.unitPrice, r.unitPrice);
assertEquals(i.name(), r.name());
assertEquals(i.id(), r.id());
}
db.close();
}
private void testCreateTableIfRequiredAnnotation() {
// Tests JQTable.createTableIfRequired=false
int errorCode = 0;
try {
Db noCreateDb = Db.open("jdbc:h2:mem:", "sa", "sa");
noCreateDb.insertAll(ProductNoCreateTable.getList());
noCreateDb.close();
} catch (Throwable e) {
if (e.getCause() instanceof JdbcSQLException) {
JdbcSQLException error = (JdbcSQLException) e.getCause();
errorCode = error.getErrorCode();
}
}
assertTrue(errorCode == ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1);
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.test.jaqu;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.h2.jaqu.Db;
import org.h2.jaqu.DbInspector;
import org.h2.jaqu.DbUpgrader;
import org.h2.jaqu.DbVersion;
import org.h2.jaqu.Table.JQDatabase;
import org.h2.jaqu.Validation;
import org.h2.test.TestBase;
import org.h2.test.jaqu.SupportedTypes.SupportedTypes2;
public class ModelsTest extends TestBase {
/**
* This object represents a database (actually a connection to the database).
*/
//## Java 1.5 begin ##
Db db;
//## Java 1.5 end ##
/**
* This method is called when executing this application from the command
* line.
*
* @param args the command line parameters
*/
public static void main(String... args) {
new ModelsTest().test();
}
public void test() {
//## Java 1.5 begin ##
db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.insertAll(Product.getList());
db.insertAll(ProductAnnotationOnly.getList());
db.insertAll(ProductMixedAnnotation.getList());
testValidateModels();
testSupportedTypes();
testModelGeneration();
testDatabaseUpgrade();
testTableUpgrade();
db.close();
//## Java 1.5 end ##
}
private void testValidateModels() {
boolean verbose = false;
DbInspector inspector = new DbInspector(db);
validateModel(inspector, new Product(), verbose);
validateModel(inspector, new ProductAnnotationOnly(), verbose);
validateModel(inspector, new ProductMixedAnnotation(), verbose);
}
private void validateModel(DbInspector inspector, Object o, boolean verbose) {
List<Validation> remarks = inspector.validateModel(o, false);
if (verbose && remarks.size() > 0) {
System.out.println("Validation Remarks for " + o.getClass().getName());
System.out.println("=============================================");
for (Validation remark:remarks)
System.out.println(remark);
System.out.println();
}
for (Validation remark:remarks)
assertFalse(remark.toString(), remark.isError());
}
private void testSupportedTypes() {
List<SupportedTypes> original = SupportedTypes.createList();
db.insertAll(original);
List<SupportedTypes> retrieved = db.from(SupportedTypes.SAMPLE).select();
assertEquals(original.size(), retrieved.size());
for (int i = 0; i < original.size(); i++) {
SupportedTypes o = original.get(i);
SupportedTypes r = retrieved.get(i);
assertTrue(o.equivalentTo(r));
}
}
private void testModelGeneration() {
DbInspector inspector = new DbInspector(db);
List<String> models = inspector.generateModel(null, "SupportedTypes",
"org.h2.test.jaqu", true, true);
assertEquals(1, models.size());
// a poor test, but a start
assertEquals(1364, models.get(0).length());
}
private void testDatabaseUpgrade() {
Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
// Insert a Database version record
db.insert(new DbVersion(1));
TestDbUpgrader dbUpgrader = new TestDbUpgrader();
db.setDbUpgrader(dbUpgrader);
List<SupportedTypes> original = SupportedTypes.createList();
db.insertAll(original);
assertEquals(1, dbUpgrader.oldVersion.get());
assertEquals(2, dbUpgrader.newVersion.get());
db.close();
}
private void testTableUpgrade() {
Db db = Db.open("jdbc:h2:mem:", "sa", "sa");
// Insert first, this will create version record automatically
List<SupportedTypes> original = SupportedTypes.createList();
db.insertAll(original);
// Reset the dbUpgrader (clears updatecheck cache)
TestDbUpgrader dbUpgrader = new TestDbUpgrader();
db.setDbUpgrader(dbUpgrader);
SupportedTypes2 s2 = new SupportedTypes2();
List<SupportedTypes2> types = db.from(s2).select();
assertEquals(10, types.size());
assertEquals(1, dbUpgrader.oldVersion.get());
assertEquals(2, dbUpgrader.newVersion.get());
db.close();
}
@JQDatabase(version=2)
private class TestDbUpgrader implements DbUpgrader {
final AtomicInteger oldVersion = new AtomicInteger(0);
final AtomicInteger newVersion = new AtomicInteger(0);
@Override
public boolean upgradeTable(Db db, String schema, String table,
int fromVersion, int toVersion) {
// Sample DbUpgrader just claims success on upgrade request
oldVersion.set(fromVersion);
newVersion.set(toVersion);
return true;
}
@Override
public boolean upgradeDatabase(Db db, int fromVersion, int toVersion) {
// Sample DbUpgrader just claims success on upgrade request
oldVersion.set(fromVersion);
newVersion.set(toVersion);
return true;
}
};
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, Version
* 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html). Initial Developer: H2 Group
*/
package org.h2.test.jaqu;
// ## Java 1.5 begin ##
import java.util.Arrays;
import java.util.List;
import org.h2.jaqu.Table.JQColumn;
import org.h2.jaqu.Table.JQIndex;
import org.h2.jaqu.Table.JQTable;
/**
* A table containing product data.
*/
// ## Java 1.5 begin ##
@JQTable(name = "AnnotatedProduct", primaryKey = "id")
@JQIndex(standard = "name, cat")
public class ProductAnnotationOnly {
@JQColumn(name = "id")
Integer productId;
@JQColumn(name = "name")
private String productName;
@JQColumn(name = "cat", maxLength = 15, trimString=true)
String category;
@SuppressWarnings("unused")
@JQColumn
private Double unitPrice;
@JQColumn
private Integer unitsInStock;
@JQColumn(autoIncrement=true)
public Integer autoIncrement;
public String unmappedField;
public ProductAnnotationOnly() {
// public constructor
}
private ProductAnnotationOnly(int productId, String productName, String category, double unitPrice,
int unitsInStock, String unmappedField) {
this.productId = productId;
this.productName = productName;
this.category = category;
this.unitPrice = unitPrice;
this.unitsInStock = unitsInStock;
this.unmappedField = unmappedField;
}
private static ProductAnnotationOnly create(int productId, String productName, String category, double unitPrice,
int unitsInStock, String unmappedField) {
return new ProductAnnotationOnly(productId, productName, category, unitPrice, unitsInStock, unmappedField);
}
public static List<ProductAnnotationOnly> getList() {
String unmappedField = "unmapped";
ProductAnnotationOnly[] list = { create(1, "Chai", "Beverages", 18, 39, unmappedField),
create(2, "Chang", "Beverages", 19.0, 17, unmappedField),
create(3, "Aniseed Syrup", "Condiments", 10.0, 13, unmappedField),
create(4, "Chef Anton's Cajun Seasoning", "Condiments", 22.0, 53, unmappedField),
create(5, "Chef Anton's Gumbo Mix", "Condiments", 21.3500, 0, unmappedField),
create(6, "Grandma's Boysenberry Spread", "Condiments", 25.0, 120, unmappedField),
create(7, "Uncle Bob's Organic Dried Pears", "Produce", 30.0, 15, unmappedField),
create(8, "Northwoods Cranberry Sauce", "Condiments", 40.0, 6, unmappedField),
create(9, "Mishi Kobe Niku", "Meat/Poultry", 97.0, 29, unmappedField),
create(10, "Ikura", "Seafood", 31.0, 31, unmappedField), };
return Arrays.asList(list);
}
public String toString() {
return productName + ": " + unitsInStock;
}
}
// ## Java 1.5 end ##
package org.h2.test.jaqu;
import java.util.Arrays;
import java.util.List;
import org.h2.jaqu.Table.JQTable;
/**
* This class inherits all its fields from a parent class which has annotated
* columns. The JQTable annotation of the parent class is ignored and only
* the JQTable annotation of this class matters.
* <p>
* However, this table inherits JQColumns from its super class.
*/
@JQTable(inheritColumns = true, annotationsOnly = false)
public class ProductInheritedAnnotation extends ProductMixedAnnotation {
public ProductInheritedAnnotation() {
// public constructor
}
private ProductInheritedAnnotation(int productId, String productName, String category, double unitPrice,
int unitsInStock, String mappedField) {
super(productId, productName, category, unitPrice, unitsInStock, mappedField);
}
private static ProductInheritedAnnotation create(int productId, String productName, String category,
double unitPrice, int unitsInStock, String mappedField) {
return new ProductInheritedAnnotation(productId, productName, category, unitPrice, unitsInStock, mappedField);
}
public static List<ProductInheritedAnnotation> getData() {
String mappedField = "mapped";
ProductInheritedAnnotation[] list = { create(1, "Chai", "Beverages", 18, 39, mappedField),
create(2, "Chang", "Beverages", 19.0, 17, mappedField),
create(3, "Aniseed Syrup", "Condiments", 10.0, 13, mappedField),
create(4, "Chef Anton's Cajun Seasoning", "Condiments", 22.0, 53, mappedField),
create(5, "Chef Anton's Gumbo Mix", "Condiments", 21.3500, 0, mappedField),
create(6, "Grandma's Boysenberry Spread", "Condiments", 25.0, 120, mappedField),
create(7, "Uncle Bob's Organic Dried Pears", "Produce", 30.0, 15, mappedField),
create(8, "Northwoods Cranberry Sauce", "Condiments", 40.0, 6, mappedField),
create(9, "Mishi Kobe Niku", "Meat/Poultry", 97.0, 29, mappedField),
create(10, "Ikura", "Seafood", 31.0, 31, mappedField), };
return Arrays.asList(list);
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, Version
* 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html). Initial Developer: H2 Group
*/
package org.h2.test.jaqu;
// ## Java 1.5 begin ##
import java.util.Arrays;
import java.util.List;
import org.h2.jaqu.Table.JQColumn;
import org.h2.jaqu.Table.JQIndex;
import org.h2.jaqu.Table.JQTable;
/**
* A table containing product data.
*/
// ## Java 1.5 begin ##
@JQTable(annotationsOnly = false)
@JQIndex(standard="name,cat")
public class ProductMixedAnnotation {
@JQColumn(name = "id", primaryKey=true)
private Integer productId;
@JQColumn(name = "name")
private String productName;
@JQColumn(name = "cat", maxLength = 255)
String category;
public Double unitPrice;
public Integer unitsInStock;
public String mappedField;
public ProductMixedAnnotation() {
// public constructor
}
protected ProductMixedAnnotation(int productId, String productName, String category, double unitPrice,
int unitsInStock, String mappedField) {
this.productId = productId;
this.productName = productName;
this.category = category;
this.unitPrice = unitPrice;
this.unitsInStock = unitsInStock;
this.mappedField = mappedField;
}
private static ProductMixedAnnotation create(int productId, String productName, String category, double unitPrice,
int unitsInStock, String mappedField) {
return new ProductMixedAnnotation(productId, productName, category, unitPrice, unitsInStock, mappedField);
}
public static List<ProductMixedAnnotation> getList() {
String mappedField = "mapped";
ProductMixedAnnotation[] list = { create(1, "Chai", "Beverages", 18, 39, mappedField),
create(2, "Chang", "Beverages", 19.0, 17, mappedField),
create(3, "Aniseed Syrup", "Condiments", 10.0, 13, mappedField),
create(4, "Chef Anton's Cajun Seasoning", "Condiments", 22.0, 53, mappedField),
create(5, "Chef Anton's Gumbo Mix", "Condiments", 21.3500, 0, mappedField),
create(6, "Grandma's Boysenberry Spread", "Condiments", 25.0, 120, mappedField),
create(7, "Uncle Bob's Organic Dried Pears", "Produce", 30.0, 15, mappedField),
create(8, "Northwoods Cranberry Sauce", "Condiments", 40.0, 6, mappedField),
create(9, "Mishi Kobe Niku", "Meat/Poultry", 97.0, 29, mappedField),
create(10, "Ikura", "Seafood", 31.0, 31, mappedField), };
return Arrays.asList(list);
}
public String toString() {
return productName + ": " + unitsInStock;
}
public int id() {
return productId;
}
public String name() {
return productName;
}
}
// ## Java 1.5 end ##
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, Version
* 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html). Initial Developer: H2 Group
*/
package org.h2.test.jaqu;
// ## Java 1.5 begin ##
import java.util.Arrays;
import java.util.List;
import org.h2.jaqu.Table.JQColumn;
import org.h2.jaqu.Table.JQTable;
/**
* A table containing product data.
*/
// ## Java 1.5 begin ##
@JQTable(createIfRequired = false)
public class ProductNoCreateTable {
@SuppressWarnings("unused")
@JQColumn(name = "id")
private Integer productId;
@SuppressWarnings("unused")
@JQColumn(name = "name")
private String productName;
public ProductNoCreateTable() {
// public constructor
}
private ProductNoCreateTable(int productId, String productName) {
this.productId = productId;
this.productName = productName;
}
private static ProductNoCreateTable create(int productId, String productName) {
return new ProductNoCreateTable(productId, productName);
}
public static List<ProductNoCreateTable> getList() {
ProductNoCreateTable[] list = { create(1, "Chai"), create(2, "Chang") };
return Arrays.asList(list);
}
}
// ## Java 1.5 end ##
......@@ -9,9 +9,14 @@ package org.h2.test.jaqu;
import static org.h2.jaqu.Function.count;
import static org.h2.jaqu.Function.isNull;
import static org.h2.jaqu.Function.length;
import static org.h2.jaqu.Function.*;
import static org.h2.jaqu.Function.max;
import static org.h2.jaqu.Function.min;
import static org.h2.jaqu.Function.not;
import static org.h2.jaqu.Function.sum;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.h2.jaqu.Db;
import org.h2.jaqu.Filter;
import org.h2.test.TestBase;
......@@ -73,6 +78,8 @@ public class SamplesTest extends TestBase {
testWhereSimple2();
testWhereSimple3();
testReverseColumns();
testLimitOffset();
testKeyRetrieval();
db.close();
//## Java 1.5 end ##
}
......@@ -265,6 +272,9 @@ public class SamplesTest extends TestBase {
deleted = db.from(p).delete();
assertEquals(9, deleted);
db.insertAll(Product.getList());
db.deleteAll(Product.getList());
assertEquals(0, db.from(p).selectCount());
db.insertAll(Product.getList());
}
private void testOrAndNot() {
......@@ -376,6 +386,25 @@ public class SamplesTest extends TestBase {
assertEquals(1, count);
}
private void testLimitOffset() {
Set<Integer> ids = new HashSet<Integer>();
Product p = new Product();
for (int i = 0; i < 5; i++) {
List<Product> products = db.from(p).limit(2).offset(2*i).select();
assertTrue(products.size() == 2);
for (Product prod:products)
assertTrue("Failed to add product id. Duplicate?", ids.add(prod.productId));
}
}
private void testKeyRetrieval() {
List<SupportedTypes> list = SupportedTypes.createList();
List<Long> keys = db.insertAllAndGetKeys(list);
Set<Long> uniqueKeys = new HashSet<Long>();
for (Long l:keys)
assertTrue("Failed to add key. Duplicate?", uniqueKeys.add(l));
}
//## Java 1.5 end ##
/**
......
package org.h2.test.jaqu;
import java.math.BigDecimal;
import java.util.List;
import java.util.Random;
import org.h2.jaqu.Table.JQColumn;
import org.h2.jaqu.Table.JQTable;
import org.h2.util.New;
@JQTable(strictTypeMapping=true, version=1)
public class SupportedTypes {
@JQColumn(primaryKey=true, autoIncrement=true)
public Integer id;
@JQColumn
private Boolean myBool = false;
@JQColumn
private Byte myByte = 2;
@JQColumn
private Short myShort;
@JQColumn
private Integer myInteger;
@JQColumn
private Long myLong;
@JQColumn
private Float myFloat = 1.0f;
@JQColumn
private Double myDouble;
@JQColumn
private BigDecimal myBigDecimal;
@JQColumn
private String myString;
@JQColumn
private java.util.Date myUtilDate;
@JQColumn
private java.sql.Date mySqlDate;
@JQColumn
private java.sql.Time mySqlTime;
@JQColumn
private java.sql.Timestamp mySqlTimestamp;
static SupportedTypes SAMPLE = new SupportedTypes();
static List<SupportedTypes> createList() {
List<SupportedTypes> list = New.arrayList();
for (int i = 0; i < 10; i++)
list.add(randomValue());
return list;
}
static SupportedTypes randomValue() {
Random rand = new Random();
SupportedTypes s = new SupportedTypes();
s.myBool = new Boolean(rand.nextBoolean());
s.myByte = new Byte((byte) rand.nextInt(Byte.MAX_VALUE));
s.myShort = new Short((short) rand.nextInt(Short.MAX_VALUE));
s.myInteger = new Integer(rand.nextInt());
s.myLong = new Long(rand.nextLong());
s.myFloat = new Float(rand.nextFloat());
s.myDouble = new Double(rand.nextDouble());
s.myBigDecimal = new BigDecimal(rand.nextDouble());
s.myString = Long.toHexString(rand.nextLong());
s.myUtilDate = new java.util.Date(rand.nextLong());
s.mySqlDate = new java.sql.Date(rand.nextLong());
s.mySqlTime = new java.sql.Time(rand.nextLong());
s.mySqlTimestamp = new java.sql.Timestamp(rand.nextLong());
return s;
}
public boolean equivalentTo(SupportedTypes s) {
boolean same = true;
same &= myBool.equals(s.myBool);
same &= myByte.equals(s.myByte);
same &= myShort.equals(s.myShort);
same &= myInteger.equals(s.myInteger);
same &= myLong.equals(s.myLong);
same &= myFloat.equals(s.myFloat);
same &= myDouble.equals(s.myDouble);
same &= myBigDecimal.equals(s.myBigDecimal);
same &= myUtilDate.getTime() == s.myUtilDate.getTime();
same &= mySqlTimestamp.getTime() == s.mySqlTimestamp.getTime();
same &= mySqlDate.toString().equals(s.mySqlDate.toString());
same &= mySqlTime.toString().equals(s.mySqlTime.toString());
return same;
}
/**
* Class to demonstrate TableUpdater
*
*/
@JQTable(name="SupportedTypes", version=2, inheritColumns=true, strictTypeMapping=true)
public static class SupportedTypes2 extends SupportedTypes {
public SupportedTypes2() {
}
}
}
......@@ -6,11 +6,11 @@
*/
package org.h2.test.jaqu;
import static java.sql.Date.valueOf;
import org.h2.jaqu.Db;
import org.h2.jaqu.util.StatementLogger;
import org.h2.test.TestBase;
import static java.sql.Date.valueOf;
/**
* Tests the Db.update() function.
*
......@@ -31,6 +31,8 @@ public class UpdateTest extends TestBase {
}
public void test() throws Exception {
// EventLogger.activateConsoleLogger();
db = Db.open("jdbc:h2:mem:", "sa", "sa");
db.insertAll(Product.getList());
db.insertAll(Customer.getList());
......@@ -40,8 +42,10 @@ public class UpdateTest extends TestBase {
testSimpleUpdateWithCombinedPrimaryKey();
testSimpleMerge();
testSimpleMergeWithCombinedPrimaryKey();
testSetColumns();
db.close();
// EventLogger.deactivateConsoleLogger();
}
private void testSimpleUpdate() {
......@@ -111,5 +115,38 @@ public class UpdateTest extends TestBase {
ourOrder.orderDate = valueOf("2007-01-02");
db.merge(ourOrder);
}
private void testSetColumns() {
Product p = new Product();
Product original = db.from(p).where(p.productId).is(1).selectFirst();
// SetColumn on String and Double
db.from(p)
.set(p.productName).to("updated")
.increment(p.unitPrice).by(3.14)
.increment(p.unitsInStock).by(2)
.where(p.productId)
.is(1).
update();
// Confirm fields were properly updated
Product revised = db.from(p).where(p.productId).is(1).selectFirst();
assertEquals("updated", revised.productName);
assertEquals(original.unitPrice + 3.14, revised.unitPrice);
assertEquals(original.unitsInStock + 2, revised.unitsInStock.intValue());
// Restore fields
db.from(p)
.set(p.productName).to(original.productName)
.set(p.unitPrice).to(original.unitPrice)
.increment(p.unitsInStock).by(-2)
.where(p.productId).is(1).update();
// Confirm fields were properly restored
Product restored = db.from(p).where(p.productId).is(1).selectFirst();
assertEquals(original.productName, restored.productName);
assertEquals(original.unitPrice, restored.unitPrice);
assertEquals(original.unitsInStock, restored.unitsInStock);
}
}
......@@ -12,16 +12,21 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.sql.DataSource;
import org.h2.jaqu.DbUpgrader.DefaultDbUpgrader;
import org.h2.jaqu.SQLDialect.DefaultSQLDialect;
import org.h2.jaqu.Table.JQDatabase;
import org.h2.jaqu.Table.JQTable;
import org.h2.jaqu.util.JdbcUtils;
import org.h2.jaqu.util.StringUtils;
import org.h2.jaqu.util.Utils;
import org.h2.jaqu.util.WeakIdentityHashMap;
import org.h2.util.JdbcUtils;
//## Java 1.5 end ##
/**
* This class represents a connection to a database.
......@@ -41,9 +46,18 @@ public class Db {
private final Connection conn;
private final Map<Class<?>, TableDefinition<?>> classMap =
Utils.newHashMap();
Db(Connection conn) {
private final SQLDialect dialect;
private DbUpgrader dbUpgrader = new DefaultDbUpgrader();
private final Set<Class<?>> upgradeChecked = Utils.newConcurrentHashSet();
public Db(Connection conn) {
this.conn = conn;
dialect = getDialect(conn.getClass().getCanonicalName());
}
SQLDialect getDialect(String clazz) {
// TODO add special cases here
return new DefaultSQLDialect();
}
static <X> X registerToken(X x, Token token) {
......@@ -105,27 +119,105 @@ public class Db {
public <T> void insert(T t) {
Class<?> clazz = t.getClass();
define(clazz).createTableIfRequired(this).insert(this, t);
upgradeDb().define(clazz).createTableIfRequired(this).insert(this, t, false);
}
public <T> long insertAndGetKey(T t) {
Class<?> clazz = t.getClass();
return upgradeDb().define(clazz).createTableIfRequired(this).insert(this, t, true);
}
public <T> void merge(T t) {
Class<?> clazz = t.getClass();
define(clazz).createTableIfRequired(this).merge(this, t);
upgradeDb().define(clazz).createTableIfRequired(this).merge(this, t);
}
public <T> void update(T t) {
Class<?> clazz = t.getClass();
define(clazz).createTableIfRequired(this).update(this, t);
upgradeDb().define(clazz).createTableIfRequired(this).update(this, t);
}
public <T> void delete(T t) {
Class<?> clazz = t.getClass();
upgradeDb().define(clazz).createTableIfRequired(this).delete(this, t);
}
public <T extends Object> Query<T> from(T alias) {
Class<?> clazz = alias.getClass();
define(clazz).createTableIfRequired(this);
upgradeDb().define(clazz).createTableIfRequired(this);
return Query.from(this, alias);
}
Db upgradeDb() {
if (!upgradeChecked.contains(dbUpgrader.getClass())) {
// Flag as checked immediately because calls are nested.
upgradeChecked.add(dbUpgrader.getClass());
JQDatabase model = dbUpgrader.getClass().getAnnotation(JQDatabase.class);
if (model.version() > 0) {
DbVersion v = new DbVersion();
DbVersion dbVersion =
// (SCHEMA="" && TABLE="") == DATABASE
from(v).where(v.schema).is("").and(v.table).is("").selectFirst();
if (dbVersion == null) {
// Database has no version registration, but model specifies
// version. Insert DbVersion entry and return.
DbVersion newDb = new DbVersion(model.version());
insert(newDb);
} else {
// Database has a version registration,
// check to see if upgrade is required.
if ((model.version() > dbVersion.version)
&& (dbUpgrader != null)) {
// Database is an older version than model.
boolean success = dbUpgrader.upgradeDatabase(this,
dbVersion.version, model.version());
if (success) {
dbVersion.version = model.version();
update(dbVersion);
}
}
}
}
}
return this;
}
<T> void upgradeTable(TableDefinition<T> model) {
if (!upgradeChecked.contains(model.getModelClass())) {
// Flag as checked immediately because calls are nested.
upgradeChecked.add(model.getModelClass());
<T> void createTable(Class<T> clazz) {
define(clazz).createTableIfRequired(this);
if (model.tableVersion > 0) {
// Table is using JaQu version tracking.
DbVersion v = new DbVersion();
String schema = StringUtils.isNullOrEmpty(model.schemaName) ? "" : model.schemaName;
DbVersion dbVersion =
from(v).where(v.schema).like(schema).and(v.table)
.like(model.tableName).selectFirst();
if (dbVersion == null) {
// Table has no version registration, but model specifies
// version. Insert DbVersion entry and return.
DbVersion newTable = new DbVersion(model.tableVersion);
newTable.schema = schema;
newTable.table = model.tableName;
insert(newTable);
} else {
// Table has a version registration.
// Check to see if upgrade is required.
if ((model.tableVersion > dbVersion.version)
&& (dbUpgrader != null)) {
// Table is an older version than model.
boolean success = dbUpgrader.upgradeTable(this, schema,
model.tableName, dbVersion.version, model.tableVersion);
if (success) {
dbVersion.version = model.tableVersion;
update(dbVersion);
}
}
}
}
}
}
<T> TableDefinition<T> define(Class<T> clazz) {
......@@ -138,10 +230,32 @@ public class Db {
T t = instance(clazz);
Table table = (Table) t;
Define.define(def, table);
} else if (clazz.isAnnotationPresent(JQTable.class)) {
// Annotated Class skips Define().define() static initializer
T t = instance(clazz);
def.mapObject(t);
}
}
return def;
}
public synchronized void setDbUpgrader(DbUpgrader upgrader) {
if (upgrader == null)
throw new RuntimeException("DbUpgrader may not be NULL!");
if (!upgrader.getClass().isAnnotationPresent(JQDatabase.class))
throw new RuntimeException("DbUpgrader must be annotated with "
+ JQDatabase.class.getSimpleName() + "!");
this.dbUpgrader = upgrader;
upgradeChecked.clear();
}
SQLDialect getDialect() {
return dialect;
}
public Connection getConnection() {
return conn;
}
public void close() {
try {
......@@ -161,8 +275,30 @@ public class Db {
}
}
PreparedStatement prepare(String sql) {
public <T> List<Long> insertAllAndGetKeys(List<T> list) {
List<Long> identities = new ArrayList<Long>();
for (T t : list) {
identities.add(insertAndGetKey(t));
}
return identities;
}
public <T> void updateAll(List<T> list) {
for (T t : list) {
update(t);
}
}
public <T> void deleteAll(List<T> list) {
for (T t : list) {
delete(t);
}
}
PreparedStatement prepare(String sql, boolean returnKey) {
try {
if (returnKey)
return conn.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
return conn.prepareStatement(sql);
} catch (SQLException e) {
throw new RuntimeException(e);
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import org.h2.jaqu.Table.JQTable;
import org.h2.jaqu.util.JdbcUtils;
import org.h2.jaqu.util.StringUtils;
import org.h2.jaqu.util.Utils;
/**
* Class to inspect a model and a database for the purposes of model validation
* and automatic model generation. This class finds the svailable schemas and
* tables and serves as the entry point for model generation and validation.
*
*/
public class DbInspector {
Db db;
DatabaseMetaData metadata;
Class<? extends java.util.Date> dateClazz = java.util.Date.class;
public DbInspector(Db db) {
this.db = db;
}
/**
* Set the preferred Date class.
* java.util.Date (default)
* java.sql.Timestamp
*
* @param dateClazz
*/
public void setPreferredDateClass(Class<? extends java.util.Date> dateClazz) {
this.dateClazz = dateClazz;
}
/**
* Generates models class skeletons for schemas and tables.
*
* @param schema (optional)
* @param table (required)
* @param packageName (optional)
* @param annotateSchema (includes schema name in annotation)
* @param trimStrings (trims strings to maxLength of column)
* @return List<String> source code models as strings
*/
public List<String> generateModel(String schema, String table,
String packageName, boolean annotateSchema, boolean trimStrings) {
try {
List<String> models = Utils.newArrayList();
List<TableInspector> tables = findTables(schema, table);
for (TableInspector t : tables) {
t.read(metadata);
String model = t.generateModel(packageName, annotateSchema,
trimStrings);
models.add(model);
}
return models;
} catch (SQLException s) {
throw new RuntimeException(s);
}
}
/**
* Validates a model.
*
* @param <T> type of model
* @param model class
* @param throwOnError
* @return
*/
public <T> List<Validation> validateModel(T model, boolean throwOnError) {
try {
TableInspector inspector = findTable(model);
inspector.read(metadata);
Class clazz = model.getClass();
TableDefinition<T> def = db.define(clazz);
return inspector.validate(def, throwOnError);
} catch (SQLException s) {
throw new RuntimeException(s);
}
}
private DatabaseMetaData metadata() throws SQLException {
if (metadata == null)
metadata = db.getConnection().getMetaData();
return metadata;
}
/**
* Attempts to find a table in the database based on the model definition.
*
* @param <T>
* @param model
* @return
* @throws SQLException
*/
private <T> TableInspector findTable(T model) throws SQLException {
Class clazz = model.getClass();
TableDefinition<T> def = db.define(clazz);
boolean forceUpperCase = metadata().storesUpperCaseIdentifiers();
String sname = (forceUpperCase && def.schemaName != null) ?
def.schemaName.toUpperCase() : def.schemaName;
String tname = forceUpperCase ? def.tableName.toUpperCase() : def.tableName;
List<TableInspector> tables = findTables(sname, tname);
return tables.get(0);
}
/**
* Returns a list of tables
*
* @param schema
* @param table
* @return
* @throws SQLException
*/
private List<TableInspector> findTables(String schema, String table) throws SQLException {
ResultSet rs = null;
try {
rs = metadata().getSchemas();
ArrayList<String> schemaList = Utils.newArrayList();
while (rs.next())
schemaList.add(rs.getString("TABLE_SCHEM"));
JdbcUtils.closeSilently(rs);
// Get JaQu Tables table name.
String jaquTables = DbVersion.class.getAnnotation(JQTable.class).name();
List<TableInspector> tables = Utils.newArrayList();
if (schemaList.size() == 0)
schemaList.add(null);
for (String s : schemaList) {
rs = metadata().getTables(null, s, null, new String[] { "TABLE" });
while (rs.next()) {
String t = rs.getString("TABLE_NAME");
if (!t.equalsIgnoreCase(jaquTables))
// Ignore JaQu versions table
tables.add(new TableInspector(s, t,
metadata().storesUpperCaseIdentifiers(), dateClazz));
}
}
if (StringUtils.isNullOrEmpty(schema) && StringUtils.isNullOrEmpty(table)) {
// All schemas and tables
return tables;
} else {
// schema subset OR table subset OR exact match
List<TableInspector> matches = Utils.newArrayList();
for (TableInspector t : tables) {
if (t.matches(schema, table))
matches.add(t);
}
if (matches.size() == 0)
throw new RuntimeException(
MessageFormat.format("Failed to find schema={0} table={1}",
schema == null ? "" : schema, table == null ? "" : table));
return matches;
}
} finally {
JdbcUtils.closeSilently(rs);
}
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, Version
* 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html). Initial Developer: James Moger
*/
package org.h2.jaqu;
import org.h2.jaqu.Table.JQDatabase;
/**
* Interface which defines a class to handle table changes based on model
* versions.
* <p>
* An implementation of <i>DbUpgrader</i> <b>MUST</b> be annotated with the
* <i>JQDatabase</i> annotation. This annotation defines the expected database
* version number.
*
*/
public interface DbUpgrader {
/**
* Defines method interface to handle database upgrades. This method is only
* called if your <i>DbUpgrader</i> implementation is annotated with
* JQDatabase.
*
* @param db
* @param fromVersion
* @param toVersion
* @return Returns <b>true</b> for successful upgrade.<br>
* If update is successful, JaQu automatically updates its version
* registry.
*/
public boolean upgradeDatabase(Db db, int fromVersion, int toVersion);
/**
* Defines method interface to handle table upgrades.
*
* @param db
* @param schema
* @param table
* @param fromVersion
* @param toVersion
* @return Returns <b>true</b> for successful upgrade.<br>
* If update is successful, JaQu automatically updates its version
* registry.
*/
public boolean upgradeTable(Db db, String schema, String table, int fromVersion, int toVersion);
/**
* Default Db Upgrader.
* <p>
* Does <b>NOT</b> handle upgrade requests. Instead, this throws
* RuntimeExceptions.
*/
@JQDatabase(version = 0)
public static class DefaultDbUpgrader implements DbUpgrader {
@Override
public boolean upgradeDatabase(Db db, int fromVersion, int toVersion) {
throw new RuntimeException("Please provide your own DbUpgrader implementation.");
}
@Override
public boolean upgradeTable(Db db, String schema, String table, int fromVersion, int toVersion) {
throw new RuntimeException("Please provide your own DbUpgrader implementation.");
}
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
import org.h2.jaqu.Table.JQColumn;
import org.h2.jaqu.Table.JQTable;
/**
* Model class for JaQu to track db and table versions.
*
*/
@JQTable(name="_jq_versions", primaryKey="schemaName tableName", memoryTable=true)
public class DbVersion {
@JQColumn(name="schemaName", allowNull=false)
String schema = "";
@JQColumn(name="tableName", allowNull = false)
String table = "";
@JQColumn(name="version")
Integer version;
public DbVersion() {
}
/**
* Constructor for defining a version entry.
* (SCHEMA="" && TABLE="") == DATABASE
*
* @param version
*/
public DbVersion(int version) {
this.schema = "";
this.table = "";
this.version = version;
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
/**
* Classes implementing this interface can be used as a declaration in a statement.
*/
public interface Declaration {
/**
* Append the SQL to the given statement using the given query.
*
* @param stat the statement to append the SQL to
*/
//## Java 1.5 begin ##
void appendSQL(SQLStatement stat);
//## Java 1.5 end ##
}
......@@ -6,6 +6,8 @@
*/
package org.h2.jaqu;
import org.h2.jaqu.Table.IndexType;
/**
* This class provides utility methods to define primary keys, indexes, and set
* the name of the table.
......@@ -23,7 +25,22 @@ public class Define {
public static void index(Object... columns) {
checkInDefine();
currentTableDefinition.addIndex(columns);
currentTableDefinition.addIndex(IndexType.STANDARD, columns);
}
public static void uniqueIndex(Object... columns) {
checkInDefine();
currentTableDefinition.addIndex(IndexType.UNIQUE, columns);
}
public static void hashIndex(Object column) {
checkInDefine();
currentTableDefinition.addIndex(IndexType.HASH, new Object [] { column });
}
public static void uniqueHashIndex(Object column) {
checkInDefine();
currentTableDefinition.addIndex(IndexType.UNIQUE_HASH, new Object [] { column });
}
public static void maxLength(Object column, int length) {
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
/**
* This class represents a "column=(column + 1)" token for a SET statement.
*
* @param <A> the new value data type
*/
//## Java 1.5 begin ##
public class IncrementColumn<T, A> implements Declaration {
private Query<T> query;
private A x;
private A y;
IncrementColumn(Query<T> query, A x) {
this.query = query;
this.x = x;
}
public Query<T> by(A y) {
query.addDeclarationToken(this);
this.y = y;
return query;
}
@Override
public void appendSQL(SQLStatement stat) {
query.appendSQL(stat, x);
stat.appendSQL("=(");
query.appendSQL(stat, x);
if (y instanceof Number) {
Number n = (Number) y;
if (n.doubleValue() > 0)
stat.appendSQL("+");
}
stat.appendSQL(y.toString());
stat.appendSQL(")");
}
}
//## Java 1.5 end ##
差异被折叠。
......@@ -8,6 +8,7 @@ package org.h2.jaqu;
//## Java 1.5 begin ##
import java.lang.reflect.Field;
import java.sql.Clob;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
......@@ -15,9 +16,10 @@ import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import org.h2.jaqu.bytecode.ClassReader;
import org.h2.jaqu.util.StatementLogger;
import org.h2.jaqu.util.JdbcUtils;
import org.h2.jaqu.util.Utils;
import org.h2.util.JdbcUtils;
import org.h2.util.New;
//import org.h2.util.JdbcUtils;
//## Java 1.5 end ##
/**
......@@ -31,10 +33,13 @@ public class Query<T> {
private Db db;
private SelectTable<T> from;
private ArrayList<Token> conditions = Utils.newArrayList();
private ArrayList<Declaration> declarations = Utils.newArrayList();
private ArrayList<SelectTable<?>> joins = Utils.newArrayList();
private final IdentityHashMap<Object, SelectColumn<T>> aliasMap = Utils.newIdentityHashMap();
private ArrayList<OrderExpression<T>> orderByList = Utils.newArrayList();
private Object[] groupByExpressions;
private long limit = 0;
private long offset = 0;
Query(Db db) {
this.db = db;
......@@ -60,7 +65,7 @@ public class Query<T> {
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JdbcUtils.closeSilently(rs);
JdbcUtils.closeSilently(rs, true);
}
}
......@@ -102,7 +107,7 @@ public class Query<T> {
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JdbcUtils.closeSilently(rs);
JdbcUtils.closeSilently(rs, true);
}
return result;
}
......@@ -112,8 +117,36 @@ public class Query<T> {
stat.appendSQL("DELETE FROM ");
from.appendSQL(stat);
appendWhere(stat);
StatementLogger.delete(stat.getSQL());
return stat.executeUpdate();
}
public <A> SetColumn<T, A> set(A field) {
return new SetColumn<T, A>(this, field);
}
public <A> IncrementColumn<T, A> increment(A field) {
return new IncrementColumn<T, A>(this, field);
}
public int update() {
if (declarations.size() == 0)
throw new RuntimeException("Please specify SET or INCREMENT before calling Update!");
SQLStatement stat = new SQLStatement(db);
stat.appendSQL("UPDATE ");
from.appendSQL(stat);
stat.appendSQL(" SET ");
int i = 0;
for (Declaration declaration:declarations) {
if (i++ > 0) {
stat.appendSQL(", ");
}
declaration.appendSQL(stat);
}
appendWhere(stat);
StatementLogger.update(stat.getSQL());
return stat.executeUpdate();
}
public <X, Z> List<X> selectDistinct(Z x) {
return select(x, true);
......@@ -147,7 +180,7 @@ public class Query<T> {
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JdbcUtils.closeSilently(rs);
JdbcUtils.closeSilently(rs, true);
}
return result;
}
......@@ -161,7 +194,12 @@ public class Query<T> {
try {
while (rs.next()) {
try {
X value = (X) rs.getObject(1);
X value;
Object o = rs.getObject(1);
if (Clob.class.isAssignableFrom(o.getClass())) {
value = (X) Utils.convert(o, String.class);
} else
value = (X) o;
result.add(value);
} catch (Exception e) {
throw new RuntimeException(e);
......@@ -170,7 +208,7 @@ public class Query<T> {
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JdbcUtils.closeSilently(rs);
JdbcUtils.closeSilently(rs, true);
}
return result;
}
......@@ -180,7 +218,7 @@ public class Query<T> {
}
public <A> QueryWhere<T> where(Filter filter) {
HashMap<String, Object> fieldMap = New.hashMap();
HashMap<String, Object> fieldMap = Utils.newHashMap();
for (Field f : filter.getClass().getDeclaredFields()) {
f.setAccessible(true);
try {
......@@ -212,6 +250,23 @@ public class Query<T> {
}
//## Java 1.5 end ##
/**
* Sets the Limit and Offset of a query.
*
* @return the query
*/
//## Java 1.5 begin ##
public Query<T> limit(long limit) {
this.limit = limit;
return this;
}
public Query<T> offset(long offset) {
this.offset = offset;
return this;
}
//## Java 1.5 end ##
/**
* Order by a number of columns.
*
......@@ -268,6 +323,10 @@ public class Query<T> {
void addConditionToken(Token condition) {
conditions.add(condition);
}
void addDeclarationToken(Declaration declaration) {
declarations.add(declaration);
}
void appendWhere(SQLStatement stat) {
if (!conditions.isEmpty()) {
......@@ -317,6 +376,11 @@ public class Query<T> {
stat.appendSQL(" ");
}
}
if (limit > 0)
db.getDialect().appendLimit(stat, limit);
if (offset > 0)
db.getDialect().appendOffset(stat, offset);
StatementLogger.select(stat.getSQL());
return stat;
}
//## Java 1.5 end ##
......
......@@ -33,6 +33,16 @@ public class QueryWhere<T> {
query.addConditionToken(ConditionAndOr.OR);
return new QueryCondition<T, A>(query, x);
}
public QueryWhere<T> limit(long limit) {
query.limit(limit);
return this;
}
public QueryWhere<T> offset(long offset) {
query.offset(offset);
return this;
}
public <X, Z> List<X> select(Z x) {
return query.select(x);
......@@ -123,6 +133,10 @@ public class QueryWhere<T> {
return query.delete();
}
public int update() {
return query.update();
}
public long selectCount() {
return query.selectCount();
}
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
import java.text.MessageFormat;
import org.h2.jaqu.TableDefinition.IndexDefinition;
import org.h2.jaqu.util.StatementBuilder;
import org.h2.jaqu.util.StringUtils;
/**
* Interface that defines points where JaQu can build different statements for
* DB-specific SQL.
*
*/
public interface SQLDialect {
public String tableName(String schema, String table);
public String createIndex(String schema, String table, IndexDefinition index);
public void appendLimit(SQLStatement stat, long limit);
public void appendOffset(SQLStatement stat, long offset);
/**
* Default implementation of an SQL dialect.
* Designed for an H2 database. May be suitable for others.
*/
public static class DefaultSQLDialect implements SQLDialect {
@Override
public String tableName(String schema, String table) {
if (StringUtils.isNullOrEmpty(schema))
return table;
return schema + "." + table;
}
@Override
public String createIndex(String schema, String table, IndexDefinition index) {
StatementBuilder cols = new StatementBuilder();
for (String col:index.columnNames) {
cols.appendExceptFirst(", ");
cols.append(col);
}
String type;
switch(index.type) {
case UNIQUE:
type = " UNIQUE ";
break;
case HASH:
type = " HASH ";
break;
case UNIQUE_HASH:
type = " UNIQUE HASH ";
break;
case STANDARD:
default:
type = " ";
break;
}
return MessageFormat.format("CREATE{0}INDEX IF NOT EXISTS {1} ON {2}({3})",
type, index.indexName, table, cols);
}
@Override
public void appendLimit(SQLStatement stat, long limit) {
stat.appendSQL(" LIMIT " + limit);
}
@Override
public void appendOffset(SQLStatement stat, long offset) {
stat.appendSQL(" OFFSET " + offset);
}
}
}
......@@ -11,6 +11,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import org.h2.jaqu.util.JdbcUtils;
//## Java 1.5 end ##
/**
......@@ -37,6 +38,10 @@ public class SQLStatement {
sql = null;
return this;
}
public SQLStatement appendTable(String schema, String table) {
return appendSQL(db.getDialect().tableName(schema, table));
}
String getSQL() {
if (sql == null) {
......@@ -49,20 +54,42 @@ public class SQLStatement {
params.add(o);
return this;
}
ResultSet executeQuery() {
try {
return prepare().executeQuery();
return prepare(false).executeQuery();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
int executeUpdate() {
PreparedStatement ps = null;
try {
ps = prepare(false);
return ps.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JdbcUtils.closeSilently(ps);
}
}
long executeInsert() {
PreparedStatement ps = null;
try {
return prepare().executeUpdate();
ps = prepare(true);
ps.executeUpdate();
long identity = -1;
ResultSet rs = ps.getGeneratedKeys();
if (rs != null && rs.next())
identity = rs.getLong(1);
JdbcUtils.closeSilently(rs);
return identity;
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JdbcUtils.closeSilently(ps);
}
}
......@@ -74,8 +101,8 @@ public class SQLStatement {
}
}
private PreparedStatement prepare() {
PreparedStatement prep = db.prepare(getSQL());
private PreparedStatement prepare(boolean returnIdentity) {
PreparedStatement prep = db.prepare(getSQL(), returnIdentity);
for (int i = 0; i < params.size(); i++) {
Object o = params.get(i);
setValue(prep, i + 1, o);
......
......@@ -55,9 +55,9 @@ class SelectTable <T> {
void appendSQL(SQLStatement stat) {
if (query.isJoin()) {
stat.appendSQL(aliasDef.tableName + " AS " + as);
stat.appendTable(aliasDef.schemaName, aliasDef.tableName).appendSQL(" AS " + as);
} else {
stat.appendSQL(aliasDef.tableName);
stat.appendTable(aliasDef.schemaName, aliasDef.tableName);
}
}
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
/**
* This class represents a "column=value" token for a SET statement.
*
* @param <A> the new value data type
*/
//## Java 1.5 begin ##
public class SetColumn<T, A> implements Declaration {
private Query<T> query;
private A x;
private A y;
SetColumn(Query<T> query, A x) {
this.query = query;
this.x = x;
}
public Query<T> to(A y) {
query.addDeclarationToken(this);
this.y = y;
return query;
}
@Override
public void appendSQL(SQLStatement stat) {
query.appendSQL(stat, x);
stat.appendSQL("=?");
stat.addParameter(y);
}
}
//## Java 1.5 end ##
差异被折叠。
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu;
import org.h2.jaqu.TableDefinition.FieldDefinition;
import org.h2.jaqu.TableInspector.ColumnInspector;
import org.h2.jaqu.util.StringUtils;
/**
* A Validation Remark is a result of running a model validation.
* <p>
* Each remark has a level, associated component (schema, table, column, index),
* and a message.
*
*/
public class Validation {
public static Validation CONSIDER(String table, String type, String message) {
return new Validation(Level.CONSIDER, table, type, message);
}
public static Validation CONSIDER(String table, ColumnInspector col, String message) {
return new Validation(Level.CONSIDER, table, col, message);
}
public static Validation WARN(String table, ColumnInspector col, String message) {
return new Validation(Level.WARN, table, col, message);
}
public static Validation WARN(String table, String type, String message) {
return new Validation(Level.WARN, table, type, message);
}
public static Validation ERROR(String table, ColumnInspector col, String message) {
return new Validation(Level.ERROR, table, col, message);
}
public static Validation ERROR(String table, String type, String message) {
return new Validation(Level.ERROR, table, type, message);
}
public static Validation ERROR(String table, FieldDefinition field, String message) {
return new Validation(Level.ERROR, table, field, message);
}
public static enum Level {
CONSIDER, WARN, ERROR;
}
Level level;
String table;
String fieldType;
String fieldName;
String message;
private Validation(Level level, String table, String type, String message) {
this.level = level;
this.table = table;
this.fieldType = type;
this.fieldName = "";
this.message = message;
}
private Validation(Level level, String table, FieldDefinition field, String message) {
this.level = level;
this.table = table;
this.fieldType = field.dataType;
this.fieldName = field.columnName;
this.message = message;
}
private Validation(Level level, String table, ColumnInspector col, String message) {
this.level = level;
this.table = table;
this.fieldType = col.type;
this.fieldName = col.name;
this.message = message;
}
public Validation throwError(boolean throwOnError) {
if (throwOnError && isError())
throw new RuntimeException(toString());
return this;
}
public boolean isError() {
return level.equals(Level.ERROR);
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(StringUtils.pad(level.name(), 9, " ", true));
sb.append(StringUtils.pad(table, 25, " ", true));
sb.append(StringUtils.pad(fieldName, 20, " ", true));
sb.append(' ');
sb.append(message);
return sb.toString();
}
public String toCSVString() {
StringBuilder sb = new StringBuilder();
sb.append(level.name()).append(',');
sb.append(table).append(',');
sb.append(fieldType).append(',');
sb.append(fieldName).append(',');
sb.append(message);
return sb.toString();
}
}
......@@ -8,7 +8,7 @@ package org.h2.jaqu.bytecode;
import org.h2.jaqu.Query;
import org.h2.jaqu.SQLStatement;
import org.h2.util.StringUtils;
import org.h2.jaqu.util.StringUtils;
/**
* A string constant.
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu.util;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.h2.jaqu.Db;
import org.h2.jaqu.DbInspector;
/**
* Generates JaQu models.
*
*/
public class GenerateModels {
/**
* The output stream where this tool writes to.
*/
protected PrintStream out = System.out;
public static void main(String... args) throws SQLException {
new GenerateModels().runTool(args);
}
public void runTool(String... args) throws SQLException {
String url = null;
String user = "sa";
String password = "";
String schema = null;
String table = null;
String packageName = "";
String folder = null;
boolean annotateSchema = true;
boolean trimStrings = false;
for (int i = 0; args != null && i < args.length; i++) {
String arg = args[i];
if (arg.equals("-url")) {
url = args[++i];
} else if (arg.equals("-user")) {
user = args[++i];
} else if (arg.equals("-password")) {
password = args[++i];
} else if (arg.equals("-schema")) {
schema = args[++i];
} else if (arg.equals("-table")) {
table = args[++i];
} else if (arg.equals("-package")) {
packageName = args[++i];
} else if (arg.equals("-folder")) {
folder = args[++i];
} else if (arg.equals("-annotateSchema")) {
try {
annotateSchema = Boolean.parseBoolean(args[++i]);
} catch (Throwable t) {
throw new SQLException("Can not parse -annotateSchema value");
}
} else if (arg.equals("-trimStrings")) {
try {
trimStrings = Boolean.parseBoolean(args[++i]);
} catch (Throwable t) {
throw new SQLException("Can not parse -trimStrings value");
}
} else {
throwUnsupportedOption(arg);
}
}
if (url == null) {
throw new SQLException("URL not set");
}
execute(url, user, password, schema, table, packageName, folder,
annotateSchema, trimStrings);
}
/**
* Generates models from the database.
*
* @param url the database URL
* @param user the user name
* @param password the password
* @param schema the schema to read from. null for all schemas.
* @param table the table to model. null for all tables within schema.
* @param packageName the package name of the model classes.
* @param folder destination folder for model classes (package path not included)
* @param annotateSchema includes the schema in the table model annotations
* @param trimStrings automatically trim strings that exceed maxLength
*/
public static void execute(String url, String user, String password,
String schema, String table, String packageName, String folder,
boolean annotateSchema, boolean trimStrings)
throws SQLException {
Connection conn = null;
try {
org.h2.Driver.load();
conn = DriverManager.getConnection(url, user, password);
Db db = Db.open(url, user, password.toCharArray());
DbInspector inspector = new DbInspector(db);
List<String> models = inspector.generateModel(schema, table,
packageName, annotateSchema, trimStrings);
File parentFile;
if (StringUtils.isNullOrEmpty(folder))
parentFile = new File(System.getProperty("user.dir"));
else
parentFile = new File(folder);
parentFile.mkdirs();
Pattern p = Pattern.compile("class ([a-zA-Z0-9]+)");
for (String model : models) {
Matcher m = p.matcher(model);
if (m.find()) {
String className = m.group().substring("class".length()).trim();
File classFile = new File(parentFile, className + ".java");
Writer o = new FileWriter(classFile, false);
PrintWriter writer = new PrintWriter(new BufferedWriter(o));
writer.write(model);
writer.close();
System.out.println("Generated " + classFile.getAbsolutePath());
}
}
} catch (SQLException s) {
throw s;
} catch (IOException i) {
throw new SQLException(i);
} finally {
JdbcUtils.closeSilently(conn);
}
}
/**
* Throw a SQLException saying this command line option is not supported.
*
* @param option the unsupported option
* @return this method never returns normally
*/
protected SQLException throwUnsupportedOption(String option) throws SQLException {
showUsage();
throw new SQLException("Unsupported option: " + option);
}
protected void showUsage() {
out.println("GenerateModels");
out.println("Usage: java "+getClass().getName());
out.println();
out.println("(*) -url jdbc:h2:~test");
out.println(" -user <string>");
out.println(" -password <string>");
out.println(" -schema <string>");
out.println(" -table <string>");
out.println(" -package <string>");
out.println(" -folder <string>");
out.println(" -annotateSchema <boolean>");
out.println(" -trimStrings <boolean>");
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.jaqu.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.naming.Context;
import javax.sql.DataSource;
import javax.sql.XAConnection;
/**
* This is a utility class with JDBC helper functions.
*/
public class JdbcUtils {
private static final String[] DRIVERS = {
"h2:", "org.h2.Driver",
"Cache:", "com.intersys.jdbc.CacheDriver",
"daffodilDB://", "in.co.daffodil.db.rmi.RmiDaffodilDBDriver",
"daffodil", "in.co.daffodil.db.jdbc.DaffodilDBDriver",
"db2:", "COM.ibm.db2.jdbc.net.DB2Driver",
"derby:net:", "org.apache.derby.jdbc.ClientDriver",
"derby://", "org.apache.derby.jdbc.ClientDriver",
"derby:", "org.apache.derby.jdbc.EmbeddedDriver",
"FrontBase:", "com.frontbase.jdbc.FBJDriver",
"firebirdsql:", "org.firebirdsql.jdbc.FBDriver",
"hsqldb:", "org.hsqldb.jdbcDriver",
"informix-sqli:", "com.informix.jdbc.IfxDriver",
"jtds:", "net.sourceforge.jtds.jdbc.Driver",
"microsoft:", "com.microsoft.jdbc.sqlserver.SQLServerDriver",
"mimer:", "com.mimer.jdbc.Driver",
"mysql:", "com.mysql.jdbc.Driver",
"odbc:", "sun.jdbc.odbc.JdbcOdbcDriver",
"oracle:", "oracle.jdbc.driver.OracleDriver",
"pervasive:", "com.pervasive.jdbc.v2.Driver",
"pointbase:micro:", "com.pointbase.me.jdbc.jdbcDriver",
"pointbase:", "com.pointbase.jdbc.jdbcUniversalDriver",
"postgresql:", "org.postgresql.Driver",
"sybase:", "com.sybase.jdbc3.jdbc.SybDriver",
"sqlserver:", "com.microsoft.sqlserver.jdbc.SQLServerDriver",
"teradata:", "com.ncr.teradata.TeraDriver",
};
private JdbcUtils() {
// utility class
}
/**
* Close a statement without throwing an exception.
*
* @param stat the statement or null
*/
public static void closeSilently(Statement stat) {
if (stat != null) {
try {
stat.close();
} catch (SQLException e) {
// ignore
}
}
}
/**
* Close a connection without throwing an exception.
*
* @param conn the connection or null
*/
public static void closeSilently(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}
/**
* Close a result set without throwing an exception.
*
* @param rs the result set or null
*/
public static void closeSilently(ResultSet rs) {
closeSilently(rs, false);
}
/**
* Close a result set, and optionally its statement without throwing an
* exception.
*
* @param rs the result set or null
*/
public static void closeSilently(ResultSet rs, boolean closeStatement) {
if (rs != null) {
Statement stat = null;
if (closeStatement) {
try {
stat = rs.getStatement();
} catch (SQLException e) {
//ignore
}
}
try {
rs.close();
} catch (SQLException e) {
// ignore
}
closeSilently(stat);
}
}
/**
* Close an XA connection set without throwing an exception.
*
* @param conn the XA connection or null
*/
//## Java 1.4 begin ##
public static void closeSilently(XAConnection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}
//## Java 1.4 end ##
/**
* Open a new database connection with the given settings.
*
* @param driver the driver class name
* @param url the database URL
* @param user the user name
* @param password the password
* @return the database connection
*/
public static Connection getConnection(String driver, String url, String user, String password) throws SQLException {
Properties prop = new Properties();
if (user != null) {
prop.setProperty("user", user);
}
if (password != null) {
prop.setProperty("password", password);
}
return getConnection(driver, url, prop);
}
/**
* Escape table or schema patterns used for DatabaseMetaData functions.
*
* @param pattern the pattern
* @return the escaped pattern
*/
public static String escapeMetaDataPattern(String pattern) {
if (pattern == null || pattern.length() == 0) {
return pattern;
}
return StringUtils.replaceAll(pattern, "\\", "\\\\");
}
/**
* Open a new database connection with the given settings.
*
* @param driver the driver class name
* @param url the database URL
* @param prop the properties containing at least the user name and password
* @return the database connection
*/
public static Connection getConnection(String driver, String url, Properties prop) throws SQLException {
if (StringUtils.isNullOrEmpty(driver)) {
JdbcUtils.load(url);
} else {
Class<?> d = ClassUtils.loadClass(driver);
if (java.sql.Driver.class.isAssignableFrom(d)) {
return DriverManager.getConnection(url, prop);
//## Java 1.4 begin ##
} else if (javax.naming.Context.class.isAssignableFrom(d)) {
// JNDI context
try {
Context context = (Context) d.newInstance();
DataSource ds = (DataSource) context.lookup(url);
String user = prop.getProperty("user");
String password = prop.getProperty("password");
if (StringUtils.isNullOrEmpty(user) && StringUtils.isNullOrEmpty(password)) {
return ds.getConnection();
}
return ds.getConnection(user, password);
} catch (Exception e) {
throw Message.convert(e);
}
//## Java 1.4 end ##
} else {
// Don't know, but maybe it loaded a JDBC Driver
return DriverManager.getConnection(url, prop);
}
}
return DriverManager.getConnection(url, prop);
}
/**
* Get the driver class name for the given URL, or null if the URL is
* unknown.
*
* @param url the database URL
* @return the driver class name
*/
public static String getDriver(String url) {
if (url.startsWith("jdbc:")) {
url = url.substring("jdbc:".length());
for (int i = 0; i < DRIVERS.length; i += 2) {
String prefix = DRIVERS[i];
if (url.startsWith(prefix)) {
return DRIVERS[i + 1];
}
}
}
return null;
}
/**
* Load the driver class for the given URL, if the database URL is known.
*
* @param url the database URL
*/
public static void load(String url) {
String driver = getDriver(url);
if (driver != null) {
ClassUtils.loadClass(driver);
}
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.jaqu.util;
/**
* A utility class to build a statement. In addition to the methods supported by
* StringBuilder, it allows to add a text only in the second iteration. This
* simplified constructs such as:
* <pre>
* StringBuilder buff = new StringBuilder();
* for (int i = 0; i &lt; args.length; i++) {
* if (i &gt; 0) {
* buff.append(&quot;, &quot;);
* }
* buff.append(args[i]);
* }
* </pre>
* to
* <pre>
* StatementBuilder buff = new StatementBuilder();
* for (String s : args) {
* buff.appendExceptFirst(&quot;, &quot;);
* buff.append(a);
* }
*</pre>
*/
public class StatementBuilder {
private final StringBuilder builder = new StringBuilder();
private int index;
/**
* Create a new builder.
*/
public StatementBuilder() {
// nothing to do
}
/**
* Create a new builder.
*
* @param string the initial string
*/
public StatementBuilder(String string) {
builder.append(string);
}
/**
* Append a text.
*
* @param s the text to append
* @return itself
*/
public StatementBuilder append(String s) {
builder.append(s);
return this;
}
/**
* Append a character.
*
* @param c the character to append
* @return itself
*/
public StatementBuilder append(char c) {
builder.append(c);
return this;
}
/**
* Append a number.
*
* @param x the number to append
* @return itself
*/
public StatementBuilder append(long x) {
builder.append(x);
return this;
}
/**
* Reset the loop counter.
*
* @return itself
*/
public StatementBuilder resetCount() {
index = 0;
return this;
}
/**
* Append a text, but only if appendExceptFirst was never called.
*
* @param s the text to append
*/
public void appendOnlyFirst(String s) {
if (index == 0) {
builder.append(s);
}
}
/**
* Append a text, except when this method is called the first time.
*
* @param s the text to append
*/
public void appendExceptFirst(String s) {
if (index++ > 0) {
builder.append(s);
}
}
public void append(StatementBuilder sb) {
builder.append(sb);
}
public void insert(int offset, char c) {
builder.insert(offset, c);
}
public String toString() {
return builder.toString();
}
/**
* Get the length.
*
* @return the length
*/
public int length() {
return builder.length();
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: James Moger
*/
package org.h2.jaqu.util;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.util.concurrent.atomic.AtomicLong;
/**
* Utility class to optionally log generated statements to an output stream.<br>
* Default output stream is System.out.<br>
* Statement logging is disabled by default.
* <p>
* This class also tracks the counts for generated statements by major type.
*
*/
public class StatementLogger {
public static boolean logStatements = false;
public static PrintWriter out = new PrintWriter(System.out);
public final static AtomicLong selectCount = new AtomicLong(0);
public final static AtomicLong createCount = new AtomicLong(0);
public final static AtomicLong insertCount = new AtomicLong(0);
public final static AtomicLong updateCount = new AtomicLong(0);
public final static AtomicLong mergeCount = new AtomicLong(0);
public final static AtomicLong deleteCount = new AtomicLong(0);
public static void create(String statement) {
createCount.incrementAndGet();
log(statement);
}
public static void insert(String statement) {
insertCount.incrementAndGet();
log(statement);
}
public static void update(String statement) {
updateCount.incrementAndGet();
log(statement);
}
public static void merge(String statement) {
mergeCount.incrementAndGet();
log(statement);
}
public static void delete(String statement) {
deleteCount.incrementAndGet();
log(statement);
}
public static void select(String statement) {
selectCount.incrementAndGet();
log(statement);
}
private static void log(String statement) {
if (logStatements)
out.println(statement);
}
public static void printStats() {
out.println("JaQu Runtime Stats");
out.println("=======================");
printStat("CREATE", createCount);
printStat("INSERT", insertCount);
printStat("UPDATE", updateCount);
printStat("MERGE", mergeCount);
printStat("DELETE", deleteCount);
printStat("SELECT", selectCount);
}
private static void printStat(String name, AtomicLong value) {
if (value.get() > 0) {
DecimalFormat df = new DecimalFormat("###,###,###,###");
out.println(name + "=" + df.format(createCount.get()));
}
}
}
\ No newline at end of file
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.jaqu.util;
public class StringUtils {
/**
* Replace all occurrences of the before string with the after string.
*
* @param s the string
* @param before the old text
* @param after the new text
* @return the string with the before string replaced
*/
public static String replaceAll(String s, String before, String after) {
int next = s.indexOf(before);
if (next < 0) {
return s;
}
StringBuilder buff = new StringBuilder(s.length() - before.length() + after.length());
int index = 0;
while (true) {
buff.append(s.substring(index, next)).append(after);
index = next + before.length();
next = s.indexOf(before, index);
if (next < 0) {
buff.append(s.substring(index));
break;
}
}
return buff.toString();
}
/**
* Check if a String is null or empty (the length is null).
*
* @param s the string to check
* @return true if it is null or empty
*/
public static boolean isNullOrEmpty(String s) {
return s == null || s.length() == 0;
}
/**
* Convert a string to a Java literal using the correct escape sequences.
* The literal is not enclosed in double quotes. The result can be used in
* properties files or in Java source code.
*
* @param s the text to convert
* @return the Java representation
*/
public static String javaEncode(String s) {
int length = s.length();
StringBuilder buff = new StringBuilder(length);
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
switch (c) {
// case '\b':
// // BS backspace
// // not supported in properties files
// buff.append("\\b");
// break;
case '\t':
// HT horizontal tab
buff.append("\\t");
break;
case '\n':
// LF linefeed
buff.append("\\n");
break;
case '\f':
// FF form feed
buff.append("\\f");
break;
case '\r':
// CR carriage return
buff.append("\\r");
break;
case '"':
// double quote
buff.append("\\\"");
break;
case '\\':
// backslash
buff.append("\\\\");
break;
default:
int ch = c & 0xffff;
if (ch >= ' ' && (ch < 0x80)) {
buff.append(c);
// not supported in properties files
// } else if(ch < 0xff) {
// buff.append("\\");
// // make sure it's three characters (0x200 is octal 1000)
// buff.append(Integer.toOctalString(0x200 |
// ch).substring(1));
} else {
buff.append("\\u");
// make sure it's four characters
buff.append(Integer.toHexString(0x10000 | ch).substring(1));
}
}
}
return buff.toString();
}
/**
* Pad a string. This method is used for the SQL function RPAD and LPAD.
*
* @param string the original string
* @param n the target length
* @param padding the padding string
* @param right true if the padding should be appended at the end
* @return the padded string
*/
public static String pad(String string, int n, String padding, boolean right) {
if (n < 0) {
n = 0;
}
if (n < string.length()) {
return string.substring(0, n);
} else if (n == string.length()) {
return string;
}
char paddingChar;
if (padding == null || padding.length() == 0) {
paddingChar = ' ';
} else {
paddingChar = padding.charAt(0);
}
StringBuilder buff = new StringBuilder(n);
n -= string.length();
if (right) {
buff.append(string);
}
for (int i = 0; i < n; i++) {
buff.append(paddingChar);
}
if (!right) {
buff.append(string);
}
return buff.toString();
}
/**
* Convert a string to a SQL literal. Null is converted to NULL. The text is
* enclosed in single quotes. If there are any special characters, the method
* STRINGDECODE is used.
*
* @param s the text to convert.
* @return the SQL literal
*/
public static String quoteStringSQL(String s) {
if (s == null) {
return "NULL";
}
int length = s.length();
StringBuilder buff = new StringBuilder(length + 2);
buff.append('\'');
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c == '\'') {
buff.append(c);
} else if (c < ' ' || c > 127) {
// need to start from the beginning because maybe there was a \
// that was not quoted
return "STRINGDECODE(" + quoteStringSQL(javaEncode(s)) + ")";
}
buff.append(c);
}
buff.append('\'');
return buff.toString();
}
}
......@@ -7,19 +7,24 @@
package org.h2.jaqu.util;
//## Java 1.5 begin ##
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Clob;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import org.h2.util.IOUtils;
//## Java 1.5 end ##
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* Generic utility methods.
......@@ -27,13 +32,31 @@ import org.h2.util.IOUtils;
public class Utils {
//## Java 1.5 begin ##
private static volatile long counter;
private static final AtomicLong counter = new AtomicLong(0);
private static final boolean MAKE_ACCESSIBLE = true;
private static final int BUFFER_BLOCK_SIZE = 4 * 1024;
public static <T> ArrayList<T> newArrayList() {
return new ArrayList<T>();
}
public static <T> ArrayList<T> newArrayList(Collection<T> c) {
return new ArrayList<T>(c);
}
public static <T> HashSet<T> newHashSet() {
return new HashSet<T>();
}
public static <T> HashSet<T> newHashSet(Collection<T> list) {
return new HashSet<T>(list);
}
public static <T> Set<T> newConcurrentHashSet() {
return Collections.newSetFromMap(new ConcurrentHashMap<T,Boolean>());
}
public static <A, B> HashMap<A, B> newHashMap() {
return new HashMap<A, B>();
......@@ -52,35 +75,35 @@ public class Utils {
public static <T> T newObject(Class<T> clazz) {
// must create new instances
if (clazz == Integer.class) {
return (T) new Integer((int) counter++);
return (T) new Integer((int) counter.incrementAndGet());
} else if (clazz == String.class) {
return (T) ("" + counter++);
return (T) ("" + counter.incrementAndGet());
} else if (clazz == Long.class) {
return (T) new Long(counter++);
return (T) new Long(counter.incrementAndGet());
} else if (clazz == Short.class) {
return (T) new Short((short) counter++);
return (T) new Short((short) counter.incrementAndGet());
} else if (clazz == Byte.class) {
return (T) new Byte((byte) counter++);
return (T) new Byte((byte) counter.incrementAndGet());
} else if (clazz == Float.class) {
return (T) new Float(counter++);
return (T) new Float(counter.incrementAndGet());
} else if (clazz == Double.class) {
return (T) new Double(counter++);
return (T) new Double(counter.incrementAndGet());
} else if (clazz == Boolean.class) {
return (T) new Boolean(false);
} else if (clazz == BigDecimal.class) {
return (T) new BigDecimal(counter++);
return (T) new BigDecimal(counter.incrementAndGet());
} else if (clazz == BigInteger.class) {
return (T) new BigInteger("" + counter++);
return (T) new BigInteger("" + counter.incrementAndGet());
} else if (clazz == java.sql.Date.class) {
return (T) new java.sql.Date(counter++);
return (T) new java.sql.Date(counter.incrementAndGet());
} else if (clazz == java.sql.Time.class) {
return (T) new java.sql.Time(counter++);
return (T) new java.sql.Time(counter.incrementAndGet());
} else if (clazz == java.sql.Timestamp.class) {
return (T) new java.sql.Timestamp(counter++);
return (T) new java.sql.Timestamp(counter.incrementAndGet());
} else if (clazz == java.util.Date.class) {
return (T) new java.util.Date(counter++);
return (T) new java.util.Date(counter.incrementAndGet());
} else if (clazz == List.class) {
return (T) new ArrayList();
return (T) newArrayList();
}
try {
return clazz.newInstance();
......@@ -137,7 +160,7 @@ public class Utils {
Clob c = (Clob) o;
try {
Reader r = c.getCharacterStream();
return IOUtils.readStringAndClose(r, -1);
return readStringAndClose(r, -1);
} catch (Exception e) {
throw new RuntimeException("Error converting CLOB to String: " + e.toString(), e);
}
......@@ -146,7 +169,11 @@ public class Utils {
}
if (Number.class.isAssignableFrom(currentType)) {
Number n = (Number) o;
if (targetType == Integer.class) {
if (targetType == Byte.class) {
return n.byteValue();
} else if (targetType == Short.class) {
return n.shortValue();
} else if (targetType == Integer.class) {
return n.intValue();
} else if (targetType == Long.class) {
return n.longValue();
......@@ -159,6 +186,37 @@ public class Utils {
throw new RuntimeException("Can not convert the value " + o +
" from " + currentType + " to " + targetType);
}
/**
* Read a number of characters from a reader and close it.
*
* @param in the reader
* @param length the maximum number of characters to read, or -1 to read
* until the end of file
* @return the string read
*/
public static String readStringAndClose(Reader in, int length) throws IOException {
try {
if (length <= 0) {
length = Integer.MAX_VALUE;
}
int block = Math.min(BUFFER_BLOCK_SIZE, length);
StringWriter out = new StringWriter(length == Integer.MAX_VALUE ? block : length);
char[] buff = new char[block];
while (length > 0) {
int len = Math.min(block, length);
len = in.read(buff, 0, len);
if (len < 0) {
break;
}
out.write(buff, 0, len);
length -= len;
}
return out.toString();
} finally {
in.close();
}
}
//## Java 1.5 end ##
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论