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