001: package simpleorm.core;
002:
003: import simpleorm.properties.*;
004: import java.sql.*;
005:
006: /*
007: * Copyright (c) 2002 Southern Cross Software Queensland (SCSQ). All rights
008: * reserved. See COPYRIGHT.txt included in this distribution.
009: */
010:
011: /** This is the generic database driver that contains minimal
012: database dependent code. Specific SimpleORM drivers extend this
013: class and specialize its methods as required. The driver type is
014: infered from the jdbc connection's meta data.<p>
015:
016: ## Profiling suggests that memoising these generaters could produce a
017: 5-10% improvement in bulk updates.<p>
018:
019: There is now one driver instance per connection so one can say
020: SConnection.getDriver().setMyFavouriteParam(...)<p>
021:
022: SQL 92 standard data types, I think:-
023: boolean, Character(n), character varying(n), date, float(p), real, double precission, smallint, int | integer,
024: decimal(p,s), numeric(p,s), time, interval, timestamp with timezone,
025: */
026: public class SDriver implements SConstants {
027:
028: /** Note that these are only prototypes.
029: * New SDriver instances are created for each connection.
030: * See static below.*/
031: static SArrayList drivers = new SArrayList();
032:
033: static {
034: SDriver[] ds = new SDriver[] { new SDriverHSQL(),
035: new SDriverPostgres(), new SDriverMySQL(),
036: new SDriverOracle(), new SDriverDaffodil(),
037: new SDriverMSSQL(), new SDriverDB2_400(),
038: new SDriverInformix(), new SDriverInterbase(),
039: new SDriverFirebird(), new SDriverSapDB(),
040: new SDriverSybase(), new SDriverDerby() };
041: for (int dx = 0; dx < ds.length; dx++)
042: ((SDriver) ds[dx]).registerDriver();
043: }
044:
045: /**
046: Add driver to the list of possible drivers that can be found by
047: SConnection.attach. The key is returned by driverName.
048: */
049: public void registerDriver() {
050: drivers.add(this );
051: }
052:
053: /** The driver name to be compared to getMetaData().getDriverName() */
054: protected String driverName() {
055: return "Generic Driver";
056: }
057:
058: /** The maximum size for table names and foreign key constraint names. */
059: public int maxIdentNameLength() {
060: return 30;
061: }
062:
063: /** Wraps identifiers in "s to avoid reserved word issues.
064: * (Override this for dbs that use different chars, eg [xxx] for MS-SQL.
065: * We do quote these days to avoid the endless problems with reserved words.
066: */
067: public String quoteIdentifier(String ident) {
068: if (ident.length() > maxIdentNameLength())
069: throw new SException.Error("Identifier '" + ident
070: + "' is longer than " + maxIdentNameLength()
071: + " chars as permitted by " + driverName());
072: return "\"" + ident + "\"";
073: }
074:
075: public String quoteColumn(String ident) {
076: return quoteIdentifier(ident);
077: }
078:
079: public String quoteTable(String ident) {
080: return quoteIdentifier(ident);
081: }
082:
083: public String quoteConstraint(String ident) {
084: return quoteIdentifier(ident);
085: }
086:
087: /** True if exclusive locks are properly supported. Otherwise
088: optimistic locks are always used. <p>
089: */
090: public boolean supportsLocking() {
091: return true;
092: }
093:
094: /** Chooses default driver based on the connection's meta data.<p>
095:
096: Note that if you have trouble with the driver defaulting mechanism, then
097: specify the driver explicitly as the third parameter to SConnection.attach.<p>
098:
099: Note also that a new driver instance is created each time.
100: */
101: static SDriver newSDriver(java.sql.Connection con, String driverName) {
102: String databaseName = null;
103: try {
104: driverName = driverName != null ? driverName : con
105: .getMetaData().getDriverName();
106: databaseName = con.getMetaData().getDatabaseProductName();
107: } catch (Exception ex) {
108: throw new SException.JDBC(ex);
109: }
110: for (int dx = 0; dx < drivers.size(); dx++) {
111: SDriver driver = (SDriver) drivers.get(dx);
112: if (driver.driverName().equals(driverName))
113: try {
114: return (SDriver) driver.getClass().newInstance();
115: } catch (Exception ex) {
116: throw new SException.Error(ex);
117: }
118: }
119:
120: SLog.slog.warn("Unknown Database '" + databaseName
121: + "' driver '" + driverName
122: + "'. Using generic SDriver.");
123: return new SDriver();
124: }
125:
126: /** These alow you to create a new SFieldMeta object at runtime
127: and then update the table to include it. Eg. for end user
128: customizations.
129:
130: ## Ideally this could be further automated so that the SRecordMeta
131: and JDBC meta data for a table could be compared and the table
132: automatically altered.
133: */
134: public String alterTableAddColumnSQL(SFieldMeta field) {
135: StringBuffer sql = new StringBuffer();
136:
137: sql.append("\nALTER TABLE ");
138: sql
139: .append(quoteTable(field.sRecordMeta
140: .getString(STABLE_NAME)));
141: sql.append(" ADD COLUMN ");
142: sql.append(quoteColumn(wholeColumnSQL(field)));
143: sql.append(clauseSeparator(" "));
144:
145: return sql.toString();
146: }
147:
148: public String alterTableDropColumnSQL(SFieldMeta field) {
149: StringBuffer sql = new StringBuffer();
150:
151: sql.append("\nALTER TABLE ");
152: sql
153: .append(quoteTable(field.sRecordMeta
154: .getString(STABLE_NAME)));
155: sql.append(" DROP COLUMN ");
156: sql.append(quoteColumn(field.getString(SCOLUMN_NAME)));
157: sql.append(clauseSeparator(" "));
158:
159: return sql.toString();
160: }
161:
162: /** Returns a <code>CREATE TABLE</code> for this table. Delegated
163: from SRecord. This is split up into many sub-methods so that
164: they can be selectively specialized by other drivers.*/
165: public String createTableSQL(SRecordMeta meta) {
166: StringBuffer sql = new StringBuffer(1000);
167: String tname = meta.getString(STABLE_NAME);
168: sql.append("\nCREATE TABLE " + quoteTable(tname) + "(");
169: for (int fx = 0; fx < meta.sFieldMetas.size(); fx++) {
170: //SLog.temp("SFld " + meta.fields.get(fx));
171: SFieldMeta fld = (SFieldMeta) meta.sFieldMetas.get(fx);
172: Object cq = fld.getProperty(SCOLUMN_QUERY);
173: if (!(fld instanceof SFieldReference) && cq == null) {
174: sql.append(clauseSeparator(" "));
175: sql.append(wholeColumnSQL(fld));
176: sql.append(", "); // Assume always a Primary Key clause
177: }
178: }
179: sql.append(clauseSeparator(" "));
180: sql.append(primaryKeySQL(meta));
181: sql.append(indexKeySQL(meta));
182: sql.append(foreignKeysSQL(meta));
183: sql.append(postTablePreParenSQL(meta));
184: String xTable = (String) meta.getProperty(SEXTRA_TABLE_DDL);
185: if (xTable != null)
186: sql.append(xTable);
187: sql.append(")");
188: sql.append(postTablePostParenSQL(meta));
189: sql.append(clauseSeparator(" "));
190: return sql.toString();
191: }
192:
193: /** Normally newline and indent to separate clauses of large SQL
194: statement */
195: protected String clauseSeparator(String indent) {
196: return "\n" + indent;
197: }
198:
199: /** Returns <code>MY_COL VARCHAR(13) NOT NULL</code>.
200: @see #SMANDATORY
201: */
202: protected String wholeColumnSQL(SFieldMeta fld) {
203: StringBuffer sql = new StringBuffer(60);
204: String columnName = fld.getString(SCOLUMN_NAME);
205: int len = columnName.length();
206: sql.append(quoteColumn(columnName)
207: + " ".substring(len > 10 ? 10 : len - 1)
208: + columnTypeSQL(fld));
209: addNull(sql, fld);
210: sql.append(postColumnSQL(fld));
211: String xCols = (String) fld.getProperty(SEXTRA_COLUMN_DDL);
212: if (xCols != null)
213: sql.append(xCols);
214: return sql.toString();
215: }
216:
217: protected void addNull(StringBuffer sql, SFieldMeta fld) {
218: if (fld.isPrimaryKey || fld.getBoolean(SMANDATORY))
219: sql.append(" NOT NULL");
220: else
221: sql.append(" NULL"); // ### NULL is redundant and troublesome, remove.
222: // ### Sybase anywhere and ALLOW_NULLS_BY_DEFAULT option. This
223: // is a hack, need to distinguish NULL from empty. See SMANDATORY.
224: }
225:
226: protected String columnTypeSQL(SFieldMeta field) {
227: String res = (String) field.getProperty(SDATA_TYPE);
228: if (res.equals("VARCHAR") || res.equals("CHAR"))
229: res = res + "(" + field.getProperty(SBYTE_SIZE) + ")";
230: return res;
231: }
232:
233: /** After NOT NULL but before the ",", ie column specific annotations. */
234: protected String postColumnSQL(SFieldMeta field) {
235: return "";
236: }
237:
238: /** Return <code>PRIMARY KEY(KCOL, KCOL)</code> appended to end. */
239: protected String primaryKeySQL(SRecordMeta meta) {
240: StringBuffer pkey = new StringBuffer("PRIMARY KEY (");
241: boolean firstpk = true;
242: for (int fx = 0; fx < meta.sFieldMetas.size(); fx++) {
243: SFieldMeta fld = (SFieldMeta) meta.sFieldMetas.get(fx);
244: if (!(fld instanceof SFieldReference)) {
245: if (fld.isPrimaryKey) {
246: if (!firstpk)
247: pkey.append(", ");
248: firstpk = false;
249: pkey
250: .append(quoteColumn(fld
251: .getString(SCOLUMN_NAME)));
252: }
253: }
254: }
255: pkey.append(")");
256: return pkey.toString();
257: }
258:
259: /** Needed for MySQL to create indexes on foreign keys */
260: protected String indexKeySQL(SRecordMeta meta) {
261: return "";
262: }
263:
264: /** Returns <code>FOREIGN KEY (FKCOL, FKCOL) REFERENCES FTABLE (KCOL,
265: KCOL)</code> appended to end. */
266: protected String foreignKeysSQL(SRecordMeta meta) {
267: return mapForeignKeys(meta, true);
268: }
269:
270: protected String mapForeignKeys(SRecordMeta meta, boolean foreignKey) {
271: StringBuffer fkey = new StringBuffer("");
272: for (int fx = 0; fx < meta.sFieldMetas.size(); fx++) {
273: SFieldMeta fld = (SFieldMeta) meta.sFieldMetas.get(fx);
274: //SLog.slog.debug("fld " + fld);
275: if (fld instanceof SFieldReference) {
276:
277: SFieldReference fldRef = (SFieldReference) fld;
278: //SLog.slog.debug(" fldRef " + fldRef);
279:
280: if (!fldRef.getBoolean(SNO_FOREIGN_KEY)
281: && (fld.sFieldReference == null || fld
282: .getBoolean(SINNER_FOREIGN_KEY))) {
283:
284: // Obtain foreign and referenced fields.
285: StringBuffer sbFkey = new StringBuffer(40);
286: StringBuffer sbRefed = new StringBuffer(40);
287:
288: // Recur through identifying foreign keys
289: aReferenceSQL(fldRef, fldRef, sbFkey, sbRefed);
290:
291: if (foreignKey)
292: makeForeignKeySQL(meta, fx, fldRef, sbFkey,
293: sbRefed, fkey);
294: else
295: makeForeignKeyIndexSQL(meta, fx, fldRef,
296: sbFkey, sbRefed, fkey);
297: }
298: }
299: }
300: return fkey.toString();
301: }
302:
303: private void makeForeignKeySQL(SRecordMeta meta, int fx,
304: SFieldReference fldRef, StringBuffer sbFkey,
305: StringBuffer sbRefed, StringBuffer fkey) {
306: /// The FOREIGN KEY clause and constraint name
307: fkey.append(",\n CONSTRAINT ");
308: String tname = meta.getString(STABLE_NAME);
309: String fxStr = fx + "";
310: if (tname.length() > maxIdentNameLength() - fxStr.length())
311: throw new SException.Error("Table name '" + tname
312: + "' is longer than " + maxIdentNameLength()
313: + " - " + (fxStr.length() + 1)
314: + " chars as permitted by " + driverName()
315: + " to allow for _nn constraint name.");
316:
317: String fkeyName = tname + "_" + fxStr; // Ensure unique
318: String fname = fldRef.getString(SFIELD_NAME);
319: int spare = maxIdentNameLength() - tname.length()
320: - (fxStr.length() + 1);
321: if (spare > 1)
322: fkeyName += "_"; // >1 so no trailing _
323: if (spare > fname.length())
324: fkeyName += fname;
325: else if (spare > 0)
326: fkeyName += fname.substring(0, spare - 1);
327: fkey.append(quoteConstraint(fkeyName));
328:
329: fkey.append("\n FOREIGN KEY (");
330:
331: fkey.append(sbFkey.toString()); // toString required for 1.3.1
332: fkey.append(")\n REFERENCES ");
333:
334: fkey.append(quoteTable(fldRef.referencedRecord
335: .getString(STABLE_NAME)));
336: fkey.append(" (");
337: fkey.append(sbRefed.toString());
338: fkey.append(")");
339:
340: String xddl = fldRef.getString(SEXTRA_FKEY_DDL);
341: if (xddl != null)
342: fkey.append(xddl);
343: }
344:
345: /*
346: * For MySQL. Index needs to be created as part of Create Index statement.
347: */
348: protected void makeForeignKeyIndexSQL(SRecordMeta meta, int fx,
349: SFieldReference fldRef, StringBuffer sbFkey,
350: StringBuffer sbRefed, StringBuffer fkey) {
351: }
352:
353: protected void aReferenceSQL(SFieldReference topRef,
354: SFieldReference fldRef, StringBuffer sbFkey,
355: StringBuffer sbRefed) {
356: for (int ff = 0; ff < fldRef.foreignKeyFields.length; ff++) {
357: SFieldMeta ref = fldRef.foreignKeyFields[ff];
358: //SLog.slog.debug(" top ref " + topRef + " fldRef " + fldRef + " ref " + ref);
359: if (!(ref instanceof SFieldReference)) {
360: if (sbFkey.length() > 0) {
361: sbFkey.append(", ");
362: sbRefed.append(", ");
363: }
364: sbFkey.append(quoteColumn(ref.getString(SCOLUMN_NAME)));
365:
366: SFieldMeta refed = findRecordRefedField(
367: topRef.referencedRecord, ref);
368: //SLog.slog.debug("Refed(" + fldRef + fldRef.referencedRecord + ref + "): " + refed);
369: sbRefed.append(quoteColumn(refed
370: .getString(SCOLUMN_NAME)));
371: } else {
372: aReferenceSQL(topRef, (SFieldReference) ref, sbFkey,
373: sbRefed);
374: }
375: }
376:
377: }
378:
379: private SFieldMeta findRecordRefedField(SRecordMeta rec,
380: SFieldMeta ref) {
381: for (;;) {
382: SFieldMeta ref2 = ref.referencedKeyField;
383: // ## May be a funny case with deep recursive keys.
384: if (ref2 == null)
385: throw new SException.InternalError("Record " + ref
386: + " has null referencedKeyField.");
387: ref = ref2;
388: if (ref.sRecordMeta == rec)
389: return ref;
390: }
391: }
392:
393: /** Any other text to be added before the final ")"*/
394: protected String postTablePreParenSQL(SRecordMeta meta) {
395: return "";
396: }
397:
398: /** Any other text to be added after the final ")". No ";". */
399: protected String postTablePostParenSQL(SRecordMeta meta) {
400: return "";
401: }
402:
403: /** Returns the SQL statement for a SELECT in a structured way.
404: Used by findOrInsert. <code>select</code> and
405: <code>where</code> are arrays of <code>SFieldMeta</code>s. Returns
406: SQL statement as a string.<p>
407:
408: This now quotes table and column names so that they become
409: case independent.<p>
410:
411: sps is links to the SPreparedStatement object. It can have arbitrary properties set to
412: provide fine control over the query. Examples include limits.
413: */
414: protected String selectSQL(SFieldMeta[] select, SRecordMeta from,
415: SFieldMeta[] where, String orderBy, boolean forUpdate,
416: boolean unrepeatableRead, SPreparedStatement sps) {
417:
418: StringBuffer ret = new StringBuffer(100);
419: for (int wx = 0; wx < where.length; wx++) {
420: if (wx > 0)
421: ret.append(" AND ");
422: SFieldMeta wfld = (SFieldMeta) where[wx];
423: ret.append(quoteColumn(wfld.getString(SCOLUMN_NAME))
424: + " = ? ");
425: }
426: return selectSQL(select, from, ret.toString(), orderBy,
427: forUpdate, unrepeatableRead, sps);
428: }
429:
430: protected String selectSQL(SFieldMeta[] select, SRecordMeta from,
431: String where, String orderBy, boolean forUpdate,
432: boolean unrepeatableRead, SPreparedStatement sps) {
433:
434: SRecordMeta[] joinTables = sps == null ? null : sps
435: .getJoinTables();
436: boolean distinct = sps == null ? false : sps.getDistinct();
437:
438: StringBuffer ret = new StringBuffer(200);
439: ret.append("SELECT ");
440:
441: // If joining, query may return multiple rows for same record. These need to
442: // be weeded out.
443: // ### AJB I do not see how this can happen. I think this is noise and should
444: // be removed.
445: if (distinct) {
446: ret.append("DISTINCT ");
447: }
448:
449: String tableName = from.getString(STABLE_NAME);
450: boolean doJoin = joinTables != null && joinTables.length > 0;
451:
452: for (int sx = 0; sx < select.length; sx++) {
453: //System.out.println("selectSQL " + from + select[sx]);
454: if (sx > 0)
455: ret.append(", ");
456: SFieldMeta sfld = (SFieldMeta) select[sx];
457: String colName = sfld.getString(SCOLUMN_QUERY);
458: if (colName == null) {
459: colName = sfld.getString(SCOLUMN_NAME);
460: if (colName == null)
461: throw new SException.InternalError(
462: "Null Column Name " + sfld);
463: colName = quoteColumn(colName);
464: }
465: if (doJoin) {
466: ret.append(quoteTable(tableName));
467: ret.append('.');
468: }
469:
470: ret.append(colName);
471: }
472:
473: ret.append(fromSQL(from, joinTables));
474: ret.append(postFromSQL(forUpdate, unrepeatableRead));
475:
476: if (where != null)
477: ret.append(" WHERE " + where);
478: if (orderBy != null)
479: ret.append(" ORDER BY " + orderBy);
480: ret.append(forUpdateSQL(forUpdate));
481: return ret.toString();
482: }
483:
484: /** Returns update clause, may not be valid in certain lock modes etc.
485: Right at the end of the query. <p>
486:
487: Oracle, Postgresql, and new in MS SQL 2005 support data versioning
488: or snapshots. This means that repeatable read is achieved by
489: caching the previous value read instead of using read locks. This
490: approach makes it critical to add FOR UPDATE where appropriate or
491: there is effectively no locking. <p>
492:
493: Indeed, in Oracle, you are guaranteed that several SELECTS will
494: return the same value, but a subsequent SELECT FOR UPDATE in the
495: same transaction may return a different value.<p>
496:
497: */
498: protected String forUpdateSQL(boolean forUpdate) {
499: if (supportsLocking() && forUpdate)
500: return " FOR UPDATE";
501: else
502: return "";
503: }
504:
505: /** Returns the FROM Table, Table... clause */
506: protected String fromSQL(SRecordMeta from, SRecordMeta[] joinTables) {
507: String tableName = from.getString(STABLE_NAME);
508: String res = " FROM " + quoteTable(tableName) + " ";
509: // Do not add + tableName.charAt(0) + " ";
510: // Breaks Oracle(!) -- which then insists that ONLY the alias be used.
511:
512: if (joinTables != null) {
513: for (int ii = 0; ii < joinTables.length; ii++)
514: res = res
515: + ", "
516: + quoteTable(joinTables[ii]
517: .getString(STABLE_NAME));
518: }
519: return res;
520: }
521:
522: /** For MSSQL. Just after all the tables in the From clause.*/
523: protected String postFromSQL(boolean forUpdate,
524: boolean unrepeatableRead) {
525: return "";
526: }
527:
528: /** Returns the SQL statement for an UPDATE in a structured way.
529: Used by flush(). <code>updates</code> and
530: <code>where</code> are SArrayLists of SFieldMetas. Returns SQL
531: statement as a string. */
532: protected String updateSQL(SArrayList updates, SRecordMeta from,
533: SArrayList where, SRecordInstance instance,
534: Object[] keyMetaValues) {
535: StringBuffer ret = new StringBuffer(200);
536: ret.append("UPDATE ");
537: ret.append(quoteTable(from.getString(STABLE_NAME)) + " SET ");
538: for (int sx = 0; sx < updates.size(); sx++) {
539: if (sx > 0)
540: ret.append(", ");
541: SFieldMeta sfld = (SFieldMeta) updates.get(sx);
542: ret.append(quoteTable(sfld.getString(SCOLUMN_NAME))
543: + " = ?");
544: }
545: whereSQL(ret, where, instance, keyMetaValues);
546: return ret.toString();
547: }
548:
549: /** Returns the SQL statement for an INSERT in a structured way.
550: Used by flush(). <code>updates</code> and
551: <code>where</code> are SArrayLists of SFieldMetas. Returns SQL
552: statement as a string. */
553: protected String insertSQL(SArrayList updates, SRecordMeta from) {
554: StringBuffer ret = new StringBuffer(200);
555: ret.append("INSERT INTO ");
556: ret.append(quoteTable(from.getString(STABLE_NAME)) + " (");
557: for (int sx = 0; sx < updates.size(); sx++) {
558: if (sx > 0)
559: ret.append(", ");
560: SFieldMeta sfld = (SFieldMeta) updates.get(sx);
561: ret.append(quoteColumn(sfld.getString(SCOLUMN_NAME)));
562: }
563: ret.append(") VALUES (");
564: for (int sx = 0; sx < updates.size(); sx++) {
565: if (sx > 0)
566: ret.append(", ");
567: ret.append("?");
568: }
569: ret.append(")");
570: return ret.toString();
571: }
572:
573: /** Returns the SQL statement for an DELETE in a structured way.
574: Used by flush(). <code>where</code> are SArrayLists of
575: SFieldMetas. Returns SQL statement as a string. */
576: protected String deleteSQL(SRecordMeta from, SArrayList where,
577: SRecordInstance instance, Object[] keyMetaValues) {
578: StringBuffer ret = new StringBuffer(200);
579: ret.append("DELETE FROM ");
580: ret.append(quoteTable(from.getString(STABLE_NAME)));
581: whereSQL(ret, where, instance, keyMetaValues);
582: return ret.toString();
583: }
584:
585: /** Produces the WHERE clause of UPDATE and DELETE statements.
586: Needs to know the instance values so that it can use the IS NULL
587: test (for optimisitic locking).*/
588: protected void whereSQL(StringBuffer ret, SArrayList where,
589: SRecordInstance instance, Object[] keyMetaValues) {
590: ret.append(" WHERE ");
591: for (int wx = 0; wx < where.size(); wx++) {
592: if (wx > 0)
593: ret.append(" AND ");
594: SFieldMeta wfld = (SFieldMeta) where.get(wx);
595: Object value = keyMetaValues[wx];
596: // not instance.fieldValues[wfld.fieldIndex]; for optimistic locking
597: if (value != null)
598: ret.append(quoteColumn(wfld.getString(SCOLUMN_NAME))
599: + " = ? ");
600: else
601: ret.append(quoteColumn(wfld.getString(SCOLUMN_NAME))
602: + " IS NULL ");
603: }
604: }
605:
606: /** Called just after executeQuery to Skip over records until offset
607: * has been reached. Drivers may optimize this in various ways,
608: * eg. LIMIT keywords where supported by the database, or by using
609: * the JDBC */
610: protected void initResultSet(SResultSet srs) {
611: ResultSet rs = srs.getDBResultSet();
612: SPreparedStatement sps = srs.getSPreparedStatement();
613: try {
614: //if (sps.getLimit() > 0)
615: //rs.setMaxRows((int)sps.getLimit()); only on statement.
616:
617: if (canResultSetAbsolute() && sps.getOffset() != 0) {
618: rs.setFetchDirection(ResultSet.FETCH_UNKNOWN);
619: rs.absolute((int) sps.getOffset()); // 1 is first row, 0 is before first row.
620: } else {
621: while (srs.getNrRetrieved() < sps.getOffset()
622: && srs.hasNext())
623: srs.getRecord();
624: srs.nrRetrieved = 0;
625: }
626: } catch (SQLException ex) {
627: throw new SException.JDBC(ex);
628: }
629: }
630:
631: /** true if resultset.absolure(nn) works properly to implement limit/offset. */
632: protected boolean canResultSetAbsolute() {
633: return false;
634: }
635:
636: /** Generates a new key using SELECT MAX+1. This will (hopefully)
637: be specialized for each database driver to be correct. Note that
638: there is a global counter kept so it will actually work OK if
639: all the updates are from one JVM. Amazing that there is still
640: no standard way to do this in SQL.<p>
641:
642: ## (There is scope to optimize this at some point so that one JDBC
643: call can both generate the sequence number and insert a new
644: record. But that means that the new record's key is not
645: available until insert time which causes problems for foreign
646: keys. Alternatively one can get batches of 10 (say) sequences
647: at a time and then use an internal counter, but this will leave
648: big holes in the sequence. Defer this to version 1.)
649: */
650: protected long generateKeySelectMax(SRecordMeta rec,
651: SFieldMeta keyFld) {
652: String columnName = keyFld.getString(SCOLUMN_NAME);
653: String tableName = rec.getString(STABLE_NAME);
654: String qry = "SELECT MAX(" + quoteColumn(columnName)
655: + ") FROM " + quoteColumn(tableName);
656:
657: Object next = SConnection.rawQueryJDBC(qry);
658:
659: long db = next == null ? 1 : SJSharp.object2Long(next) + 1;
660: return keyFld.nextGeneratedValue(db);
661: }
662:
663: protected long generateKeySequence(SRecordMeta rec,
664: SFieldMeta keyFld) {
665: throw new SException.Error(
666: "Database does not support SEQUENCES");
667: }
668:
669: public boolean supportsKeySequences() {
670: return false;
671: }
672:
673: protected String createSequenceDDL(String name) {
674: throw new SException.Error("Sequences not supported");
675: }
676:
677: protected String dropSequenceDDL(String name) {
678: throw new SException.Error("Sequences not supported");
679: }
680:
681: /** Utility routine for dropping tables called by SConnection.
682: Driver specific versions should only hide table non existant
683: errors (and not warn about them).*/
684: public void dropTableNoError(String table) {
685: Connection con = SConnection.getBegunDBConnection();
686: try {
687: PreparedStatement ps = con.prepareStatement("DROP TABLE "
688: + table);
689: ps.executeUpdate();
690: } catch (SQLException ex) {
691: SLog.slog.warn("DROPPING " + table + ": " + ex);
692: }
693: }
694:
695: }
|