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 abstract long nextVal(Connection con, Sequence seq) throws SQLException;
36 public boolean createTable(Connection con, Table table) throws SQLException
38 String sql = createTableSql(table);
39 try (PreparedStatement ps = con.prepareStatement(sql)) {
42 catch (SQLException exc) {
43 throw new SQLException("Failed to executeUpdate: " + sql, exc);
49 public void dropTable(Connection con, Table table) throws SQLException
51 String sql = dropTableSql(table);
52 try (PreparedStatement ps = con.prepareStatement(sql)) {
55 catch (SQLException exc) {
56 throw new SQLException("Failed to drop table: " + sql, exc);
60 public boolean createSequence(Connection con, Sequence seq) throws SQLException
62 String sql = createSequenceSql(seq);
63 try (PreparedStatement ps = con.prepareStatement(sql)) {
66 catch (SQLException exc) {
67 throw new SQLException("Failed to executeUpdate: " + sql, exc);
73 public void dropSequence(Connection con, Sequence seq) throws SQLException
75 String sql = dropSequenceSql(seq);
76 try (PreparedStatement ps = con.prepareStatement(sql)) {
79 catch (SQLException exc) {
80 throw new SQLException("Failed to drop sequence: " + sql, exc);
84 public Row selectExactlyOne(Connection con, Column[] columns, Table[] tables, Condition[] conditions)
87 Sort[] sorts = new Sort[0];
89 List<Row> rows = select(con, columns, tables, conditions, sorts, limit);
90 if (rows.size() < 1) {
91 throw new SQLException("Expected one result, but found none: ", selectSql(columns, tables, conditions, sorts, limit));
93 if (rows.size() > 1) {
94 throw new SQLException("Expected one result, but found more than one: " + selectSql(columns, tables, conditions, sorts, limit));
100 public List<Row> select(Connection con, Column[] columns, Table[] tables, Condition[] conditions)
103 Sort[] sorts = new Sort[0];
104 int limit = (-1); // no limit
106 return select(con, columns, tables, conditions, sorts, limit);
109 public List<Row> select(Connection con, Column[] columns, Table[] tables, Condition[] conditions, Sort[] sorts, int limit)
112 String sql = selectSql(columns, tables, conditions, sorts, limit);
113 ArrayList<Row> result = new ArrayList<Row>();
115 try (PreparedStatement ps = con.prepareStatement(sql)) {
117 for (Condition condition : conditions) {
118 if (condition.getOperation().hasParam()) {
119 Column column = condition.getColumn();
121 column.setObject(ps, index, condition.getValue());
125 try (ResultSet rs = ps.executeQuery()) {
127 Object[] values = new Object[columns.length];
128 for (index = 0; index < columns.length; ++index) {
129 if (columns[index].getType().equals(Type.TIMESTAMPTZ)) {
130 long milliseconds = rs.getLong(index + 1);
131 values[index] = new java.util.Date(milliseconds);
134 values[index] = rs.getObject(index + 1);
137 Row row = new Row(columns, values);
142 catch (SQLException se) {
143 throw new SQLException("Error with SQL: " + sql, se);
149 // Returns the number of rows inserted
150 public int insert(Connection con, Table table, Object[][] values) throws SQLException
153 int pendingValues = 0;
155 assert( isValidInsert(table, values));
157 String sql = insertSql(table);
159 try (PreparedStatement ps = con.prepareStatement(sql))
161 for (int row = 0; row < values.length; ++row) {
162 Object[] data = values[row];
164 assert(null != data);
165 assert(data.length == table.getNumColumns());
167 for (int idx = 0; idx < data.length; ++idx) {
168 Object obj = data[idx];
169 Column column = table.getColumn(idx);
170 column.setObject(ps, idx + 1, obj);
175 int rowsFlushed = checkFlushBatch(ps, pendingValues, false);
176 if (rowsFlushed > 0) {
177 count += rowsFlushed;
182 count += checkFlushBatch(ps, pendingValues, true);
188 int checkFlushBatch(PreparedStatement ps, int pendingValues, boolean forceFlush) throws SQLException
192 if (forceFlush || (pendingValues >= PENDING_LIMIT))
194 int[] updateCounts = ps.executeBatch();
195 for (int i = 0; i < updateCounts.length; ++i) {
196 if (updateCounts[i] > 0) {
197 count += updateCounts[i];
205 String insertSql(Table table) {
206 StringBuilder sb = new StringBuilder("INSERT INTO ");
207 sb.append(table.getName())
208 .append(" VALUES (");
210 for (int i = 0; i < table.getNumColumns(); ++i) {
218 return sb.toString();
221 protected String selectSql(Column[] columns, Table[] tables, Condition[] conditions, Sort[] sorts, int limit)
223 StringBuilder sb = new StringBuilder("SELECT ");
225 boolean firstColumn = true;
226 for (Column column : columns) {
233 sb.append(column.getName());
238 boolean firstTable = true;
239 for (Table table : tables) {
246 sb.append(table.getName());
249 if (null != conditions && conditions.length > 0) {
250 sb.append(" WHERE ");
252 boolean firstCondition = true;
254 for (Condition condition : conditions) {
255 if (firstCondition) {
256 firstCondition = false;
262 sb.append(condition.getColumn().getName())
263 .append(condition.getOperation().getSql());
267 if (null != sorts && sorts.length > 0) {
268 sb.append(" ORDER BY ");
270 boolean firstSort = true;
272 for (Sort sort : sorts) {
280 sb.append(sort.getColumn().getName());
282 if (sort.getDirection().equals(Sort.Direction.ASCENDING)) {
292 sb.append(" LIMIT " + limit + " ");
295 return sb.toString();
298 protected String typeName(Type type) {
299 // Special case: TIMESTAMPTZ stored as INTEGER (milliseconds since the epoch)
300 // Reading a TIMESTAMPTZ back from the DB, and converting it to a java.util.Date,
301 // is fraught with peril. The best way around this is to store the dates in
302 // milliseconds-since-the-epoch (01.01.1970 00:00:00.000 UTC).
303 if (Type.TIMESTAMPTZ.equals(type)) {
304 return Type.INTEGER.toString();
307 return type.toString();
310 protected String createColumnSql(Column column)
312 String result = column.getName() + " " + typeName(column.getType());
313 if (column.getWidth() > 0) {
314 result += "(" + column.getWidth() + ")";
317 if (NOT_NULL == column.getNull()) {
318 result += " NOT NULL";
327 protected String createTableSql(Table table)
329 assert(null != table);
330 assert(null != table.getName());
331 assert(table.getNumColumns() > 0);
333 StringBuilder sb = new StringBuilder();
335 sb.append("CREATE TABLE ")
336 .append(table.getName())
339 for (int idx = 0; idx < table.getNumColumns(); ++idx) {
343 sb.append(createColumnSql(table.getColumn(idx)));
348 return sb.toString();
351 protected String dropTableSql(Table table) {
352 assert(null != table);
353 assert(null != table.getName());
355 return "DROP TABLE " + table.getName();
358 protected String createSequenceSql(Sequence seq) {
360 assert(null != seq.getName());
362 return "CREATE SEQUENCE " + seq.getName();
365 protected String dropSequenceSql(Sequence seq) {
367 assert(null != seq.getName());
369 return "DROP SEQUENCE " + seq.getName();
372 boolean isValidInsert(Table table, Object[][] values)
374 if (null == table) return false;
375 if (null == values) return false;
377 for (Object[] rowValues : values) {
378 if (rowValues.length != table.getNumColumns()) {
381 for (int idx = 0; idx < rowValues.length; ++idx) {
389 int executeUpdate(Connection con, String sql) throws SQLException
391 try (PreparedStatement ps = con.prepareStatement(sql))
393 return ps.executeUpdate();
395 catch (SQLException exc) {
396 throw new SQLException("Failed to executeUpdate: " + sql, exc);