001: /**
002: * Copyright (C) 2006 NetMind Consulting Bt.
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 3 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */package hu.netmind.persistence;
018:
019: import java.util.Map;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.Date;
023: import java.sql.Connection;
024: import java.sql.Types;
025: import hu.netmind.persistence.parser.Expression;
026: import hu.netmind.persistence.parser.ConstantTerm;
027: import hu.netmind.persistence.parser.ReferenceTerm;
028: import org.apache.log4j.Logger;
029:
030: /**
031: * MySQL database implementation.
032: * Limitations:
033: * <ul>
034: * <li>Date fields are stored only with seconds precision.</li>
035: * <li>Can not support more than 255 characters of map keys.</li>
036: * <li>InnoDB support must be compiled into MySQL to support</li>
037: * transactions.</li>
038: * </ul>
039: * @author Brautigam Robert
040: * @version CVS Revision: $Revision$
041: */
042: public class MysqlDatabaseImpl extends GenericDatabase {
043: private static final int STRING_LENGTH = 255;
044:
045: private static Logger logger = Logger
046: .getLogger(MysqlDatabaseImpl.class);
047:
048: public MysqlDatabaseImpl() {
049: super ();
050: }
051:
052: /**
053: * Get the limit component of statement, if it can be expressed in
054: * the current database with simple statement part.
055: * @param limits The limits to apply.
056: */
057: protected String getLimitStatement(String statement, Limits limits,
058: List types) {
059: StringBuffer result = new StringBuffer(statement);
060: if (limits.getLimit() > 0)
061: result.append(" limit " + limits.getLimit());
062: if (limits.getOffset() > 0)
063: result.append(" offset " + limits.getOffset());
064: return result.toString();
065: }
066:
067: /**
068: * Create table with given name, attribute types, and keys.
069: * Difference from generic: Mysql does not support 'text' type primary
070: * keys, so avoid in keys.
071: * @param tableName The table to create.
072: * @param attributeTypes The attribute names together with which
073: * java class they should hold.
074: */
075: protected DatabaseStatistics createTable(Connection connection,
076: String tableName, Map attributeTypes, List keyAttributeNames) {
077: DatabaseStatistics stats = new DatabaseStatistics();
078: // Create statement
079: StringBuffer statement = new StringBuffer("create table "
080: + tableName + " (");
081: Iterator iterator = attributeTypes.entrySet().iterator();
082: while (iterator.hasNext()) {
083: Map.Entry entry = (Map.Entry) iterator.next();
084: String sqlTypeName = getSQLTypeName(getSQLType(((Class) entry
085: .getValue())));
086: // If attribute was a text type and a key, then alter type to
087: // constrainted character string.
088: if ((sqlTypeName.startsWith("text"))
089: && (keyAttributeNames.contains(entry.getKey())))
090: statement.append(entry.getKey().toString()
091: + " varchar(" + STRING_LENGTH + "),");
092: else
093: statement.append(entry.getKey().toString() + " "
094: + sqlTypeName + ",");
095: }
096: if ((keyAttributeNames != null)
097: && (keyAttributeNames.size() > 0)) {
098: statement.append(" primary key (");
099: for (int i = 0; i < keyAttributeNames.size(); i++)
100: statement.append(keyAttributeNames.get(i) + ",");
101: statement
102: .delete(statement.length() - 1, statement.length());
103: statement.append(") ");
104: }
105: statement.delete(statement.length() - 1, statement.length());
106: statement.append(") type innodb");
107: // Execute statement
108: long startTime = System.currentTimeMillis();
109: executeUpdate(connection, statement.toString());
110: long endTime = System.currentTimeMillis();
111: stats.setSchemaCount(1);
112: stats.setSchemaTime(endTime - startTime);
113: // Create initial indexes (currently all attributes will be indexed)
114: stats.add(createIndexes(connection, tableName, attributeTypes));
115: return stats;
116: }
117:
118: /**
119: * Convert incoming values into database acceptable format.
120: */
121: protected Object getSQLValue(Object value) {
122: // Characters will be forwarded as strings
123: if (value instanceof Character)
124: return value.toString();
125: return super .getSQLValue(value);
126: }
127:
128: /**
129: * Convert incoming value from database into java format.
130: */
131: protected Object getJavaValue(Object value, int type, Class javaType) {
132: try {
133: if (value == null)
134: return null;
135: if (((Boolean.class.equals(javaType)) || (boolean.class
136: .equals(javaType)))
137: && (value instanceof Integer))
138: return new Boolean(((Integer) value).intValue() > 0);
139: if ((value instanceof byte[])
140: && (type == Types.LONGVARBINARY)
141: && (String.class.equals(javaType)))
142: return new String((byte[]) value, "utf8");
143: return super .getJavaValue(value, type, javaType);
144: } catch (StoreException e) {
145: throw e;
146: } catch (Exception e) {
147: throw new StoreException(
148: "conversion error tried to convert: " + value + "("
149: + value.getClass() + "), of sql type: "
150: + type + ", java type: " + javaType);
151: }
152: }
153:
154: /**
155: * Get the class for an sql type. Override timestamp to set default.
156: */
157: protected String getSQLTypeName(int sqltype) {
158: switch (sqltype) {
159: case Types.TINYINT:
160: return "boolean";
161: case Types.TIMESTAMP:
162: return "datetime";
163: case Types.LONGVARCHAR:
164: default:
165: return super .getSQLTypeName(sqltype);
166: }
167: }
168:
169: /**
170: * Transform 'ilike' to 'like', and 'like' to 'like binary' operators.
171: * @param expr The expression to possibly transform.
172: * @return A transformed expression.
173: */
174: protected Expression transformExpression(Expression expr) {
175: Expression result = new Expression(expr);
176: result.clear();
177: for (int i = 0; i < expr.size(); i++) {
178: Object item = expr.get(i);
179: if ("like".equals(item) || "=".equals(item)
180: || "!=".equals(item) || "<".equals(item)
181: || "<=".equals(item) || ">=".equals(item)
182: || ">".equals(item) || "<>".equals(item)) {
183: result.add(item);
184: // Now, if any of the arguments to the operator is
185: // a string, then we must apply 'binary' to make
186: // the operator case sensitive.
187: // Note: collection constant terms are not handled,
188: // because they may be empty anyway.
189: // Lhs and Rhs must exist, because these
190: // operators are infix operators.
191: Object lhs = expr.get(i - 1);
192: Object rhs = expr.get(i + 1);
193: if (("like".equals(item)) || (isStringType(lhs))
194: || (isStringType(rhs)))
195: result.add("binary");
196: } else if ("ilike".equals(item))
197: result.add("like");
198: else
199: result.add(item);
200: }
201: return result;
202: }
203:
204: /**
205: * Determine whether a term is string type or not.
206: * @return True if the term represents a String type, false if unknown.
207: */
208: private boolean isStringType(Object term) {
209: if (term instanceof ConstantTerm) {
210: ConstantTerm constantTerm = (ConstantTerm) term;
211: return (constantTerm.getValue() != null)
212: && (constantTerm.getValue() instanceof String);
213: }
214: if (term instanceof ReferenceTerm) {
215: ReferenceTerm refTerm = (ReferenceTerm) term;
216: return String.class.equals(getAttributeType(refTerm
217: .getTableName(), refTerm.getColumnName()));
218: }
219: return false;
220: }
221:
222: /**
223: * Get index creation statement for a given table and field.
224: * @return The statement to use, or null, of no such index can be
225: * created.
226: */
227: protected String getCreateIndexStatement(String indexName,
228: String tableName, String field, Class fieldClass) {
229: if (fieldClass.equals(String.class))
230: return "create index " + indexName + " on " + tableName
231: + " (" + field + "(" + STRING_LENGTH + "))";
232: return super.getCreateIndexStatement(indexName, tableName,
233: field, fieldClass);
234: }
235:
236: }
|