1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.jdbc.kernel;
20
21 import java.io.Serializable;
22 import java.sql.Connection;
23 import java.sql.PreparedStatement;
24 import java.sql.ResultSet;
25 import java.sql.SQLException;
26 import java.sql.Types;
27 import java.util.HashMap;
28
29 import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
30 import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl;
31 import org.apache.openjpa.jdbc.meta.ClassMapping;
32 import org.apache.openjpa.jdbc.schema.Column;
33 import org.apache.openjpa.jdbc.schema.PrimaryKey;
34 import org.apache.openjpa.jdbc.schema.Schema;
35 import org.apache.openjpa.jdbc.schema.SchemaGroup;
36 import org.apache.openjpa.jdbc.schema.SchemaTool;
37 import org.apache.openjpa.jdbc.schema.Schemas;
38 import org.apache.openjpa.jdbc.schema.Table;
39 import org.apache.openjpa.jdbc.sql.DBDictionary;
40 import org.apache.openjpa.jdbc.sql.RowImpl;
41 import org.apache.openjpa.jdbc.sql.SQLBuffer;
42 import org.apache.openjpa.jdbc.sql.SQLExceptions;
43 import org.apache.openjpa.lib.conf.Configurable;
44 import org.apache.openjpa.lib.conf.Configuration;
45 import org.apache.openjpa.lib.conf.Configurations;
46 import org.apache.openjpa.lib.log.Log;
47 import org.apache.openjpa.lib.util.Localizer;
48 import org.apache.openjpa.lib.util.Options;
49 import org.apache.openjpa.meta.JavaTypes;
50 import org.apache.openjpa.util.InvalidStateException;
51 import serp.util.Numbers;
52 import serp.util.Strings;
53
54 ////////////////////////////////////////////////////////////
55 // NOTE: Do not change property names; see SequenceMetaData
56 // and SequenceMapping for standard property names.
57 ////////////////////////////////////////////////////////////
58
59 /**
60 * {@link JDBCSeq} implementation that uses a database table
61 * for sequence number generation. This base implementation uses a single
62 * row for a global sequence number.
63 *
64 * @author Abe White
65 */
66 public class TableJDBCSeq
67 extends AbstractJDBCSeq
68 implements Configurable {
69
70 public static final String ACTION_DROP = "drop";
71 public static final String ACTION_ADD = "add";
72 public static final String ACTION_GET = "get";
73 public static final String ACTION_SET = "set";
74
75 private static final Localizer _loc = Localizer.forPackage
76 (TableJDBCSeq.class);
77
78 private transient JDBCConfiguration _conf = null;
79 private transient Log _log = null;
80 private int _alloc = 50;
81 private int _intValue = 1;
82 private final HashMap _stat = new HashMap();
83
84 private String _table = "OPENJPA_SEQUENCE_TABLE";
85 private String _seqColumnName = "SEQUENCE_VALUE";
86 private String _pkColumnName = "ID";
87
88 private Column _seqColumn = null;
89 private Column _pkColumn = null;
90 private int _schemasIdx = 0;
91
92 /**
93 * The sequence table name. Defaults to <code>OPENJPA_SEQUENCE_TABLE</code>.
94 * By default, the table will be placed in the first schema listed in your
95 * <code>openjpa.jdbc.Schemas</code> property, or in the default schema if
96 * the property is not given. If you specify a table name in the form
97 * <code><schema>.<table></code>, then the given schema
98 * will be used.
99 */
100 public String getTable() {
101 return _table;
102 }
103
104 /**
105 * The sequence table name. Defaults to <code>OPENJPA_SEQUENCE_TABLE</code>.
106 * By default, the table will be placed in the first schema listed in your
107 * <code>openjpa.jdbc.Schemas</code> property, or in the default schema if
108 * the property is not given. If you specify a table name in the form
109 * <code><schema>.<table></code>, then the given schema
110 * will be used.
111 */
112 public void setTable(String name) {
113 _table = name;
114 }
115
116 /**
117 * @deprecated Use {@link #setTable}. Retained for
118 * backwards-compatibility with auto-configuration.
119 */
120 public void setTableName(String name) {
121 setTable(name);
122 }
123
124 /**
125 * The name of the column that holds the sequence value. Defaults
126 * to <code>SEQUENCE_VALUE</code>.
127 */
128 public String getSequenceColumn() {
129 return _seqColumnName;
130 }
131
132 /**
133 * The name of the column that holds the sequence value. Defaults
134 * to <code>SEQUENCE_VALUE</code>.
135 */
136 public void setSequenceColumn(String sequenceColumn) {
137 _seqColumnName = sequenceColumn;
138 }
139
140 /**
141 * The name of the table's primary key column. Defaults to
142 * <code>ID</code>.
143 */
144 public String getPrimaryKeyColumn() {
145 return _pkColumnName;
146 }
147
148 /**
149 * The name of the table's primary key column. Defaults to
150 * <code>ID</code>.
151 */
152 public void setPrimaryKeyColumn(String primaryKeyColumn) {
153 _pkColumnName = primaryKeyColumn;
154 }
155
156 /**
157 * Return the number of sequences to allocate for each update of the
158 * sequence table. Sequence numbers will be grabbed in blocks of this
159 * value to reduce the number of transactions that must be performed on
160 * the sequence table.
161 */
162 public int getAllocate() {
163 return _alloc;
164 }
165
166 /**
167 * Return the number of sequences to allocate for each update of the
168 * sequence table. Sequence numbers will be grabbed in blocks of this
169 * value to reduce the number of transactions that must be performed on
170 * the sequence table.
171 */
172 public void setAllocate(int alloc) {
173 _alloc = alloc;
174 }
175
176 /**
177 * Return the number as the initial number for the
178 * GeneratedValue.TABLE strategy to start with.
179 * @return an initial number
180 */
181 public int getInitialValue() {
182 return _intValue;
183 }
184
185 /**
186 * Set the initial number in the table for the GeneratedValue.TABLE
187 * strategy to use as initial number.
188 * @param intValue. The initial number
189 */
190 public void setInitialValue(int intValue) {
191 _intValue = intValue;
192 }
193
194 /**
195 * @deprecated Use {@link #setAllocate}. Retained for backwards
196 * compatibility of auto-configuration.
197 */
198 public void setIncrement(int inc) {
199 setAllocate(inc);
200 }
201
202 public JDBCConfiguration getConfiguration() {
203 return _conf;
204 }
205
206 public void setConfiguration(Configuration conf) {
207 _conf = (JDBCConfiguration) conf;
208 _log = _conf.getLog(JDBCConfiguration.LOG_RUNTIME);
209 }
210
211 public void startConfiguration() {
212 }
213
214 public void endConfiguration() {
215 buildTable();
216 }
217
218
219 public void addSchema(ClassMapping mapping, SchemaGroup group) {
220 // Since the table is created by openjpa internally
221 // we can create the table for each schema within the PU
222 // in here.
223
224 Schema[] schemas = group.getSchemas();
225 for (int i = 0; i < schemas.length; i++) {
226 String schemaName = Strings.getPackageName(_table);
227 if (schemaName.length() == 0)
228 schemaName = Schemas.getNewTableSchema(_conf);
229 if (schemaName == null)
230 schemaName = schemas[i].getName();
231
232 // create table in this group
233 Schema schema = group.getSchema(schemaName);
234 if (schema == null)
235 schema = group.addSchema(schemaName);
236
237 schema.importTable(_pkColumn.getTable());
238 // we need to reset the table name in the column with the
239 // fully qualified name for matching the table name from the
240 // Column.
241 _pkColumn.resetTableName(schemaName + "."
242 + _pkColumn.getTableName());
243 // some databases require to create an index for the sequence table
244 _conf.getDBDictionaryInstance().createIndexIfNecessary(schema,
245 _table, _pkColumn);
246
247 }
248 }
249
250 protected Object nextInternal(JDBCStore store, ClassMapping mapping)
251 throws Exception {
252 // if needed, grab the next handful of ids
253 Status stat = getStatus(mapping);
254 if (stat == null)
255 throw new InvalidStateException(_loc.get("bad-seq-type",
256 getClass(), mapping));
257
258 while (true) {
259 synchronized (stat) {
260 // make sure seq is at least 1, since autoassigned ids of 0 can
261 // conflict with uninitialized values
262 stat.seq = Math.max(stat.seq, 1);
263 if (stat.seq < stat.max)
264 return Numbers.valueOf(stat.seq++);
265 }
266 allocateSequence(store, mapping, stat, _alloc, true);
267 }
268 }
269
270 protected Object currentInternal(JDBCStore store, ClassMapping mapping)
271 throws Exception {
272 if (current == null) {
273 Connection conn = getConnection(store);
274 try {
275 long cur = getSequence(mapping, conn);
276 if (cur != -1)
277 current = Numbers.valueOf(cur);
278 } finally {
279 closeConnection(conn);
280 }
281 }
282 return super.currentInternal(store, mapping);
283 }
284
285 protected void allocateInternal(int count, JDBCStore store,
286 ClassMapping mapping)
287 throws SQLException {
288 Status stat = getStatus(mapping);
289 if (stat == null)
290 return;
291
292 while (true) {
293 int available;
294 synchronized (stat) {
295 available = (int) (stat.max - stat.seq);
296 if (available >= count)
297 return;
298 }
299 allocateSequence(store, mapping, stat, count - available, false);
300 }
301 }
302
303 /**
304 * Return the appropriate status object for the given class, or null
305 * if cannot handle the given class. The mapping may be null.
306 */
307 protected Status getStatus(ClassMapping mapping) {
308 Status status = (Status)_stat.get(mapping);
309 if (status == null){
310 status = new Status();
311 _stat.put(mapping, status);
312 }
313 return status;
314
315 }
316
317 /**
318 * Add the primary key column to the given table and return it.
319 */
320 protected Column addPrimaryKeyColumn(Table table) {
321 DBDictionary dict = _conf.getDBDictionaryInstance();
322 Column pkColumn = table.addColumn(dict.getValidColumnName
323 (getPrimaryKeyColumn(), table));
324 pkColumn.setType(dict.getPreferredType(Types.TINYINT));
325 pkColumn.setJavaType(JavaTypes.INT);
326 return pkColumn;
327 }
328
329 /**
330 * Return the primary key value for the given class.
331 */
332 protected Object getPrimaryKey(ClassMapping mapping) {
333 return Numbers.valueOf(0);
334 }
335
336 /**
337 * Creates the object-level representation of the sequence table.
338 */
339 private void buildTable() {
340 String tableName = Strings.getClassName(_table);
341 String schemaName = Strings.getPackageName(_table);
342 if (schemaName.length() == 0)
343 schemaName = Schemas.getNewTableSchema(_conf);
344
345 SchemaGroup group = new SchemaGroup();
346 Schema schema = group.addSchema(schemaName);
347
348 Table table = schema.addTable(tableName);
349 _pkColumn = addPrimaryKeyColumn(table);
350 PrimaryKey pk = table.addPrimaryKey();
351 pk.addColumn(_pkColumn);
352
353 DBDictionary dict = _conf.getDBDictionaryInstance();
354 _seqColumn = table.addColumn(dict.getValidColumnName
355 (_seqColumnName, table));
356 _seqColumn.setType(dict.getPreferredType(Types.BIGINT));
357 _seqColumn.setJavaType(JavaTypes.LONG);
358 }
359
360 /**
361 * Updates the max available sequence value.
362 */
363 private void allocateSequence(JDBCStore store, ClassMapping mapping,
364 Status stat, int alloc, boolean updateStatSeq)
365 throws SQLException {
366 Connection conn = getConnection(store);
367 try {
368 if (setSequence(mapping, stat, alloc, updateStatSeq, conn))
369 return;
370 } catch (SQLException se) {
371 throw SQLExceptions.getStore(_loc.get("bad-seq-up", _table),
372 se, _conf.getDBDictionaryInstance());
373 } finally {
374 closeConnection(conn);
375 }
376
377 try {
378 // possible that we might get errors when inserting if
379 // another thread/process is inserting same pk at same time
380 SQLException err = null;
381 // ### why does this not call getConnection() / closeConnection()?
382 conn = _conf.getDataSource2(store.getContext()).getConnection();
383 try {
384 insertSequence(mapping, conn);
385 } catch (SQLException se) {
386 err = se;
387 } finally {
388 try { conn.close(); } catch (SQLException se) {}
389 }
390
391 // now we should be able to update...
392 conn = getConnection(store);
393 try {
394 if (!setSequence(mapping, stat, alloc, updateStatSeq, conn))
395 throw (err != null) ? err : new SQLException(_loc.get
396 ("no-seq-row", mapping, _table).getMessage());
397 } finally {
398 closeConnection(conn);
399 }
400 } catch (SQLException se2) {
401 throw SQLExceptions.getStore(_loc.get("bad-seq-up", _table),
402 se2, _conf.getDBDictionaryInstance());
403 }
404 }
405
406 /**
407 * Inserts the initial sequence information into the database, if any.
408 */
409 private void insertSequence(ClassMapping mapping, Connection conn)
410 throws SQLException {
411 if (_log.isTraceEnabled())
412 _log.trace(_loc.get("insert-seq"));
413
414 Object pk = getPrimaryKey(mapping);
415 if (pk == null)
416 throw new InvalidStateException(_loc.get("bad-seq-type",
417 getClass(), mapping));
418
419 DBDictionary dict = _conf.getDBDictionaryInstance();
420 String tableName = resolveTableName(mapping, _pkColumn.getTable());
421 SQLBuffer insert = new SQLBuffer(dict).append("INSERT INTO ").
422 append(tableName).append(" (").
423 append(_pkColumn).append(", ").append(_seqColumn).
424 append(") VALUES (").
425 appendValue(pk, _pkColumn).append(", ").
426 appendValue(_intValue, _seqColumn).append(")");
427
428 boolean wasAuto = conn.getAutoCommit();
429 if (!wasAuto && !suspendInJTA())
430 conn.setAutoCommit(true);
431
432 PreparedStatement stmnt = null;
433 try {
434 stmnt = prepareStatement(conn, insert);
435 executeUpdate(_conf, conn, stmnt, insert, RowImpl.ACTION_INSERT);
436 } finally {
437 if (stmnt != null)
438 try { stmnt.close(); } catch (SQLException se) {}
439 if (!wasAuto && !suspendInJTA())
440 conn.setAutoCommit(false);
441 }
442 }
443
444 /**
445 * Return the current sequence value, or -1 if unattainable.
446 */
447 protected long getSequence(ClassMapping mapping, Connection conn)
448 throws SQLException {
449 if (_log.isTraceEnabled())
450 _log.trace(_loc.get("get-seq"));
451
452 Object pk = getPrimaryKey(mapping);
453 if (pk == null)
454 return -1;
455
456 DBDictionary dict = _conf.getDBDictionaryInstance();
457 SQLBuffer sel = new SQLBuffer(dict).append(_seqColumn);
458 SQLBuffer where = new SQLBuffer(dict).append(_pkColumn).append(" = ").
459 appendValue(pk, _pkColumn);
460 String tableName = resolveTableName(mapping, _seqColumn.getTable());
461 SQLBuffer tables = new SQLBuffer(dict).append(tableName);
462
463 SQLBuffer select = dict.toSelect(sel, null, tables, where, null, null,
464 null, false, dict.supportsSelectForUpdate, 0, Long.MAX_VALUE,
465 false, true);
466
467 PreparedStatement stmnt = prepareStatement(conn, select);
468 ResultSet rs = null;
469 try {
470 rs = executeQuery(_conf, conn, stmnt, select);
471 return getSequence(rs, dict);
472 } finally {
473 if (rs != null)
474 try { rs.close(); } catch (SQLException se) {}
475 if (stmnt != null)
476 try { stmnt.close(); } catch (SQLException se) {}
477 }
478 }
479
480 /**
481 * Grabs the next handful of sequence numbers.
482 *
483 * @return true if the sequence was updated, false if no sequence
484 * row existed for this mapping
485 */
486 protected boolean setSequence(ClassMapping mapping, Status stat, int inc,
487 boolean updateStatSeq, Connection conn)
488 throws SQLException {
489 if (_log.isTraceEnabled())
490 _log.trace(_loc.get("update-seq"));
491
492 Object pk = getPrimaryKey(mapping);
493 if (pk == null)
494 throw new InvalidStateException(_loc.get("bad-seq-type",
495 getClass(), mapping));
496
497 DBDictionary dict = _conf.getDBDictionaryInstance();
498 SQLBuffer where = new SQLBuffer(dict).append(_pkColumn).append(" = ").
499 appendValue(pk, _pkColumn);
500
501 // loop until we have a successful atomic select/update sequence
502 long cur = 0;
503 PreparedStatement stmnt;
504 ResultSet rs;
505 SQLBuffer upd;
506 for (int updates = 0; updates == 0;) {
507 stmnt = null;
508 rs = null;
509 try {
510 cur = getSequence(mapping, conn);
511 if (cur == -1)
512 return false;
513
514 // update the value
515 upd = new SQLBuffer(dict);
516 String tableName = resolveTableName(mapping, _seqColumn.getTable());
517 upd.append("UPDATE ").append(tableName).
518 append(" SET ").append(_seqColumn).append(" = ").
519 appendValue(Numbers.valueOf(cur + inc), _seqColumn).
520 append(" WHERE ").append(where).append(" AND ").
521 append(_seqColumn).append(" = ").
522 appendValue(Numbers.valueOf(cur), _seqColumn);
523
524 stmnt = prepareStatement(conn, upd);
525 updates = executeUpdate(_conf, conn, stmnt, upd, RowImpl.ACTION_UPDATE);
526 } finally {
527 if (rs != null)
528 try { rs.close(); } catch (SQLException se) {}
529 if (stmnt != null)
530 try { stmnt.close(); } catch (SQLException se) {}
531 }
532 }
533
534 // setup new sequence range
535 synchronized (stat) {
536 if (updateStatSeq && stat.seq < cur)
537 stat.seq = cur;
538 if (stat.max < cur + inc)
539 stat.max = cur + inc;
540 }
541 return true;
542 }
543 /**
544 * Resolve a fully qualified table name
545 *
546 * @param class
547 * mapping to get the schema name
548 */
549 public String resolveTableName(ClassMapping mapping, Table table) {
550 String sName = mapping.getTable().getSchemaName();
551 String tableName;
552 if (sName == null)
553 tableName = table.getFullName();
554 else
555 tableName = sName + "." + table.getName();
556 return tableName;
557 }
558
559 /**
560 * Creates the sequence table in the DB.
561 */
562 public void refreshTable()
563 throws SQLException {
564 if (_log.isInfoEnabled())
565 _log.info(_loc.get("make-seq-table"));
566
567 // create the table
568 SchemaTool tool = new SchemaTool(_conf);
569 tool.setIgnoreErrors(true);
570 tool.createTable(_pkColumn.getTable());
571 }
572
573 /**
574 * Drops the sequence table in the DB.
575 */
576 public void dropTable()
577 throws SQLException {
578 if (_log.isInfoEnabled())
579 _log.info(_loc.get("drop-seq-table"));
580
581 // drop the table
582 SchemaTool tool = new SchemaTool(_conf);
583 tool.setIgnoreErrors(true);
584 tool.dropTable(_pkColumn.getTable());
585 }
586
587 /////////
588 // Main
589 /////////
590
591 /**
592 * Usage: java org.apache.openjpa.jdbc.schema.TableJDBCSequence [option]*
593 * -action/-a <add | drop | get | set> [value]
594 * Where the following options are recognized.
595 * <ul>
596 * <li><i>-properties/-p <properties file or resource></i>: The
597 * path or resource name of a OpenJPA properties file containing
598 * information such as the license key and connection data as
599 * outlined in {@link JDBCConfiguration}. Optional.</li>
600 * <li><i>-<property name> <property value></i>: All bean
601 * properties of the OpenJPA {@link JDBCConfiguration} can be set by
602 * using their names and supplying a value. For example:
603 * <code>-licenseKey adslfja83r3lkadf</code></li>
604 * </ul>
605 * The various actions are as follows.
606 * <ul>
607 * <li><i>add</i>: Create the sequence table.</li>
608 * <li><i>drop</i>: Drop the sequence table.</li>
609 * <li><i>get</i>: Print the current sequence value.</li>
610 * <li><i>set</i>: Set the sequence value.</li>
611 * </ul>
612 */
613 public static void main(String[] args)
614 throws Exception {
615 Options opts = new Options();
616 final String[] arguments = opts.setFromCmdLine(args);
617 boolean ret = Configurations.runAgainstAllAnchors(opts,
618 new Configurations.Runnable() {
619 public boolean run(Options opts) throws Exception {
620 JDBCConfiguration conf = new JDBCConfigurationImpl();
621 try {
622 return TableJDBCSeq.run(conf, arguments, opts);
623 } finally {
624 conf.close();
625 }
626 }
627 });
628 if (!ret)
629 System.out.println(_loc.get("seq-usage"));
630 }
631
632 /**
633 * Run the tool. Returns false if invalid options were given.
634 */
635 public static boolean run(JDBCConfiguration conf, String[] args,
636 Options opts)
637 throws Exception {
638 String action = opts.removeProperty("action", "a", null);
639 Configurations.populateConfiguration(conf, opts);
640 return run(conf, args, action);
641 }
642
643 /**
644 * Run the tool. Return false if an invalid option was given.
645 */
646 public static boolean run(JDBCConfiguration conf, String[] args,
647 String action)
648 throws Exception {
649 if (args.length > 1 || (args.length != 0
650 && !ACTION_SET.equals(action)))
651 return false;
652
653 TableJDBCSeq seq = new TableJDBCSeq();
654 String props = Configurations.getProperties(conf.getSequence());
655 Configurations.configureInstance(seq, conf, props);
656
657 if (ACTION_DROP.equals(action))
658 seq.dropTable();
659 else if (ACTION_ADD.equals(action))
660 seq.refreshTable();
661 else if (ACTION_GET.equals(action) || ACTION_SET.equals(action)) {
662 Connection conn = conf.getDataSource2(null).getConnection();
663 try {
664 long cur = seq.getSequence(null, conn);
665 if (ACTION_GET.equals(action))
666 System.out.println(cur);
667 else {
668 long set;
669 if (args.length > 0)
670 set = Long.parseLong(args[0]);
671 else
672 set = cur + seq.getAllocate();
673 if (set < cur)
674 set = cur;
675 else {
676 Status stat = seq.getStatus(null);
677 seq.setSequence(null, stat, (int) (set - cur), true,
678 conn);
679 set = stat.seq;
680 }
681 System.err.println(set);
682 }
683 }
684 catch (NumberFormatException nfe) {
685 return false;
686 } finally {
687 try { conn.close(); } catch (SQLException se) {}
688 }
689 } else
690 return false;
691 return true;
692 }
693
694 /**
695 * Helper struct to hold status information.
696 */
697 protected static class Status
698 implements Serializable {
699
700 public long seq = 1L;
701 public long max = 0L;
702 }
703
704 /**
705 * This method is to provide override for non-JDBC or JDBC-like
706 * implementation of preparing statement.
707 */
708 protected PreparedStatement prepareStatement(Connection conn, SQLBuffer buf)
709 throws SQLException {
710 return buf.prepareStatement(conn);
711 }
712
713 /**
714 * This method is to provide override for non-JDBC or JDBC-like
715 * implementation of executing update.
716 */
717 protected int executeUpdate(JDBCConfiguration conf, Connection conn,
718 PreparedStatement stmnt, SQLBuffer buf, int opcode) throws SQLException {
719 return stmnt.executeUpdate();
720 }
721
722 /**
723 * This method is to provide override for non-JDBC or JDBC-like
724 * implementation of executing query.
725 */
726 protected ResultSet executeQuery(JDBCConfiguration conf, Connection conn,
727 PreparedStatement stmnt, SQLBuffer buf) throws SQLException {
728 return stmnt.executeQuery();
729 }
730
731 /**
732 * This method is to provide override for non-JDBC or JDBC-like
733 * implementation of getting sequence from the result set.
734 */
735 protected long getSequence(ResultSet rs, DBDictionary dict) throws SQLException {
736 if (rs == null || !rs.next())
737 return -1;
738 return dict.getLong(rs, 1);
739 }
740 }