From 44db92489ff5a457ea701e197aacfed193a91bdf Mon Sep 17 00:00:00 2001
From: jkee <jkee@yandex-team.ru>
Date: Thu, 19 Mar 2015 00:03:33 +0300
Subject: [PATCH] METR-15511: different metadata, some types support

---
 .../clickhouse/CHDatabaseMetadata.java        | 207 ++++++++++++++++--
 .../copypaste/AbstractResultSet.java          |   5 -
 .../clickhouse/copypaste/CHResultSet.java     | 129 +++++++++--
 3 files changed, 298 insertions(+), 43 deletions(-)

diff --git a/src/main/java/ru/yandex/metrika/clickhouse/CHDatabaseMetadata.java b/src/main/java/ru/yandex/metrika/clickhouse/CHDatabaseMetadata.java
index 5322e29c..a33ab50f 100644
--- a/src/main/java/ru/yandex/metrika/clickhouse/CHDatabaseMetadata.java
+++ b/src/main/java/ru/yandex/metrika/clickhouse/CHDatabaseMetadata.java
@@ -1,6 +1,11 @@
 package ru.yandex.metrika.clickhouse;
 
+import ru.yandex.metrika.clickhouse.copypaste.CHResultBuilder;
+import ru.yandex.metrika.clickhouse.copypaste.CHResultSet;
+
 import java.sql.*;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Created by jkee on 14.03.15.
@@ -612,12 +617,42 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) throws SQLException {
-        return request("SELECT 'bullshit_procedure'");
+        CHResultBuilder builder = CHResultBuilder.builder(9);
+        builder.names(
+                "PROCEDURE_CAT",
+                "PROCEDURE_SCHEM",
+                "PROCEDURE_NAME",
+                "RES_1",
+                "RES_2",
+                "RES_3",
+                "REMARKS",
+                "PROCEDURE_TYPE",
+                "SPECIFIC_NAME"
+        );
+
+        builder.types(
+                "String",
+                "String",
+                "String",
+                "String",
+                "String",
+                "String",
+                "String",
+                "UInt8",
+                "String"
+        );
+
+        return builder.build();
     }
 
     @Override
     public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) throws SQLException {
-        return request("SELECT 'bullshit_procedure_columns'");
+        CHResultBuilder builder = CHResultBuilder.builder(20);
+        builder.names("1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20");
+
+        builder.types("UInt32","UInt32","UInt32","UInt32","UInt32","UInt32","UInt32","UInt32","UInt32","UInt32","UInt32","UInt32","UInt32","UInt32","UInt32","UInt32","UInt32","UInt32","UInt32","UInt32");
+
+        return builder.build();
     }
 
     @Override
@@ -638,16 +673,45 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
             catalog = "default";
         }
         String sql = "select " +
-                "database, name, name, 'TABLE', '', '', '', '', '', '' " +
+                "database, name, name " +
                 "from system.tables " +
                 "where database = '" + catalog + "' " +
                 "order by name";
-        return request(sql);
+        ResultSet result = request(sql);
+
+        CHResultBuilder builder = CHResultBuilder.builder(10);
+        builder.names("TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "TABLE_TYPE", "REMARKS", "TYPE_CAT", "TYPE_SCHEM", "TYPE_NAME", "SELF_REFERENCING_COL_NAME", "REF_GENERATION");
+        builder.types("String", "String", "String", "String", "String", "String", "String", "String", "String", "String");
+
+        while(result.next()) {
+            List<String> row = new ArrayList<String>();
+            row.add(result.getString(1));
+            row.add(result.getString(2));
+            row.add(result.getString(3));
+            row.add("TABLE"); // можно сделать точнее
+            for (int i = 4; i < 10; i++) {
+                row.add(null);
+            }
+            builder.addRow(row);
+        }
+        return builder.build();
     }
 
     @Override
     public ResultSet getSchemas() throws SQLException {
-        return request("select name, database from system.tables");
+        return getSchemas(null, null);
+    }
+
+    @Override
+    public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException {
+        String sql = "select name, database from system.tables";
+        if (catalog != null) sql += " where database = '" + catalog + '\'';
+        if (schemaPattern != null) {
+            if (catalog != null) sql += " and ";
+            else sql += " where ";
+            sql += "name = '" + schemaPattern + '\'';
+        }
+        return request(sql);
     }
 
     @Override
