[jOOQ/jOOQ#6492] Added support for XML based meta data, and GenerationOption

This commit is contained in:
Lukas Eder 2021-12-04 16:50:14 +01:00
parent be557019a1
commit 9e940cccc6
15 changed files with 264 additions and 34 deletions

View File

@ -37,6 +37,8 @@
*/
package org.jooq.codegen;
import static org.jooq.impl.QOM.GenerationOption.STORED;
import static org.jooq.impl.QOM.GenerationOption.VIRTUAL;
import static org.jooq.tools.StringUtils.isBlank;
import static org.jooq.util.xml.jaxb.TableConstraintType.CHECK;
import static org.jooq.util.xml.jaxb.TableConstraintType.FOREIGN_KEY;
@ -197,6 +199,12 @@ public class XMLGenerator extends AbstractGenerator {
column.setOrdinalPosition(co.getPosition());
column.setReadonly(co.isReadonly());
if (type.isComputed()) {
column.setIsGenerated(type.isComputed());
column.setGenerationExpression(type.getGeneratedAlwaysAs());
column.setGenerationOption(type.getGenerationOption() == VIRTUAL ? "VIRTUAL" : type.getGenerationOption() == STORED ? "STORED" : null);
}
is.getColumns().add(column);
}
}

View File

@ -62,18 +62,18 @@
<version>${org.hibernate.version}</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>${jaxb.version}</version>
</annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
<dependencies>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>${jaxb.version}</version>
</dependency>
</dependencies>
</plugin>

View File

