From 33f651bb05449f36b2ca0181a388bb2eeb451cbc Mon Sep 17 00:00:00 2001
From: jkee <jkee@yandex-team.ru>
Date: Tue, 24 Mar 2015 14:24:33 +0300
Subject: [PATCH] METR-15511: catalogs, schemas

---
 .../metrika/clickhouse/CHConnection.java      |   9 +-
 .../clickhouse/CHDatabaseMetadata.java        | 110 +++++++++++-------
 .../yandex/metrika/clickhouse/CHDriver.java   |   4 +-
 .../clickhouse/copypaste/CHResultBuilder.java |  12 +-
 .../clickhouse/copypaste/CHResultSet.java     |   3 +-
 .../clickhouse/util/CopypasteUtils.java       |  34 ++++++
 6 files changed, 123 insertions(+), 49 deletions(-)

diff --git a/src/main/java/ru/yandex/metrika/clickhouse/CHConnection.java b/src/main/java/ru/yandex/metrika/clickhouse/CHConnection.java
index c9f986c0..9fdf2d6c 100644
--- a/src/main/java/ru/yandex/metrika/clickhouse/CHConnection.java
+++ b/src/main/java/ru/yandex/metrika/clickhouse/CHConnection.java
@@ -106,7 +106,7 @@ public class CHConnection implements Connection {
 
     @Override
     public DatabaseMetaData getMetaData() throws SQLException {
-        return new CHDatabaseMetadata(url, this);
+        return LogProxy.wrap(DatabaseMetaData.class, new CHDatabaseMetadata(url, this));
     }
 
     @Override
@@ -247,7 +247,12 @@ public class CHConnection implements Connection {
 
     @Override
     public boolean isValid(int timeout) throws SQLException {
-        return false;
+        // todo timeout
+        Statement statement = createStatement();
+        statement.execute("SELECT 1");
+        statement.close();
+        // no exception - fine
+        return true;
     }
 
     @Override
diff --git a/src/main/java/ru/yandex/metrika/clickhouse/CHDatabaseMetadata.java b/src/main/java/ru/yandex/metrika/clickhouse/CHDatabaseMetadata.java
index 3d609e70..b229e7e1 100644
--- a/src/main/java/ru/yandex/metrika/clickhouse/CHDatabaseMetadata.java
+++ b/src/main/java/ru/yandex/metrika/clickhouse/CHDatabaseMetadata.java
@@ -2,6 +2,7 @@ package ru.yandex.metrika.clickhouse;
 
 import ru.yandex.metrika.clickhouse.copypaste.CHResultBuilder;
 import ru.yandex.metrika.clickhouse.copypaste.CHResultSet;
+import ru.yandex.metrika.clickhouse.util.CopypasteUtils;
 import ru.yandex.metrika.clickhouse.util.Logger;
 
 import java.sql.*;
@@ -13,6 +14,8 @@ import java.util.List;
  */
 public class CHDatabaseMetadata implements DatabaseMetaData {
 
+    private static final String DEFAULT_CAT = "default";
+
     private static final Logger log = Logger.of(CHDatabaseMetadata.class);
 
     private String url;
@@ -325,17 +328,17 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public String getSchemaTerm() throws SQLException {
-        return "schema";
+        return "database";
     }
 
     @Override
     public String getProcedureTerm() throws SQLException {
-        return "some bullshit";
+        return "procedure";
     }
 
     @Override
     public String getCatalogTerm() throws SQLException {
-        return "database";
+        return "catalog";
     }
 
     @Override
@@ -350,32 +353,32 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public boolean supportsSchemasInDataManipulation() throws SQLException {
-        return false;
+        return true;
     }
 
     @Override
     public boolean supportsSchemasInProcedureCalls() throws SQLException {
-        return false;
+        return true;
     }
 
     @Override
     public boolean supportsSchemasInTableDefinitions() throws SQLException {
-        return false;
+        return true;
     }
 
     @Override
     public boolean supportsSchemasInIndexDefinitions() throws SQLException {
-        return false;
+        return true;
     }
 
     @Override
     public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException {
-        return false;
+        return true;
     }
 
     @Override
     public boolean supportsCatalogsInDataManipulation() throws SQLException {
-        return true;
+        return false;
     }
 
     @Override
@@ -385,12 +388,12 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public boolean supportsCatalogsInTableDefinitions() throws SQLException {
-        return true;
+        return false;
     }
 
     @Override
     public boolean supportsCatalogsInIndexDefinitions() throws SQLException {
-        return true;
+        return false;
     }
 
     @Override
@@ -672,14 +675,18 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
          SELF_REFERENCING_COL_NAME String => name of the designated "identifier" column of a typed table (may be null)
          REF_GENERATION String => specifies how values in SELF_REFERENCING_COL_NAME are created. Values are "SYSTEM", "USER", "DERIVED". (may be null)
          */
-        if (catalog == null || catalog.isEmpty()) {
-            catalog = "default";
-        }
         String sql = "select " +
-                "database, name, name " +
-                "from system.tables " +
-                "where database = '" + catalog + "' " +
-                "order by name";
+                "database, name " +
+                "from system.tables";
+        if (schemaPattern != null) {
+            sql += " where database like '" + schemaPattern + "'";
+        }
+        if (tableNamePattern != null) {
+            if (schemaPattern != null) sql += " and";
+            else sql += " where";
+            sql += " name like '" + tableNamePattern + "'";
+        }
+        sql += " order by database, name";
         ResultSet result = request(sql);
 
         CHResultBuilder builder = CHResultBuilder.builder(10);
@@ -688,15 +695,16 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
         while(result.next()) {
             List<String> row = new ArrayList<String>();
+            row.add(DEFAULT_CAT);
             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++) {
+            for (int i = 3; i < 9; i++) {
                 row.add(null);
             }
             builder.addRow(row);
         }
+        result.close();
         return builder.build();
     }
 
@@ -707,19 +715,27 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @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 + '\'';
+        // это запрос к system.tables, который теоретически не нужен. Однако, system.databases отсутствует,
+        // а по show databases нельзя сделать LIKE.
+        String sql = "select distinct database as TABLE_SCHEM, '" +
+                DEFAULT_CAT + "' as TABLE_CATALOG from system.tables";
+        if (catalog != null) sql += " where TABLE_CATALOG = '" + catalog + '\'';
         if (schemaPattern != null) {
             if (catalog != null) sql += " and ";
             else sql += " where ";
-            sql += "name = '" + schemaPattern + '\'';
+            sql += "name LIKE '" + schemaPattern + '\'';
         }
         return request(sql);
     }
 
     @Override
     public ResultSet getCatalogs() throws SQLException {
-        return request("show databases");
+        CHResultBuilder builder = CHResultBuilder.builder(1);
+        builder.names("TABLE_CAT");
+        builder.types("String");
+
+        builder.addRow(DEFAULT_CAT);
+        return builder.build();
     }
 
     @Override
@@ -783,9 +799,10 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
                 "Int32",
                 "String"
         );
