2121import org .apache .doris .catalog .Type ;
2222import org .apache .doris .datasource .jdbc .util .JdbcFieldSchema ;
2323
24+ import java .sql .Types ;
25+ import java .util .Locale ;
26+ import java .util .regex .Matcher ;
27+ import java .util .regex .Pattern ;
28+
2429public class JdbcSQLServerClient extends JdbcClient {
2530
31+ // TYPE_NAME of an IDENTITY column decorates the base type: "int identity", "decimal() identity",
32+ // "numeric(18, 0) identity", "decimal(18,0) IDENTITY(1,1)". IDENTITY is only allowed on these base types.
33+ private static final Pattern IDENTITY_TYPE_NAME = Pattern .compile (
34+ "^(tinyint|smallint|int|bigint|decimal|numeric)\\ s*(\\ ([^)]*\\ ))?\\ s+identity(\\ s*\\ ([^)]*\\ ))?$" ,
35+ Pattern .CASE_INSENSITIVE );
36+
2637 protected JdbcSQLServerClient (JdbcClientConfig jdbcClientConfig ) {
2738 super (jdbcClientConfig );
2839 }
@@ -34,18 +45,48 @@ protected String getSchemaPatternForDatabaseNameList() {
3445 return "%" ;
3546 }
3647
48+ /**
49+ * The base type of an IDENTITY column's TYPE_NAME, or the name unchanged.
50+ * <p>
51+ * The decoration is trusted only when {@code DATA_TYPE} is the code of the named base type: an alias type
52+ * may legally be named like that ({@code CREATE TYPE dbo.[int identity] FROM varchar(10)}, or
53+ * {@code dbo.[int alias]}), and it then has to be resolved by its code, not by the words of its name.
54+ */
55+ static String identityBaseType (String typeName , int dataType ) {
56+ Matcher matcher = IDENTITY_TYPE_NAME .matcher (typeName );
57+ if (!matcher .matches ()) {
58+ return typeName ;
59+ }
60+ String baseType = matcher .group (1 ).toLowerCase (Locale .ROOT );
61+ boolean codeMatches ;
62+ switch (baseType ) {
63+ case "tinyint" :
64+ codeMatches = dataType == Types .TINYINT ;
65+ break ;
66+ case "smallint" :
67+ codeMatches = dataType == Types .SMALLINT ;
68+ break ;
69+ case "int" :
70+ codeMatches = dataType == Types .INTEGER ;
71+ break ;
72+ case "bigint" :
73+ codeMatches = dataType == Types .BIGINT ;
74+ break ;
75+ default :
76+ codeMatches = dataType == Types .DECIMAL || dataType == Types .NUMERIC ;
77+ break ;
78+ }
79+ return codeMatches ? baseType : typeName ;
80+ }
81+
3782 @ Override
3883 protected Type jdbcTypeToDoris (JdbcFieldSchema fieldSchema ) {
3984 String originSqlserverType = fieldSchema .getDataTypeName ().orElse ("unknown" );
40- // For sqlserver IDENTITY type, such as 'INT IDENTITY'
41- // originSqlserverType is "int identity", so we only get "int".
42- // For types with parameters like 'decimal(18,0) IDENTITY(1,1)', we need to extract the base type
43- String sqlserverType = originSqlserverType .split (" " )[0 ];
44-
45- // Handle types with parentheses like decimal(18,0), varchar(50), etc.
46- if (sqlserverType .contains ("(" )) {
47- sqlserverType = sqlserverType .substring (0 , sqlserverType .indexOf ("(" ));
48- }
85+ // An IDENTITY column is reported as "int identity" or "decimal(18,0) identity": only the base type
86+ // is matched below. Any other name is matched as it is: system type names are single words, and a
87+ // user-defined alias type may be named with spaces or parentheses ("int alias") and must not be
88+ // mistaken for the system type its name starts with.
89+ String sqlserverType = identityBaseType (originSqlserverType , fieldSchema .getDataType ());
4990
5091 switch (sqlserverType ) {
5192 case "bit" :
@@ -100,6 +141,76 @@ protected Type jdbcTypeToDoris(JdbcFieldSchema fieldSchema) {
100141 case "binary" :
101142 case "varbinary" :
102143 return ScalarType .createVarbinaryType (fieldSchema .requiredColumnSize ());
144+ case "xml" :
145+ case "sql_variant" :
146+ case "geometry" :
147+ case "geography" :
148+ case "hierarchyid" :
149+ case "json" :
150+ case "vector" :
151+ // SQL Server system types that Doris does not support. They are listed explicitly
152+ // so that they never reach the JDBC type code fallback below.
153+ return Type .UNSUPPORTED ;
154+ default :
155+ return jdbcTypeCodeToDoris (fieldSchema );
156+ }
157+ }
158+
159+ /**
160+ * Fallback for type names that are not SQL Server system types.
161+ * <p>
162+ * User-defined alias types ({@code CREATE TYPE dbo.my_type FROM varchar(50)}) are reported by
163+ * {@code DatabaseMetaData.getColumns()} with {@code TYPE_NAME} set to the alias name, so they can not be
164+ * matched by name. {@code DATA_TYPE}, {@code COLUMN_SIZE} and {@code DECIMAL_DIGITS} still describe the
165+ * base type, so the standard {@link Types} code is used to resolve the Doris type. The mapping mirrors
166+ * the name based one above.
167+ * <p>
168+ * Binary codes are deliberately not mapped: mssql-jdbc also reports CLR user-defined types
169+ * (geometry, geography, hierarchyid, ...) as {@link Types#VARBINARY}, so they can not be told apart from
170+ * an alias over a binary type by the type code alone. Vendor specific codes stay unsupported as well.
171+ */
172+ private Type jdbcTypeCodeToDoris (JdbcFieldSchema fieldSchema ) {
173+ switch (fieldSchema .getDataType ()) {
174+ case Types .BIT :
175+ case Types .BOOLEAN :
176+ return Type .BOOLEAN ;
177+ // SQL Server tinyint is unsigned (0 to 255), so it needs SMALLINT
178+ case Types .TINYINT :
179+ case Types .SMALLINT :
180+ return Type .SMALLINT ;
181+ case Types .INTEGER :
182+ return Type .INT ;
183+ case Types .BIGINT :
184+ return Type .BIGINT ;
185+ case Types .REAL :
186+ return Type .FLOAT ;
187+ case Types .FLOAT :
188+ case Types .DOUBLE :
189+ return Type .DOUBLE ;
190+ case Types .DECIMAL :
191+ case Types .NUMERIC : {
192+ // money and smallmoney are reported as DECIMAL(19,4) and DECIMAL(10,4)
193+ int precision = fieldSchema .getColumnSize ().orElse (0 );
194+ int scale = fieldSchema .getDecimalDigits ().orElse (0 );
195+ return createDecimalOrStringType (precision , scale );
196+ }
197+ case Types .DATE :
198+ return ScalarType .createDateV2Type ();
199+ case Types .TIMESTAMP : {
200+ int scale = fieldSchema .getDecimalDigits ().orElse (0 );
201+ if (scale > 6 ) {
202+ scale = 6 ;
203+ }
204+ return ScalarType .createDatetimeV2Type (scale );
205+ }
206+ case Types .CHAR :
207+ case Types .VARCHAR :
208+ case Types .LONGVARCHAR :
209+ case Types .NCHAR :
210+ case Types .NVARCHAR :
211+ case Types .LONGNVARCHAR :
212+ case Types .TIME :
213+ return ScalarType .createStringType ();
103214 default :
104215 return Type .UNSUPPORTED ;
105216 }
0 commit comments