001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2013-2013 Pentaho 008// All Rights Reserved. 009*/ 010package mondrian.spi.impl; 011 012import java.sql.Connection; 013import java.sql.DatabaseMetaData; 014import java.sql.SQLException; 015import java.util.List; 016import java.util.regex.Matcher; 017import java.util.regex.Pattern; 018import java.util.regex.PatternSyntaxException; 019 020/** 021 * Dialect for Cloudera's Impala DB. 022 * 023 * @author cboyden 024 * @since 2/11/13 025 */ 026public class ImpalaDialect extends HiveDialect { 027 private final String flagsRegexp = "^(\\(\\?([a-zA-Z])\\)).*$"; 028 private final Pattern flagsPattern = Pattern.compile(flagsRegexp); 029 private final String escapeRegexp = "(\\\\Q([^\\\\Q]+)\\\\E)"; 030 private final Pattern escapePattern = Pattern.compile(escapeRegexp); 031 032 /** 033 * Creates an ImpalaDialect. 034 * 035 * @param connection Connection 036 * @throws java.sql.SQLException on error 037 */ 038 public ImpalaDialect(Connection connection) throws SQLException { 039 super(connection); 040 } 041 042 public static final JdbcDialectFactory FACTORY = 043 new JdbcDialectFactory( 044 ImpalaDialect.class, 045 DatabaseProduct.HIVE) 046 { 047 protected boolean acceptsConnection(Connection connection) { 048 return super.acceptsConnection(connection) 049 && isDatabase(DatabaseProduct.IMPALA, connection); 050 } 051 }; 052 053 protected String deduceIdentifierQuoteString( 054 DatabaseMetaData databaseMetaData) 055 { 056 return null; 057 } 058 059 @Override 060 public DatabaseProduct getDatabaseProduct() { 061 return DatabaseProduct.IMPALA; 062 } 063 064 @Override 065 protected String generateOrderByNulls( 066 String expr, 067 boolean ascending, 068 boolean collateNullsLast) 069 { 070 if (ascending) { 071 return expr + " ASC"; 072 } else { 073 return expr + " DESC"; 074 } 075 } 076 077 078 @Override 079 public String generateOrderItem( 080 String expr, 081 boolean nullable, 082 boolean ascending, 083 boolean collateNullsLast) 084 { 085 String ret = null; 086 087 if (nullable && collateNullsLast) { 088 ret = "CASE WHEN " + expr + " IS NULL THEN 1 ELSE 0 END, "; 089 } else { 090 ret = "CASE WHEN " + expr + " IS NULL THEN 0 ELSE 1 END, "; 091 } 092 093 if (ascending) { 094 ret += expr + " ASC"; 095 } else { 096 ret += expr + " DESC"; 097 } 098 099 return ret; 100 } 101 102 @Override 103 public boolean allowsMultipleCountDistinct() { 104 return false; 105 } 106 107 @Override 108 public boolean allowsCompoundCountDistinct() { 109 return true; 110 } 111 112 @Override 113 public boolean requiresOrderByAlias() { 114 return false; 115 } 116 117 @Override 118 public boolean requiresAliasForFromQuery() { 119 return true; 120 } 121 122 @Override 123 public boolean supportsGroupByExpressions() { 124 return false; 125 } 126 127 @Override 128 public boolean allowsSelectNotInGroupBy() { 129 return false; 130 } 131 132 @Override 133 public String generateInline( 134 List<String> columnNames, 135 List<String> columnTypes, 136 List<String[]> valueList) 137 { 138 // TODO: fix this, when Impala has the necessary features. See bug 139 // http://jira.pentaho.com/browse/MONDRIAN-1512. 140 return ""; 141 } 142 143 public boolean allowsJoinOn() { 144 return false; 145 } 146 147 @Override 148 public void quoteStringLiteral( 149 StringBuilder buf, 150 String value) 151 { 152 // REVIEW: Are Impala's rules for string literals so very different 153 // from the standard? Or from Hive's? 154 String quote = "\'"; 155 String s0 = value; 156 157 if (value.contains("'")) { 158 quote = "\""; 159 } 160 161 if (value.contains(quote)) { 162 s0 = value.replaceAll(quote, "\\\\" + quote); 163 } 164 165 buf.append(quote); 166 167 buf.append(s0); 168 169 buf.append(quote); 170 } 171 172 public boolean allowsRegularExpressionInWhereClause() { 173 return true; 174 } 175 176 public String generateRegularExpression( 177 String source, 178 String javaRegex) 179 { 180 try { 181 Pattern.compile(javaRegex); 182 } catch (PatternSyntaxException e) { 183 // Not a valid Java regex. Too risky to continue. 184 return null; 185 } 186 187 // We might have to use case-insensitive matching 188 final Matcher flagsMatcher = flagsPattern.matcher(javaRegex); 189 boolean caseSensitive = true; 190 if (flagsMatcher.matches()) { 191 final String flags = flagsMatcher.group(2); 192 if (flags.contains("i")) { 193 caseSensitive = false; 194 } 195 } 196 if (flagsMatcher.matches()) { 197 javaRegex = 198 javaRegex.substring(0, flagsMatcher.start(1)) 199 + javaRegex.substring(flagsMatcher.end(1)); 200 } 201 final Matcher escapeMatcher = escapePattern.matcher(javaRegex); 202 while (escapeMatcher.find()) { 203 javaRegex = 204 javaRegex.replace( 205 escapeMatcher.group(1), 206 escapeMatcher.group(2)); 207 } 208 final StringBuilder sb = new StringBuilder(); 209 210 // Now build the string. 211 if (caseSensitive) { 212 sb.append(source); 213 } else { 214 sb.append("UPPER("); 215 sb.append(source); 216 sb.append(")"); 217 } 218 sb.append(" REGEXP "); 219 if (caseSensitive) { 220 quoteStringLiteral(sb, javaRegex); 221 } else { 222 quoteStringLiteral(sb, javaRegex.toUpperCase()); 223 } 224 return sb.toString(); 225 } 226} 227// End ImpalaDialect.java