Source code: org/objectstyle/cayenne/access/trans/QueryAssemblerHelper.java
1 /* ====================================================================
2 *
3 * The ObjectStyle Group Software License, Version 1.0
4 *
5 * Copyright (c) 2002-2003 The ObjectStyle Group
6 * and individual authors of the software. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution, if
21 * any, must include the following acknowlegement:
22 * "This product includes software developed by the
23 * ObjectStyle Group (http://objectstyle.org/)."
24 * Alternately, this acknowlegement may appear in the software itself,
25 * if and wherever such third-party acknowlegements normally appear.
26 *
27 * 4. The names "ObjectStyle Group" and "Cayenne"
28 * must not be used to endorse or promote products derived
29 * from this software without prior written permission. For written
30 * permission, please contact andrus@objectstyle.org.
31 *
32 * 5. Products derived from this software may not be called "ObjectStyle"
33 * nor may "ObjectStyle" appear in their names without prior written
34 * permission of the ObjectStyle Group.
35 *
36 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
40 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * SUCH DAMAGE.
48 * ====================================================================
49 *
50 * This software consists of voluntary contributions made by many
51 * individuals on behalf of the ObjectStyle Group. For more
52 * information on the ObjectStyle Group, please see
53 * <http://objectstyle.org/>.
54 *
55 */
56 package org.objectstyle.cayenne.access.trans;
57
58 import java.util.Iterator;
59 import java.util.List;
60 import java.util.Map;
61
62 import org.apache.log4j.Logger;
63 import org.objectstyle.cayenne.CayenneRuntimeException;
64 import org.objectstyle.cayenne.DataObject;
65 import org.objectstyle.cayenne.ObjectId;
66 import org.objectstyle.cayenne.exp.Expression;
67 import org.objectstyle.cayenne.map.DbAttribute;
68 import org.objectstyle.cayenne.map.DbAttributePair;
69 import org.objectstyle.cayenne.map.DbEntity;
70 import org.objectstyle.cayenne.map.DbRelationship;
71 import org.objectstyle.cayenne.map.ObjAttribute;
72 import org.objectstyle.cayenne.map.ObjEntity;
73 import org.objectstyle.cayenne.map.ObjRelationship;
74
75 /**
76 * Translates parts of the query to SQL.
77 * Always works in the context of parent Translator.
78 *
79 * @author Andrei Adamchik
80 */
81 public abstract class QueryAssemblerHelper {
82 private static Logger logObj = Logger.getLogger(QueryAssemblerHelper.class);
83
84 protected QueryAssembler queryAssembler;
85
86 public QueryAssemblerHelper() {
87 }
88
89 /** Creates QueryAssemblerHelper. Sets queryAssembler property. */
90 public QueryAssemblerHelper(QueryAssembler queryAssembler) {
91 this.queryAssembler = queryAssembler;
92 }
93
94 /** Returns parent QueryAssembler that uses this helper. */
95 public QueryAssembler getQueryAssembler() {
96 return queryAssembler;
97 }
98
99 public void setQueryAssembler(QueryAssembler queryAssembler) {
100 this.queryAssembler = queryAssembler;
101 }
102
103 /** Translates the part of parent translator's query that is supported
104 * by this PartTranslator. For example, QualifierTranslator will process
105 * qualifier expression, OrderingTranslator - ordering of the query.
106 * In the process of translation parent translator is notified of any
107 * join tables added (so that it can update its "FROM" clause).
108 * Also parent translator is consulted about table aliases to use
109 * when translating columns. */
110 public abstract String doTranslation();
111
112 public ObjEntity getObjEntity() {
113 return getQueryAssembler().getRootEntity();
114 }
115
116 public DbEntity getDbEntity() {
117 return getQueryAssembler().getRootDbEntity();
118 }
119
120 /** Processes parts of the OBJ_PATH expression. */
121 protected void appendObjPath(StringBuffer buf, Expression pathExp) {
122
123 Iterator it = getObjEntity().resolvePathComponents(pathExp);
124 ObjRelationship lastRelationship = null;
125
126 while (it.hasNext()) {
127 Object pathComp = it.next();
128
129 if (pathComp instanceof ObjRelationship) {
130 ObjRelationship rel = (ObjRelationship) pathComp;
131
132 // if this is a last relationship in the path,
133 // it needs special handling
134 if (!it.hasNext()) {
135 processRelTermination(buf, rel);
136 } else {
137 // find and add joins ....
138 Iterator relit = rel.getDbRelationships().iterator();
139 while (relit.hasNext()) {
140 queryAssembler.dbRelationshipAdded(
141 (DbRelationship) relit.next());
142 }
143 }
144 lastRelationship = rel;
145 } else {
146 ObjAttribute objAttr = (ObjAttribute) pathComp;
147 if (lastRelationship != null) {
148 List lastDbRelList = lastRelationship.getDbRelationships();
149 DbRelationship lastDbRel =
150 (DbRelationship) lastDbRelList.get(
151 lastDbRelList.size() - 1);
152 processColumn(buf, objAttr.getDbAttribute(), lastDbRel);
153 } else {
154 processColumn(buf, objAttr.getDbAttribute());
155 }
156 }
157 }
158 }
159
160 protected void appendDbPath(StringBuffer buf, Expression pathExp) {
161 Iterator it = getDbEntity().resolvePathComponents(pathExp);
162
163 while (it.hasNext()) {
164 Object pathComp = it.next();
165 if (pathComp instanceof DbRelationship) {
166 DbRelationship rel = (DbRelationship) pathComp;
167
168 // if this is a last relationship in the path,
169 // it needs special handling
170 if (!it.hasNext()) {
171 processRelTermination(buf, rel);
172 } else {
173 // find and add joins ....
174 queryAssembler.dbRelationshipAdded(rel);
175 }
176 } else {
177 DbAttribute dbAttr = (DbAttribute) pathComp;
178 processColumn(buf, dbAttr);
179 }
180 }
181 }
182
183 /** Appends column name of a column in a root entity. */
184 protected void processColumn(StringBuffer buf, Expression nameExp) {
185 if (queryAssembler.supportsTableAliases()) {
186 String alias = queryAssembler.aliasForTable(getDbEntity());
187 buf.append(alias).append('.');
188 }
189
190 buf.append(nameExp.getOperand(0));
191 }
192
193 protected void processColumn(
194 StringBuffer buf,
195 DbAttribute dbAttr,
196 DbRelationship rel) {
197 String alias =
198 (queryAssembler.supportsTableAliases())
199 ? queryAssembler.aliasForTable(
200 (DbEntity) dbAttr.getEntity(),
201 rel)
202 : null;
203
204 buf.append(dbAttr.getAliasedName(alias));
205 }
206
207 protected void processColumn(StringBuffer buf, DbAttribute dbAttr) {
208 String alias =
209 (queryAssembler.supportsTableAliases())
210 ? queryAssembler.aliasForTable((DbEntity) dbAttr.getEntity())
211 : null;
212
213 buf.append(dbAttr.getAliasedName(alias));
214 }
215
216
217 /**
218 * Appends SQL code to the query buffer to handle <code>val</code> as a
219 * parameter to the PreparedStatement being built. Adds <code>val</code>
220 * into QueryAssembler parameter list.
221 *
222 * <p>If <code>val</code> is null, "NULL" is appended to the query. </p>
223 *
224 * <p>If <code>val</code> is a DataObject, its primary key value is
225 * used as a parameter. <i>Only objects with a single column primary key
226 * can be used.</i>
227 *
228 * @param buf query buffer.
229 *
230 * @param val object that should be appended as a literal to the query.
231 * Must be of one of "standard JDBC" types, null or a DataObject.
232 *
233 * @param attr DbAttribute that has information on what type of parameter
234 * is being appended.
235 *
236 */
237 protected void appendLiteral(
238 StringBuffer buf,
239 Object val,
240 DbAttribute attr,
241 Expression parentExpression) {
242 if (val == null) {
243 buf.append("NULL");
244 } else if (val instanceof DataObject) {
245 ObjectId id = ((DataObject) val).getObjectId();
246
247 // check if this id is acceptable to be a parameter
248 if (id == null) {
249 throw new CayenneRuntimeException("Can't use TRANSIENT object as a query parameter.");
250 }
251
252 if (id.isTemporary()) {
253 throw new CayenneRuntimeException("Can't use NEW object as a query parameter.");
254 }
255
256 Map snap = id.getIdSnapshot();
257 if (snap.size() != 1) {
258 StringBuffer msg = new StringBuffer();
259 msg
260 .append("Object must have a single primary key column ")
261 .append("to serve as a query parameter. ")
262 .append("This object has ")
263 .append(snap.size())
264 .append(": ")
265 .append(snap);
266
267 throw new CayenneRuntimeException(msg.toString());
268 }
269
270 // checks have been passed, use id value
271 appendLiteralDirect(
272 buf,
273 snap.get(snap.keySet().iterator().next()),
274 attr,
275 parentExpression);
276 } else {
277 appendLiteralDirect(buf, val, attr, parentExpression);
278 }
279 }
280
281 /**
282 * Appends SQL code to the query buffer to handle <code>val</code> as a
283 * parameter to the PreparedStatement being built. Adds <code>val</code>
284 * into QueryAssembler parameter list.
285 *
286 *
287 * @param buf query buffer
288 * @param val object that should be appended as a literal to the query.
289 * Must be of one of "standard JDBC" types. Can not be null.
290 */
291 protected void appendLiteralDirect(
292 StringBuffer buf,
293 Object val,
294 DbAttribute attr,
295 Expression parentExpression) {
296 buf.append('?');
297
298 // we are hoping that when processing parameter list,
299 // the correct type will be
300 // guessed without looking at DbAttribute...
301 queryAssembler.addToParamList(attr, val);
302 }
303
304 /**
305 * Returns database type of expression parameters or
306 * null if it can not be determined.
307 */
308 protected DbAttribute paramsDbType(Expression e) {
309 int len = e.getOperandCount();
310 // ignore unary expressions
311 if (len < 2) {
312 return null;
313 }
314
315 // naive algorithm:
316
317 // if at least one of the sibling operands is a
318 // OBJ_PATH expression, use its attribute type as
319 // a final answer.
320
321 for (int i = 0; i < len; i++) {
322 Object op = e.getOperand(i);
323 if (op instanceof Expression) {
324 Expression ope = (Expression) op;
325 if (ope.getType() == Expression.OBJ_PATH) {
326
327 Iterator it = getObjEntity().resolvePathComponents(ope);
328 while (it.hasNext()) {
329 Object pathComp = it.next();
330
331 if (pathComp instanceof ObjAttribute) {
332 return ((ObjAttribute) pathComp).getDbAttribute();
333 }
334 }
335 }
336 }
337 }
338
339 return null;
340 }
341
342 /** Processes case when an OBJ_PATH expression ends with relationship.
343 * If this is a "to many" relationship, a join is added and a column
344 * expression for the target entity primary key. If this is a "to one"
345 * relationship, column expresion for the source foreign key is added.
346 */
347 protected void processRelTermination(
348 StringBuffer buf,
349 ObjRelationship rel) {
350
351 Iterator dbRels = rel.getDbRelationships().iterator();
352
353 // scan DbRelationships
354 while (dbRels.hasNext()) {
355 DbRelationship dbRel = (DbRelationship) dbRels.next();
356
357 // if this is a last relationship in the path,
358 // it needs special handling
359 if (!dbRels.hasNext()) {
360 processRelTermination(buf, dbRel);
361 } else {
362 // find and add joins ....
363 queryAssembler.dbRelationshipAdded(dbRel);
364 }
365 }
366 }
367
368 /**
369 * Handles case when a DB_NAME expression ends with relationship.
370 * If this is a "to many" relationship, a join is added and a column
371 * expression for the target entity primary key. If this is a "to one"
372 * relationship, column expresion for the source foreign key is added.
373 */
374 protected void processRelTermination(
375 StringBuffer buf,
376 DbRelationship rel) {
377
378 if (rel.isToMany()) {
379 // append joins
380 queryAssembler.dbRelationshipAdded(rel);
381 }
382
383 // get last DbRelationship on the list
384 List joins = rel.getJoins();
385 if (joins.size() != 1) {
386 StringBuffer msg = new StringBuffer();
387 msg
388 .append("OBJ_PATH expressions are only supported ")
389 .append("for a single-join relationships. ")
390 .append("This relationship has ")
391 .append(joins.size())
392 .append(" joins.");
393
394 throw new CayenneRuntimeException(msg.toString());
395 }
396
397 DbAttributePair join = (DbAttributePair) joins.get(0);
398
399 DbAttribute att = null;
400
401 if (rel.isToMany()) {
402 DbEntity ent = (DbEntity) join.getTarget().getEntity();
403 List pk = ent.getPrimaryKey();
404 if (pk.size() != 1) {
405 StringBuffer msg = new StringBuffer();
406 msg
407 .append("DB_NAME expressions can only support ")
408 .append("targets with a single column PK. ")
409 .append("This entity has ")
410 .append(pk.size())
411 .append(" columns in primary key.");
412
413 throw new CayenneRuntimeException(msg.toString());
414 }
415
416 att = (DbAttribute) pk.get(0);
417 } else {
418 att = join.getSource();
419 }
420
421 processColumn(buf, att);
422 }
423 }