PageRenderTime 65ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/src/sql/java/StatementPeer.java

https://bitbucket.org/bedlaczech/fan-1.0
Java | 576 lines | 461 code | 59 blank | 56 comment | 88 complexity | 52f74ec1dc67590e426caa0659b96093 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. //
  2. // Copyright (c) 2007, John Sublett
  3. // Licensed under the Academic Free License version 3.0
  4. //
  5. // History:
  6. // 26 Dec 07 John Sublett Creation
  7. //
  8. package fan.sql;
  9. import java.sql.*;
  10. import java.util.HashMap;
  11. import java.util.Iterator;
  12. import fan.sys.*;
  13. import fan.sql.Statement;
  14. public class StatementPeer
  15. {
  16. public static StatementPeer make(Statement fan)
  17. {
  18. return new StatementPeer();
  19. }
  20. public void init(Statement self)
  21. {
  22. // at this point the conn and sql fields are configured,
  23. // figure out our auto-generated key mode
  24. this.isInsert = FanStr.indexIgnoreCase(self.sql, "insert ") != null;
  25. this.isAutoKeys = this.isInsert && self.conn.peer.supportsGetGenKeys;
  26. this.autoKeyMode = this.isAutoKeys ?
  27. java.sql.Statement.RETURN_GENERATED_KEYS :
  28. java.sql.Statement.NO_GENERATED_KEYS;
  29. }
  30. public Statement prepare(Statement self)
  31. {
  32. // Fan uses the ADO .NET prepared statement syntax, so
  33. // for Java the sql needs to be translated and the @param
  34. // syntax must be replaced with ?. It's not a simple
  35. // replace though because we need to keep the key/value
  36. // map.
  37. parse(self.sql);
  38. try
  39. {
  40. prepared = true;
  41. createStatement(self);
  42. }
  43. catch (SQLException ex)
  44. {
  45. throw SqlConnImplPeer.err(ex);
  46. }
  47. return self;
  48. }
  49. public List query(Statement self, Map params)
  50. {
  51. try
  52. {
  53. ResultSet rs = null;
  54. if (prepared)
  55. {
  56. setParameters(params);
  57. rs = ((PreparedStatement)stmt).executeQuery();
  58. }
  59. else
  60. {
  61. createStatement(self);
  62. rs = stmt.executeQuery(self.sql);
  63. }
  64. return toRows(rs);
  65. }
  66. catch (SQLException ex)
  67. {
  68. throw SqlConnImplPeer.err(ex);
  69. }
  70. finally
  71. {
  72. try { if (!prepared) stmt.close(); } catch (Exception ex) {}
  73. }
  74. }
  75. /**
  76. * Invoke the 'eachFunc' on every row in the result.
  77. */
  78. void each(ResultSet rs, Func eachFunc)
  79. throws SQLException
  80. {
  81. Cols cols = makeCols(rs);
  82. Row row = null;
  83. SqlUtil.SqlToFan[] converters = makeConverters(rs);
  84. while (rs.next())
  85. {
  86. if (row == null)
  87. row = makeRow(rs, cols, converters);
  88. else
  89. updateRow(rs, row, converters);
  90. eachFunc.call(row);
  91. }
  92. }
  93. /**
  94. * Map result set columns to Fan columns.
  95. * result set.
  96. */
  97. Cols makeCols(ResultSet rs)
  98. throws SQLException
  99. {
  100. // map the meta-data to a dynamic type
  101. ResultSetMetaData meta = rs.getMetaData();
  102. int numCols = meta.getColumnCount();
  103. List cols = new List(SqlUtil.colType, numCols);
  104. for (int i=0; i<numCols; ++i)
  105. {
  106. String name = meta.getColumnLabel(i+1);
  107. String typeName = meta.getColumnTypeName(i+1);
  108. Type fanType = SqlUtil.sqlToFanType(meta.getColumnType(i+1));
  109. if (fanType == null)
  110. {
  111. System.out.println("WARNING: Cannot map " + typeName + " to Fan type");
  112. fanType = Sys.StrType;
  113. }
  114. cols.add(Col.make(Long.valueOf(i), name, fanType, typeName));
  115. }
  116. return new Cols(cols);
  117. }
  118. /**
  119. * Make a row of the specified dynamic type and set the cell values
  120. * from the specified result set.
  121. */
  122. Row makeRow(ResultSet rs, Cols cols, SqlUtil.SqlToFan[] converters)
  123. throws SQLException
  124. {
  125. Row row = Row.make();
  126. int numCols = rs.getMetaData().getColumnCount();
  127. Object[] cells = new Object[numCols];
  128. row.peer.cols = cols;
  129. row.peer.cells = cells;
  130. for (int i=0; i<numCols; ++i)
  131. cells[i] = converters[i].toObj(rs, i+1);
  132. return row;
  133. }
  134. /**
  135. * Update an existing row with new values from the specified result set.
  136. */
  137. Object updateRow(ResultSet rs, Row row, SqlUtil.SqlToFan[] converters)
  138. throws SQLException
  139. {
  140. int numCols = rs.getMetaData().getColumnCount();
  141. Object[] cells = row.peer.cells;
  142. for (int i=0; i<numCols; ++i)
  143. cells[i] = converters[i].toObj(rs, i+1);
  144. return row;
  145. }
  146. /**
  147. * Make the list of converters for the specified result set.
  148. */
  149. SqlUtil.SqlToFan[] makeConverters(ResultSet rs)
  150. throws SQLException
  151. {
  152. int numCols = rs.getMetaData().getColumnCount();
  153. SqlUtil.SqlToFan[] converters = new SqlUtil.SqlToFan[numCols];
  154. for (int i=0; i<numCols; i++)
  155. converters[i] = SqlUtil.converter(rs, i+1);
  156. return converters;
  157. }
  158. /**
  159. * Convert the result set to a list of the 'of' type.
  160. */
  161. List toRows(ResultSet rs)
  162. throws SQLException
  163. {
  164. Cols cols = makeCols(rs);
  165. SqlUtil.SqlToFan[] converters = makeConverters(rs);
  166. List rows = new List(SqlUtil.rowType);
  167. while (rs.next()) rows.add(makeRow(rs, cols, converters));
  168. return rows;
  169. }
  170. public void queryEach(Statement self, Map params, Func eachFunc)
  171. {
  172. try
  173. {
  174. ResultSet rs = null;
  175. if (prepared)
  176. {
  177. setParameters(params);
  178. rs = ((PreparedStatement)stmt).executeQuery();
  179. }
  180. else
  181. {
  182. createStatement(self);
  183. rs = stmt.executeQuery(self.sql);
  184. }
  185. each(rs, eachFunc);
  186. }
  187. catch (SQLException ex)
  188. {
  189. throw SqlConnImplPeer.err(ex);
  190. }
  191. finally
  192. {
  193. try { if (!prepared) stmt.close(); } catch (Exception ex) {}
  194. }
  195. }
  196. public Object execute(Statement self, Map params)
  197. {
  198. try
  199. {
  200. if (prepared)
  201. {
  202. setParameters(params);
  203. boolean isResultSet = ((PreparedStatement)stmt).execute();
  204. return executeResult(self, isResultSet);
  205. }
  206. else
  207. {
  208. createStatement(self);
  209. try
  210. {
  211. boolean isResultSet = stmt.execute(self.sql, autoKeyMode);
  212. return executeResult(self, isResultSet);
  213. }
  214. finally
  215. {
  216. stmt.close();
  217. }
  218. }
  219. }
  220. catch (SQLException ex)
  221. {
  222. throw SqlConnImplPeer.err(ex);
  223. }
  224. }
  225. private Object executeResult(Statement self, boolean isResultSet)
  226. {
  227. try
  228. {
  229. // if result is ResultSet, then return Row[]
  230. if (isResultSet)
  231. {
  232. return toRows(stmt.getResultSet());
  233. }
  234. // if auto-generated keys, then return Int[]
  235. // Some databases like Oracle do not allow access to
  236. // keys as Int, so return keys as Str[]
  237. if (isAutoKeys)
  238. {
  239. ResultSet rs = stmt.getGeneratedKeys();
  240. List keys = null;
  241. while (rs.next())
  242. {
  243. // get key as Long or String
  244. Object key;
  245. try { key = rs.getLong(1); }
  246. catch (Exception e) { key = rs.getString(1); }
  247. // lazily create keys list with proper type
  248. if (keys == null)
  249. keys = new List(key instanceof Long ? Sys.IntType : Sys.StrType);
  250. keys.add(key);
  251. }
  252. if (keys != null) return keys;
  253. }
  254. // othertise return the update count
  255. return Long.valueOf(stmt.getUpdateCount());
  256. }
  257. catch (Exception e)
  258. {
  259. e.printStackTrace();
  260. }
  261. return Long.valueOf(-1);
  262. }
  263. public List more(Statement self)
  264. {
  265. try
  266. {
  267. if (stmt.getMoreResults())
  268. return toRows(stmt.getResultSet());
  269. else
  270. return null;
  271. }
  272. catch (SQLException ex)
  273. {
  274. throw SqlConnImplPeer.err(ex);
  275. }
  276. }
  277. /**
  278. * Set the parameters for the underlying prepared statement
  279. * using the values specified in the map.
  280. */
  281. private void setParameters(Map params)
  282. {
  283. if (!prepared)
  284. throw SqlErr.make("Statement has not been prepared.");
  285. PreparedStatement pstmt = (PreparedStatement)stmt;
  286. Iterator i = paramMap.entrySet().iterator();
  287. while (i.hasNext())
  288. {
  289. java.util.Map.Entry entry = (java.util.Map.Entry)i.next();
  290. String key = (String)entry.getKey();
  291. Object value = params.get(key);
  292. Object jobj = SqlUtil.fanToSqlObj(value);
  293. int[] locs = (int[])entry.getValue();
  294. for (int j = 0; j < locs.length; j++)
  295. {
  296. try
  297. {
  298. pstmt.setObject(locs[j], jobj);
  299. }
  300. catch (SQLException e)
  301. {
  302. throw SqlErr.make("Param name='" + key + "' class='" + value.getClass().getName() + "'; " +
  303. e.getMessage(), Err.make(e));
  304. }
  305. }
  306. }
  307. }
  308. public void close(Statement self)
  309. {
  310. try
  311. {
  312. stmt.close();
  313. }
  314. catch (SQLException ex)
  315. {
  316. throw SqlConnImplPeer.err(ex);
  317. }
  318. }
  319. public Long limit(Statement self)
  320. {
  321. return limit <= 0 ? null : Long.valueOf(limit);
  322. }
  323. public void limit(Statement self, Long limit)
  324. {
  325. this.limit = 0;
  326. if (limit != null && limit.longValue() < Integer.MAX_VALUE)
  327. this.limit = limit.intValue();
  328. }
  329. private void createStatement(Statement self)
  330. throws SQLException
  331. {
  332. if (prepared)
  333. stmt = self.conn.peer.jconn.prepareStatement(translated, autoKeyMode);
  334. else
  335. stmt = self.conn.peer.jconn.createStatement();
  336. if (limit > 0) stmt.setMaxRows(limit);
  337. }
  338. //////////////////////////////////////////////////////////////////////////
  339. // Parse
  340. //////////////////////////////////////////////////////////////////////////
  341. private void parse(String sql)
  342. {
  343. StringBuffer jsql = new StringBuffer(sql.length());
  344. int index = sql.indexOf('@');
  345. // make sure the sql has at least one parameter
  346. // before bothering with the parse
  347. if (index == -1)
  348. {
  349. translated = sql;
  350. paramMap = new HashMap();
  351. return;
  352. }
  353. Tokenizer t = new Tokenizer(sql);
  354. String s;
  355. int pIndex = 1;
  356. while ((s = t.next()) != null)
  357. {
  358. if (s.length() == 0) continue;
  359. if (s.charAt(0) == '@')
  360. {
  361. if (s.length() == 1)
  362. jsql.append(s);
  363. else
  364. {
  365. if (paramMap == null) paramMap = new HashMap();
  366. // param
  367. String key = s.substring(1);
  368. int[] locs = (int[])paramMap.get(key);
  369. if (locs == null)
  370. {
  371. locs = new int[] { pIndex };
  372. paramMap.put(key, locs);
  373. }
  374. else
  375. {
  376. int[] temp = new int[locs.length+1];
  377. System.arraycopy(locs, 0, temp, 0, locs.length);
  378. temp[locs.length] = pIndex;
  379. paramMap.put(key, temp);
  380. }
  381. pIndex++;
  382. jsql.append("?");
  383. }
  384. }
  385. else
  386. jsql.append(s);
  387. }
  388. translated = jsql.toString();
  389. }
  390. //////////////////////////////////////////////////////////////////////////
  391. // Tokenizer
  392. //////////////////////////////////////////////////////////////////////////
  393. private class Tokenizer
  394. {
  395. public Tokenizer(String sql)
  396. {
  397. this.sql = sql;
  398. len = sql.length();
  399. current = 0;
  400. }
  401. public String next()
  402. {
  403. switch (mode)
  404. {
  405. case MODE_TEXT: return text();
  406. case MODE_PARAM: return param();
  407. case MODE_QUOTE: return quotedText();
  408. case MODE_END: return null;
  409. default: return null;
  410. }
  411. }
  412. private String text()
  413. {
  414. int start = current;
  415. while (current != len)
  416. {
  417. int ch = sql.charAt(current);
  418. if (ch == '@') { mode = MODE_PARAM; break; }
  419. if (ch == '\'') { mode = MODE_QUOTE; break; }
  420. current++;
  421. if (current == len) mode = MODE_END;
  422. }
  423. return sql.substring(start, current);
  424. }
  425. private String param()
  426. {
  427. int start = current;
  428. current++;
  429. if (current == len)
  430. throw SqlErr.make("Invalid parameter. Parameter name required.");
  431. int ch = sql.charAt(current);
  432. // @@ means we really wanted @
  433. if (sql.charAt(current) == '@')
  434. {
  435. current++;
  436. return "@";
  437. }
  438. while (current != len)
  439. {
  440. ch = sql.charAt(current);
  441. boolean valid =
  442. ((ch >= 'a') && (ch <= 'z')) ||
  443. ((ch >= 'A') && (ch <= 'Z')) ||
  444. ((ch >= '0') && (ch <= '9')) ||
  445. (ch == '_');
  446. if (!valid)
  447. {
  448. if (ch == '\'')
  449. {
  450. mode = MODE_QUOTE;
  451. break;
  452. }
  453. else
  454. {
  455. mode = MODE_TEXT;
  456. break;
  457. }
  458. }
  459. current++;
  460. if (current == len) mode = MODE_END;
  461. }
  462. if (current == start+1)
  463. throw SqlErr.make("Invalid parameter. Parameter name required.");
  464. return sql.substring(start, current);
  465. }
  466. private String quotedText()
  467. {
  468. int start = current;
  469. int end = -1;
  470. current++;
  471. if (current == len)
  472. throw SqlErr.make("Unterminated quoted text. Expecting '.");
  473. while (current != len)
  474. {
  475. int ch = sql.charAt(current);
  476. if (ch == '\'')
  477. {
  478. end = current;
  479. current++;
  480. break;
  481. }
  482. current++;
  483. }
  484. if (end == -1)
  485. throw SqlErr.make("Unterminated quoted text. Expecting '.");
  486. if (current == len)
  487. mode = MODE_END;
  488. else
  489. {
  490. int ch = sql.charAt(current);
  491. if (ch == '@')
  492. mode = MODE_PARAM;
  493. else if (ch == '\'')
  494. mode = MODE_QUOTE;
  495. else
  496. mode = MODE_TEXT;
  497. }
  498. return sql.substring(start, end+1);
  499. }
  500. String sql;
  501. int mode = MODE_TEXT;
  502. int len;
  503. int current;
  504. }
  505. private static final int MODE_TEXT = 0;
  506. private static final int MODE_QUOTE = 1;
  507. private static final int MODE_PARAM = 2;
  508. private static final int MODE_END = 3;
  509. private boolean prepared = false;
  510. private String translated;
  511. private java.sql.Statement stmt;
  512. private HashMap paramMap;
  513. private int limit = 0; // limit field value
  514. private boolean isInsert; // does sql contain insert keyword
  515. private boolean isAutoKeys; // isInsert and connector supports auto-gen keys
  516. private int autoKeyMode; // JDBC constant for auto-gen keys
  517. }