001/* 002 * (C) Copyright 2008-2010 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Florent Guillaume 016 */ 017 018package org.nuxeo.ecm.platform.tag; 019 020import java.util.List; 021 022import org.nuxeo.ecm.core.query.QueryFilter; 023import org.nuxeo.ecm.core.query.QueryParseException; 024import org.nuxeo.ecm.core.storage.sql.ColumnType; 025import org.nuxeo.ecm.core.storage.sql.Model; 026import org.nuxeo.ecm.core.storage.sql.Session.PathResolver; 027import org.nuxeo.ecm.core.storage.sql.jdbc.NXQLQueryMaker; 028import org.nuxeo.ecm.core.storage.sql.jdbc.SQLInfo; 029import org.nuxeo.ecm.core.storage.sql.jdbc.db.Column; 030import org.nuxeo.ecm.core.storage.sql.jdbc.db.Join; 031import org.nuxeo.ecm.core.storage.sql.jdbc.db.Select; 032import org.nuxeo.ecm.core.storage.sql.jdbc.db.Table; 033 034/** 035 * Query Maker specialized for tagging queries that need joins. 036 */ 037public class TagQueryMaker extends NXQLQueryMaker { 038 039 /** The NXTAG query type. */ 040 public static final String NXTAG = "NXTAG"; 041 042 public static final String SCHEMA_TAG = "tag"; 043 044 public static final String SCHEMA_RELATION = "relation"; 045 046 public static final String PROPERTY_SOURCE = "source"; 047 048 public static final String PROPERTY_TARGET = "target"; 049 050 /** 051 * Makes sure the Tag table is joined with the relation target instead of the hierarchy id. 052 */ 053 public static final String TAG_IS_TARGET = "TAGISTARGET: "; 054 055 /** 056 * Adds a COUNT() on the relation source, to count documents. 057 */ 058 public static final String COUNT_SOURCE = "COUNTSOURCE: "; 059 060 protected String type; 061 062 protected Table relationTable; 063 064 protected Column firstSelectedColumn; 065 066 @Override 067 public String getName() { 068 return NXTAG; 069 } 070 071 @Override 072 public boolean accepts(String queryType) { 073 return queryType.equals(NXTAG); 074 } 075 076 @Override 077 public Query buildQuery(SQLInfo sqlInfo, Model model, PathResolver pathResolver, String query, 078 QueryFilter queryFilter, Object... params) { 079 if (query.startsWith(TAG_IS_TARGET)) { 080 type = TAG_IS_TARGET; 081 } else if (query.startsWith(COUNT_SOURCE)) { 082 type = COUNT_SOURCE; 083 // SELECT "TAG"."LABEL" AS "_C1", 084 // COUNT(DISTINCT "RELATION"."SOURCE") AS "_C2" 085 // FROM "HIERARCHY" 086 // JOIN "RELATION" ON "HIERARCHY"."ID" = "RELATION"."ID" 087 // JOIN "DUBLINCORE" ON "HIERARCHY"."ID" = "DUBLINCORE"."ID" 088 // JOIN "TAG" ON "RELATION"."TARGET" = "TAG"."ID" 089 // WHERE "HIERARCHY"."PRIMARYTYPE" IN ('Tagging') 090 // AND ("RELATION"."SOURCE" = '47c4c0f7...') -- or IN () 091 // AND ("DUBLINCORE"."CREATOR" = 'Administrator') 092 // GROUP BY "_C1" 093 } else { 094 throw new QueryParseException("Bad query: " + query); 095 } 096 query = query.substring(type.length()); 097 return super.buildQuery(sqlInfo, model, pathResolver, query, queryFilter, params); 098 } 099 100 /** 101 * Adds an initial join on the Relation table, and records it. 102 */ 103 @Override 104 protected void fixInitialJoins() { 105 relationTable = getFragmentTable(dataHierTable, SCHEMA_RELATION, SCHEMA_RELATION, -1, false); 106 } 107 108 /** 109 * Patches the Tag join to join on the relation target instead of the hierarchy id. 110 */ 111 @Override 112 protected void addJoin(int kind, String alias, Table table, String column, Table contextTable, 113 String contextColumn, String name, int index, String primaryType) { 114 if (table.getKey().equals(SCHEMA_TAG)) { 115 kind = Join.INNER; 116 contextTable = relationTable; 117 contextColumn = PROPERTY_TARGET; 118 } 119 super.addJoin(kind, alias, table, column, contextTable, contextColumn, name, index, null); 120 } 121 122 @Override 123 protected String getSelectColName(Column col) { 124 String name = super.getSelectColName(col); 125 if (firstSelectedColumn == null) { 126 firstSelectedColumn = col; 127 } 128 if (type == COUNT_SOURCE && col.getTable().getKey().equals(SCHEMA_RELATION) 129 && col.getKey().equals(PROPERTY_SOURCE)) { 130 name = String.format("COUNT(DISTINCT %s)", name); 131 } 132 return name; 133 } 134 135 @Override 136 protected void fixWhatColumns(List<Column> whatColumns) { 137 if (type == COUNT_SOURCE) { 138 // 2nd col is a COUNT -> different type 139 Column targetCol = whatColumns.remove(1); 140 Column countCol = new Column(targetCol.getTable(), null, ColumnType.INTEGER, null); 141 whatColumns.add(countCol); 142 } 143 } 144 145 @Override 146 protected void fixSelect(Select select) { 147 if (type == COUNT_SOURCE) { 148 // add a GROUP BY on first col 149 String name; 150 if (dialect.needsOriginalColumnInGroupBy()) { 151 name = firstSelectedColumn.getFullQuotedName(); 152 } else { 153 name = dialect.openQuote() + COL_ALIAS_PREFIX + "1" + dialect.closeQuote(); 154 } 155 select.setGroupBy(name); 156 } 157 } 158 159}