diff --git a/jOOQ-test/.classpath b/jOOQ-test/.classpath index 3dbc43b404..3c3f21971f 100644 --- a/jOOQ-test/.classpath +++ b/jOOQ-test/.classpath @@ -24,7 +24,7 @@ - + diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/GeneralTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/GeneralTests.java index 3e836123c5..204fd2fdcd 100644 --- a/jOOQ-test/src/org/jooq/test/_/testcases/GeneralTests.java +++ b/jOOQ-test/src/org/jooq/test/_/testcases/GeneralTests.java @@ -40,18 +40,7 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; -import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; -import static org.jooq.SQLDialect.ASE; -import static org.jooq.SQLDialect.CUBRID; -import static org.jooq.SQLDialect.DB2; -import static org.jooq.SQLDialect.H2; -import static org.jooq.SQLDialect.HSQLDB; -import static org.jooq.SQLDialect.MYSQL; -import static org.jooq.SQLDialect.ORACLE; -import static org.jooq.SQLDialect.POSTGRES; -import static org.jooq.SQLDialect.SQLITE; -import static org.jooq.SQLDialect.SYBASE; import static org.jooq.impl.Factory.castNull; import static org.jooq.impl.Factory.count; import static org.jooq.impl.Factory.deg; @@ -68,8 +57,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.math.BigDecimal; -import java.math.BigInteger; import java.sql.Connection; import java.sql.Date; import java.util.Arrays; @@ -82,22 +69,16 @@ import org.jooq.Record2; import org.jooq.Record3; import org.jooq.Record6; import org.jooq.Result; -import org.jooq.SQLDialect; -import org.jooq.Schema; import org.jooq.Select; import org.jooq.SelectQuery; import org.jooq.Sequence; -import org.jooq.Table; import org.jooq.TableRecord; -import org.jooq.UDT; import org.jooq.UpdatableRecord; -import org.jooq.UpdatableTable; import org.jooq.UpdateQuery; import org.jooq.conf.Settings; import org.jooq.exception.DetachedException; import org.jooq.impl.DefaultExecuteListener; import org.jooq.impl.Executor; -import org.jooq.impl.SQLDataType; import org.jooq.test.BaseTest; import org.jooq.test.jOOQAbstractTest; @@ -439,290 +420,6 @@ extends BaseTest table : schema.getTables()) { - assertEquals(table, schema.getTable(table.getName())); - } - for (UDT udt : schema.getUDTs()) { - assertEquals(udt, schema.getUDT(udt.getName())); - } - for (Sequence sequence : schema.getSequences()) { - assertEquals(sequence, schema.getSequence(sequence.getName())); - } - - int tables = 17; - - // The additional T_DIRECTORY table for recursive queries - if (supportsRecursiveQueries()) { - tables++; - } - - // The additional T_TRIGGERS table for INSERT .. RETURNING - if (TTriggers() != null) { - tables++; - } - - // The additional T_UNSIGNED table - if (TUnsigned() != null) { - tables++; - } - - // The additional T_IDENTITY table - if (TIdentity() != null) { - tables++; - } - - // The additional T_IDENTITY_PK table - if (TIdentityPK() != null) { - tables++; - } - - // [#959] The T_959 table for enum collisions with Java keywords - if (getDialect() == MYSQL || - getDialect() == POSTGRES) { - tables++; - } - - // [#986] Some foreign key name collision checks - if (getDialect() == ASE || - getDialect() == CUBRID || - getDialect() == DB2 || - getDialect() == POSTGRES || - getDialect() == SQLITE || - getDialect() == SYBASE) { - - tables += 2; - } - - if (TArrays() == null) { - assertEquals(tables, schema.getTables().size()); - } - - // [#877] The T_877 table is only available in H2 - else if (getDialect() == H2) { - assertEquals(tables + 2, schema.getTables().size()); - } - - // [#624] The V_INCOMPLETE view is only available in Oracle - else if (getDialect() == ORACLE) { - assertEquals(tables + 3, schema.getTables().size()); - } - - // [#610] Collision-prone entities are only available in HSQLDB - else if (getDialect() == HSQLDB) { - assertEquals(tables + 11, schema.getTables().size()); - } - - else { - assertEquals(tables + 1, schema.getTables().size()); - } - - if (cUAddressType() == null) { - assertEquals(0, schema.getUDTs().size()); - } - // [#643] The U_INVALID types are only available in Oracle - // [#799] The member procedure UDT's too - else if (getDialect() == ORACLE) { - assertEquals(7, schema.getUDTs().size()); - } - else { - assertEquals(2, schema.getUDTs().size()); - } - } - - // Test correct source code generation for identity columns - assertNull(TAuthor().getIdentity()); - assertNull(TBook().getIdentity()); - - if (TIdentity() != null || TIdentityPK() != null) { - if (TIdentity() != null) { - assertEquals(TIdentity(), TIdentity().getIdentity().getTable()); - assertEquals(TIdentity_ID(), TIdentity().getIdentity().getField()); - } - - if (TIdentityPK() != null) { - assertEquals(TIdentityPK(), TIdentityPK().getIdentity().getTable()); - assertEquals(TIdentityPK_ID(), TIdentityPK().getIdentity().getField()); - } - } - else { - log.info("SKIPPING", "Identity tests"); - } - - // Test correct source code generation for relations - assertNotNull(TAuthor().getMainKey()); - assertNotNull(TAuthor().getKeys()); - assertTrue(TAuthor().getKeys().contains(TAuthor().getMainKey())); - assertEquals(1, TAuthor().getKeys().size()); - assertEquals(1, TAuthor().getMainKey().getFields().size()); - assertEquals(TAuthor_ID(), TAuthor().getMainKey().getFields().get(0)); - assertEquals(Record1.class, TAuthor().getRecordType().getMethod("key").getReturnType()); - assertTrue(TAuthor().getRecordType().getMethod("key").toGenericString().contains("org.jooq.Record1")); - assertEquals(Record2.class, TBookToBookStore().getRecordType().getMethod("key").getReturnType()); - assertTrue(TBookToBookStore().getRecordType().getMethod("key").toGenericString().contains("org.jooq.Record2")); - - if (supportsReferences()) { - - // Without aliasing - assertEquals(0, TAuthor().getReferences().size()); - assertEquals(2, TAuthor().getMainKey().getReferences().size()); - assertEquals(TBook(), TAuthor().getMainKey().getReferences().get(0).getTable()); - assertEquals(TBook(), TAuthor().getMainKey().getReferences().get(1).getTable()); - assertEquals(Arrays.asList(), TAuthor().getReferencesTo(TBook())); - assertTrue(TBook().getReferences().containsAll(TAuthor().getReferencesFrom(TBook()))); - assertTrue(TBook().getReferences().containsAll(TBook().getReferencesFrom(TAuthor()))); - assertEquals(TBook().getReferencesTo(TAuthor()), TAuthor().getReferencesFrom(TBook())); - - // [#1460] With aliasing - Table a = TAuthor().as("a"); - Table b = TBook().as("b"); - - assertEquals(0, a.getReferences().size()); - assertEquals(Arrays.asList(), a.getReferencesTo(b)); - - // This should work with both types of meta-models (static, non-static) - assertEquals(TBook().getReferencesTo(TAuthor()), TBook().getReferencesTo(a)); - assertEquals(TBook().getReferencesTo(TAuthor()), b.getReferencesTo(a)); - assertEquals(TBook().getReferencesTo(TAuthor()), b.getReferencesTo(TAuthor())); - - // Only with a non-static meta model - if (a instanceof UpdatableTable && b instanceof UpdatableTable) { - UpdatableTable ua = (UpdatableTable) a; - UpdatableTable ub = (UpdatableTable) b; - - assertEquals(2, ua.getMainKey().getReferences().size()); - assertEquals(TBook(), ua.getMainKey().getReferences().get(0).getTable()); - assertEquals(TBook(), ua.getMainKey().getReferences().get(1).getTable()); - assertTrue(b.getReferences().containsAll(ua.getReferencesFrom(b))); - assertTrue(b.getReferences().containsAll(ub.getReferencesFrom(a))); - assertEquals(b.getReferencesTo(a), ua.getReferencesFrom(b)); - assertEquals(TBook().getReferencesTo(a), ua.getReferencesFrom(b)); - assertEquals(b.getReferencesTo(a), TAuthor().getReferencesFrom(b)); - } - } - else { - log.info("SKIPPING", "References tests"); - } - - for (Field field : T639().getFields()) { - if ("BYTE".equalsIgnoreCase(field.getName())) { - assertEquals(Byte.class, field.getType()); - assertEquals(SQLDataType.TINYINT, field.getDataType()); - } - else if ("SHORT".equalsIgnoreCase(field.getName())) { - assertEquals(Short.class, field.getType()); - assertEquals(SQLDataType.SMALLINT, field.getDataType()); - } - else if ("INTEGER".equalsIgnoreCase(field.getName())) { - assertEquals(Integer.class, field.getType()); - assertEquals(SQLDataType.INTEGER, field.getDataType()); - } - else if ("LONG".equalsIgnoreCase(field.getName())) { - assertEquals(Long.class, field.getType()); - assertEquals(SQLDataType.BIGINT, field.getDataType()); - } - else if ("BYTE_DECIMAL".equalsIgnoreCase(field.getName())) { - assertEquals(Byte.class, field.getType()); - assertEquals(SQLDataType.TINYINT, field.getDataType()); - } - else if ("SHORT_DECIMAL".equalsIgnoreCase(field.getName())) { - assertEquals(Short.class, field.getType()); - assertEquals(SQLDataType.SMALLINT, field.getDataType()); - } - else if ("INTEGER_DECIMAL".equalsIgnoreCase(field.getName())) { - assertEquals(Integer.class, field.getType()); - assertEquals(SQLDataType.INTEGER, field.getDataType()); - } - else if ("LONG_DECIMAL".equalsIgnoreCase(field.getName())) { - assertEquals(Long.class, field.getType()); - assertEquals(SQLDataType.BIGINT, field.getDataType()); - } - else if ("BIG_INTEGER".equalsIgnoreCase(field.getName())) { - assertEquals(BigInteger.class, field.getType()); - assertEquals(SQLDataType.DECIMAL_INTEGER, field.getDataType()); - } - - // [#745] TODO: Unify distinction between NUMERIC and DECIMAL - else if ("BIG_DECIMAL".equalsIgnoreCase(field.getName()) - && getDialect() != SQLDialect.ORACLE - && getDialect() != SQLDialect.POSTGRES - && getDialect() != SQLDialect.SQLITE - && getDialect() != SQLDialect.SQLSERVER) { - - assertEquals(BigDecimal.class, field.getType()); - assertEquals(SQLDataType.DECIMAL, field.getDataType()); - } - else if ("BIG_DECIMAL".equalsIgnoreCase(field.getName())) { - assertEquals(BigDecimal.class, field.getType()); - assertEquals(SQLDataType.NUMERIC, field.getDataType()); - } - - // [#746] TODO: Interestingly, HSQLDB and MySQL match REAL with DOUBLE. - // There is no matching type for java.lang.Float... - else if ("FLOAT".equalsIgnoreCase(field.getName()) - && getDialect() != SQLDialect.HSQLDB - && getDialect() != SQLDialect.MYSQL - && getDialect() != SQLDialect.SYBASE) { - - assertEquals(Float.class, field.getType()); - assertEquals(SQLDataType.REAL, field.getDataType()); - } - else if ("FLOAT".equalsIgnoreCase(field.getName()) - && getDialect() != SQLDialect.MYSQL - && getDialect() != SQLDialect.SYBASE) { - - assertEquals(Double.class, field.getType()); - assertEquals(SQLDataType.DOUBLE, field.getDataType()); - } - else if ("FLOAT".equalsIgnoreCase(field.getName())) { - assertEquals(Double.class, field.getType()); - assertEquals(SQLDataType.FLOAT, field.getDataType()); - } - - // [#746] TODO: Fix this, too - else if ("DOUBLE".equalsIgnoreCase(field.getName()) - && getDialect() != SQLDialect.SQLSERVER - && getDialect() != SQLDialect.ASE) { - - assertEquals(Double.class, field.getType()); - assertEquals(SQLDataType.DOUBLE, field.getDataType()); - } - else if ("DOUBLE".equalsIgnoreCase(field.getName())) { - assertEquals(Double.class, field.getType()); - assertEquals(SQLDataType.FLOAT, field.getDataType()); - } - } - } - @Test public void testBatchSingle() throws Exception { jOOQAbstractTest.reset = false; diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/MetaDataTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/MetaDataTests.java new file mode 100644 index 0000000000..ff64e89cb1 --- /dev/null +++ b/jOOQ-test/src/org/jooq/test/_/testcases/MetaDataTests.java @@ -0,0 +1,456 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.test._.testcases; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static org.jooq.SQLDialect.ASE; +import static org.jooq.SQLDialect.CUBRID; +import static org.jooq.SQLDialect.DB2; +import static org.jooq.SQLDialect.H2; +import static org.jooq.SQLDialect.HSQLDB; +import static org.jooq.SQLDialect.MYSQL; +import static org.jooq.SQLDialect.ORACLE; +import static org.jooq.SQLDialect.POSTGRES; +import static org.jooq.SQLDialect.SQLITE; +import static org.jooq.SQLDialect.SYBASE; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Date; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.jooq.Field; +import org.jooq.Meta; +import org.jooq.Record1; +import org.jooq.Record2; +import org.jooq.Record3; +import org.jooq.Record6; +import org.jooq.SQLDialect; +import org.jooq.Schema; +import org.jooq.Sequence; +import org.jooq.Table; +import org.jooq.TableRecord; +import org.jooq.UDT; +import org.jooq.UpdatableRecord; +import org.jooq.UpdatableTable; +import org.jooq.impl.SQLDataType; +import org.jooq.test.BaseTest; +import org.jooq.test.jOOQAbstractTest; + +import org.junit.Test; + +public class MetaDataTests< + A extends UpdatableRecord & Record6, + AP, + B extends UpdatableRecord, + S extends UpdatableRecord & Record1, + B2S extends UpdatableRecord & Record3, + BS extends UpdatableRecord, + L extends TableRecord & Record2, + X extends TableRecord, + DATE extends UpdatableRecord, + BOOL extends UpdatableRecord, + D extends UpdatableRecord, + T extends UpdatableRecord, + U extends TableRecord, + I extends TableRecord, + IPK extends UpdatableRecord, + T725 extends UpdatableRecord, + T639 extends UpdatableRecord, + T785 extends TableRecord> +extends BaseTest { + + public MetaDataTests(jOOQAbstractTest delegate) { + super(delegate); + } + + @Test + public void testMetaModel() throws Exception { + + // Test correct source code generation for the meta model + Schema schema = TAuthor().getSchema(); + if (schema != null) { + int sequences = 0; + + if (cSequences() != null) { + sequences++; + + // DB2 has an additional sequence for the T_TRIGGERS table + if (getDialect() == DB2 || + getDialect() == H2) { + + sequences++; + } + + // CUBRID generates sequences for AUTO_INCREMENT columns + else if (getDialect() == CUBRID) { + sequences += 3; + } + + // Oracle has additional sequences for [#961] + else if (getDialect() == ORACLE) { + sequences += 5; + } + } + + assertEquals(sequences, schema.getSequences().size()); + for (Table table : schema.getTables()) { + assertEquals(table, schema.getTable(table.getName())); + } + for (UDT udt : schema.getUDTs()) { + assertEquals(udt, schema.getUDT(udt.getName())); + } + for (Sequence sequence : schema.getSequences()) { + assertEquals(sequence, schema.getSequence(sequence.getName())); + } + + int tables = 17; + + // The additional T_DIRECTORY table for recursive queries + if (supportsRecursiveQueries()) { + tables++; + } + + // The additional T_TRIGGERS table for INSERT .. RETURNING + if (TTriggers() != null) { + tables++; + } + + // The additional T_UNSIGNED table + if (TUnsigned() != null) { + tables++; + } + + // The additional T_IDENTITY table + if (TIdentity() != null) { + tables++; + } + + // The additional T_IDENTITY_PK table + if (TIdentityPK() != null) { + tables++; + } + + // [#959] The T_959 table for enum collisions with Java keywords + if (getDialect() == MYSQL || + getDialect() == POSTGRES) { + tables++; + } + + // [#986] Some foreign key name collision checks + if (getDialect() == ASE || + getDialect() == CUBRID || + getDialect() == DB2 || + getDialect() == POSTGRES || + getDialect() == SQLITE || + getDialect() == SYBASE) { + + tables += 2; + } + + if (TArrays() == null) { + assertEquals(tables, schema.getTables().size()); + } + + // [#877] The T_877 table is only available in H2 + else if (getDialect() == H2) { + assertEquals(tables + 2, schema.getTables().size()); + } + + // [#624] The V_INCOMPLETE view is only available in Oracle + else if (getDialect() == ORACLE) { + assertEquals(tables + 3, schema.getTables().size()); + } + + // [#610] Collision-prone entities are only available in HSQLDB + else if (getDialect() == HSQLDB) { + assertEquals(tables + 11, schema.getTables().size()); + } + + else { + assertEquals(tables + 1, schema.getTables().size()); + } + + if (cUAddressType() == null) { + assertEquals(0, schema.getUDTs().size()); + } + // [#643] The U_INVALID types are only available in Oracle + // [#799] The member procedure UDT's too + else if (getDialect() == ORACLE) { + assertEquals(7, schema.getUDTs().size()); + } + else { + assertEquals(2, schema.getUDTs().size()); + } + } + + // Test correct source code generation for identity columns + assertNull(TAuthor().getIdentity()); + assertNull(TBook().getIdentity()); + + if (TIdentity() != null || TIdentityPK() != null) { + if (TIdentity() != null) { + assertEquals(TIdentity(), TIdentity().getIdentity().getTable()); + assertEquals(TIdentity_ID(), TIdentity().getIdentity().getField()); + } + + if (TIdentityPK() != null) { + assertEquals(TIdentityPK(), TIdentityPK().getIdentity().getTable()); + assertEquals(TIdentityPK_ID(), TIdentityPK().getIdentity().getField()); + } + } + else { + log.info("SKIPPING", "Identity tests"); + } + + // Test correct source code generation for relations + assertNotNull(TAuthor().getMainKey()); + assertNotNull(TAuthor().getKeys()); + assertTrue(TAuthor().getKeys().contains(TAuthor().getMainKey())); + assertEquals(1, TAuthor().getKeys().size()); + assertEquals(1, TAuthor().getMainKey().getFields().size()); + assertEquals(TAuthor_ID(), TAuthor().getMainKey().getFields().get(0)); + assertEquals(Record1.class, TAuthor().getRecordType().getMethod("key").getReturnType()); + assertTrue(TAuthor().getRecordType().getMethod("key").toGenericString().contains("org.jooq.Record1")); + assertEquals(Record2.class, TBookToBookStore().getRecordType().getMethod("key").getReturnType()); + assertTrue(TBookToBookStore().getRecordType().getMethod("key").toGenericString().contains("org.jooq.Record2")); + + if (supportsReferences()) { + + // Without aliasing + assertEquals(0, TAuthor().getReferences().size()); + assertEquals(2, TAuthor().getMainKey().getReferences().size()); + assertEquals(TBook(), TAuthor().getMainKey().getReferences().get(0).getTable()); + assertEquals(TBook(), TAuthor().getMainKey().getReferences().get(1).getTable()); + assertEquals(Arrays.asList(), TAuthor().getReferencesTo(TBook())); + assertTrue(TBook().getReferences().containsAll(TAuthor().getReferencesFrom(TBook()))); + assertTrue(TBook().getReferences().containsAll(TBook().getReferencesFrom(TAuthor()))); + assertEquals(TBook().getReferencesTo(TAuthor()), TAuthor().getReferencesFrom(TBook())); + + // [#1460] With aliasing + Table a = TAuthor().as("a"); + Table b = TBook().as("b"); + + assertEquals(0, a.getReferences().size()); + assertEquals(Arrays.asList(), a.getReferencesTo(b)); + + // This should work with both types of meta-models (static, non-static) + assertEquals(TBook().getReferencesTo(TAuthor()), TBook().getReferencesTo(a)); + assertEquals(TBook().getReferencesTo(TAuthor()), b.getReferencesTo(a)); + assertEquals(TBook().getReferencesTo(TAuthor()), b.getReferencesTo(TAuthor())); + + // Only with a non-static meta model + if (a instanceof UpdatableTable && b instanceof UpdatableTable) { + UpdatableTable ua = (UpdatableTable) a; + UpdatableTable ub = (UpdatableTable) b; + + assertEquals(2, ua.getMainKey().getReferences().size()); + assertEquals(TBook(), ua.getMainKey().getReferences().get(0).getTable()); + assertEquals(TBook(), ua.getMainKey().getReferences().get(1).getTable()); + assertTrue(b.getReferences().containsAll(ua.getReferencesFrom(b))); + assertTrue(b.getReferences().containsAll(ub.getReferencesFrom(a))); + assertEquals(b.getReferencesTo(a), ua.getReferencesFrom(b)); + assertEquals(TBook().getReferencesTo(a), ua.getReferencesFrom(b)); + assertEquals(b.getReferencesTo(a), TAuthor().getReferencesFrom(b)); + } + } + else { + log.info("SKIPPING", "References tests"); + } + + for (Field field : T639().getFields()) { + if ("BYTE".equalsIgnoreCase(field.getName())) { + assertEquals(Byte.class, field.getType()); + assertEquals(SQLDataType.TINYINT, field.getDataType()); + } + else if ("SHORT".equalsIgnoreCase(field.getName())) { + assertEquals(Short.class, field.getType()); + assertEquals(SQLDataType.SMALLINT, field.getDataType()); + } + else if ("INTEGER".equalsIgnoreCase(field.getName())) { + assertEquals(Integer.class, field.getType()); + assertEquals(SQLDataType.INTEGER, field.getDataType()); + } + else if ("LONG".equalsIgnoreCase(field.getName())) { + assertEquals(Long.class, field.getType()); + assertEquals(SQLDataType.BIGINT, field.getDataType()); + } + else if ("BYTE_DECIMAL".equalsIgnoreCase(field.getName())) { + assertEquals(Byte.class, field.getType()); + assertEquals(SQLDataType.TINYINT, field.getDataType()); + } + else if ("SHORT_DECIMAL".equalsIgnoreCase(field.getName())) { + assertEquals(Short.class, field.getType()); + assertEquals(SQLDataType.SMALLINT, field.getDataType()); + } + else if ("INTEGER_DECIMAL".equalsIgnoreCase(field.getName())) { + assertEquals(Integer.class, field.getType()); + assertEquals(SQLDataType.INTEGER, field.getDataType()); + } + else if ("LONG_DECIMAL".equalsIgnoreCase(field.getName())) { + assertEquals(Long.class, field.getType()); + assertEquals(SQLDataType.BIGINT, field.getDataType()); + } + else if ("BIG_INTEGER".equalsIgnoreCase(field.getName())) { + assertEquals(BigInteger.class, field.getType()); + assertEquals(SQLDataType.DECIMAL_INTEGER, field.getDataType()); + } + + // [#745] TODO: Unify distinction between NUMERIC and DECIMAL + else if ("BIG_DECIMAL".equalsIgnoreCase(field.getName()) + && getDialect() != SQLDialect.ORACLE + && getDialect() != SQLDialect.POSTGRES + && getDialect() != SQLDialect.SQLITE + && getDialect() != SQLDialect.SQLSERVER) { + + assertEquals(BigDecimal.class, field.getType()); + assertEquals(SQLDataType.DECIMAL, field.getDataType()); + } + else if ("BIG_DECIMAL".equalsIgnoreCase(field.getName())) { + assertEquals(BigDecimal.class, field.getType()); + assertEquals(SQLDataType.NUMERIC, field.getDataType()); + } + + // [#746] TODO: Interestingly, HSQLDB and MySQL match REAL with DOUBLE. + // There is no matching type for java.lang.Float... + else if ("FLOAT".equalsIgnoreCase(field.getName()) + && getDialect() != SQLDialect.HSQLDB + && getDialect() != SQLDialect.MYSQL + && getDialect() != SQLDialect.SYBASE) { + + assertEquals(Float.class, field.getType()); + assertEquals(SQLDataType.REAL, field.getDataType()); + } + else if ("FLOAT".equalsIgnoreCase(field.getName()) + && getDialect() != SQLDialect.MYSQL + && getDialect() != SQLDialect.SYBASE) { + + assertEquals(Double.class, field.getType()); + assertEquals(SQLDataType.DOUBLE, field.getDataType()); + } + else if ("FLOAT".equalsIgnoreCase(field.getName())) { + assertEquals(Double.class, field.getType()); + assertEquals(SQLDataType.FLOAT, field.getDataType()); + } + + // [#746] TODO: Fix this, too + else if ("DOUBLE".equalsIgnoreCase(field.getName()) + && getDialect() != SQLDialect.SQLSERVER + && getDialect() != SQLDialect.ASE) { + + assertEquals(Double.class, field.getType()); + assertEquals(SQLDataType.DOUBLE, field.getDataType()); + } + else if ("DOUBLE".equalsIgnoreCase(field.getName())) { + assertEquals(Double.class, field.getType()); + assertEquals(SQLDataType.FLOAT, field.getDataType()); + } + } + } + + @Test + public void testMetaData() throws Exception { + Meta meta = create().meta(); + + if (schema() != null) { + + // The schema returned from meta should be equal to the + // generated test schema + List metaSchemas = meta.getSchemas(); + assertTrue(metaSchemas.contains(schema())); + + Schema metaSchema = metaSchemas.get(metaSchemas.indexOf(schema())); + assertEquals(schema(), metaSchema); + + // The schema returned from meta should contain at least all the + // generated test tables + List> metaTables = metaSchema.getTables(); + assertTrue(metaTables.containsAll(schema().getTables())); + assertTrue(metaTables.size() >= schema().getTables().size()); + + metaTableChecks(metaTables); + } + + // Some sample checks about tables returned from meta + List> metaTables = meta.getTables(); + assertTrue(metaTables.contains(TAuthor())); + assertTrue(metaTables.contains(TBook())); + assertTrue(metaTables.contains(TBookStore())); + assertTrue(metaTables.contains(TBookToBookStore())); + assertTrue(metaTables.contains(VAuthor())); + assertTrue(metaTables.contains(VBook())); + assertTrue(metaTables.contains(VLibrary())); + + metaTableChecks(metaTables); + } + + private void metaTableChecks(Collection> metaTables) { + for (Table metaTable : metaTables) { + Table generatedTable = schema().getTable(metaTable.getName()); + + // Every table returned from meta should have a corresponding + // table by name in the generated test tables + if (generatedTable != null) { + assertNotNull(generatedTable); + assertEquals(metaTable, generatedTable); + + // Check if fields match, as well + List> metaFields = metaTable.getFields(); + assertTrue(metaFields.containsAll(generatedTable.getFields())); + + // Check if relations are correctly loaded (and typed) as well + // [#1977] Fix this, once the "main key" concept has been removed + if (generatedTable instanceof UpdatableTable && metaTable instanceof UpdatableTable) { + UpdatableTable generatedUTable = (UpdatableTable) generatedTable; + UpdatableTable metaUTable = (UpdatableTable) metaTable; + + // [#1977] TODO: Add key checks + } + + // Only truly updatable tables should be "Updatable" + else { + assertFalse(metaTable instanceof UpdatableTable); + } + } + } + } +} diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index 6a976ac9d5..9dbf69bad7 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -110,6 +110,7 @@ import org.jooq.test._.testcases.GroupByTests; import org.jooq.test._.testcases.InsertUpdateTests; import org.jooq.test._.testcases.JoinTests; import org.jooq.test._.testcases.LoaderTests; +import org.jooq.test._.testcases.MetaDataTests; import org.jooq.test._.testcases.OrderByTests; import org.jooq.test._.testcases.PlainSQLTests; import org.jooq.test._.testcases.PredicateTests; @@ -869,7 +870,12 @@ public abstract class jOOQAbstractTest< @Test public void testMetaModel() throws Exception { - new GeneralTests(this).testMetaModel(); + new MetaDataTests(this).testMetaModel(); + } + + @Test + public void testMetaData() throws Exception { + new MetaDataTests(this).testMetaData(); } @Test diff --git a/jOOQ/src/main/java/org/jooq/Meta.java b/jOOQ/src/main/java/org/jooq/Meta.java new file mode 100644 index 0000000000..2e7d056da2 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/Meta.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq; + +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +import org.jooq.exception.DataAccessException; +import org.jooq.impl.Executor; + +/** + * A wrapping object for {@link DatabaseMetaData} + *

