Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
/** Type converter for OceanBase. */
public class OceanBaseTypeConverter extends JdbcTypeConverter {

private static final int MAX_TIMESTAMP_PRECISION = 6;

static final String TINYINT = "tinyint";
static final String TINYINT_UNSIGNED = "tinyint unsigned";
static final String SMALLINT = "smallint";
Expand Down Expand Up @@ -137,6 +139,13 @@ public String fromGravitino(Type type) {
return type.simpleString();
} else if (type instanceof Types.TimestampType) {
Types.TimestampType timestampType = (Types.TimestampType) type;
if (timestampType.hasPrecisionSet() && timestampType.precision() > MAX_TIMESTAMP_PRECISION) {
throw new IllegalArgumentException(
String.format(
"OceanBase MySQL mode cannot preserve timestamp precision %d; "
+ "the maximum supported precision is %d",
timestampType.precision(), MAX_TIMESTAMP_PRECISION));
}
String baseType = timestampType.hasTimeZone() ? TIMESTAMP : DATETIME;
return timestampType.hasPrecisionSet()
? String.format("%s(%d)", baseType, timestampType.precision())
Expand All @@ -149,6 +158,18 @@ public String fromGravitino(Type type) {
return type.simpleString();
} else if (type instanceof Types.BinaryType) {
return type.simpleString();
} else if (type instanceof Types.NullType) {
throw new IllegalArgumentException(
"OceanBase has no column type that preserves the Gravitino unknown type");
} else if (type instanceof Types.VariantType) {
throw new IllegalArgumentException(
"OceanBase JSON cannot losslessly preserve the Gravitino variant type");
} else if (type instanceof Types.GeometryType) {
throw new IllegalArgumentException(
"OceanBase JDBC metadata cannot preserve Gravitino geometry CRS/SRID metadata");
} else if (type instanceof Types.GeographyType) {
throw new IllegalArgumentException(
"OceanBase has no geography column type that preserves CRS and edge algorithm metadata");
} else if (type instanceof Types.ExternalType) {
return ((Types.ExternalType) type).catalogString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,59 @@ public void testFromGravitinoType() {
() -> OCEANBASE_TYPE_CONVERTER.fromGravitino(Types.UnparsedType.of(USER_DEFINED_TYPE)));
}

@Test
public void testRejectNanosecondTimestampTypes() {
assertFromGravitinoRejected(
Types.TimestampType.withoutTimeZone(9),
"OceanBase MySQL mode cannot preserve timestamp precision 9; "
+ "the maximum supported precision is 6");
assertFromGravitinoRejected(
Types.TimestampType.withTimeZone(9),
"OceanBase MySQL mode cannot preserve timestamp precision 9; "
+ "the maximum supported precision is 6");
}

@Test
public void testRejectVariantType() {
assertFromGravitinoRejected(
Types.VariantType.get(),
"OceanBase JSON cannot losslessly preserve the Gravitino variant type");
}

@Test
public void testRejectUnknownType() {
assertFromGravitinoRejected(
Types.NullType.get(),
"OceanBase has no column type that preserves the Gravitino unknown type");
}

@Test
public void testRejectGeometryTypes() {
String expectedMessage =
"OceanBase JDBC metadata cannot preserve Gravitino geometry CRS/SRID metadata";
assertFromGravitinoRejected(Types.GeometryType.crs84(), expectedMessage);
assertFromGravitinoRejected(Types.GeometryType.of("EPSG:3857"), expectedMessage);
}

@Test
public void testRejectGeographyTypes() {
String expectedMessage =
"OceanBase has no geography column type that preserves CRS and edge algorithm metadata";
assertFromGravitinoRejected(Types.GeographyType.crs84(), expectedMessage);
assertFromGravitinoRejected(Types.GeographyType.of("EPSG:4326", "karney"), expectedMessage);
}

protected void checkGravitinoTypeToJdbcType(String jdbcTypeName, Type gravitinoType) {
Assertions.assertEquals(jdbcTypeName, OCEANBASE_TYPE_CONVERTER.fromGravitino(gravitinoType));
}

private void assertFromGravitinoRejected(Type type, String expectedMessage) {
IllegalArgumentException exception =
Assertions.assertThrows(
IllegalArgumentException.class, () -> OCEANBASE_TYPE_CONVERTER.fromGravitino(type));
Assertions.assertEquals(expectedMessage, exception.getMessage());
}

protected void checkJdbcTypeToGravitinoType(
Type gravitinoType,
String jdbcTypeName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
import org.apache.gravitino.rel.indexes.Index;
import org.apache.gravitino.rel.indexes.Indexes;
import org.apache.gravitino.rel.types.Decimal;
import org.apache.gravitino.rel.types.Type;
import org.apache.gravitino.rel.types.Types;
import org.apache.gravitino.utils.RandomNameUtils;
import org.junit.jupiter.api.AfterAll;
Expand Down Expand Up @@ -1990,4 +1991,78 @@ void testTimeTypePrecision() {
}
}
}

@Test
void testRejectNanosecondTimestampsWithoutCreatingTables() {
assertCreateRejectedWithoutSideEffect(
"timestamp_ns",
Types.TimestampType.withoutTimeZone(9),
"OceanBase MySQL mode cannot preserve timestamp precision 9; "
+ "the maximum supported precision is 6");
assertCreateRejectedWithoutSideEffect(
"timestamptz_ns",
Types.TimestampType.withTimeZone(9),
"OceanBase MySQL mode cannot preserve timestamp precision 9; "
+ "the maximum supported precision is 6");
}

@Test
void testRejectVariantWithoutCreatingTable() {
assertCreateRejectedWithoutSideEffect(
"variant",
Types.VariantType.get(),
"OceanBase JSON cannot losslessly preserve the Gravitino variant type");
}

@Test
void testRejectUnknownWithoutCreatingTable() {
assertCreateRejectedWithoutSideEffect(
"unknown",
Types.NullType.get(),
"OceanBase has no column type that preserves the Gravitino unknown type");
}

@Test
void testRejectGeometryWithoutCreatingTables() {
String expectedMessage =
"OceanBase JDBC metadata cannot preserve Gravitino geometry CRS/SRID metadata";
assertCreateRejectedWithoutSideEffect(
"geometry_crs84", Types.GeometryType.crs84(), expectedMessage);
assertCreateRejectedWithoutSideEffect(
"geometry_epsg3857", Types.GeometryType.of("EPSG:3857"), expectedMessage);
}

@Test
void testRejectGeographyWithoutCreatingTables() {
String expectedMessage =
"OceanBase has no geography column type that preserves CRS and edge algorithm metadata";
assertCreateRejectedWithoutSideEffect(
"geography_crs84", Types.GeographyType.crs84(), expectedMessage);
assertCreateRejectedWithoutSideEffect(
"geography_karney", Types.GeographyType.of("EPSG:4326", "karney"), expectedMessage);
}

private void assertCreateRejectedWithoutSideEffect(
String tablePrefix, Type type, String expectedMessage) {
TableCatalog tableCatalog = catalog.asTableCatalog();
NameIdentifier tableIdentifier =
NameIdentifier.of(schemaName, GravitinoITUtils.genRandomName(tablePrefix));

IllegalArgumentException exception =
Assertions.assertThrows(
IllegalArgumentException.class,
() ->
tableCatalog.createTable(
tableIdentifier,
new Column[] {Column.of("value", type)},
"unsupported type",
Collections.emptyMap()));

Assertions.assertTrue(
exception.getMessage().contains(expectedMessage),
String.format(
"Expected rejection to contain \"%s\", but was: %s",
expectedMessage, exception.getMessage()));
Assertions.assertFalse(tableCatalog.tableExists(tableIdentifier));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.apache.gravitino.rel.expressions.literals.Literals;
import org.apache.gravitino.rel.expressions.transforms.Transforms;
import org.apache.gravitino.rel.indexes.Indexes;
import org.apache.gravitino.rel.types.Type;
import org.apache.gravitino.rel.types.Types;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -89,4 +90,53 @@ public void testCreateTableWithNonEmptyStringDefaultValue() {
sql.contains("DEFAULT " + converter.fromGravitino(col1.defaultValue())),
"Should contain DEFAULT value but was: " + sql);
}

@Test
public void testRejectNanosecondTimestampsBeforeSqlGeneration() {
String expectedMessage =
"OceanBase MySQL mode cannot preserve timestamp precision 9; "
+ "the maximum supported precision is 6";
assertCreateTableRejected(Types.TimestampType.withoutTimeZone(9), expectedMessage);
assertCreateTableRejected(Types.TimestampType.withTimeZone(9), expectedMessage);
}

@Test
public void testRejectVariantBeforeSqlGeneration() {
assertCreateTableRejected(
Types.VariantType.get(),
"OceanBase JSON cannot losslessly preserve the Gravitino variant type");
}

@Test
public void testRejectUnknownBeforeSqlGeneration() {
assertCreateTableRejected(
Types.NullType.get(),
"OceanBase has no column type that preserves the Gravitino unknown type");
}

@Test
public void testRejectGeometryBeforeSqlGeneration() {
assertCreateTableRejected(
Types.GeometryType.crs84(),
"OceanBase JDBC metadata cannot preserve Gravitino geometry CRS/SRID metadata");
}

@Test
public void testRejectGeographyBeforeSqlGeneration() {
assertCreateTableRejected(
Types.GeographyType.crs84(),
"OceanBase has no geography column type that preserves CRS and edge algorithm metadata");
}

private void assertCreateTableRejected(Type type, String expectedMessage) {
TestableOceanBaseTableOperations ops = new TestableOceanBaseTableOperations();
JdbcColumn column =
JdbcColumn.builder().withName("col1").withType(type).withNullable(true).build();

IllegalArgumentException exception =
Assertions.assertThrows(
IllegalArgumentException.class,
() -> ops.createTableSql("test_table", new JdbcColumn[] {column}));
Assertions.assertEquals(expectedMessage, exception.getMessage());
}
}
13 changes: 13 additions & 0 deletions docs/jdbc-oceanbase-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,19 @@ Refer to [Manage Relational Metadata Using Gravitino](./manage-relational-metada
| `FixedChar` | `FixedChar` |
| `Binary` | `Binary` |

#### Iceberg V3 type interoperability

The OceanBase catalog either preserves an Iceberg V3 type exactly or rejects it before executing
DDL. The pinned OceanBase 4.2.1 test environment uses MySQL compatibility mode.

| Gravitino type family | OceanBase behavior |
|-----------------------------------------|------------------------------------------------------------------------------------------------------|
| `Timestamp(9)`, `Timestamp_tz(9)` | Rejected because MySQL-mode `DATETIME` and `TIMESTAMP` preserve at most microsecond precision (0–6). |
| `Variant` | Rejected because OceanBase `JSON` cannot losslessly preserve the complete Gravitino variant domain. |
| `Unknown` (`NullType`) | Rejected because OceanBase has no corresponding column type. |
| `Geometry(crs)` | Rejected because the connector cannot currently reload OceanBase spatial subtype and SRID metadata. |
| `Geography(crs, algorithm)` | Rejected because OceanBase has no type preserving geography edge semantics. |

:::info
OceanBase doesn't support Gravitino `Boolean` `Fixed` `Struct` `List` `Map` `IntervalDay` `IntervalYear` `Union` `UUID` type.
Meanwhile, the data types other than listed above are mapped to Gravitino **[External Type](./manage-relational-metadata-using-gravitino.md#external-type)** that represents an unresolvable data type since 0.6.0-incubating.
Expand Down
Loading