+        // todo это всё брехня, ждем https://st.yandex-team.ru/METR-15619
         String sql = "desc table ";
-        if (catalog != null) sql += catalog + '.';
-        sql += tableNamePattern;
+        if (schemaPattern != null) sql += CopypasteUtils.unEscapeString(schemaPattern) + '.';
+        sql += CopypasteUtils.unEscapeString(tableNamePattern);
         ResultSet descTable = request(sql);
         int colNum = 1;
         while (descTable.next()) {
@@ -795,8 +812,8 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
                 continue;
             }
             List<String> row = new ArrayList<String>();
-            row.add(catalog);
-            row.add(tableNamePattern);
+            row.add(DEFAULT_CAT);
+            row.add(schemaPattern);
             row.add(tableNamePattern);
             row.add(descTable.getString(1));
             String type = descTable.getString(2);
@@ -842,48 +859,53 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
             builder.addRow(row);
 
         }
+        descTable.close();
 
         return builder.build();
     }
 
+    private ResultSet getEmptyResultSet() {
+        return CHResultBuilder.builder(1).names("bullshit").types("String").build();
+    }
+
     @Override
     public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
@@ -1010,7 +1032,7 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
@@ -1079,7 +1101,7 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
@@ -1109,17 +1131,17 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
@@ -1184,17 +1206,17 @@ public class CHDatabaseMetadata implements DatabaseMetaData {
 
     @Override
     public ResultSet getClientInfoProperties() throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
     public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) throws SQLException {
-        return null;
+        return getEmptyResultSet();
     }
 
     @Override