@@ -662,7 +726,107 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException {
-        return request("desc table " + catalog + '.' + tableNamePattern);
+        CHResultBuilder builder = CHResultBuilder.builder(23);
+        builder.names(
+                "TABLE_CAT",
+                "TABLE_SCHEM",
+                "TABLE_NAME",
+                "COLUMN_NAME",
+                "DATA_TYPE",
+                "TYPE_NAME",
+                "COLUMN_SIZE",
+                "BUFFER_LENGTH",
+                "DECIMAL_DIGITS",
+                "NUM_PREC_RADIX",
+                "NULLABLE",
+                "REMARKS",
+                "COLUMN_DEF",
+                "SQL_DATA_TYPE",
+                "SQL_DATETIME_SUB",
+                "CHAR_OCTET_LENGTH",
+                "ORDINAL_POSITION",
+                "IS_NULLABLE",
+                "SCOPE_CATLOG",
+                "SCOPE_SCHEMA",
+                "SCOPE_TABLE",
+                "SOURCE_DATA_TYPE",
+                "IS_AUTOINCREMENT"
+        );
+        builder.types(
+                "String",
+                "String",
+                "String",
+                "String",
+                "Int32",
+                "String",
+                "Int32",
+                "Int32",
+                "Int32",
+                "Int32",
+                "Int32",
+                "String",
+                "String",
+                "Int32",
+                "Int32",
+                "Int32",
+                "Int32",
+                "String",
+                "String",
+                "String",
+                "String",
+                "Int32",
+                "String"
+        );
+        ResultSet descTable = request("desc table " + catalog + '.' + tableNamePattern);
+        int colNum = 1;
+        while (descTable.next()) {
+            List<String> row = new ArrayList<String>();
+            row.add(catalog);
+            row.add(tableNamePattern);
+            row.add(tableNamePattern);
+            row.add(descTable.getString(1));
+            String type = descTable.getString(2);
+            row.add(Integer.toString(CHResultSet.toSqlType(type)));
+            row.add(type);
+
+            // column size ?
+            row.add("0");
+            row.add("0");
+
+            // decimal digits
+            if (type.contains("Int")) {
+                String bits = type.substring(type.indexOf("Int") + "Int".length());
+                row.add(bits); //bullshit
+            }
+
+            // radix
+            row.add("10");
+            // nullable
+            row.add(String.valueOf(columnNoNulls));
+
+            row.add(null);
+            row.add(null);
+            row.add(null);
+            row.add(null);
+
+            // char octet length
+            row.add("0");
+            // ordinal
+            row.add(String.valueOf(colNum));
+            colNum += 1;
+            row.add("NO");
+
+            row.add(null);
+            row.add(null);
+            row.add(null);
+            row.add(null);
+            row.add(null);
+
+            builder.addRow(row);
+
+        }
+
+        return builder.build();
     }
 
     @Override
@@ -717,6 +881,10 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public boolean supportsResultSetType(int type) throws SQLException {
+        int[] types = CHResultSet.supportedTypes();
+        for (int i : types) {
+            if (i == type) return true;
+        }
         return false;
     }
 
@@ -727,32 +895,32 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public boolean ownUpdatesAreVisible(int type) throws SQLException {
-        return false;
+        return true;
     }
 
     @Override
     public boolean ownDeletesAreVisible(int type) throws SQLException {
-        return false;
+        return true;
     }
 
     @Override
     public boolean ownInsertsAreVisible(int type) throws SQLException {
-        return false;
+        return true;
     }
 
     @Override
     public boolean othersUpdatesAreVisible(int type) throws SQLException {
-        return false;
+        return true;
     }
 
     @Override
     public boolean othersDeletesAreVisible(int type) throws SQLException {
-        return false;
+        return true;
     }
 
     @Override
     public boolean othersInsertsAreVisible(int type) throws SQLException {
-        return false;
+        return true;
     }
 
     @Override
@@ -782,7 +950,7 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public Connection getConnection() throws SQLException {
-        return null;
+        return connection;
     }
 
     @Override
