From 4593bb0c3e7af6f2d27bb6588d374b0e3e3d472e Mon Sep 17 00:00:00 2001 From: Magnus Persson <1431685+magnusp@users.noreply.github.com> Date: Sat, 24 Oct 2026 17:14:45 +0200 Subject: [PATCH] refactor(jdbc): extract xerial column type mapping --- .../libsql/jdbc/LibsqlResultSetMetaData.java | 139 +---------------- .../magnusp/libsql/jdbc/XerialColumnType.java | 143 ++++++++++++++++++ 2 files changed, 149 insertions(+), 133 deletions(-) create mode 100644 libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/XerialColumnType.java diff --git a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetMetaData.java b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetMetaData.java index 2138ea9..f2d0727 100644 --- a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetMetaData.java +++ b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetMetaData.java @@ -8,7 +8,6 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; -import java.sql.Types; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -113,7 +112,7 @@ public boolean isSigned(int column) throws SQLException { } // An expression column has no declared type, so signedness follows the // storage class of the current value, as xerial does. - return switch (storageType(runtimeValue(column))) { + return switch (XerialColumnType.storageClass(runtimeValue(column))) { case INTEGER, FLOAT, NULL -> true; case TEXT, BLOB -> false; }; @@ -185,91 +184,7 @@ public int getColumnType(int column) throws SQLException { getCol(column); String typeName = getColumnTypeName(column); Value value = runtimeValue(column); - SampleType valueType = storageType(value); - - if (valueType == SampleType.INTEGER || valueType == SampleType.NULL) { - if ("BOOLEAN".equals(typeName)) { - return Types.BOOLEAN; - } - if ("TINYINT".equals(typeName)) { - return Types.TINYINT; - } - if ("SMALLINT".equals(typeName) || "INT2".equals(typeName)) { - return Types.SMALLINT; - } - if ("BIGINT".equals(typeName) - || "INT8".equals(typeName) - || "UNSIGNED BIG INT".equals(typeName)) { - return Types.BIGINT; - } - if ("DATE".equals(typeName)) { - return Types.DATE; - } - if ("DATETIME".equals(typeName) || "TIMESTAMP".equals(typeName)) { - return Types.TIMESTAMP; - } - if (valueType == SampleType.INTEGER - || "INT".equals(typeName) - || "INTEGER".equals(typeName) - || "MEDIUMINT".equals(typeName)) { - long val = integer(value); - return val > Integer.MAX_VALUE || val < Integer.MIN_VALUE ? Types.BIGINT : Types.INTEGER; - } - } - - if (valueType == SampleType.FLOAT || valueType == SampleType.NULL) { - if ("DECIMAL".equals(typeName)) { - return Types.DECIMAL; - } - if ("DOUBLE".equals(typeName) || "DOUBLE PRECISION".equals(typeName)) { - return Types.DOUBLE; - } - if ("NUMERIC".equals(typeName)) { - return Types.NUMERIC; - } - if ("REAL".equals(typeName)) { - return Types.REAL; - } - if (valueType == SampleType.FLOAT || "FLOAT".equals(typeName)) { - return Types.FLOAT; - } - } - - if (valueType == SampleType.TEXT || valueType == SampleType.NULL) { - if ("CHARACTER".equals(typeName) - || "NCHAR".equals(typeName) - || "NATIVE CHARACTER".equals(typeName) - || "CHAR".equals(typeName)) { - return Types.CHAR; - } - if ("CLOB".equals(typeName)) { - return Types.CLOB; - } - if ("DATE".equals(typeName)) { - return Types.DATE; - } - if ("DATETIME".equals(typeName) || "TIMESTAMP".equals(typeName)) { - return Types.TIMESTAMP; - } - if (valueType == SampleType.TEXT - || "VARCHAR".equals(typeName) - || "VARYING CHARACTER".equals(typeName) - || "NVARCHAR".equals(typeName) - || "TEXT".equals(typeName)) { - return Types.VARCHAR; - } - } - - if (valueType == SampleType.BLOB || valueType == SampleType.NULL) { - if ("BINARY".equals(typeName)) { - return Types.BINARY; - } - if (valueType == SampleType.BLOB || "BLOB".equals(typeName)) { - return Types.BLOB; - } - } - - return Types.NUMERIC; + return XerialColumnType.jdbcType(value, typeName); } @Override @@ -305,23 +220,7 @@ public boolean isDefinitelyWritable(int column) throws SQLException { @Override public String getColumnClassName(int column) throws SQLException { getCol(column); - return className(runtimeValue(column)); - } - - private static String className(Value value) { - if (value instanceof Value.Integer i) { - long val = i.value(); - return val > Integer.MAX_VALUE || val < Integer.MIN_VALUE - ? Long.class.getName() - : Integer.class.getName(); - } - if (value instanceof Value.Float) { - return Double.class.getName(); - } - if (value instanceof Value.Text) { - return String.class.getName(); - } - return Object.class.getName(); + return XerialColumnType.className(runtimeValue(column)); } private String declType(int column) throws SQLException { @@ -363,22 +262,6 @@ private Value sampleValue(int column) { return null; } - private static SampleType storageType(Value value) { - if (value instanceof Value.Integer) { - return SampleType.INTEGER; - } - if (value instanceof Value.Float) { - return SampleType.FLOAT; - } - if (value instanceof Value.Blob) { - return SampleType.BLOB; - } - if (value instanceof Value.Text) { - return SampleType.TEXT; - } - return SampleType.NULL; - } - private TableInfo tableForColumn(int column) throws SQLException { getCol(column); SqlTableReference.TableRef reference = originTable(column); @@ -505,7 +388,9 @@ private static TableInfo toTableInfo(StmtResult result, String fallbackName) { if (columnName != null) { columns.put( columnName.toLowerCase(Locale.ROOT), - new ColumnInfo(integer(values.get(3)) != 0, integer(values.get(4)) != 0)); + new ColumnInfo( + XerialColumnType.integer(values.get(3)) != 0, + XerialColumnType.integer(values.get(4)) != 0)); } } return new TableInfo(tableName != null ? tableName : fallbackName, createSql, columns); @@ -515,18 +400,6 @@ private static String text(Value value) { return value instanceof Value.Text t ? t.value() : null; } - private static long integer(Value value) { - return value instanceof Value.Integer i ? i.value() : 0L; - } - - private enum SampleType { - INTEGER, - FLOAT, - BLOB, - TEXT, - NULL - } - private record ColumnInfo(boolean notNull, boolean primaryKey) {} private record TableInfo(String name, String createSql, Map columns) {} diff --git a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/XerialColumnType.java b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/XerialColumnType.java new file mode 100644 index 0000000..2c985b4 --- /dev/null +++ b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/XerialColumnType.java @@ -0,0 +1,143 @@ +package com.github.magnusp.libsql.jdbc; + +import com.github.magnusp.libsql.hrana.codec.Value; + +import java.sql.Types; + +/** xerial 3.53.4.0 column type mapping. */ +final class XerialColumnType { + + private XerialColumnType() {} + + static int jdbcType(Value value, String typeName) { + StorageClass valueType = storageClass(value); + + if (valueType == StorageClass.INTEGER || valueType == StorageClass.NULL) { + if ("BOOLEAN".equals(typeName)) { + return Types.BOOLEAN; + } + if ("TINYINT".equals(typeName)) { + return Types.TINYINT; + } + if ("SMALLINT".equals(typeName) || "INT2".equals(typeName)) { + return Types.SMALLINT; + } + if ("BIGINT".equals(typeName) + || "INT8".equals(typeName) + || "UNSIGNED BIG INT".equals(typeName)) { + return Types.BIGINT; + } + if ("DATE".equals(typeName)) { + return Types.DATE; + } + if ("DATETIME".equals(typeName) || "TIMESTAMP".equals(typeName)) { + return Types.TIMESTAMP; + } + if (valueType == StorageClass.INTEGER + || "INT".equals(typeName) + || "INTEGER".equals(typeName) + || "MEDIUMINT".equals(typeName)) { + long val = integer(value); + return val > Integer.MAX_VALUE || val < Integer.MIN_VALUE ? Types.BIGINT : Types.INTEGER; + } + } + + if (valueType == StorageClass.FLOAT || valueType == StorageClass.NULL) { + if ("DECIMAL".equals(typeName)) { + return Types.DECIMAL; + } + if ("DOUBLE".equals(typeName) || "DOUBLE PRECISION".equals(typeName)) { + return Types.DOUBLE; + } + if ("NUMERIC".equals(typeName)) { + return Types.NUMERIC; + } + if ("REAL".equals(typeName)) { + return Types.REAL; + } + if (valueType == StorageClass.FLOAT || "FLOAT".equals(typeName)) { + return Types.FLOAT; + } + } + + if (valueType == StorageClass.TEXT || valueType == StorageClass.NULL) { + if ("CHARACTER".equals(typeName) + || "NCHAR".equals(typeName) + || "NATIVE CHARACTER".equals(typeName) + || "CHAR".equals(typeName)) { + return Types.CHAR; + } + if ("CLOB".equals(typeName)) { + return Types.CLOB; + } + if ("DATE".equals(typeName)) { + return Types.DATE; + } + if ("DATETIME".equals(typeName) || "TIMESTAMP".equals(typeName)) { + return Types.TIMESTAMP; + } + if (valueType == StorageClass.TEXT + || "VARCHAR".equals(typeName) + || "VARYING CHARACTER".equals(typeName) + || "NVARCHAR".equals(typeName) + || "TEXT".equals(typeName)) { + return Types.VARCHAR; + } + } + + if (valueType == StorageClass.BLOB || valueType == StorageClass.NULL) { + if ("BINARY".equals(typeName)) { + return Types.BINARY; + } + if (valueType == StorageClass.BLOB || "BLOB".equals(typeName)) { + return Types.BLOB; + } + } + + return Types.NUMERIC; + } + + static String className(Value value) { + if (value instanceof Value.Integer i) { + long val = i.value(); + return val > Integer.MAX_VALUE || val < Integer.MIN_VALUE + ? Long.class.getName() + : Integer.class.getName(); + } + if (value instanceof Value.Float) { + return Double.class.getName(); + } + if (value instanceof Value.Text) { + return String.class.getName(); + } + return Object.class.getName(); + } + + static StorageClass storageClass(Value value) { + if (value instanceof Value.Integer) { + return StorageClass.INTEGER; + } + if (value instanceof Value.Float) { + return StorageClass.FLOAT; + } + if (value instanceof Value.Blob) { + return StorageClass.BLOB; + } + if (value instanceof Value.Text) { + return StorageClass.TEXT; + } + return StorageClass.NULL; + } + + static long integer(Value value) { + return value instanceof Value.Integer i ? i.value() : 0L; + } + + enum StorageClass { + INTEGER, + FLOAT, + BLOB, + TEXT, + NULL + } +}