diff --git a/src/main/java/ru/yandex/metrika/clickhouse/CHDriver.java b/src/main/java/ru/yandex/metrika/clickhouse/CHDriver.java
index abc2b374..f82ecc94 100644
--- a/src/main/java/ru/yandex/metrika/clickhouse/CHDriver.java
+++ b/src/main/java/ru/yandex/metrika/clickhouse/CHDriver.java
@@ -1,5 +1,6 @@
 package ru.yandex.metrika.clickhouse;
 
+import ru.yandex.metrika.clickhouse.util.LogProxy;
 import ru.yandex.metrika.clickhouse.util.Logger;
 
 import java.sql.*;
@@ -33,7 +34,8 @@ public class CHDriver implements Driver {
 
     @Override
     public Connection connect(String url, Properties info) throws SQLException {
-        return new CHConnection(url);
+        logger.info("Creating connection");
+        return LogProxy.wrap(Connection.class, new CHConnection(url));
     }
 
     @Override
diff --git a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultBuilder.java b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultBuilder.java
index 5f340e79..565ff48d 100644
--- a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultBuilder.java
+++ b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultBuilder.java
@@ -89,7 +89,17 @@ public class CHResultBuilder {
             baos.write('\\');
             baos.write('N');
         } else {
-            ByteFragment.escape(o.toString().getBytes(), baos);
+            String value;
+            if (o instanceof Boolean) {
+                if ((Boolean) o) {
+                    value = "1";
+                } else {
+                    value = "0";
+                }
+            } else {
+                value = o.toString();
+            }
+            ByteFragment.escape(value.getBytes(), baos);
         }
     }
 
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 ea0c0ff1..613288d9 100644
--- a/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultSet.java
+++ b/src/main/java/ru/yandex/metrika/clickhouse/copypaste/CHResultSet.java
@@ -90,12 +90,13 @@ public class CHResultSet extends AbstractResultSet {
                 nextLine = bis.next();
                 if (nextLine == null || nextLine.length() == 0 || (maxRows != 0 && rowNumber >= maxRows)) {
                     bis.close();
+                    nextLine = null;
                 }
             } catch (IOException e) {
                 throw new SQLException(e);
             }
         }
-        return nextLine != null && nextLine.length() > 0;
+        return nextLine != null;
     }
     @Override
     public boolean next() throws SQLException {
diff --git a/src/main/java/ru/yandex/metrika/clickhouse/util/CopypasteUtils.java b/src/main/java/ru/yandex/metrika/clickhouse/util/CopypasteUtils.java
index 77a5ae71..69d1753c 100644
--- a/src/main/java/ru/yandex/metrika/clickhouse/util/CopypasteUtils.java
+++ b/src/main/java/ru/yandex/metrika/clickhouse/util/CopypasteUtils.java
@@ -75,6 +75,40 @@ public class CopypasteUtils {
         return list.toArray(new String[list.size()]);
     }
 
+    /**
+     * раскукоживатель
+     */
+    public static String unEscapeString(String string) {
+        if (isBlank(string)) return string;
+
+        char current = 0;
+        int length = string.length();
+        StringBuilder sb = new StringBuilder(length + 4);
+        for (int i = 0; i < length; i += 1) {
+            current = string.charAt(i);
+            if (current == '\\') {
+                if (i + 1 >= length) {
+                    return sb.toString();
+                }
+                if (string.charAt(i + 1) == 'u') {
+                    if (i + 5 >= length) {
+                        return sb.toString();
+                    }
+                    sb.append((char) Integer.parseInt(string.substring(i + 2, i + 6), 16));
+                    //noinspection AssignmentToForLoopParameter
+                    i += 4;
+                } else {
+                    sb.append(string.charAt(i + 1));
+                }
+                //noinspection AssignmentToForLoopParameter
+                i++;
+            } else {
+                sb.append(current);
+            }
+        }
+
+        return sb.toString();
+    }
 
     /////// Apache StringUtils ////////
 
-- 
GitLab