This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new ee17a0acab7 [fix](jdbc) Resolve SQL Server user-defined alias types by
JDBC type code (#67916)
ee17a0acab7 is described below
commit ee17a0acab76deb013f8982f593aad178f22cbf1
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Mon Sep 14 18:06:59 2026 +0800
[fix](jdbc) Resolve SQL Server user-defined alias types by JDBC type code
(#67916)
### What problem does this PR solve?
Issue Number: close #67793
Related PR: none
Problem Summary:
Columns declared with a SQL Server user-defined alias type (`CREATE TYPE
dbo.customtexttype FROM varchar(50)`) are mapped to `UNSUPPORTED_TYPE`
by the JDBC catalog, so `SELECT *` on such a table fails with
```
type UNSUPPORTED is unsupported for Nereids
```
`JdbcSQLServerClient.jdbcTypeToDoris()` (and its counterpart
`JdbcSQLServerConnectorClient.jdbcTypeToConnectorType()` in
`fe-connector-jdbc`) dispatches only on `TYPE_NAME`. For an alias type,
`DatabaseMetaData.getColumns()` reports the alias name
(`customtexttype`) as `TYPE_NAME`, so the name never matches and the
`default` branch returns `UNSUPPORTED`. The same result set however
still carries the base type in `DATA_TYPE` (`java.sql.Types.VARCHAR`),
`COLUMN_SIZE` (50) and `DECIMAL_DIGITS`.
This PR keeps the name-based switch as the primary mapping (it carries
SQL Server specific choices such as `tinyint -> SMALLINT` and `money ->
DECIMAL(19,4)`) and, only when the name is not recognised, resolves the
column by its standard `java.sql.Types` code. The fallback mirrors the
name-based mapping and is applied in both implementations:
- `fe/fe-core/.../jdbc/client/JdbcSQLServerClient.java`
-
`fe/fe-connector/fe-connector-jdbc/.../JdbcSQLServerConnectorClient.java`
Scope of the fallback (verified against the mssql-jdbc `DataTypeFilter`
that post-processes `getColumns()`):
| base type of the alias | `DATA_TYPE` | Doris type |
|---|---|---|
| bit | BIT | BOOLEAN |
| tinyint / smallint | TINYINT / SMALLINT | SMALLINT |
| int / bigint | INTEGER / BIGINT | INT / BIGINT |
| real | REAL | FLOAT |
| float | DOUBLE (driver maps ODBC FLOAT to DOUBLE) | DOUBLE |
| decimal / numeric / money / smallmoney | DECIMAL / NUMERIC with the
base precision and scale | DECIMALV3(p, s), string when p > 38 |
| date | DATE | DATEV2 |
| datetime / datetime2 / smalldatetime | TIMESTAMP, scale capped at 6 |
DATETIMEV2(scale) |
| char / varchar / text / nchar / nvarchar / ntext / time /
uniqueidentifier / sysname | CHAR / VARCHAR / LONGVARCHAR / NCHAR /
NVARCHAR / LONGNVARCHAR / TIME | STRING |
The name-based path strips the IDENTITY decoration a column is reported
with (`int identity`, `decimal() identity`, `numeric(18, 0) identity`,
`decimal(18,0) IDENTITY(1,1)`) only when the name has that form for one
of the base types IDENTITY is allowed on **and** `DATA_TYPE` is that
base type's code. Any other name is matched as it is: SQL Server allows
an alias to be named with spaces or parentheses (`CREATE TYPE dbo.[int
alias] FROM varchar(50)`, reported as `TYPE_NAME = int alias`,
`DATA_TYPE = VARCHAR`), and cutting the name at the first space or
parenthesis, as both clients did, turned such an alias into the system
type its name starts with (an INT column for a varchar alias). An alias
cannot share a system type's name outright (`CREATE TYPE dbo.[int] ...`
is rejected by SQL Server), so an unrecognised name is an alias and goes
to the code fallback.
Deliberately **not** resolved by the fallback:
- Binary codes (`BINARY`, `VARBINARY`, `LONGVARBINARY`): mssql-jdbc
reports CLR user-defined types (`geometry`, `geography`, `hierarchyid`,
...) as `VARBINARY` too, so an alias over `varbinary` cannot be told
apart from an unsupported CLR type by the type code alone. They stay
`UNSUPPORTED`.
- Vendor specific codes (`sql_variant`, `datetimeoffset` aliases): stay
`UNSUPPORTED`.
- A consequence of the two rules above: an alias whose *name* starts
with a binary type name and whose base type is binary too (`CREATE TYPE
dbo.[varbinary alias] FROM varbinary(20)`) is `UNSUPPORTED` as well.
Before this PR its name was cut to `varbinary` and the column happened
to be readable; now the name is not a system type name and the binary
code is not resolved.
- `xml`, `sql_variant`, `geometry`, `geography`, `hierarchyid`, `json`,
`vector` are now listed explicitly as unsupported system types so that
the fallback never changes their existing behavior (`xml` would
otherwise be reported as `LONGNVARCHAR`).
The BE side needs no change: the scanner reads values by the Doris
column type (`getObject()` / `getBigDecimal()`), and the driver returns
the Java object of the base type for alias columns.
---
.../sqlserver/init/03-create-table.sql | 105 +++++++++++++
.../docker-compose/sqlserver/init/04-insert.sql | 20 +++
.../jdbc/client/JdbcSQLServerConnectorClient.java | 121 ++++++++++++++-
.../client/JdbcSQLServerConnectorClientTest.java | 131 +++++++++++++++-
.../jdbc/client/JdbcSQLServerClient.java | 129 ++++++++++++++--
.../jdbc/client/JdbcSQLServerClientTest.java | 165 +++++++++++++++++++++
.../jdbc/test_sqlserver_jdbc_catalog.out | 80 ++++++++++
.../jdbc/test_sqlserver_jdbc_catalog.groovy | 25 ++++
8 files changed, 762 insertions(+), 14 deletions(-)
diff --git
a/docker/thirdparties/docker-compose/sqlserver/init/03-create-table.sql
b/docker/thirdparties/docker-compose/sqlserver/init/03-create-table.sql
index d0b1989ce30..c57c0c2f384 100644
--- a/docker/thirdparties/docker-compose/sqlserver/init/03-create-table.sql
+++ b/docker/thirdparties/docker-compose/sqlserver/init/03-create-table.sql
@@ -284,3 +284,108 @@ CREATE TABLE dbo.test_date_filter (
datetime_value datetime NULL,
datetime2_value datetime2 NULL
);
+
+-- User-defined alias types (CREATE TYPE ... FROM base_type).
DatabaseMetaData.getColumns()
+-- reports such columns with TYPE_NAME set to the alias name, see #67793.
+-- They must exist before the tables below are created, hence the batch
separator.
+CREATE TYPE dbo.doris_alias_varchar FROM varchar(50) NOT NULL;
+CREATE TYPE dbo.doris_alias_varcharmax FROM varchar(max) NULL;
+CREATE TYPE dbo.doris_alias_nvarchar FROM nvarchar(20) NULL;
+CREATE TYPE dbo.doris_alias_nvarcharmax FROM nvarchar(max) NULL;
+CREATE TYPE dbo.doris_alias_char FROM char(10) NULL;
+CREATE TYPE dbo.doris_alias_nchar FROM nchar(10) NULL;
+CREATE TYPE dbo.doris_alias_text FROM text NULL;
+CREATE TYPE dbo.doris_alias_ntext FROM ntext NULL;
+CREATE TYPE dbo.doris_alias_bit FROM bit NULL;
+CREATE TYPE dbo.doris_alias_tinyint FROM tinyint NULL;
+CREATE TYPE dbo.doris_alias_smallint FROM smallint NULL;
+CREATE TYPE dbo.doris_alias_int FROM int NULL;
+CREATE TYPE dbo.doris_alias_bigint FROM bigint NULL;
+CREATE TYPE dbo.doris_alias_real FROM real NULL;
+CREATE TYPE dbo.doris_alias_float FROM float NULL;
+CREATE TYPE dbo.doris_alias_decimal FROM decimal(10, 2) NULL;
+CREATE TYPE dbo.doris_alias_numeric FROM numeric(38, 10) NULL;
+CREATE TYPE dbo.doris_alias_money FROM money NULL;
+CREATE TYPE dbo.doris_alias_smallmoney FROM smallmoney NULL;
+CREATE TYPE dbo.doris_alias_date FROM date NULL;
+CREATE TYPE dbo.doris_alias_time FROM time NULL;
+CREATE TYPE dbo.doris_alias_datetime FROM datetime NULL;
+CREATE TYPE dbo.doris_alias_datetime2 FROM datetime2(3) NULL;
+CREATE TYPE dbo.doris_alias_datetime2_default FROM datetime2 NULL;
+CREATE TYPE dbo.doris_alias_smalldatetime FROM smalldatetime NULL;
+CREATE TYPE dbo.doris_alias_guid FROM uniqueidentifier NULL;
+CREATE TYPE dbo.doris_alias_identity FROM int NOT NULL;
+-- Aliases over types that the JDBC catalog can not resolve by type code. They
must stay UNSUPPORTED.
+CREATE TYPE dbo.doris_alias_binary FROM binary(20) NULL;
+CREATE TYPE dbo.doris_alias_varbinary FROM varbinary(20) NULL;
+CREATE TYPE dbo.doris_alias_image FROM image NULL;
+CREATE TYPE dbo.doris_alias_datetimeoffset FROM datetimeoffset NULL;
+CREATE TYPE dbo.doris_alias_variant FROM sql_variant NULL;
+-- Alias names that start with a system type name: they are reported as they
are and must not be
+-- mistaken for that system type.
+CREATE TYPE dbo.[int alias] FROM varchar(50) NULL;
+CREATE TYPE dbo.[decimal(18,0) identity] FROM nvarchar(20) NULL;
+CREATE TYPE dbo.[int identity] FROM varchar(10) NULL;
+GO
+
+-- Every supported base type family behind an alias, plus sysname (a built-in
alias over nvarchar(128)).
+CREATE TABLE dbo.test_alias_type (
+ id int PRIMARY KEY NOT NULL,
+ plain_col varchar(50) NULL,
+ alias_varchar_col dbo.doris_alias_varchar NULL,
+ alias_varcharmax_col dbo.doris_alias_varcharmax NULL,
+ alias_nvarchar_col dbo.doris_alias_nvarchar NULL,
+ alias_nvarcharmax_col dbo.doris_alias_nvarcharmax NULL,
+ alias_char_col dbo.doris_alias_char NULL,
+ alias_nchar_col dbo.doris_alias_nchar NULL,
+ alias_text_col dbo.doris_alias_text NULL,
+ alias_ntext_col dbo.doris_alias_ntext NULL,
+ alias_bit_col dbo.doris_alias_bit NULL,
+ alias_tinyint_col dbo.doris_alias_tinyint NULL,
+ alias_smallint_col dbo.doris_alias_smallint NULL,
+ alias_int_col dbo.doris_alias_int NULL,
+ alias_bigint_col dbo.doris_alias_bigint NULL,
+ alias_real_col dbo.doris_alias_real NULL,
+ alias_float_col dbo.doris_alias_float NULL,
+ alias_decimal_col dbo.doris_alias_decimal NULL,
+ alias_numeric_col dbo.doris_alias_numeric NULL,
+ alias_money_col dbo.doris_alias_money NULL,
+ alias_smallmoney_col dbo.doris_alias_smallmoney NULL,
+ alias_date_col dbo.doris_alias_date NULL,
+ alias_time_col dbo.doris_alias_time NULL,
+ alias_datetime_col dbo.doris_alias_datetime NULL,
+ alias_datetime2_col dbo.doris_alias_datetime2 NULL,
+ alias_datetime2_default_col dbo.doris_alias_datetime2_default NULL,
+ alias_smalldatetime_col dbo.doris_alias_smalldatetime NULL,
+ alias_guid_col dbo.doris_alias_guid NULL,
+ sysname_col sysname NULL
+);
+
+-- IDENTITY on an alias typed column: the driver reports the plain alias name
as TYPE_NAME.
+CREATE TABLE dbo.test_alias_identity (
+ id dbo.doris_alias_identity IDENTITY(1,1) PRIMARY KEY,
+ val dbo.doris_alias_varchar NULL
+);
+
+-- Alias names that start with a system type name, next to a real IDENTITY
column.
+CREATE TABLE dbo.test_alias_name (
+ id int IDENTITY(1,1) PRIMARY KEY,
+ alias_named_int_col dbo.[int alias] NULL,
+ alias_named_decimal_identity_col dbo.[decimal(18,0) identity] NULL,
+ alias_named_int_identity_col dbo.[int identity] NULL
+);
+
+-- Negative cases: aliases over binary types, datetimeoffset and sql_variant,
and the xml / CLR system
+-- types. All of them must be reported as UNSUPPORTED while the other columns
stay readable.
+CREATE TABLE dbo.test_alias_unsupported (
+ id int PRIMARY KEY NOT NULL,
+ plain_col varchar(50) NULL,
+ alias_binary_col dbo.doris_alias_binary NULL,
+ alias_varbinary_col dbo.doris_alias_varbinary NULL,
+ alias_image_col dbo.doris_alias_image NULL,
+ alias_datetimeoffset_col dbo.doris_alias_datetimeoffset NULL,
+ alias_variant_col dbo.doris_alias_variant NULL,
+ xml_col xml NULL,
+ geometry_col geometry NULL,
+ hierarchyid_col hierarchyid NULL
+);
diff --git a/docker/thirdparties/docker-compose/sqlserver/init/04-insert.sql
b/docker/thirdparties/docker-compose/sqlserver/init/04-insert.sql
index 894773761c6..64275f091e9 100644
--- a/docker/thirdparties/docker-compose/sqlserver/init/04-insert.sql
+++ b/docker/thirdparties/docker-compose/sqlserver/init/04-insert.sql
@@ -132,3 +132,23 @@ Insert into dbo.test_date_filter values
(3, '2024-12-31', '2024-12-31 23:59:59', '2024-12-31 23:59:59.999'),
(4, '2023-01-17', '2023-01-17 08:00:00', '2023-01-17 08:00:00'),
(5, '2025-03-15', '2025-03-15 12:00:00', '2025-03-15 12:00:00.500');
+
+-- Alias typed columns, see #67793
+Insert into dbo.test_alias_type values
+(1, 'plain', 'alias', 'alias varchar max', 'alias nvarchar', 'alias nvarchar
max', 'Doris', 'Doris', 'alias text', 'alias ntext',
+ 1, 255, 32767, 1, 9223372036854775807, 123.123, 1.5, 12345.67,
1234567890123456789012345678.0123456789, 123.4567, 214748.3647,
+ '2023-01-17', '16:49:05.1234567', '2023-01-17 16:49:05', '2023-01-17
10:30:45.123', '2023-01-17 16:49:05.1234567', '2023-01-17 16:49:05',
+ 'FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF', 'sysname value'),
+(2, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL);
+
+Insert into dbo.test_alias_identity (val) values ('first'), ('second');
+
+Insert into dbo.test_alias_name (alias_named_int_col,
alias_named_decimal_identity_col, alias_named_int_identity_col)
+values ('not an int', 'not a decimal', 'not an id');
+
+Insert into dbo.test_alias_unsupported values
+(1, 'plain', 0x01, 0x0102, 0x03, '2023-01-17 16:49:05 +08:00', 1, '<a/>',
geometry::STGeomFromText('POINT (1 2)', 0), hierarchyid::GetRoot()),
+(2, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
diff --git
a/fe/fe-connector/fe-connector-jdbc/src/main/java/org/apache/doris/connector/jdbc/client/JdbcSQLServerConnectorClient.java
b/fe/fe-connector/fe-connector-jdbc/src/main/java/org/apache/doris/connector/jdbc/client/JdbcSQLServerConnectorClient.java
index 2cdab21a0b6..81c656ab154 100644
---
a/fe/fe-connector/fe-connector-jdbc/src/main/java/org/apache/doris/connector/jdbc/client/JdbcSQLServerConnectorClient.java
+++
b/fe/fe-connector/fe-connector-jdbc/src/main/java/org/apache/doris/connector/jdbc/client/JdbcSQLServerConnectorClient.java
@@ -26,7 +26,11 @@ import org.apache.logging.log4j.Logger;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
+import java.sql.Types;
+import java.util.Locale;
import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* SQL Server-specific JDBC connector client.
@@ -36,6 +40,12 @@ public class JdbcSQLServerConnectorClient extends
JdbcConnectorClient {
private static final Logger LOG =
LogManager.getLogger(JdbcSQLServerConnectorClient.class);
+ // TYPE_NAME of an IDENTITY column decorates the base type: "int
identity", "decimal() identity",
+ // "numeric(18, 0) identity", "decimal(18,0) IDENTITY(1,1)". IDENTITY is
only allowed on these base types.
+ private static final Pattern IDENTITY_TYPE_NAME = Pattern.compile(
+
"^(tinyint|smallint|int|bigint|decimal|numeric)\\s*(\\([^)]*\\))?\\s+identity(\\s*\\([^)]*\\))?$",
+ Pattern.CASE_INSENSITIVE);
+
public JdbcSQLServerConnectorClient(
String catalogName, JdbcDbType dbType, String jdbcUrl,
boolean onlySpecifiedDatabase,
@@ -48,13 +58,48 @@ public class JdbcSQLServerConnectorClient extends
JdbcConnectorClient {
enableMappingVarbinary, enableMappingTimestampTz);
}
+ /**
+ * The base type of an IDENTITY column's TYPE_NAME, or the name unchanged.
+ * <p>
+ * The decoration is trusted only when {@code DATA_TYPE} is the code of
the named base type: an alias type
+ * may legally be named like that ({@code CREATE TYPE dbo.[int identity]
FROM varchar(10)}, or
+ * {@code dbo.[int alias]}), and it then has to be resolved by its code,
not by the words of its name.
+ */
+ static String identityBaseType(String typeName, int dataType) {
+ Matcher matcher = IDENTITY_TYPE_NAME.matcher(typeName);
+ if (!matcher.matches()) {
+ return typeName;
+ }
+ String baseType = matcher.group(1).toLowerCase(Locale.ROOT);
+ boolean codeMatches;
+ switch (baseType) {
+ case "tinyint":
+ codeMatches = dataType == Types.TINYINT;
+ break;
+ case "smallint":
+ codeMatches = dataType == Types.SMALLINT;
+ break;
+ case "int":
+ codeMatches = dataType == Types.INTEGER;
+ break;
+ case "bigint":
+ codeMatches = dataType == Types.BIGINT;
+ break;
+ default:
+ codeMatches = dataType == Types.DECIMAL || dataType ==
Types.NUMERIC;
+ break;
+ }
+ return codeMatches ? baseType : typeName;
+ }
+
@Override
public ConnectorType jdbcTypeToConnectorType(JdbcFieldInfo fieldInfo) {
String rawType =
fieldInfo.getDataTypeName().orElse("unknown").toLowerCase();
- // SQL Server JDBC driver decorates type names for IDENTITY columns,
- // e.g., "int identity", "decimal() identity". Strip parenthesized
parts
- // and suffixes to get the base type name.
- String ssType = rawType.replaceAll("[\\s(].*", "");
+ // An IDENTITY column is reported as "int identity" or "decimal(18,0)
identity": only the base type
+ // is matched below. Any other name is matched as it is: system type
names are single words, and a
+ // user-defined alias type may be named with spaces or parentheses
("int alias") and must not be
+ // mistaken for the system type its name starts with.
+ String ssType = identityBaseType(rawType, fieldInfo.getDataType());
switch (ssType) {
case "bit":
return ConnectorType.of("BOOLEAN");
@@ -105,6 +150,74 @@ public class JdbcSQLServerConnectorClient extends
JdbcConnectorClient {
return enableMappingVarbinary
? ConnectorType.of("VARBINARY",
fieldInfo.requiredColumnSize(), -1)
: ConnectorType.of("STRING");
+ case "xml":
+ case "sql_variant":
+ case "geometry":
+ case "geography":
+ case "hierarchyid":
+ case "json":
+ case "vector":
+ // SQL Server system types that Doris does not support. They
are listed explicitly
+ // so that they never reach the JDBC type code fallback below.
+ return ConnectorType.of("UNSUPPORTED");
+ default:
+ return jdbcTypeCodeToConnectorType(fieldInfo);
+ }
+ }
+
+ /**
+ * Fallback for type names that are not SQL Server system types.
+ * <p>
+ * User-defined alias types ({@code CREATE TYPE dbo.my_type FROM
varchar(50)}) are reported by
+ * {@code DatabaseMetaData.getColumns()} with {@code TYPE_NAME} set to the
alias name, so they can not be
+ * matched by name. {@code DATA_TYPE}, {@code COLUMN_SIZE} and {@code
DECIMAL_DIGITS} still describe the
+ * base type, so the standard {@link Types} code is used to resolve the
Doris type. The mapping mirrors
+ * the name based one above.
+ * <p>
+ * Binary codes are deliberately not mapped: mssql-jdbc also reports CLR
user-defined types
+ * (geometry, geography, hierarchyid, ...) as {@link Types#VARBINARY}, so
they can not be told apart from
+ * an alias over a binary type by the type code alone. Vendor specific
codes stay unsupported as well.
+ */
+ private ConnectorType jdbcTypeCodeToConnectorType(JdbcFieldInfo fieldInfo)
{
+ switch (fieldInfo.getDataType()) {
+ case Types.BIT:
+ case Types.BOOLEAN:
+ return ConnectorType.of("BOOLEAN");
+ // SQL Server tinyint is unsigned (0 to 255), so it needs SMALLINT
+ case Types.TINYINT:
+ case Types.SMALLINT:
+ return ConnectorType.of("SMALLINT");
+ case Types.INTEGER:
+ return ConnectorType.of("INT");
+ case Types.BIGINT:
+ return ConnectorType.of("BIGINT");
+ case Types.REAL:
+ return ConnectorType.of("FLOAT");
+ case Types.FLOAT:
+ case Types.DOUBLE:
+ return ConnectorType.of("DOUBLE");
+ case Types.DECIMAL:
+ case Types.NUMERIC: {
+ // money and smallmoney are reported as DECIMAL(19,4) and
DECIMAL(10,4)
+ int precision = fieldInfo.requiredColumnSize();
+ int scale = fieldInfo.requiredDecimalDigits();
+ return createDecimalOrString(precision, scale);
+ }
+ case Types.DATE:
+ return ConnectorType.of("DATEV2");
+ case Types.TIMESTAMP: {
+ int scale = fieldInfo.getDecimalDigits().orElse(0);
+ scale = Math.min(scale, JDBC_DATETIME_SCALE);
+ return ConnectorType.of("DATETIMEV2", scale, -1);
+ }
+ case Types.CHAR:
+ case Types.NCHAR:
+ case Types.VARCHAR:
+ case Types.NVARCHAR:
+ case Types.LONGVARCHAR:
+ case Types.LONGNVARCHAR:
+ case Types.TIME:
+ return ConnectorType.of("STRING");
default:
return ConnectorType.of("UNSUPPORTED");
}
diff --git
a/fe/fe-connector/fe-connector-jdbc/src/test/java/org/apache/doris/connector/jdbc/client/JdbcSQLServerConnectorClientTest.java
b/fe/fe-connector/fe-connector-jdbc/src/test/java/org/apache/doris/connector/jdbc/client/JdbcSQLServerConnectorClientTest.java
index 313f7680054..4e20424712c 100644
---
a/fe/fe-connector/fe-connector-jdbc/src/test/java/org/apache/doris/connector/jdbc/client/JdbcSQLServerConnectorClientTest.java
+++
b/fe/fe-connector/fe-connector-jdbc/src/test/java/org/apache/doris/connector/jdbc/client/JdbcSQLServerConnectorClientTest.java
@@ -23,15 +23,20 @@ import org.apache.doris.connector.spi.ConnectorType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import java.sql.Types;
import java.util.Collections;
import java.util.Optional;
/**
* Tests for {@link JdbcSQLServerConnectorClient}, focusing on SQL Server
- * IDENTITY column type name handling.
+ * IDENTITY column type name handling and user-defined alias type resolution.
*/
public class JdbcSQLServerConnectorClientTest {
+ // ODBC type codes that mssql-jdbc passes through
DatabaseMetaData.getColumns() unchanged
+ private static final int SQL_VARIANT = -150;
+ private static final int SQL_SS_TIMESTAMPOFFSET = -155;
+
private JdbcSQLServerConnectorClient createClient() {
return new JdbcSQLServerConnectorClient(
"test_catalog",
@@ -134,4 +139,128 @@ public class JdbcSQLServerConnectorClientTest {
Assertions.assertEquals("%",
client.getSchemaPatternForDatabaseNameList());
}
+
+ /**
+ * One DatabaseMetaData.getColumns() row as reported by mssql-jdbc. For a
user-defined alias type,
+ * TYPE_NAME is the alias name while DATA_TYPE, COLUMN_SIZE and
DECIMAL_DIGITS describe the base type.
+ */
+ private static JdbcFieldInfo column(String typeName, int dataType, int
columnSize, int decimalDigits) {
+ return new JdbcFieldInfo("col", Optional.of(typeName), dataType,
+ Optional.of(columnSize), Optional.of(decimalDigits),
Optional.empty());
+ }
+
+ private static String typeOf(JdbcSQLServerConnectorClient client,
JdbcFieldInfo info) {
+ return client.jdbcTypeToConnectorType(info).getTypeName();
+ }
+
+ @Test
+ void testAliasTypeIsResolvedByJdbcTypeCode() {
+ JdbcSQLServerConnectorClient client = createClient();
+
+ // CREATE TYPE dbo.customtexttype FROM varchar(50), the case reported
in #67793
+ Assertions.assertEquals("STRING", typeOf(client,
column("customtexttype", Types.VARCHAR, 50, 0)));
+ // sysname is a built-in alias over nvarchar(128)
+ Assertions.assertEquals("STRING", typeOf(client, column("sysname",
Types.NVARCHAR, 128, 0)));
+ Assertions.assertEquals("STRING", typeOf(client, column("alias_nchar",
Types.NCHAR, 10, 0)));
+ Assertions.assertEquals("STRING",
+ typeOf(client, column("alias_text", Types.LONGVARCHAR,
Integer.MAX_VALUE, 0)));
+ Assertions.assertEquals("STRING",
+ typeOf(client, column("alias_ntext", Types.LONGNVARCHAR,
Integer.MAX_VALUE / 2, 0)));
+ Assertions.assertEquals("STRING", typeOf(client, column("alias_time",
Types.TIME, 16, 7)));
+ // uniqueidentifier is reported as CHAR(36)
+ Assertions.assertEquals("STRING", typeOf(client, column("alias_guid",
Types.CHAR, 36, 0)));
+
+ Assertions.assertEquals("BOOLEAN", typeOf(client, column("alias_bit",
Types.BIT, 1, 0)));
+ // SQL Server tinyint is unsigned, so it keeps the SMALLINT mapping of
the name based path
+ Assertions.assertEquals("SMALLINT", typeOf(client,
column("alias_tinyint", Types.TINYINT, 3, 0)));
+ Assertions.assertEquals("SMALLINT", typeOf(client,
column("alias_smallint", Types.SMALLINT, 5, 0)));
+ Assertions.assertEquals("INT", typeOf(client, column("alias_int",
Types.INTEGER, 10, 0)));
+ Assertions.assertEquals("BIGINT", typeOf(client,
column("alias_bigint", Types.BIGINT, 19, 0)));
+ Assertions.assertEquals("FLOAT", typeOf(client, column("alias_real",
Types.REAL, 24, 0)));
+ Assertions.assertEquals("DOUBLE", typeOf(client, column("alias_float",
Types.DOUBLE, 53, 0)));
+
+ ConnectorType decimal =
client.jdbcTypeToConnectorType(column("alias_decimal", Types.DECIMAL, 10, 2));
+ Assertions.assertEquals("DECIMALV3", decimal.getTypeName());
+ Assertions.assertEquals(10, decimal.getPrecision());
+ Assertions.assertEquals(2, decimal.getScale());
+ // money is reported as DECIMAL(19,4), the same as the name based
mapping produces
+ ConnectorType money =
client.jdbcTypeToConnectorType(column("alias_money", Types.DECIMAL, 19, 4));
+ Assertions.assertEquals("DECIMALV3", money.getTypeName());
+ Assertions.assertEquals(19, money.getPrecision());
+ Assertions.assertEquals(4, money.getScale());
+ // precision beyond DECIMAL128 falls back to STRING like the name
based path
+ Assertions.assertEquals("STRING", typeOf(client,
column("alias_numeric", Types.NUMERIC, 39, 0)));
+
+ Assertions.assertEquals("DATEV2", typeOf(client, column("alias_date",
Types.DATE, 10, 0)));
+ ConnectorType datetime =
client.jdbcTypeToConnectorType(column("alias_datetime", Types.TIMESTAMP, 23,
3));
+ Assertions.assertEquals("DATETIMEV2", datetime.getTypeName());
+ Assertions.assertEquals(3, datetime.getPrecision());
+ // datetime2 defaults to 7 fractional digits, Doris supports at most 6
+ ConnectorType datetime2 =
client.jdbcTypeToConnectorType(column("alias_datetime2", Types.TIMESTAMP, 27,
7));
+ Assertions.assertEquals("DATETIMEV2", datetime2.getTypeName());
+ Assertions.assertEquals(6, datetime2.getPrecision());
+ }
+
+ @Test
+ void testUnknownTypesStayUnsupported() {
+ JdbcSQLServerConnectorClient client = createClient();
+
+ // CLR user-defined types are reported as VARBINARY, exactly like an
alias over varbinary,
+ // so binary codes must not be resolved by the fallback
+ Assertions.assertEquals("UNSUPPORTED",
+ typeOf(client, column("geometry", Types.VARBINARY,
Integer.MAX_VALUE, 0)));
+ Assertions.assertEquals("UNSUPPORTED", typeOf(client,
column("my_clr_type", Types.VARBINARY, 8000, 0)));
+ Assertions.assertEquals("UNSUPPORTED", typeOf(client,
column("alias_binary", Types.BINARY, 20, 0)));
+ Assertions.assertEquals("UNSUPPORTED",
+ typeOf(client, column("alias_image", Types.LONGVARBINARY,
Integer.MAX_VALUE, 0)));
+ // vendor specific type codes
+ Assertions.assertEquals("UNSUPPORTED", typeOf(client,
column("sql_variant", SQL_VARIANT, 8000, 0)));
+ Assertions.assertEquals("UNSUPPORTED",
+ typeOf(client, column("alias_datetimeoffset",
SQL_SS_TIMESTAMPOFFSET, 34, 7)));
+ // explicitly unsupported system types keep that behavior whatever
type code the driver reports
+ Assertions.assertEquals("UNSUPPORTED",
+ typeOf(client, column("xml", Types.LONGNVARCHAR,
Integer.MAX_VALUE / 2, 0)));
+ Assertions.assertEquals("UNSUPPORTED",
+ typeOf(client, column("json", Types.LONGNVARCHAR,
Integer.MAX_VALUE / 2, 0)));
+ Assertions.assertEquals("UNSUPPORTED", typeOf(client,
column("hierarchyid", Types.VARBINARY, 892, 0)));
+ }
+
+ @Test
+ void testAliasNamedLikeASystemTypeIsResolvedByJdbcTypeCode() {
+ JdbcSQLServerConnectorClient client = createClient();
+
+ // A delimited alias name may contain spaces and parentheses ([int
alias], [decimal(18,0) identity]);
+ // it is reported as is, and the base type is still what DATA_TYPE says
+ Assertions.assertEquals("STRING", typeOf(client, column("int alias",
Types.VARCHAR, 50, 0)));
+ Assertions.assertEquals("STRING", typeOf(client, column("decimal(18,0)
identity", Types.NVARCHAR, 20, 0)));
+ Assertions.assertEquals("STRING", typeOf(client, column("int
identity", Types.VARCHAR, 10, 0)));
+ Assertions.assertEquals("STRING", typeOf(client, column("bigint
identity", Types.NVARCHAR, 20, 0)));
+ Assertions.assertEquals("DATETIMEV2", typeOf(client,
column("varchar(50) alias", Types.TIMESTAMP, 23, 3)));
+
+ // The IDENTITY decoration of a real system type, in the forms the
driver versions report it in
+ Assertions.assertEquals("INT", typeOf(client, column("int identity",
Types.INTEGER, 10, 0)));
+ Assertions.assertEquals("BIGINT", typeOf(client, column("bigint
identity", Types.BIGINT, 19, 0)));
+ Assertions.assertEquals("SMALLINT", typeOf(client, column("tinyint
identity", Types.TINYINT, 3, 0)));
+ for (String decorated : new String[] {"decimal identity", "decimal()
identity",
+ "decimal(18,0) IDENTITY(1,1)", "DECIMAL(18, 0) IDENTITY"}) {
+ ConnectorType ct =
client.jdbcTypeToConnectorType(column(decorated, Types.DECIMAL, 18, 0));
+ Assertions.assertEquals("DECIMALV3", ct.getTypeName(), decorated);
+ Assertions.assertEquals(18, ct.getPrecision(), decorated);
+ Assertions.assertEquals(0, ct.getScale(), decorated);
+ }
+ ConnectorType numeric =
client.jdbcTypeToConnectorType(column("numeric(18, 0) identity", Types.NUMERIC,
18, 0));
+ Assertions.assertEquals("DECIMALV3", numeric.getTypeName());
+ Assertions.assertEquals(18, numeric.getPrecision());
+ }
+
+ @Test
+ void testSystemTypeNamesTakePrecedence() {
+ JdbcSQLServerConnectorClient client = createClient();
+
+ // the name based mapping is unchanged, the type code is only
consulted for unknown names
+ Assertions.assertEquals("SMALLINT", typeOf(client, column("tinyint",
Types.TINYINT, 3, 0)));
+ Assertions.assertEquals("STRING", typeOf(client, column("varbinary",
Types.VARBINARY, 20, 0)));
+ Assertions.assertEquals("STRING",
+ typeOf(client, column("datetimeoffset",
SQL_SS_TIMESTAMPOFFSET, 34, 7)));
+ }
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/datasource/jdbc/client/JdbcSQLServerClient.java
b/fe/fe-core/src/main/java/org/apache/doris/datasource/jdbc/client/JdbcSQLServerClient.java
index 50cd5ed9244..d1aaa42ef3c 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/datasource/jdbc/client/JdbcSQLServerClient.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/datasource/jdbc/client/JdbcSQLServerClient.java
@@ -21,8 +21,19 @@ import org.apache.doris.catalog.ScalarType;
import org.apache.doris.catalog.Type;
import org.apache.doris.datasource.jdbc.util.JdbcFieldSchema;
+import java.sql.Types;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
public class JdbcSQLServerClient extends JdbcClient {
+ // TYPE_NAME of an IDENTITY column decorates the base type: "int
identity", "decimal() identity",
+ // "numeric(18, 0) identity", "decimal(18,0) IDENTITY(1,1)". IDENTITY is
only allowed on these base types.
+ private static final Pattern IDENTITY_TYPE_NAME = Pattern.compile(
+
"^(tinyint|smallint|int|bigint|decimal|numeric)\\s*(\\([^)]*\\))?\\s+identity(\\s*\\([^)]*\\))?$",
+ Pattern.CASE_INSENSITIVE);
+
protected JdbcSQLServerClient(JdbcClientConfig jdbcClientConfig) {
super(jdbcClientConfig);
}
@@ -34,18 +45,48 @@ public class JdbcSQLServerClient extends JdbcClient {
return "%";
}
+ /**
+ * The base type of an IDENTITY column's TYPE_NAME, or the name unchanged.
+ * <p>
+ * The decoration is trusted only when {@code DATA_TYPE} is the code of
the named base type: an alias type
+ * may legally be named like that ({@code CREATE TYPE dbo.[int identity]
FROM varchar(10)}, or
+ * {@code dbo.[int alias]}), and it then has to be resolved by its code,
not by the words of its name.
+ */
+ static String identityBaseType(String typeName, int dataType) {
+ Matcher matcher = IDENTITY_TYPE_NAME.matcher(typeName);
+ if (!matcher.matches()) {
+ return typeName;
+ }
+ String baseType = matcher.group(1).toLowerCase(Locale.ROOT);
+ boolean codeMatches;
+ switch (baseType) {
+ case "tinyint":
+ codeMatches = dataType == Types.TINYINT;
+ break;
+ case "smallint":
+ codeMatches = dataType == Types.SMALLINT;
+ break;
+ case "int":
+ codeMatches = dataType == Types.INTEGER;
+ break;
+ case "bigint":
+ codeMatches = dataType == Types.BIGINT;
+ break;
+ default:
+ codeMatches = dataType == Types.DECIMAL || dataType ==
Types.NUMERIC;
+ break;
+ }
+ return codeMatches ? baseType : typeName;
+ }
+
@Override
protected Type jdbcTypeToDoris(JdbcFieldSchema fieldSchema) {
String originSqlserverType =
fieldSchema.getDataTypeName().orElse("unknown");
- // For sqlserver IDENTITY type, such as 'INT IDENTITY'
- // originSqlserverType is "int identity", so we only get "int".
- // For types with parameters like 'decimal(18,0) IDENTITY(1,1)', we
need to extract the base type
- String sqlserverType = originSqlserverType.split(" ")[0];
-
- // Handle types with parentheses like decimal(18,0), varchar(50), etc.
- if (sqlserverType.contains("(")) {
- sqlserverType = sqlserverType.substring(0,
sqlserverType.indexOf("("));
- }
+ // An IDENTITY column is reported as "int identity" or "decimal(18,0)
identity": only the base type
+ // is matched below. Any other name is matched as it is: system type
names are single words, and a
+ // user-defined alias type may be named with spaces or parentheses
("int alias") and must not be
+ // mistaken for the system type its name starts with.
+ String sqlserverType = identityBaseType(originSqlserverType,
fieldSchema.getDataType());
switch (sqlserverType) {
case "bit":
@@ -99,6 +140,76 @@ public class JdbcSQLServerClient extends JdbcClient {
case "varbinary":
return enableMappingVarbinary ?
ScalarType.createVarbinaryType(fieldSchema.requiredColumnSize())
: ScalarType.createStringType();
+ case "xml":
+ case "sql_variant":
+ case "geometry":
+ case "geography":
+ case "hierarchyid":
+ case "json":
+ case "vector":
+ // SQL Server system types that Doris does not support. They
are listed explicitly
+ // so that they never reach the JDBC type code fallback below.
+ return Type.UNSUPPORTED;
+ default:
+ return jdbcTypeCodeToDoris(fieldSchema);
+ }
+ }
+
+ /**
+ * Fallback for type names that are not SQL Server system types.
+ * <p>
+ * User-defined alias types ({@code CREATE TYPE dbo.my_type FROM
varchar(50)}) are reported by
+ * {@code DatabaseMetaData.getColumns()} with {@code TYPE_NAME} set to the
alias name, so they can not be
+ * matched by name. {@code DATA_TYPE}, {@code COLUMN_SIZE} and {@code
DECIMAL_DIGITS} still describe the
+ * base type, so the standard {@link Types} code is used to resolve the
Doris type. The mapping mirrors
+ * the name based one above.
+ * <p>
+ * Binary codes are deliberately not mapped: mssql-jdbc also reports CLR
user-defined types
+ * (geometry, geography, hierarchyid, ...) as {@link Types#VARBINARY}, so
they can not be told apart from
+ * an alias over a binary type by the type code alone. Vendor specific
codes stay unsupported as well.
+ */
+ private Type jdbcTypeCodeToDoris(JdbcFieldSchema fieldSchema) {
+ switch (fieldSchema.getDataType()) {
+ case Types.BIT:
+ case Types.BOOLEAN:
+ return Type.BOOLEAN;
+ // SQL Server tinyint is unsigned (0 to 255), so it needs SMALLINT
+ case Types.TINYINT:
+ case Types.SMALLINT:
+ return Type.SMALLINT;
+ case Types.INTEGER:
+ return Type.INT;
+ case Types.BIGINT:
+ return Type.BIGINT;
+ case Types.REAL:
+ return Type.FLOAT;
+ case Types.FLOAT:
+ case Types.DOUBLE:
+ return Type.DOUBLE;
+ case Types.DECIMAL:
+ case Types.NUMERIC: {
+ // money and smallmoney are reported as DECIMAL(19,4) and
DECIMAL(10,4)
+ int precision = fieldSchema.getColumnSize().orElse(0);
+ int scale = fieldSchema.getDecimalDigits().orElse(0);
+ return createDecimalOrStringType(precision, scale);
+ }
+ case Types.DATE:
+ return ScalarType.createDateV2Type();
+ case Types.TIMESTAMP: {
+ int scale = fieldSchema.getDecimalDigits().orElse(0);
+ if (scale > 6) {
+ scale = 6;
+ }
+ return ScalarType.createDatetimeV2Type(scale);
+ }
+ case Types.CHAR:
+ case Types.VARCHAR:
+ case Types.LONGVARCHAR:
+ case Types.NCHAR:
+ case Types.NVARCHAR:
+ case Types.LONGNVARCHAR:
+ case Types.TIME:
+ return ScalarType.createStringType();
default:
return Type.UNSUPPORTED;
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/datasource/jdbc/client/JdbcSQLServerClientTest.java
b/fe/fe-core/src/test/java/org/apache/doris/datasource/jdbc/client/JdbcSQLServerClientTest.java
new file mode 100644
index 00000000000..d7b8ad645bf
--- /dev/null
+++
b/fe/fe-core/src/test/java/org/apache/doris/datasource/jdbc/client/JdbcSQLServerClientTest.java
@@ -0,0 +1,165 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.datasource.jdbc.client;
+
+import org.apache.doris.catalog.ScalarType;
+import org.apache.doris.catalog.Type;
+import org.apache.doris.datasource.jdbc.util.JdbcFieldSchema;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.Answers;
+import org.mockito.Mockito;
+
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+
+public class JdbcSQLServerClientTest {
+
+ // ODBC type codes that mssql-jdbc passes through
DatabaseMetaData.getColumns() unchanged
+ private static final int SQL_VARIANT = -150;
+ private static final int SQL_SS_TIMESTAMPOFFSET = -155;
+
+ private final JdbcSQLServerClient client =
Mockito.mock(JdbcSQLServerClient.class, Answers.CALLS_REAL_METHODS);
+
+ /**
+ * Builds the schema of one DatabaseMetaData.getColumns() row as reported
by mssql-jdbc.
+ * For a user-defined alias type, TYPE_NAME is the alias name while
DATA_TYPE, COLUMN_SIZE
+ * and DECIMAL_DIGITS describe the base type.
+ */
+ private static JdbcFieldSchema column(String typeName, int dataType, int
columnSize, int decimalDigits)
+ throws SQLException {
+ ResultSet rs = Mockito.mock(ResultSet.class);
+ Mockito.when(rs.getString("COLUMN_NAME")).thenReturn("col");
+ Mockito.when(rs.getInt("DATA_TYPE")).thenReturn(dataType);
+ Mockito.when(rs.getString("TYPE_NAME")).thenReturn(typeName);
+ Mockito.when(rs.getInt("COLUMN_SIZE")).thenReturn(columnSize);
+ Mockito.when(rs.getInt("DECIMAL_DIGITS")).thenReturn(decimalDigits);
+
Mockito.when(rs.getInt("NULLABLE")).thenReturn(DatabaseMetaData.columnNullable);
+ return new JdbcFieldSchema(rs);
+ }
+
+ @Test
+ public void testAliasTypeIsResolvedByJdbcTypeCode() throws SQLException {
+ // CREATE TYPE dbo.customtexttype FROM varchar(50), the case reported
in #67793
+ Assertions.assertEquals(Type.STRING,
client.jdbcTypeToDoris(column("customtexttype", Types.VARCHAR, 50, 0)));
+ // sysname is a built-in alias over nvarchar(128)
+ Assertions.assertEquals(Type.STRING,
client.jdbcTypeToDoris(column("sysname", Types.NVARCHAR, 128, 0)));
+ Assertions.assertEquals(Type.STRING,
client.jdbcTypeToDoris(column("alias_nchar", Types.NCHAR, 10, 0)));
+ Assertions.assertEquals(Type.STRING,
+ client.jdbcTypeToDoris(column("alias_text", Types.LONGVARCHAR,
Integer.MAX_VALUE, 0)));
+ Assertions.assertEquals(Type.STRING,
+ client.jdbcTypeToDoris(column("alias_ntext",
Types.LONGNVARCHAR, Integer.MAX_VALUE / 2, 0)));
+ Assertions.assertEquals(Type.STRING,
client.jdbcTypeToDoris(column("alias_time", Types.TIME, 16, 7)));
+ // uniqueidentifier is reported as CHAR(36)
+ Assertions.assertEquals(Type.STRING,
client.jdbcTypeToDoris(column("alias_guid", Types.CHAR, 36, 0)));
+
+ Assertions.assertEquals(Type.BOOLEAN,
client.jdbcTypeToDoris(column("alias_bit", Types.BIT, 1, 0)));
+ // SQL Server tinyint is unsigned, so it keeps the SMALLINT mapping of
the name based path
+ Assertions.assertEquals(Type.SMALLINT,
client.jdbcTypeToDoris(column("alias_tinyint", Types.TINYINT, 3, 0)));
+ Assertions.assertEquals(Type.SMALLINT,
client.jdbcTypeToDoris(column("alias_smallint", Types.SMALLINT, 5, 0)));
+ Assertions.assertEquals(Type.INT,
client.jdbcTypeToDoris(column("alias_int", Types.INTEGER, 10, 0)));
+ Assertions.assertEquals(Type.BIGINT,
client.jdbcTypeToDoris(column("alias_bigint", Types.BIGINT, 19, 0)));
+ Assertions.assertEquals(Type.FLOAT,
client.jdbcTypeToDoris(column("alias_real", Types.REAL, 24, 0)));
+ Assertions.assertEquals(Type.DOUBLE,
client.jdbcTypeToDoris(column("alias_float", Types.DOUBLE, 53, 0)));
+
+ Assertions.assertEquals(ScalarType.createDecimalV3Type(10, 2),
+ client.jdbcTypeToDoris(column("alias_decimal", Types.DECIMAL,
10, 2)));
+ Assertions.assertEquals(ScalarType.createDecimalV3Type(38, 10),
+ client.jdbcTypeToDoris(column("alias_numeric", Types.NUMERIC,
38, 10)));
+ // money is reported as DECIMAL(19,4), the same as the name based
mapping produces
+ Assertions.assertEquals(ScalarType.createDecimalV3Type(19, 4),
+ client.jdbcTypeToDoris(column("alias_money", Types.DECIMAL,
19, 4)));
+
+ Assertions.assertEquals(Type.DATEV2,
client.jdbcTypeToDoris(column("alias_date", Types.DATE, 10, 0)));
+ Assertions.assertEquals(ScalarType.createDatetimeV2Type(3),
+ client.jdbcTypeToDoris(column("alias_datetime",
Types.TIMESTAMP, 23, 3)));
+ // datetime2 defaults to 7 fractional digits, Doris supports at most 6
+ Assertions.assertEquals(ScalarType.createDatetimeV2Type(6),
+ client.jdbcTypeToDoris(column("alias_datetime2",
Types.TIMESTAMP, 27, 7)));
+ Assertions.assertEquals(ScalarType.createDatetimeV2Type(0),
+ client.jdbcTypeToDoris(column("alias_smalldatetime",
Types.TIMESTAMP, 16, 0)));
+ }
+
+ @Test
+ public void testUnknownTypesStayUnsupported() throws SQLException {
+ // CLR user-defined types are reported as VARBINARY, exactly like an
alias over varbinary,
+ // so binary codes must not be resolved by the fallback
+ Assertions.assertEquals(Type.UNSUPPORTED,
+ client.jdbcTypeToDoris(column("geometry", Types.VARBINARY,
Integer.MAX_VALUE, 0)));
+ Assertions.assertEquals(Type.UNSUPPORTED,
+ client.jdbcTypeToDoris(column("my_clr_type", Types.VARBINARY,
8000, 0)));
+ Assertions.assertEquals(Type.UNSUPPORTED,
client.jdbcTypeToDoris(column("alias_binary", Types.BINARY, 20, 0)));
+ Assertions.assertEquals(Type.UNSUPPORTED,
+ client.jdbcTypeToDoris(column("alias_image",
Types.LONGVARBINARY, Integer.MAX_VALUE, 0)));
+ // vendor specific type codes
+ Assertions.assertEquals(Type.UNSUPPORTED,
client.jdbcTypeToDoris(column("sql_variant", SQL_VARIANT, 8000, 0)));
+ Assertions.assertEquals(Type.UNSUPPORTED,
+ client.jdbcTypeToDoris(column("alias_datetimeoffset",
SQL_SS_TIMESTAMPOFFSET, 34, 7)));
+ // explicitly unsupported system types keep that behavior whatever
type code the driver reports
+ Assertions.assertEquals(Type.UNSUPPORTED,
+ client.jdbcTypeToDoris(column("xml", Types.LONGNVARCHAR,
Integer.MAX_VALUE / 2, 0)));
+ Assertions.assertEquals(Type.UNSUPPORTED,
+ client.jdbcTypeToDoris(column("json", Types.LONGNVARCHAR,
Integer.MAX_VALUE / 2, 0)));
+ Assertions.assertEquals(Type.UNSUPPORTED,
+ client.jdbcTypeToDoris(column("hierarchyid", Types.VARBINARY,
892, 0)));
+ }
+
+ @Test
+ public void testAliasNamedLikeASystemTypeIsResolvedByJdbcTypeCode() throws
SQLException {
+ // A delimited alias name may contain spaces and parentheses ([int
alias], [decimal(18,0) identity]);
+ // it is reported as is, and the base type is still what DATA_TYPE says
+ Assertions.assertEquals(Type.STRING,
client.jdbcTypeToDoris(column("int alias", Types.VARCHAR, 50, 0)));
+ Assertions.assertEquals(Type.STRING,
+ client.jdbcTypeToDoris(column("decimal(18,0) identity",
Types.NVARCHAR, 20, 0)));
+ Assertions.assertEquals(Type.STRING,
client.jdbcTypeToDoris(column("int identity", Types.VARCHAR, 10, 0)));
+ Assertions.assertEquals(Type.STRING,
+ client.jdbcTypeToDoris(column("bigint identity",
Types.NVARCHAR, 20, 0)));
+ Assertions.assertEquals(ScalarType.createDatetimeV2Type(3),
+ client.jdbcTypeToDoris(column("varchar(50) alias",
Types.TIMESTAMP, 23, 3)));
+
+ // The IDENTITY decoration of a real system type, in the forms the
driver versions report it in
+ Assertions.assertEquals(Type.INT, client.jdbcTypeToDoris(column("int
identity", Types.INTEGER, 10, 0)));
+ Assertions.assertEquals(Type.BIGINT,
client.jdbcTypeToDoris(column("bigint identity", Types.BIGINT, 19, 0)));
+ Assertions.assertEquals(Type.SMALLINT,
+ client.jdbcTypeToDoris(column("tinyint identity",
Types.TINYINT, 3, 0)));
+ Assertions.assertEquals(ScalarType.createDecimalV3Type(18, 0),
+ client.jdbcTypeToDoris(column("decimal identity",
Types.DECIMAL, 18, 0)));
+ Assertions.assertEquals(ScalarType.createDecimalV3Type(18, 0),
+ client.jdbcTypeToDoris(column("decimal() identity",
Types.DECIMAL, 18, 0)));
+ Assertions.assertEquals(ScalarType.createDecimalV3Type(18, 0),
+ client.jdbcTypeToDoris(column("numeric(18, 0) identity",
Types.NUMERIC, 18, 0)));
+ Assertions.assertEquals(ScalarType.createDecimalV3Type(18, 0),
+ client.jdbcTypeToDoris(column("decimal(18,0) IDENTITY(1,1)",
Types.DECIMAL, 18, 0)));
+ }
+
+ @Test
+ public void testSystemTypeNamesTakePrecedence() throws SQLException {
+ // the name based mapping is unchanged, the type code is only
consulted for unknown names
+ Assertions.assertEquals(Type.SMALLINT,
client.jdbcTypeToDoris(column("tinyint", Types.TINYINT, 3, 0)));
+ Assertions.assertEquals(Type.INT, client.jdbcTypeToDoris(column("int
identity", Types.INTEGER, 10, 0)));
+ Assertions.assertEquals(ScalarType.createDecimalV3Type(19, 4),
+ client.jdbcTypeToDoris(column("money", Types.DECIMAL, 19, 4)));
+ Assertions.assertEquals(Type.STRING,
client.jdbcTypeToDoris(column("varbinary", Types.VARBINARY, 20, 0)));
+ Assertions.assertEquals(Type.STRING,
client.jdbcTypeToDoris(column("timestamp", Types.BINARY, 8, 0)));
+ Assertions.assertEquals(Type.STRING,
+ client.jdbcTypeToDoris(column("datetimeoffset",
SQL_SS_TIMESTAMPOFFSET, 34, 7)));
+ }
+}
diff --git
a/regression-test/data/external_table_p2/jdbc/test_sqlserver_jdbc_catalog.out
b/regression-test/data/external_table_p2/jdbc/test_sqlserver_jdbc_catalog.out
index 3069fb1904f..aa34b8d79ac 100644
---
a/regression-test/data/external_table_p2/jdbc/test_sqlserver_jdbc_catalog.out
+++
b/regression-test/data/external_table_p2/jdbc/test_sqlserver_jdbc_catalog.out
@@ -133,6 +133,74 @@ timestamp_col text Yes true \N
-- !identity_decimal --
1 1
+-- !desc_alias_type --
+alias_bigint_col bigint Yes true \N
+alias_bit_col boolean Yes true \N
+alias_char_col text Yes true \N
+alias_date_col date Yes true \N
+alias_datetime2_col datetime(3) Yes true \N
+alias_datetime2_default_col datetime(6) Yes true \N
+alias_datetime_col datetime(3) Yes true \N
+alias_decimal_col decimal(10,2) Yes true \N
+alias_float_col double Yes true \N
+alias_guid_col text Yes true \N
+alias_int_col int Yes true \N
+alias_money_col decimal(19,4) Yes true \N
+alias_nchar_col text Yes true \N
+alias_ntext_col text Yes true \N
+alias_numeric_col decimal(38,10) Yes true \N
+alias_nvarchar_col text Yes true \N
+alias_nvarcharmax_col text Yes true \N
+alias_real_col float Yes true \N
+alias_smalldatetime_col datetime Yes true \N
+alias_smallint_col smallint Yes true \N
+alias_smallmoney_col decimal(10,4) Yes true \N
+alias_text_col text Yes true \N
+alias_time_col text Yes true \N
+alias_tinyint_col smallint Yes true \N
+alias_varchar_col text Yes true \N
+alias_varcharmax_col text Yes true \N
+id int No true \N
+plain_col text Yes true \N
+sysname_col text Yes true \N
+
+-- !alias_type --
+1 plain alias alias varchar max alias nvarchar alias nvarchar
max Doris Doris alias text alias ntext true
255 32767 1 9223372036854775807 123.123 1.5 12345.67
1234567890123456789012345678.0123456789 123.4567 214748.3647
2023-01-17 16:49:05.123 2023-01-17T16:49:05 2023-01-17T10:30:45.123
2023-01-17T16:49:05.123456 2023-01-17T16:49
FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF sysname value
+2 \N \N \N \N \N \N \N \N \N
\N \N \N \N \N \N \N \N \N \N
\N \N \N \N \N \N \N \N \N
+
+-- !desc_alias_identity --
+id int No true \N
+val text Yes true \N
+
+-- !alias_identity --
+1 first
+2 second
+
+-- !desc_alias_name --
+alias_named_decimal_identity_col text Yes true \N
+alias_named_int_col text Yes true \N
+alias_named_int_identity_col text Yes true \N
+id int No true \N
+
+-- !alias_name --
+1 not an int not a decimal not an id
+
+-- !desc_alias_unsupported --
+alias_binary_col unknown type: UNSUPPORTED_TYPE Yes true \N
+alias_datetimeoffset_col unknown type: UNSUPPORTED_TYPE Yes true
\N
+alias_image_col unknown type: UNSUPPORTED_TYPE Yes true \N
+alias_varbinary_col unknown type: UNSUPPORTED_TYPE Yes true \N
+alias_variant_col unknown type: UNSUPPORTED_TYPE Yes true \N
+geometry_col unknown type: UNSUPPORTED_TYPE Yes true \N
+hierarchyid_col unknown type: UNSUPPORTED_TYPE Yes true \N
+id int No true \N
+plain_col text Yes true \N
+xml_col unknown type: UNSUPPORTED_TYPE Yes true \N
+
+-- !alias_unsupported --
+1 plain
+2 \N
+
-- !datetime_eq --
1 2023-01-17 2023-01-17T10:30 2023-01-17T10:30:00.123
@@ -179,6 +247,18 @@ bit_value boolean No true \N
id int No true \N
varbinary_value varbinary(20) Yes true \N
+-- !desc_alias_unsupported_varbinary --
+alias_binary_col unknown type: UNSUPPORTED_TYPE Yes true \N
+alias_datetimeoffset_col unknown type: UNSUPPORTED_TYPE Yes true
\N
+alias_image_col unknown type: UNSUPPORTED_TYPE Yes true \N
+alias_varbinary_col unknown type: UNSUPPORTED_TYPE Yes true \N
+alias_variant_col unknown type: UNSUPPORTED_TYPE Yes true \N
+geometry_col unknown type: UNSUPPORTED_TYPE Yes true \N
+hierarchyid_col unknown type: UNSUPPORTED_TYPE Yes true \N
+id int No true \N
+plain_col text Yes true \N
+xml_col unknown type: UNSUPPORTED_TYPE Yes true \N
+
-- !query --
1 false 0x4D616B6520446F72697320477265617421000000
0x4D616B6520446F72697320477265617421
2 true 0x4D616B6520446F72697320477265617421000000
0x4D616B6520446F72697320477265617421
diff --git
a/regression-test/suites/external_table_p2/jdbc/test_sqlserver_jdbc_catalog.groovy
b/regression-test/suites/external_table_p2/jdbc/test_sqlserver_jdbc_catalog.groovy
index 01648cdca6c..fbbb3078e85 100644
---
a/regression-test/suites/external_table_p2/jdbc/test_sqlserver_jdbc_catalog.groovy
+++
b/regression-test/suites/external_table_p2/jdbc/test_sqlserver_jdbc_catalog.groovy
@@ -86,6 +86,29 @@ suite("test_sqlserver_jdbc_catalog", "p2,external") {
order_qt_identity_decimal """ select * from test_identity_decimal
order by id; """
+ // Regression test for https://github.com/apache/doris/issues/67793
+ // Columns declared with a user-defined alias type (CREATE TYPE ...
FROM base_type) are reported
+ // by the driver with the alias name as TYPE_NAME. They must resolve
to the Doris type of their
+ // base type instead of UNSUPPORTED, so that both DESC and SELECT *
work.
+ order_qt_desc_alias_type """ desc test_alias_type; """
+ order_qt_alias_type """ select * from test_alias_type order by id; """
+ // IDENTITY on an alias typed column
+ order_qt_desc_alias_identity """ desc test_alias_identity; """
+ order_qt_alias_identity """ select * from test_alias_identity order by
id; """
+ // Alias types named like a system type ([int alias], [decimal(18,0)
identity], [int identity]) are
+ // resolved by their base type; only a real IDENTITY column is
reported with its base type decorated.
+ order_qt_desc_alias_name """ desc test_alias_name; """
+ order_qt_alias_name """ select * from test_alias_name order by id; """
+ // Aliases over binary types, datetimeoffset and sql_variant can not
be resolved by the JDBC type
+ // code, and the xml / CLR system types are not supported either. They
stay UNSUPPORTED, the other
+ // columns of the table remain readable and SELECT * still fails on
the unsupported columns.
+ order_qt_desc_alias_unsupported """ desc test_alias_unsupported; """
+ order_qt_alias_unsupported """ select id, plain_col from
test_alias_unsupported order by id; """
+ test {
+ sql """ select * from test_alias_unsupported order by id; """
+ exception "UNSUPPORTED"
+ }
+
// Test cases for SQL Server date format pushdown
(handleSQLServerDateFormat)
// Uses test_date_filter table which has diverse date/datetime values
across rows
// to verify that filters genuinely include/exclude the correct rows.
@@ -138,6 +161,8 @@ suite("test_sqlserver_jdbc_catalog", "p2,external") {
sql """ use ${ex_db_name} """
order_qt_desc """ desc test_binary; """
+ // enable.mapping.varbinary only applies to the native binary types,
aliases over them stay UNSUPPORTED
+ order_qt_desc_alias_unsupported_varbinary """ desc
test_alias_unsupported; """
sql """ CALL EXECUTE_STMT("test_sqlserver_jdbc_catalog_binary",
"DELETE FROM dbo.test_binary WHERE id = 4") """
order_qt_query """ select * from test_binary order by id; """
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]