@@ -837,7 +1005,7 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public int getDatabaseMinorVersion() throws SQLException {
-        return 0;
+        return 1;
     }
 
     @Override
@@ -847,12 +1015,12 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public int getJDBCMinorVersion() throws SQLException {
-        return 0;
+        return 1;
     }
 
     @Override
     public int getSQLStateType() throws SQLException {
-        return 0;
+        return sqlStateSQL;
     }
 
     @Override
@@ -867,12 +1035,7 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public RowIdLifetime getRowIdLifetime() throws SQLException {
-        return null;
-    }
-
-    @Override
-    public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException {
-        return null;
+        return RowIdLifetime.ROWID_UNSUPPORTED;
     }
 
     @Override
diff --git a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/AbstractResultSet.java b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/AbstractResultSet.java
index 603cb6fc..d753caa4 100644
--- a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/AbstractResultSet.java
+++ b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/AbstractResultSet.java
@@ -284,11 +284,6 @@ public abstract class AbstractResultSet implements ResultSet {
         throw new UnsupportedOperationException();
     }
 
-    @Override
-    public int getRow() throws SQLException {
-        throw new UnsupportedOperationException();
-    }
-
     @Override
     public boolean absolute(int row) throws SQLException {
         throw new UnsupportedOperationException();
diff --git a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultSet.java b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultSet.java
index 2f7ef804..0c82b9f0 100644
--- a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultSet.java
+++ b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultSet.java
@@ -2,10 +2,7 @@ package ru.yandex.metrika.clickhouse.copypaste;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.sql.ResultSetMetaData;
-import java.sql.SQLException;
-import java.sql.Timestamp;
-import java.sql.Types;
+import java.sql.*;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
@@ -29,6 +26,7 @@ public class CHResultSet extends AbstractResultSet {
     private final String[] types;
 
     private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //
+    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); //
 
     // current line
     private ByteFragment[] values;
@@ -38,6 +36,9 @@ public class CHResultSet extends AbstractResultSet {
     // next line
     private ByteFragment nextLine;
 
+    // row counter
+    private int rowNumber;
+
     public CHResultSet(InputStream is, int bufferSize) throws IOException {
         bis = new StreamSplitter(is, (byte) 0x0A, bufferSize);  ///   \n
         ByteFragment headerFragment = bis.next();
@@ -90,6 +91,7 @@ public class CHResultSet extends AbstractResultSet {
         if (hasNext()) {
             values = nextLine.split((byte) 0x09);
             nextLine = null;
+            rowNumber += 1;
             return true;
         } else return false;
     }
@@ -129,7 +131,7 @@ public class CHResultSet extends AbstractResultSet {
     @Override
     public boolean wasNull() throws SQLException {
         if (lastReadColumn == 0) throw new IllegalStateException("You should get something before check nullability");
-        return values[lastReadColumn - 1].isNull();
+        return getValue(lastReadColumn).isNull();
     }
 
     @Override
@@ -182,57 +184,126 @@ public class CHResultSet extends AbstractResultSet {
     }
 
 
+    @Override
+    public double getDouble(String columnLabel) throws SQLException {
+        return getDouble(asColNum(columnLabel));
+    }
+
+    @Override
+    public float getFloat(String columnLabel) throws SQLException {
+        return getFloat(asColNum(columnLabel));
+    }
+
+    @Override
+    public Date getDate(String columnLabel) throws SQLException {
+        return getDate(asColNum(columnLabel));
+    }
+
+    @Override
+    public Time getTime(String columnLabel) throws SQLException {
+        return getTime(asColNum(columnLabel));
+    }
+
+    @Override
+    public Object getObject(String columnLabel) throws SQLException {
+        return getObject(asColNum(columnLabel));
+    }
+
     /////////////////////////////////////////////////////////
 
     @Override
     public String getString(int colNum) {
-        return toString(values[colNum - 1]);
+        return toString(getValue(colNum));
     }
 
+
     @Override
     public int getInt(int colNum) {
-        return ByteFragmentUtils.parseInt(values[colNum - 1]);
+        return ByteFragmentUtils.parseInt(getValue(colNum));
     }
 
     @Override
     public boolean getBoolean(int colNum) {
-        return toBoolean(values[colNum - 1]);
+        return toBoolean(getValue(colNum));
     }
 
     @Override
     public long getLong(int colNum) {
-        return ByteFragmentUtils.parseLong(values[colNum - 1]);
+        return ByteFragmentUtils.parseLong(getValue(colNum));
     }
 
     @Override
     public byte[] getBytes(int colNum) {
-        return toBytes(values[colNum - 1]);
+        return toBytes(getValue(colNum));
     }
 
     public long getTimestampAsLong(int colNum) {
-        return toTimestamp(values[colNum - 1]);
+        return toTimestamp(getValue(colNum));
     }
 
     @Override
     public Timestamp getTimestamp(int columnIndex) throws SQLException {
-        if (values[columnIndex - 1].isNull()) return null;
+        if (getValue(columnIndex).isNull()) return null;
         return new Timestamp(getTimestampAsLong(columnIndex));
     }
 
     @Override
     public short getShort(int colNum) {
-        return toShort(values[colNum - 1]);
+        return toShort(getValue(colNum));
     }
 
     @Override
     public byte getByte(int colNum) {
-        return toByte(values[colNum - 1]);
+        return toByte(getValue(colNum));
     }
 
     public long[] getLongArray(int colNum) {
-        return toLongArray(values[colNum - 1]);
+        return toLongArray(getValue(colNum));
+    }
+
+    @Override
+    public float getFloat(int columnIndex) throws SQLException {
+        return (float) getDouble(columnIndex);
+    }
+
+    @Override
+    public double getDouble(int columnIndex) throws SQLException {
+        String string = getString(columnIndex);
+        if (string == null) return 0;
+        return Double.parseDouble(string);
+    }
+
+    @Override
+    public Date getDate(int columnIndex) throws SQLException {
+        // дата из кликхауса приходит в виде строки
+        ByteFragment value = getValue(columnIndex);
+        if (value.isNull()) return null;
+        try {
+            return new Date(dateFormat.parse(value.asString()).getTime());
+        } catch (ParseException e) {
+            return null;
+        }
     }
 
+    @Override
+    public Time getTime(int columnIndex) throws SQLException {
+        return new Time(getTimestamp(columnIndex).getTime());
+    }
+
+    @Override
+    public Object getObject(int columnIndex) throws SQLException {
+        int type = toSqlType(types[columnIndex - 1]);
+        switch (type) {
+            case Types.BIGINT:      return getLong(columnIndex);
+            case Types.INTEGER:     return getInt(columnIndex);
+            case Types.VARCHAR:     return getString(columnIndex);
+            case Types.FLOAT:       return getFloat(columnIndex);
+            case Types.DATE:        return getDate(columnIndex);
+            case Types.TIMESTAMP:   return getTime(columnIndex);
+            case Types.BLOB:        return getString(columnIndex);
+        }
+        return getString(columnIndex);
+    }
 
     /////////////////////////////////////////////////////////
 
@@ -282,6 +353,20 @@ public class CHResultSet extends AbstractResultSet {
         }
     }
 
+    //////
+
+    @Override
+    public int getType() throws SQLException {
+        return TYPE_FORWARD_ONLY;
+    }
+
+    @Override
+    public int getRow() throws SQLException {
+        return rowNumber + 1;
+    }
+
+    /////
+
     // 1-based insex in column list
     private int asColNum(String column) {
         if (col.containsKey(column)) {
@@ -291,7 +376,12 @@ public class CHResultSet extends AbstractResultSet {
         }
     }
 
-    int toSqlType(String type) {
+    private ByteFragment getValue(int colNum) {
+        lastReadColumn = colNum;
+        return values[colNum - 1];
+    }
+
+    public static int toSqlType(String type) {
 
         if (type.startsWith("Int") || type.startsWith("UInt")) {
             if (type.endsWith("64")) return Types.BIGINT;
@@ -308,4 +398,11 @@ public class CHResultSet extends AbstractResultSet {
 
     }
 
+    public static int[] supportedTypes() {
+        return new int[] {
+                Types.BIGINT, Types.INTEGER, Types.VARCHAR, Types.FLOAT,
+                Types.DATE, Types.TIMESTAMP, Types.BLOB
+        };
+    }
+
 }
-- 
GitLab