/* * Copyright (c) 2009-2013, 2015, 2016 Eike Stepper (Berlin, Germany) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Kai Schlamp - initial API and implementation * Eike Stepper - maintenance * Kai Schlamp - Bug 284812: [DB] Query non CDO object fails * Erdal Karaca - added cdoObjectResultAsMap parameter to return Map<String,Object> in result */ package org.eclipse.emf.cdo.server.internal.db; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.common.util.CDOQueryInfo; import org.eclipse.emf.cdo.server.IQueryContext; import org.eclipse.emf.cdo.server.IQueryHandler; import org.eclipse.emf.cdo.server.db.IIDHandler; import org.eclipse.net4j.db.DBException; import org.eclipse.net4j.db.DBUtil; import org.eclipse.net4j.db.IDBPreparedStatement; import org.eclipse.net4j.db.IDBPreparedStatement.ReuseProbability; import java.sql.Clob; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Implements server side SQL query execution. * * @author Kai Schlamp */ public class SQLQueryHandler implements IQueryHandler { public static final String QUERY_LANGUAGE = "sql"; public static final String FIRST_RESULT = "firstResult"; public static final String CDO_OBJECT_QUERY = "cdoObjectQuery"; public static final String MAP_QUERY = "mapQuery"; public static final String QUERY_STATEMENT = "queryStatement"; private DBStoreAccessor accessor; public SQLQueryHandler(DBStoreAccessor storeAccessor) { accessor = storeAccessor; } public DBStoreAccessor getStoreAccessor() { return accessor; } /** * Executes SQL queries. Gets the connection from {@link DBStoreAccessor}, creates a SQL query and sets the parameters * taken from the {@link CDOQueryInfo#getParameters()}. * <p> * Takes into account the {@link CDOQueryInfo#getMaxResults()} and the {@link SQLQueryHandler#FIRST_RESULT} (numbered * from 0) values for paging. * <p> * By default (parameter {@link SQLQueryHandler#CDO_OBJECT_QUERY} == true) a query for CDO Objects is exectued. The * SQL query must return the CDO ID in the first column for this to work. If you set * {@link SQLQueryHandler#CDO_OBJECT_QUERY} parameter to false, the value of the first column of a row itself is * returned. * <p> * By default (parameter {@link SQLQueryHandler#QUERY_STATEMENT} == true) query statements are executed. Set this * parameter to false for update/DDL statements. * <p> * It is possible to use variables inside the SQL string with ":" as prefix. E.g. * "SELECT cdo_id FROM Company WHERE name LIKE :name". The value must then be set by using a parameter. E.g. * query.setParameter(":name", "Foo%"); * * @param info * the object containing the query and parameters * @param context * the query results are placed in the context * @see IQueryHandler#executeQuery(CDOQueryInfo, IQueryContext) */ public void executeQuery(CDOQueryInfo info, IQueryContext context) { String language = info.getQueryLanguage(); if (!QUERY_LANGUAGE.equals(language)) { throw new IllegalArgumentException("Unsupported query language: " + language); } HashMap<String, List<Integer>> paramMap = new HashMap<String, List<Integer>>(); String query = parse(info.getQueryString(), paramMap); int firstResult = -1; boolean queryStatement = true; boolean objectQuery = true; boolean mapQuery = false; IIDHandler idHandler = accessor.getStore().getIDHandler(); IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY, ReuseProbability.MEDIUM); ResultSet resultSet = null; try { for (String key : info.getParameters().keySet()) { if (FIRST_RESULT.equalsIgnoreCase(key)) { final Object o = info.getParameters().get(key); if (o != null) { try { firstResult = (Integer)o; } catch (ClassCastException ex) { throw new IllegalArgumentException("Parameter " + FIRST_RESULT + " must be an integer but it is a " + o + " class " + o.getClass().getName(), ex); } } } else if (QUERY_STATEMENT.equalsIgnoreCase(key)) { final Object o = info.getParameters().get(key); if (o != null) { try { queryStatement = (Boolean)o; } catch (ClassCastException ex) { throw new IllegalArgumentException("Parameter " + QUERY_STATEMENT + " must be an boolean but it is a " + o + " class " + o.getClass().getName(), ex); } } } else if (CDO_OBJECT_QUERY.equalsIgnoreCase(key)) { final Object o = info.getParameters().get(key); if (o != null) { try { objectQuery = (Boolean)o; } catch (ClassCastException ex) { throw new IllegalArgumentException("Parameter " + CDO_OBJECT_QUERY + " must be a boolean but it is a " + o + " class " + o.getClass().getName(), ex); } } } else if (MAP_QUERY.equalsIgnoreCase(key)) { final Object o = info.getParameters().get(key); if (o != null) { try { mapQuery = (Boolean)o; } catch (ClassCastException ex) { throw new IllegalArgumentException("Parameter " + MAP_QUERY + " must be a boolean but it is a " + o + " class " + o.getClass().getName(), ex); } } } else { if (!paramMap.containsKey(key) || paramMap.get(key) == null) { throw new IllegalArgumentException("No parameter value found for named parameter " + key); } Integer[] indexes = paramMap.get(key).toArray(new Integer[0]); for (int i = 0; i < indexes.length; i++) { Object parameter = info.getParameters().get(key); // parameter = convertToSQL(parameter); stmt.setObject(indexes[i], parameter); } } } if (queryStatement) { resultSet = stmt.executeQuery(); if (firstResult > -1) { resultSet.absolute(1 + firstResult); } String[] columnNames = null; if (mapQuery) { columnNames = new String[resultSet.getMetaData().getColumnCount()]; for (int i = 1; i <= columnNames.length; i++) { columnNames[i - 1] = resultSet.getMetaData().getColumnName(i); } } int maxResults = info.getMaxResults(); int counter = 0; while (firstResult > -1 || resultSet.next()) { if (maxResults != CDOQueryInfo.UNLIMITED_RESULTS && counter++ >= maxResults) { break; } if (objectQuery) { CDOID result = idHandler.getCDOID(resultSet, 1); context.addResult(result); } else { int columnCount = resultSet.getMetaData().getColumnCount(); if (columnCount == 1) { Object result = convertFromSQL(resultSet.getObject(1)); context.addResult(mapQuery ? toMap(columnNames, new Object[] { result }) : result); } else { Object[] results = new Object[columnCount]; for (int i = 0; i < columnCount; i++) { results[i] = convertFromSQL(resultSet.getObject(i + 1)); } context.addResult(mapQuery ? toMap(columnNames, results) : results); } } } } else { int result = stmt.executeUpdate(); context.addResult(result); } } catch (SQLException ex) { throw new DBException("Problem while executing SQL query: " + query, ex); } finally { DBUtil.close(resultSet); DBUtil.close(stmt); } } @SuppressWarnings("unused") private Object convertToSQL(Object value) { if (value instanceof java.util.Date) { java.util.Date date = (java.util.Date)value; value = new java.sql.Date(date.getTime()); } return value; } private Object convertFromSQL(Object value) { // Conversion of java.sql.Date not needed in this direction if (value instanceof Clob) { Clob clob = (Clob)value; try { value = clob.getSubString(1, (int)clob.length()); } catch (SQLException ex) { throw new DBException("Could not extract CLOB value", ex); } } return value; } private Map<String, Object> toMap(String[] columnNames, Object[] results) { Map<String, Object> ret = new HashMap<String, Object>(); for (int i = 0; i < columnNames.length; i++) { String columnName = columnNames[i]; ret.put(columnName, results[i]); } return ret; } private String parse(String query, Map<String, List<Integer>> paramMap) { int length = query.length(); StringBuilder builder = new StringBuilder(length); boolean inSingleQuote = false; boolean inDoubleQuote = false; int index = 1; for (int i = 0; i < length; i++) { char c = query.charAt(i); if (inSingleQuote) { if (c == '\'') { inSingleQuote = false; } } else if (inDoubleQuote) { if (c == '"') { inDoubleQuote = false; } } else { if (c == '\'') { inSingleQuote = true; } else if (c == '"') { inDoubleQuote = true; } else if (c == ':' && i + 1 < length && Character.isJavaIdentifierStart(query.charAt(i + 1))) { int j = i + 2; while (j < length && Character.isJavaIdentifierPart(query.charAt(j))) { j++; } String name = query.substring(i + 1, j); c = '?'; i += name.length(); List<Integer> indexList = paramMap.get(name); if (indexList == null) { indexList = new ArrayList<Integer>(); paramMap.put(name, indexList); } indexList.add(new Integer(index)); index++; } } builder.append(c); } return builder.toString(); } }