Source code: org/objectstyle/cayenne/dba/JdbcAdapter.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
57 package org.objectstyle.cayenne.dba;
58
59 import java.sql.Connection;
60 import java.sql.PreparedStatement;
61 import java.sql.SQLException;
62 import java.util.Iterator;
63
64 import org.objectstyle.cayenne.CayenneRuntimeException;
65 import org.objectstyle.cayenne.access.DataNode;
66 import org.objectstyle.cayenne.access.OperationObserver;
67 import org.objectstyle.cayenne.access.QueryTranslator;
68 import org.objectstyle.cayenne.access.trans.DeleteTranslator;
69 import org.objectstyle.cayenne.access.trans.FlattenedRelationshipDeleteTranslator;
70 import org.objectstyle.cayenne.access.trans.FlattenedRelationshipInsertTranslator;
71 import org.objectstyle.cayenne.access.trans.InsertTranslator;
72 import org.objectstyle.cayenne.access.trans.ProcedureTranslator;
73 import org.objectstyle.cayenne.access.trans.QualifierTranslator;
74 import org.objectstyle.cayenne.access.trans.QueryAssembler;
75 import org.objectstyle.cayenne.access.trans.SelectTranslator;
76 import org.objectstyle.cayenne.access.trans.SqlModifyTranslator;
77 import org.objectstyle.cayenne.access.trans.SqlSelectTranslator;
78 import org.objectstyle.cayenne.access.trans.UpdateTranslator;
79 import org.objectstyle.cayenne.access.types.ByteArrayType;
80 import org.objectstyle.cayenne.access.types.CharType;
81 import org.objectstyle.cayenne.access.types.ExtendedType;
82 import org.objectstyle.cayenne.access.types.ExtendedTypeMap;
83 import org.objectstyle.cayenne.access.types.UtilDateType;
84 import org.objectstyle.cayenne.map.DbAttribute;
85 import org.objectstyle.cayenne.map.DbAttributePair;
86 import org.objectstyle.cayenne.map.DbEntity;
87 import org.objectstyle.cayenne.map.DbRelationship;
88 import org.objectstyle.cayenne.map.DerivedDbEntity;
89 import org.objectstyle.cayenne.query.BatchQuery;
90 import org.objectstyle.cayenne.query.DeleteQuery;
91 import org.objectstyle.cayenne.query.FlattenedRelationshipDeleteQuery;
92 import org.objectstyle.cayenne.query.FlattenedRelationshipInsertQuery;
93 import org.objectstyle.cayenne.query.InsertQuery;
94 import org.objectstyle.cayenne.query.ProcedureQuery;
95 import org.objectstyle.cayenne.query.Query;
96 import org.objectstyle.cayenne.query.SelectQuery;
97 import org.objectstyle.cayenne.query.SqlModifyQuery;
98 import org.objectstyle.cayenne.query.SqlSelectQuery;
99 import org.objectstyle.cayenne.query.UpdateQuery;
100
101 /**
102 * A generic DbAdapter implementation.
103 * Can be used as a default adapter or as
104 * a superclass of a concrete adapter implementation.
105 *
106 * @author Andrei Adamchik
107 */
108 public class JdbcAdapter implements DbAdapter {
109 protected PkGenerator pkGenerator;
110 protected TypesHandler typesHandler;
111 protected ExtendedTypeMap extendedTypes;
112 protected boolean supportsBatchUpdates;
113
114 public JdbcAdapter() {
115 // create Pk generator
116 this.pkGenerator = this.createPkGenerator();
117 this.typesHandler = TypesHandler.getHandler(this.getClass());
118 this.extendedTypes = new ExtendedTypeMap();
119 this.configureExtendedTypes(extendedTypes);
120 this.setSupportsBatchUpdates(false);
121 }
122
123 /**
124 * Installs appropriate ExtendedTypes as converters for passing values
125 * between JDBC and Java layers. Called from default constructor.
126 */
127 protected void configureExtendedTypes(ExtendedTypeMap map) {
128 // Create a default CHAR handler with some generic settings.
129 // Subclasses may need to install their own CharType or reconfigure
130 // this one to work better with the target database.
131 map.registerType(new CharType(false, true));
132
133 // enable java.util.Dates as "persistent" values
134 map.registerType(new UtilDateType());
135
136 // enable "small" BLOBs
137 map.registerType(new ByteArrayType(false, true));
138 }
139
140 /**
141 * Creates and returns a primary key generator. This factory
142 * method should be overriden by JdbcAdapter subclasses to
143 * provide custom implementations of PKGenerator.
144 */
145 protected PkGenerator createPkGenerator() {
146 return new JdbcPkGenerator();
147 }
148
149 /** Returns primary key generator associated with this DbAdapter. */
150 public PkGenerator getPkGenerator() {
151 return pkGenerator;
152 }
153
154 public QueryTranslator getQueryTranslator(Query query) throws Exception {
155 Class queryClass = queryTranslatorClass(query);
156
157 try {
158 QueryTranslator t = (QueryTranslator) queryClass.newInstance();
159 t.setQuery(query);
160 t.setAdapter(this);
161 return t;
162 }
163 catch (Exception ex) {
164 throw new CayenneRuntimeException(
165 "Can't load translator class: " + queryClass);
166 }
167 }
168
169 /**
170 * Returns a class of the query translator that
171 * should be used to translate the query <code>q</code>
172 * to SQL. Exists mainly for the benefit of subclasses
173 * that can override this method providing their own translator.
174 */
175 protected Class queryTranslatorClass(Query q) {
176 if (q == null) {
177 throw new NullPointerException("Null query.");
178 }
179 else if (q instanceof SelectQuery) {
180 return SelectTranslator.class;
181 }
182 else if (q instanceof UpdateQuery) {
183 return UpdateTranslator.class;
184 }
185 else if (q instanceof FlattenedRelationshipInsertQuery) {
186 return FlattenedRelationshipInsertTranslator.class;
187 }
188 else if (q instanceof InsertQuery) {
189 return InsertTranslator.class;
190 }
191 else if (q instanceof FlattenedRelationshipDeleteQuery) {
192 return FlattenedRelationshipDeleteTranslator.class;
193 }
194 else if (q instanceof DeleteQuery) {
195 return DeleteTranslator.class;
196 }
197 else if (q instanceof SqlSelectQuery) {
198 return SqlSelectTranslator.class;
199 }
200 else if (q instanceof SqlModifyQuery) {
201 return SqlModifyTranslator.class;
202 }
203 else if (q instanceof ProcedureQuery) {
204 return ProcedureTranslator.class;
205 }
206 else {
207 throw new CayenneRuntimeException(
208 "Unrecognized query class..." + q.getClass().getName());
209 }
210 }
211
212 /** Returns true. */
213 public boolean supportsFkConstraints() {
214 return true;
215 }
216
217 /**
218 * Returns a SQL string to drop a table corresponding
219 * to <code>ent</code> DbEntity.
220 */
221 public String dropTable(DbEntity ent) {
222 return "DROP TABLE " + ent.getFullyQualifiedName();
223 }
224
225 /**
226 * Returns a SQL string that can be used to create database table
227 * corresponding to <code>ent</code> parameter.
228 */
229 public String createTable(DbEntity ent) {
230 // later we may support view creation
231 // for derived DbEntities
232 if (ent instanceof DerivedDbEntity) {
233 throw new CayenneRuntimeException(
234 "Can't create table for derived DbEntity '" + ent.getName() + "'.");
235 }
236
237 StringBuffer buf = new StringBuffer();
238 buf.append("CREATE TABLE ").append(ent.getFullyQualifiedName()).append(" (");
239
240 // columns
241 Iterator it = ent.getAttributes().iterator();
242 boolean first = true;
243 while (it.hasNext()) {
244 if (first) {
245 first = false;
246 }
247 else {
248 buf.append(", ");
249 }
250
251 DbAttribute at = (DbAttribute) it.next();
252
253 // attribute may not be fully valid, do a simple check
254 if (at.getType() == TypesMapping.NOT_DEFINED) {
255 throw new CayenneRuntimeException(
256 "Undefined type for attribute '"
257 + ent.getFullyQualifiedName()
258 + "."
259 + at.getName()
260 + "'.");
261 }
262
263 String[] types = externalTypesForJdbcType(at.getType());
264 if (types == null || types.length == 0) {
265 throw new CayenneRuntimeException(
266 "Undefined type for attribute '"
267 + ent.getFullyQualifiedName()
268 + "."
269 + at.getName()
270 + "': "
271 + at.getType());
272 }
273
274 String type = types[0];
275 buf.append(at.getName()).append(' ').append(type);
276
277 // append size and precision (if applicable)
278 if (TypesMapping.supportsLength(at.getType())) {
279 int len = at.getMaxLength();
280 int prec = TypesMapping.isDecimal(at.getType()) ? at.getPrecision() : -1;
281
282 // sanity check
283 if (prec > len) {
284 prec = -1;
285 }
286
287 if (len > 0) {
288 buf.append('(').append(len);
289
290 if (prec >= 0) {
291 buf.append(", ").append(prec);
292 }
293
294 buf.append(')');
295 }
296 }
297
298 if (at.isMandatory()) {
299 buf.append(" NOT NULL");
300 }
301 else {
302 buf.append(" NULL");
303 }
304 }
305
306 // primary key clause
307 Iterator pkit = ent.getPrimaryKey().iterator();
308 if (pkit.hasNext()) {
309 if (first)
310 first = false;
311 else
312 buf.append(", ");
313
314 buf.append("PRIMARY KEY (");
315 boolean firstPk = true;
316 while (pkit.hasNext()) {
317 if (firstPk)
318 firstPk = false;
319 else
320 buf.append(", ");
321
322 DbAttribute at = (DbAttribute) pkit.next();
323 buf.append(at.getName());
324 }
325 buf.append(')');
326 }
327 buf.append(')');
328 return buf.toString();
329 }
330
331 /**
332 * Returns a SQL string that can be used to create
333 * a foreign key constraint for the relationship.
334 */
335 public String createFkConstraint(DbRelationship rel) {
336 StringBuffer buf = new StringBuffer();
337 StringBuffer refBuf = new StringBuffer();
338
339 buf
340 .append("ALTER TABLE ")
341 .append(((DbEntity) rel.getSourceEntity()).getFullyQualifiedName())
342 .append(" ADD FOREIGN KEY (");
343
344 Iterator jit = rel.getJoins().iterator();
345 boolean first = true;
346 while (jit.hasNext()) {
347 DbAttributePair join = (DbAttributePair) jit.next();
348 if (!first) {
349 buf.append(", ");
350 refBuf.append(", ");
351 }
352 else
353 first = false;
354
355 buf.append(join.getSource().getName());
356 refBuf.append(join.getTarget().getName());
357 }
358
359 buf
360 .append(") REFERENCES ")
361 .append(((DbEntity) rel.getTargetEntity()).getFullyQualifiedName())
362 .append(" (")
363 .append(refBuf.toString())
364 .append(')');
365 return buf.toString();
366 }
367
368 public String[] externalTypesForJdbcType(int type) {
369 return typesHandler.externalTypesForJdbcType(type);
370 }
371
372 public ExtendedTypeMap getExtendedTypes() {
373 return extendedTypes;
374 }
375
376 public DbAttribute buildAttribute(
377 String name,
378 String typeName,
379 int type,
380 int size,
381 int precision,
382 boolean allowNulls) {
383
384 DbAttribute attr = new DbAttribute();
385 attr.setName(name);
386 attr.setType(type);
387 attr.setMandatory(!allowNulls);
388
389 if (size >= 0) {
390 attr.setMaxLength(size);
391 }
392
393 if (precision >= 0) {
394 attr.setPrecision(precision);
395 }
396
397 return attr;
398 }
399
400 public String tableTypeForTable() {
401 return "TABLE";
402 }
403
404 public String tableTypeForView() {
405 return "VIEW";
406 }
407
408 /**
409 * Creates and returns a default implementation of a qualifier translator.
410 */
411 public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
412 return new QualifierTranslator(queryAssembler);
413 }
414
415 /**
416 * Creates an instance of DataNode class.
417 */
418 public DataNode createDataNode(String name) {
419 DataNode node = new DataNode(name);
420 node.setAdapter(this);
421 return node;
422 }
423
424 public void bindParameter(
425 PreparedStatement statement,
426 Object object,
427 int pos,
428 int sqlType,
429 int precision)
430 throws SQLException, Exception {
431
432 if (object == null) {
433 statement.setNull(pos, sqlType);
434 }
435 else {
436 ExtendedType typeProcessor =
437 getExtendedTypes().getRegisteredType(object.getClass());
438 typeProcessor.setJdbcObject(statement, object, pos, sqlType, precision);
439 }
440 }
441
442 public boolean supportsBatchUpdates() {
443 return this.supportsBatchUpdates;
444 }
445
446 public void setSupportsBatchUpdates(boolean flag) {
447 this.supportsBatchUpdates = flag;
448 }
449
450 /**
451 * Always returns <code>true</code>, letting DataNode
452 * to handle the query.
453 */
454 public boolean shouldRunBatchQuery(
455 DataNode node,
456 Connection con,
457 BatchQuery query,
458 OperationObserver delegate)
459 throws SQLException, Exception {
460 return true;
461 }
462 }