@ -52,6 +52,7 @@ import java.util.regex.Pattern;
import org.jooq.Record;
import org.jooq.TableOptions.TableType;
import org.jooq.impl.QOM.GenerationOption;
import org.jooq.meta.AbstractTableDefinition;
import org.jooq.meta.ColumnDefinition;
import org.jooq.meta.DataTypeDefinition;
@ -113,6 +114,12 @@ public class MySQLTableDefinition extends AbstractTableDefinition {
// [#6492] MariaDB supports a standard IS_GENERATED, but MySQL doesn't (yet)
boolean generated = record.get(COLUMNS.EXTRA) != null && record.get(COLUMNS.EXTRA).toUpperCase().contains("GENERATED");
GenerationOption generationOption =
"VIRTUAL GENERATED".equalsIgnoreCase(record.get(COLUMNS.EXTRA))
? GenerationOption.VIRTUAL
: "STORED GENERATED".equalsIgnoreCase(record.get(COLUMNS.EXTRA))
? GenerationOption.STORED
: null;
columnTypeFix:
if (unsigned || displayWidths) {
@ -148,7 +155,9 @@ public class MySQLTableDefinition extends AbstractTableDefinition {
record.get(COLUMNS.IS_NULLABLE, boolean.class),
generated ? null : record.get(COLUMNS.COLUMN_DEFAULT),
name(getSchema().getName(), getName() + "_" + record.get(COLUMNS.COLUMN_NAME))
).generatedAlwaysAs(generated ? record.get(COLUMNS.GENERATION_EXPRESSION) : null);
)
.generatedAlwaysAs(generated ? record.get(COLUMNS.GENERATION_EXPRESSION) : null)
.generationOption(generationOption);
result.add(new DefaultColumnDefinition(
getDatabase().getTable(getSchema(), getName()),

View File

@ -1112,6 +1112,20 @@ public class PostgresDatabase extends AbstractDatabase implements ResultQueryDat

View File

@ -60,6 +60,7 @@ import org.jooq.Condition;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.TableOptions.TableType;
import org.jooq.impl.QOM.GenerationOption;
import org.jooq.meta.AbstractTableDefinition;
import org.jooq.meta.ColumnDefinition;
import org.jooq.meta.DataTypeDefinition;
@ -92,6 +93,10 @@ public class PostgresTableDefinition extends AbstractTableDefinition {
Field<String> udtSchema = COLUMNS.UDT_SCHEMA;
Field<Integer> precision = nvl(COLUMNS.DATETIME_PRECISION, COLUMNS.NUMERIC_PRECISION);
Field<String> serialColumnDefault = inline("nextval('%_seq'::regclass)");
Field<String> generationExpression = COLUMNS.GENERATION_EXPRESSION;
Field<String> attgenerated = PG_ATTRIBUTE.ATTGENERATED;
@ -123,7 +128,8 @@ public class PostgresTableDefinition extends AbstractTableDefinition {
COLUMNS.NUMERIC_SCALE,
(when(isIdentity, inline("YES"))).as(COLUMNS.IS_IDENTITY),
COLUMNS.IS_NULLABLE,
COLUMNS.GENERATION_EXPRESSION,
generationExpression.as(COLUMNS.GENERATION_EXPRESSION),
attgenerated.as(PG_ATTRIBUTE.ATTGENERATED),
(when(isIdentity, inline(null, String.class)).else_(COLUMNS.COLUMN_DEFAULT)).as(COLUMNS.COLUMN_DEFAULT),
coalesce(COLUMNS.DOMAIN_SCHEMA, udtSchema).as(COLUMNS.UDT_SCHEMA),
coalesce(COLUMNS.DOMAIN_NAME, COLUMNS.UDT_NAME).as(COLUMNS.UDT_NAME),
@ -166,7 +172,15 @@ public class PostgresTableDefinition extends AbstractTableDefinition {
record.get(COLUMNS.UDT_SCHEMA),
record.get(COLUMNS.UDT_NAME)
)
).generatedAlwaysAs(record.get(COLUMNS.GENERATION_EXPRESSION));
)
.generatedAlwaysAs(record.get(COLUMNS.GENERATION_EXPRESSION))
.generationOption(
"s".equals(record.get(PG_ATTRIBUTE.ATTGENERATED))
? GenerationOption.STORED
: "v".equals(record.get(PG_ATTRIBUTE.ATTGENERATED))
? GenerationOption.VIRTUAL
: null
);
ColumnDefinition column = new DefaultColumnDefinition(
getDatabase().getTable(getSchema(), getName()),

View File

@ -42,6 +42,8 @@ import static org.jooq.impl.DSL.field;
import static org.jooq.impl.DSL.inline;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.selectOne;
import static org.jooq.impl.QOM.GenerationOption.STORED;
import static org.jooq.impl.QOM.GenerationOption.VIRTUAL;
import static org.jooq.meta.sqlite.sqlite_master.SQLiteMaster.SQLITE_MASTER;
import static org.jooq.tools.StringUtils.isBlank;
@ -49,7 +51,6 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.jooq.Check;
import org.jooq.Configuration;
import org.jooq.Field;
import org.jooq.Query;
@ -61,7 +62,6 @@ import org.jooq.impl.DSL;
import org.jooq.impl.ParserException;
import org.jooq.meta.AbstractTableDefinition;
import org.jooq.meta.ColumnDefinition;
import org.jooq.meta.DefaultCheckConstraintDefinition;
import org.jooq.meta.DefaultColumnDefinition;
import org.jooq.meta.DefaultDataTypeDefinition;
import org.jooq.meta.SchemaDefinition;
@ -123,8 +123,9 @@ public class SQLiteTableDefinition extends AbstractTableDefinition {
.select(fName, fType, fNotnull, fDefaultValue, fPk, fHidden)
.from("pragma_table_xinfo({0})", inline(getName()))
// 0 = ordinary column
// 2 = generated column
.where("hidden in (0, 2)")
// 2 = generated column (virtual)
// 3 = generated column (stored)
.where("hidden in (0, 2, 3)")
) {
String name = record.get(fName);
@ -138,7 +139,7 @@ public class SQLiteTableDefinition extends AbstractTableDefinition {
int pk = record.get(fPk);
int hidden = record.get(fHidden);
boolean identity = false;
boolean generated = hidden == 2;
boolean generated = hidden == 2 || hidden == 3;
String generator = null;
// [#8278] [#11172] SQLite doesn't store the data type for all views or virtual tables
@ -198,7 +199,9 @@ public class SQLiteTableDefinition extends AbstractTableDefinition {
scale,
!record.get(fNotnull),
record.get(fDefaultValue)
).generatedAlwaysAs(generator);
)
.generatedAlwaysAs(generator)
.generationOption(hidden == 2 ? VIRTUAL : hidden == 3 ? STORED : null);
result.add(new DefaultColumnDefinition(
getDatabase().getTable(getSchema(), getName()),

View File

@ -38,6 +38,8 @@
package org.jooq.meta.xml;
import static java.lang.Boolean.TRUE;
import static org.jooq.impl.QOM.GenerationOption.STORED;
import static org.jooq.impl.QOM.GenerationOption.VIRTUAL;
import static org.jooq.meta.xml.XMLDatabase.unbox;
import static org.jooq.tools.StringUtils.defaultIfNull;
@ -100,7 +102,15 @@ public class XMLTableDefinition extends AbstractTableDefinition {
unbox(column.getNumericScale()),
column.isIsNullable(),
column.getColumnDefault()
);
)
.generatedAlwaysAs(TRUE.equals(column.isIsGenerated()) ? column.getGenerationExpression() : null)
.generationOption(TRUE.equals(column.isIsGenerated())
? "STORED".equalsIgnoreCase(column.getGenerationOption())
? STORED
: "VIRTUAL".equalsIgnoreCase(column.getGenerationOption())
? VIRTUAL
: null
: null);
result.add(new DefaultColumnDefinition(
this,

View File

@ -37,6 +37,8 @@
*/
package org.jooq.impl;
import static org.jooq.impl.QOM.GenerationOption.STORED;
import static org.jooq.impl.QOM.GenerationOption.VIRTUAL;
import static org.jooq.tools.StringUtils.isBlank;
import static org.jooq.util.xml.jaxb.TableConstraintType.CHECK;
import static org.jooq.util.xml.jaxb.TableConstraintType.FOREIGN_KEY;
@ -51,6 +53,7 @@ import java.util.Set;
import org.jooq.Catalog;
import org.jooq.Check;
import org.jooq.Configuration;
import org.jooq.DataType;
import org.jooq.Domain;
import org.jooq.Field;
import org.jooq.ForeignKey;
@ -64,6 +67,7 @@ import org.jooq.SortField;
import org.jooq.SortOrder;
import org.jooq.Table;
import org.jooq.UniqueKey;
import org.jooq.impl.QOM.GenerationOption;
import org.jooq.util.xml.jaxb.CheckConstraint;
import org.jooq.util.xml.jaxb.Column;
import org.jooq.util.xml.jaxb.IndexColumnUsage;
@ -328,6 +332,7 @@ final class InformationSchemaExport {
Field<?>[] fields = t.fields();
for (int i = 0; i < fields.length; i++) {
Field<?> f = fields[i];
DataType<?> type = f.getDataType();
Column ic = new Column();
if (!isBlank(catalogName))
@ -339,21 +344,27 @@ final class InformationSchemaExport {
ic.setTableName(t.getName());
ic.setColumnName(f.getName());
ic.setComment(f.getComment());
ic.setDataType(f.getDataType().getTypeName(configuration));
ic.setDataType(type.getTypeName(configuration));
if (f.getDataType().lengthDefined())
ic.setCharacterMaximumLength(f.getDataType().length());
if (type.lengthDefined())
ic.setCharacterMaximumLength(type.length());
if (f.getDataType().precisionDefined())
ic.setNumericPrecision(f.getDataType().precision());
if (type.precisionDefined())
ic.setNumericPrecision(type.precision());
if (f.getDataType().scaleDefined())
ic.setNumericScale(f.getDataType().scale());
if (type.scaleDefined())
ic.setNumericScale(type.scale());
ic.setColumnDefault(DSL.using(configuration).render(f.getDataType().defaultValue()));
ic.setIsNullable(f.getDataType().nullable());
ic.setColumnDefault(DSL.using(configuration).render(type.defaultValue()));
ic.setIsNullable(type.nullable());
ic.setOrdinalPosition(i + 1);
ic.setReadonly(f.getDataType().readonly());
ic.setReadonly(type.readonly());
if (type.computed()) {
ic.setIsGenerated(type.computed());
ic.setGenerationExpression(DSL.using(configuration).render(type.generatedAlwaysAs()));
ic.setGenerationOption(type.generationOption() == VIRTUAL ? "VIRTUAL" : type.generationOption() == STORED ? "STORED" : null);
}
result.getColumns().add(ic);
}

View File

@ -43,19 +43,27 @@ import static java.util.Collections.emptyList;
import static java.util.Comparator.comparing;
import static java.util.Comparator.comparingInt;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.QOM.GenerationOption.STORED;
import static org.jooq.impl.QOM.GenerationOption.VIRTUAL;
import static org.jooq.impl.Tools.EMPTY_CHECK;
import static org.jooq.impl.Tools.EMPTY_SORTFIELD;
import static org.jooq.tools.StringUtils.defaultIfNull;
import static org.jooq.util.xml.jaxb.TableConstraintType.PRIMARY_KEY;
import java.math.BigInteger;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.jooq.Catalog;
import org.jooq.Check;
import org.jooq.Configuration;
import org.jooq.DataType;
import org.jooq.Domain;
import org.jooq.Field;
import org.jooq.ForeignKey;
import org.jooq.Index;
import org.jooq.Name;
@ -69,6 +77,7 @@ import org.jooq.TableOptions;
import org.jooq.TableOptions.TableType;
import org.jooq.UniqueKey;
import org.jooq.exception.SQLDialectNotSupportedException;
import org.jooq.impl.QOM.GenerationOption;
import org.jooq.tools.StringUtils;
import org.jooq.util.xml.jaxb.CheckConstraint;
import org.jooq.util.xml.jaxb.Column;
@ -201,7 +210,7 @@ final class InformationSchemaMetaImpl extends AbstractMeta {
InformationSchemaDomain<?> id = new InformationSchemaDomain<Object>(
schema,
name(d.getDomainName()),
(DataType) type(d.getDataType(), length, precision, scale, nullable, false),
(DataType) type(d.getDataType(), length, precision, scale, nullable, false, null, null),
checks.toArray(EMPTY_CHECK)
);
domains.add(id);
@ -276,6 +285,16 @@ final class InformationSchemaMetaImpl extends AbstractMeta {
int scale = xc.getNumericScale() == null ? 0 : xc.getNumericScale();
boolean nullable = !FALSE.equals(xc.isIsNullable());
boolean readonly = TRUE.equals(xc.isReadonly());
Field<?> generatedAlwaysAs = TRUE.equals(xc.isIsGenerated())
? DSL.field(xc.getGenerationExpression())
: null;
GenerationOption generationOption = TRUE.equals(xc.isIsGenerated())
? "STORED".equalsIgnoreCase(xc.getGenerationOption())
? STORED
: "VIRTUAL".equalsIgnoreCase(xc.getGenerationOption())
? VIRTUAL
: null
: null;
// TODO: Exception handling should be moved inside SQLDataType
Name tableName = name(xc.getTableCatalog(), xc.getTableSchema(), xc.getTableName());
@ -288,7 +307,7 @@ final class InformationSchemaMetaImpl extends AbstractMeta {
AbstractTable.createField(
name(xc.getColumnName()),
type(typeName, length, precision, scale, nullable, readonly),
type(typeName, length, precision, scale, nullable, readonly, generatedAlwaysAs, generationOption),
table,
xc.getComment()
);
@ -507,7 +526,7 @@ final class InformationSchemaMetaImpl extends AbstractMeta {
InformationSchemaSequence is = new InformationSchemaSequence(
xs.getSequenceName(),
schema,
type(typeName, length, precision, scale, nullable, false),
type(typeName, length, precision, scale, nullable, false, null, null),
startWith,
incrementBy,
minvalue,
@ -541,7 +560,17 @@ final class InformationSchemaMetaImpl extends AbstractMeta {
lookup.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
}
private final DataType<?> type(String typeName, int length, int precision, int scale, boolean nullable, boolean readonly) {
@SuppressWarnings("unchecked")
private final DataType<?> type(
String typeName,
int length,
int precision,
int scale,
boolean nullable,
boolean readonly,
Field<?> generatedAlwaysAs,
GenerationOption generationOption
) {
DataType<?> type = null;
try {
@ -553,6 +582,11 @@ final class InformationSchemaMetaImpl extends AbstractMeta {
type = type.length(length);
else if (precision != 0 || scale != 0)
type = type.precision(precision, scale);
if (generatedAlwaysAs != null)
type = type.generatedAlwaysAs((Field) generatedAlwaysAs);
if (generationOption != null)
type = type.generationOption(generationOption);
}
catch (SQLDialectNotSupportedException e) {
type = SQLDataType.OTHER;

View File

@ -693,7 +693,7 @@ final class MetaImpl extends AbstractMeta {
@Override
public final List<Index> getIndexes() {
// See https://github.com/h2database/h2database/issues/3236
return ignoreNPE(
return Tools.<List<Index>, RuntimeException>ignoreNPE(
() -> {
Result<Record> result = removeSystemIndexes(meta(meta -> {
try (ResultSet rs = catalogSchema(getCatalog(), getSchema(), (c, s) -> meta.getIndexInfo(c, s, getName(), false, true))) {

View File

@ -4597,7 +4597,10 @@ final class DefaultParseContext extends AbstractScope implements ParseContext {
continue;
}
else if (!computed && parseKeywordIf("AS") && requireProEdition()) {
else if (!computed
&& (parseKeywordIf("AS")
|| parseKeywordIf("COMPUTED") && (parseKeywordIf("BY") || true))
&& requireProEdition()) {

View File

@ -174,6 +174,7 @@ import static org.jooq.impl.Keywords.K_THROW;
import static org.jooq.impl.Keywords.K_VIRTUAL;
import static org.jooq.impl.Keywords.K_WHEN;
import static org.jooq.impl.QOM.GenerationOption.STORED;
import static org.jooq.impl.QOM.GenerationOption.VIRTUAL;
import static org.jooq.impl.SQLDataType.BLOB;
import static org.jooq.impl.SQLDataType.CLOB;
import static org.jooq.impl.SQLDataType.INTEGER;
@ -321,6 +322,7 @@ import org.jooq.exception.MappingException;
import org.jooq.exception.NoDataFoundException;
import org.jooq.exception.TemplatingException;
import org.jooq.exception.TooManyRowsException;
import org.jooq.impl.QOM.GenerationOption;
import org.jooq.impl.ResultsImpl.ResultOrRowsImpl;
import org.jooq.tools.Ints;
import org.jooq.tools.JooqLogger;
@ -5273,6 +5275,26 @@ final class Tools {

View File

@ -42,6 +42,9 @@ import org.jooq.util.jaxb.tools.XMLBuilder;
* &lt;element name="column_default" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/&gt;
* &lt;element name="comment" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/&gt;
* &lt;element name="readonly" type="{http://www.w3.org/2001/XMLSchema}boolean" minOccurs="0"/&gt;
* &lt;element name="is_generated" type="{http://www.w3.org/2001/XMLSchema}boolean" minOccurs="0"/&gt;
* &lt;element name="generation_expression" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/&gt;
* &lt;element name="generation_option" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/&gt;
* &lt;/all&gt;
* &lt;/restriction&gt;
* &lt;/complexContent&gt;
@ -113,6 +116,14 @@ public class Column implements Serializable, XMLAppendable
@XmlJavaTypeAdapter(StringAdapter.class)
protected String comment;
protected Boolean readonly;
@XmlElement(name = "is_generated")
protected Boolean isGenerated;
@XmlElement(name = "generation_expression")
@XmlJavaTypeAdapter(StringAdapter.class)
protected String generationExpression;
@XmlElement(name = "generation_option")
@XmlJavaTypeAdapter(StringAdapter.class)
protected String generationOption;
public String getTableCatalog() {
return tableCatalog;
@ -306,6 +317,46 @@ public class Column implements Serializable, XMLAppendable
this.readonly = value;
}
/**
* Gets the value of the isGenerated property.
*
* @return
* possible object is
* {@link Boolean }
*
*/
public Boolean isIsGenerated() {
return isGenerated;
}
/**
* Sets the value of the isGenerated property.
*
* @param value
* allowed object is
* {@link Boolean }
*
*/
public void setIsGenerated(Boolean value) {
this.isGenerated = value;
}
public String getGenerationExpression() {
return generationExpression;
}
public void setGenerationExpression(String value) {
this.generationExpression = value;
}
public String getGenerationOption() {
return generationOption;
}
public void setGenerationOption(String value) {
this.generationOption = value;
}
public Column withTableCatalog(String value) {
setTableCatalog(value);
return this;
@ -406,6 +457,21 @@ public class Column implements Serializable, XMLAppendable
return this;
}
public Column withIsGenerated(Boolean value) {
setIsGenerated(value);
return this;
}
public Column withGenerationExpression(String value) {
setGenerationExpression(value);
return this;
}
public Column withGenerationOption(String value) {
setGenerationOption(value);
return this;
}
@Override
public final void appendTo(XMLBuilder builder) {
builder.append("table_catalog", tableCatalog);
@ -428,6 +494,9 @@ public class Column implements Serializable, XMLAppendable
builder.append("column_default", columnDefault);
builder.append("comment", comment);
builder.append("readonly", readonly);
builder.append("is_generated", isGenerated);
builder.append("generation_expression", generationExpression);
builder.append("generation_option", generationOption);
}
@Override
@ -629,6 +698,33 @@ public class Column implements Serializable, XMLAppendable
return false;
}
}
if (isGenerated == null) {
if (other.isGenerated!= null) {
return false;
}
} else {
if (!isGenerated.equals(other.isGenerated)) {
return false;
}
}
if (generationExpression == null) {
if (other.generationExpression!= null) {
return false;
}
} else {
if (!generationExpression.equals(other.generationExpression)) {
return false;
}
}
if (generationOption == null) {
if (other.generationOption!= null) {
return false;
}
} else {
if (!generationOption.equals(other.generationOption)) {
return false;
}
}
return true;
}
@ -656,6 +752,9 @@ public class Column implements Serializable, XMLAppendable
result = ((prime*result)+((columnDefault == null)? 0 :columnDefault.hashCode()));
result = ((prime*result)+((comment == null)? 0 :comment.hashCode()));
result = ((prime*result)+((readonly == null)? 0 :readonly.hashCode()));
result = ((prime*result)+((isGenerated == null)? 0 :isGenerated.hashCode()));
result = ((prime*result)+((generationExpression == null)? 0 :generationExpression.hashCode()));
result = ((prime*result)+((generationOption == null)? 0 :generationOption.hashCode()));
return result;
}

View File

@ -84,6 +84,9 @@
<element name="column_default" type="string" minOccurs="0" maxOccurs="1" />
<element name="comment" type="string" minOccurs="0" maxOccurs="1" />
<element name="readonly" type="boolean" minOccurs="0" maxOccurs="1" />
<element name="is_generated" type="boolean" minOccurs="0" maxOccurs="1" />
<element name="generation_expression" type="string" minOccurs="0" maxOccurs="1" />
<element name="generation_option" type="string" minOccurs="0" maxOccurs="1" />
</all>
</complexType>

View File

@ -34,7 +34,7 @@
<!-- JDBC drivers for jOOQ-xyz-extensions modules and vendor-specific API access -->
<postgres.version>42.3.0</postgres.version>
<sqlserver.version>9.2.1.jre11</sqlserver.version>
<sqlserver.version>9.4.0.jre11</sqlserver.version>
<oracle.version>21.3.0.0</oracle.version>
<redshift.version>2.0.0.6</redshift.version>