1 package net.jaekl.cfb.db.driver;
3 // Copyright (C) 2015 Christian Jaekl
5 import static net.jaekl.cfb.db.Column.Null.NOT_NULL;
7 import java.sql.Connection;
8 import java.sql.PreparedStatement;
9 import java.sql.ResultSet;
10 import java.sql.SQLException;
11 import java.util.ArrayList;
12 import java.util.List;
14 import net.jaekl.cfb.db.Column;
15 import net.jaekl.cfb.db.Column.Type;
16 import net.jaekl.cfb.db.Condition;
17 import net.jaekl.cfb.db.Row;
18 import net.jaekl.cfb.db.Sequence;
19 import net.jaekl.cfb.db.Sort;
20 import net.jaekl.cfb.db.Table;
22 public abstract class DbDriver {
23 static int PENDING_LIMIT = 1024; // Rough limit at which point we'll start a new batch for batch updates
29 // Load the JDBC driver
30 public abstract void load() throws ClassNotFoundException;
32 public abstract Connection connect(String host, int port, String dbName, String user, String pass) throws SQLException;
34 public boolean createTable(Connection con, Table table) throws SQLException
36 String sql = createTableSql(table);
37 try (PreparedStatement ps = con.prepareStatement(sql)) {
40 catch (SQLException exc) {
41 throw new SQLException("Failed to executeUpdate: " + sql, exc);
47 public void dropTable(Connection con, Table table) throws SQLException
49 String sql = dropTableSql(table);
50 try (PreparedStatement ps = con.prepareStatement(sql)) {
53 catch (SQLException exc) {
54 throw new SQLException("Failed to drop table: " + sql, exc);
58 public boolean createSequence(Connection con, Sequence seq) throws SQLException
60 String sql = createSequenceSql(seq);
61 try (PreparedStatement ps = con.prepareStatement(sql)) {
64 catch (SQLException exc) {
65 throw new SQLException("Failed to executeUpdate: " + sql, exc);
71 public void dropSequence(Connection con, Sequence seq) throws SQLException
73 String sql = dropSequenceSql(seq);
74 try (PreparedStatement ps = con.prepareStatement(sql)) {
77 catch (SQLException exc) {
78 throw new SQLException("Failed to drop sequence: " + sql, exc);
82 public Row selectExactlyOne(Connection con, Column[] columns, Table[] tables, Condition[] conditions)
85 Sort[] sorts = new Sort[0];
87 List<Row> rows = select(con, columns, tables, conditions, sorts, limit);
88 if (rows.size() < 1) {
89 throw new SQLException("Expected one result, but found none: ", selectSql(columns, tables, conditions, sorts, limit));
91 if (rows.size() > 1) {
92 throw new SQLException("Expected one result, but found more than one: " + selectSql(columns, tables, conditions, sorts, limit));
98 public List<Row> select(Connection con, Column[] columns, Table[] tables, Condition[] conditions)
101 Sort[] sorts = new Sort[0];
102 int limit = (-1); // no limit
104 return select(con, columns, tables, conditions, sorts, limit);
107 public List<Row> select(Connection con, Column[] columns, Table[] tables, Condition[] conditions, Sort[] sorts, int limit)
110 String sql = selectSql(columns, tables, conditions, sorts, limit);
111 ArrayList<Row> result = new ArrayList<Row>();
113 try (PreparedStatement ps = con.prepareStatement(sql)) {
115 for (Condition condition : conditions) {
116 if (condition.getOperation().hasParam()) {
117 Column column = condition.getColumn();
119 column.setObject(ps, index, condition.getValue());
123 try (ResultSet rs = ps.executeQuery()) {
125 Object[] values = new Object[columns.length];
126 for (index = 0; index < columns.length; ++index) {
127 if (columns[index].getType().equals(Type.TIMESTAMPTZ)) {
128 long milliseconds = rs.getLong(index + 1);
129 values[index] = new java.util.Date(milliseconds);
132 values[index] = rs.getObject(index + 1);
135 Row row = new Row(columns, values);
140 catch (SQLException se) {
141 throw new SQLException("Error with SQL: " + sql, se);
147 // Returns the number of rows inserted
148 public int insert(Connection con, Table table, Object[][] values) throws SQLException
151 int pendingValues = 0;
153 assert( isValidInsert(table, values));
155 String sql = insertSql(table);
157 try (PreparedStatement ps = con.prepareStatement(sql))
159 for (int row = 0; row < values.length; ++row) {
160 Object[] data = values[row];
162 assert(null != data);
163 assert(data.length == table.getNumColumns());
165 for (int idx = 0; idx < data.length; ++idx) {
166 Object obj = data[idx];
167 Column column = table.getColumn(idx);
168 column.setObject(ps, idx + 1, obj);
173 int rowsFlushed = checkFlushBatch(ps, pendingValues, false);
174 if (rowsFlushed > 0) {
175 count += rowsFlushed;
180 count += checkFlushBatch(ps, pendingValues, true);
186 public long nextVal(Connection con, Sequence seq) throws SQLException
188 String sql = nextValSql(seq);
190 try (PreparedStatement ps = con.prepareStatement(sql))
192 try (ResultSet rs = ps.executeQuery()) {
194 return rs.getLong(1);
199 throw new SQLException("No value returned for sequence: " + sql);
202 int checkFlushBatch(PreparedStatement ps, int pendingValues, boolean forceFlush) throws SQLException
206 if (forceFlush || (pendingValues >= PENDING_LIMIT))
208 int[] updateCounts = ps.executeBatch();
209 for (int i = 0; i < updateCounts.length; ++i) {
210 if (updateCounts[i] > 0) {
211 count += updateCounts[i];
219 String insertSql(Table table) {
220 StringBuilder sb = new StringBuilder("INSERT INTO ");
221 sb.append(table.getName())
222 .append(" VALUES (");
224 for (int i = 0; i < table.getNumColumns(); ++i) {
232 return sb.toString();
235 protected String selectSql(Column[] columns, Table[] tables, Condition[] conditions, Sort[] sorts, int limit)
237 StringBuilder sb = new StringBuilder("SELECT ");
239 boolean firstColumn = true;
240 for (Column column : columns) {
247 sb.append(column.getName());
252 boolean firstTable = true;
253 for (Table table : tables) {
260 sb.append(table.getName());
263 if (null != conditions && conditions.length > 0) {
264 sb.append(" WHERE ");
266 boolean firstCondition = true;
268 for (Condition condition : conditions) {
269 if (firstCondition) {
270 firstCondition = false;
276 sb.append(condition.getColumn().getName())
277 .append(condition.getOperation().getSql());
281 if (null != sorts && sorts.length > 0) {
282 sb.append(" ORDER BY ");
284 boolean firstSort = true;
286 for (Sort sort : sorts) {
294 sb.append(sort.getColumn().getName());
296 if (sort.getDirection().equals(Sort.Direction.ASCENDING)) {
306 sb.append(" LIMIT " + limit + " ");
309 return sb.toString();
312 protected String typeName(Type type) {
313 // Special case: TIMESTAMPTZ stored as INTEGER (milliseconds since the epoch)
314 // Reading a TIMESTAMPTZ back from the DB, and converting it to a java.util.Date,
315 // is fraught with peril. The best way around this is to store the dates in
316 // milliseconds-since-the-epoch (01.01.1970 00:00:00.000 UTC).
317 if (Type.TIMESTAMPTZ.equals(type)) {
318 return Type.INTEGER.toString();
321 return type.toString();
324 protected String createColumnSql(Column column)
326 String result = column.getName() + " " + typeName(column.getType());
327 if (column.getWidth() > 0) {
328 result += "(" + column.getWidth() + ")";
331 if (NOT_NULL == column.getNull()) {
332 result += " NOT NULL";
341 protected String createTableSql(Table table)
343 assert(null != table);
344 assert(null != table.getName());
345 assert(table.getNumColumns() > 0);
347 StringBuilder sb = new StringBuilder();
349 sb.append("CREATE TABLE ")
350 .append(table.getName())
353 for (int idx = 0; idx < table.getNumColumns(); ++idx) {
357 sb.append(createColumnSql(table.getColumn(idx)));
362 return sb.toString();
365 protected String dropTableSql(Table table) {
366 assert(null != table);
367 assert(null != table.getName());
369 return "DROP TABLE " + table.getName();
372 protected String createSequenceSql(Sequence seq) {
374 assert(null != seq.getName());
376 return "CREATE SEQUENCE " + seq.getName();
379 protected String dropSequenceSql(Sequence seq) {
381 assert(null != seq.getName());
383 return "DROP SEQUENCE " + seq.getName();
386 abstract protected String nextValSql(Sequence seq);
388 boolean isValidInsert(Table table, Object[][] values)
390 if (null == table) return false;
391 if (null == values) return false;
393 for (Object[] rowValues : values) {
394 if (rowValues.length != table.getNumColumns()) {
397 for (int idx = 0; idx < rowValues.length; ++idx) {