+ * This object can be obtained through {@link Executor#meta()} in order to + * provide convenient access to your database meta data. This abstraction has + * two purposes: + *

+ *

    + *
  1. To increase API convenience, as no checked {@link SQLException} is + * thrown, only the unchecked {@link DataAccessException}
  2. + *
  3. To increase API convenience, as the returned objects are always jOOQ + * objects, not JDBC {@link ResultSet} objects with hard-to-remember API + * constraints
  4. + *
+ * + * @author Lukas Eder + */ +public interface Meta { + + /** + * Get all schema objects from the underlying {@link DatabaseMetaData} + */ + List getSchemas(); + + /** + * Get all table objects from the underlying {@link DatabaseMetaData} + */ + List> getTables(); +} diff --git a/jOOQ/src/main/java/org/jooq/impl/MetaImpl.java b/jOOQ/src/main/java/org/jooq/impl/MetaImpl.java new file mode 100644 index 0000000000..b8cfbf1eca --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/MetaImpl.java @@ -0,0 +1,213 @@ +/** + * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com + * All rights reserved. + * + * This software is licensed to you under the Apache License, Version 2.0 + * (the "License"); You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * . Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * . Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * . Neither the name "jOOQ" nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jooq.impl; + +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.jooq.ForeignKey; +import org.jooq.Meta; +import org.jooq.Record; +import org.jooq.Result; +import org.jooq.Schema; +import org.jooq.Table; +import org.jooq.TableField; +import org.jooq.UniqueKey; +import org.jooq.UpdatableTable; +import org.jooq.exception.DataAccessException; + +/** + * @author Lukas Eder + */ +class MetaImpl implements Meta { + + private final Executor executor; + private transient DatabaseMetaData meta; + + MetaImpl(Executor executor) { + this.executor = executor; + } + + private final DatabaseMetaData meta() { + if (meta == null) { + try { + meta = executor.getConnection().getMetaData(); + } + catch (SQLException e) { + throw new DataAccessException("Error while accessing DatabaseMetaData", e); + } + } + + return meta; + } + + @Override + public final List getSchemas() { + try { + List result = new ArrayList(); + Result schemas = executor.fetch(meta().getSchemas()); + + for (String name : schemas.getValues(0, String.class)) { + result.add(new MetaSchema(name)); + } + + return result; + } + catch (SQLException e) { + throw new DataAccessException("Error while accessing DatabaseMetaData", e); + } + } + + @Override + public final List> getTables() { + List> result = new ArrayList>(); + + for (Schema schema : getSchemas()) { + result.addAll(schema.getTables()); + } + + return result; + } + + private class MetaSchema extends SchemaImpl { + + /** + * Generated UID + */ + private static final long serialVersionUID = -2621899850912554198L; + + MetaSchema(String name) { + super(name); + } + + @Override + public final List> getTables() { + try { + List> result = new ArrayList>(); + Result tables = executor.fetch(meta().getTables(null, getName(), "%", null)); + + for (Record table : tables) { + String catalog = table.getValue(0, String.class); + String schema = table.getValue(1, String.class); + String name = table.getValue(2, String.class); + + Result pkColumns = executor.fetch(meta().getPrimaryKeys(catalog, schema, name)) + .sortAsc("KEY_SEQ"); + + if (pkColumns.size() == 0) { + result.add(new MetaTable(name, this)); + } + else { + result.add(new MetaUpdatableTable(name, this)); + } + } + + return result; + } + catch (SQLException e) { + throw new DataAccessException("Error while accessing DatabaseMetaData", e); + } + } + } + + private class MetaTable extends TableImpl { + + /** + * Generated UID + */ + private static final long serialVersionUID = 4843841667753000233L; + + MetaTable(String name, Schema schema) { + super(name, schema); + + init(); + } + + private final void init() { + try { + Result columns = executor.fetch(meta().getColumns(null, getSchema().getName(), getName(), "%")); + + for (Record column : columns) { + createField(column.getValue("COLUMN_NAME", String.class), SQLDataType.OTHER, this); + } + } + catch (SQLException e) { + throw new DataAccessException("Error while accessing DatabaseMetaData", e); + } + } + } + + private class MetaUpdatableTable extends MetaTable implements UpdatableTable { + + /** + * Generated UID + */ + private static final long serialVersionUID = -4555457095396846609L; + + MetaUpdatableTable(String name, Schema schema) { + super(name, schema); + } + + @Override + public final UniqueKey getMainKey() { + return null; + } + + @Override + public final List> getKeys() { + return Collections.emptyList(); + } + + @Override + public final List> getReferencesFrom(Table other) { + return other.getReferencesTo(this); + } + + @Override + public final TableField getRecordVersion() { + return null; + } + + @Override + public final TableField getRecordTimestamp() { + return null; + } + } +}