]> source.dussan.org Git - jackcess.git/commitdiff
initial work towards supporting reading/formatting access queries
authorJames Ahlborn <jtahlborn@yahoo.com>
Mon, 10 Nov 2008 03:05:42 +0000 (03:05 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Mon, 10 Nov 2008 03:05:42 +0000 (03:05 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@387 f203690c-595d-4dc9-a70b-905162fa7fd2

14 files changed:
src/changes/changes.xml
src/java/com/healthmarketscience/jackcess/query/AppendQuery.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/query/BaseSelectQuery.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/query/CrossTabQuery.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/query/DataDefinitionQuery.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/query/DeleteQuery.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/query/MakeTableQuery.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/query/PassthroughQuery.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/query/Query.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/query/QueryFormat.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/query/SelectQuery.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/query/UnionQuery.java [new file with mode: 0644]
src/java/com/healthmarketscience/jackcess/query/UpdateQuery.java [new file with mode: 0644]
test/src/java/com/healthmarketscience/jackcess/query/QueryTest.java [new file with mode: 0644]

index ff1c14609e53d85f412ef341bf257116b35a6fff..fb479cbf1287854c891cab3f8a03b5f3082732c9 100644 (file)
@@ -5,6 +5,11 @@
     <author email="jahlborn@users.sf.net">James Ahlborn</author>
   </properties>
   <body>
+    <release version="1.1.19" date="TBD">
+      <action dev="jahlborn" type="add">
+        Add Query reading support.
+      </action>
+    </release>
     <release version="1.1.18" date="2008-11-08">
       <action dev="jahlborn" type="add">
         Add ErrorHandler utility for customizing error handling during row
diff --git a/src/java/com/healthmarketscience/jackcess/query/AppendQuery.java b/src/java/com/healthmarketscience/jackcess/query/AppendQuery.java
new file mode 100644 (file)
index 0000000..38842e1
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess.query;
+
+import java.util.List;
+
+import static com.healthmarketscience.jackcess.query.QueryFormat.*;
+
+
+/**
+ * @author James Ahlborn
+ */
+public class AppendQuery extends BaseSelectQuery 
+{
+
+  public AppendQuery(String name, List<Row> rows, int objectId) {
+    super(name, rows, objectId, Type.APPEND);
+  }
+
+  public String getTargetTable() {
+    return getTypeRow().name1;
+  }
+
+  public String getRemoteDb() {
+    return getTypeRow().name2;
+  }
+
+  protected List<Row> getValueRows() {
+    return filterRowsByFlag(super.getColumnRows(), APPEND_VALUE_FLAG);
+  }
+
+  @Override
+  protected List<Row> getColumnRows() {
+    return filterRowsByNotFlag(super.getColumnRows(), APPEND_VALUE_FLAG);
+  }
+
+  public List<String> getValues() {
+    return new RowFormatter(getValueRows()) {
+        @Override protected void format(StringBuilder builder, Row row) {
+          builder.append(row.expression);
+        }
+      }.format();
+  }
+
+  @Override
+  protected void toSQLString(StringBuilder builder)
+  {
+    builder.append("INSERT INTO ").append(getTargetTable());
+    toRemoteDb(builder, getRemoteDb());
+    builder.append(NEWLINE);
+    List<String> values = getValues();
+    if(!values.isEmpty()) {
+      builder.append("VALUES (").append(values).append(")");
+    } else {
+      toSQLSelectString(builder, true);
+    }
+  }
+  
+}
diff --git a/src/java/com/healthmarketscience/jackcess/query/BaseSelectQuery.java b/src/java/com/healthmarketscience/jackcess/query/BaseSelectQuery.java
new file mode 100644 (file)
index 0000000..24a6f96
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess.query;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import static com.healthmarketscience.jackcess.query.QueryFormat.*;
+
+
+/**
+ * @author James Ahlborn
+ */
+public abstract class BaseSelectQuery extends Query 
+{
+
+  protected BaseSelectQuery(String name, List<Row> rows, int objectId,
+                            Type type) {
+    super(name, rows, objectId, type);
+  }
+
+  protected void toSQLSelectString(StringBuilder builder,
+                                   boolean useSelectPrefix) 
+  {
+    if(useSelectPrefix) {
+      builder.append("SELECT ");
+      String selectType = getSelectType();
+      if(!DEFAULT_TYPE.equals(selectType)) {
+        builder.append(selectType).append(" ");
+      }
+    }
+
+    builder.append(getSelectColumns());
+    toSelectInto(builder);
+
+    List<String> fromTables = getFromTables();
+    if(!fromTables.isEmpty()) {
+      builder.append(NEWLINE).append("FROM ").append(fromTables);
+      toRemoteDb(builder, getFromRemoteDb());
+    }
+
+    String whereExpr = getWhereExpression();
+    if(whereExpr != null) {
+      builder.append(NEWLINE).append("WHERE ").append(whereExpr);
+    }
+
+    List<String> groupings = getGroupings();
+    if(!groupings.isEmpty()) {
+      builder.append(NEWLINE).append("GROUP BY ").append(groupings);
+    }
+
+    String havingExpr = getHavingExpression();
+    if(havingExpr != null) {
+      builder.append(NEWLINE).append("HAVING ").append(havingExpr);
+    }
+
+    List<String> orderings = getOrderings();
+    if(!orderings.isEmpty()) {
+      builder.append(NEWLINE).append("ORDER BY ").append(orderings);
+    }
+  }
+
+  public String getSelectType()
+  {
+    if(hasFlag(DISTINCT_SELECT_TYPE)) {
+      return "DISTINCT";
+    }
+
+    if(hasFlag(DISTINCT_ROW_SELECT_TYPE)) {
+      return "DISTINCTROW";
+    }
+
+    if(hasFlag(TOP_SELECT_TYPE)) {
+      StringBuilder builder = new StringBuilder();
+      builder.append("TOP ").append(getFlagRow().name1);
+      if(hasFlag(PERCENT_SELECT_TYPE)) {
+        builder.append(" PERCENT");
+      }
+      return builder.toString();
+    }
+
+    return DEFAULT_TYPE;
+  }
+
+  public List<String> getSelectColumns() 
+  {
+    List<String> result = (new RowFormatter(getColumnRows()) {
+        @Override protected void format(StringBuilder builder, Row row) {
+          // note column expression are always quoted appropriately
+          builder.append(row.expression);
+          toAlias(builder, row.name1);
+        }
+      }).format();
+    if(hasFlag(SELECT_STAR_SELECT_TYPE)) {
+      result.add("*");
+    }
+    return result;
+  }
+
+  protected void toSelectInto(StringBuilder builder)
+  {
+    // base does nothing
+  }
+
+  @Override
+  public List<String> getFromTables() 
+  {
+    return super.getFromTables();
+  }
+
+  @Override
+  public String getFromRemoteDb() 
+  {
+    return super.getFromRemoteDb();
+  }
+
+  @Override
+  public String getWhereExpression()
+  {
+    return super.getWhereExpression();
+  }
+
+  public List<String> getGroupings() 
+  {
+    return (new RowFormatter(getGroupByRows()) {
+        @Override protected void format(StringBuilder builder, Row row) {
+          builder.append(row.expression);
+        }
+      }).format();
+  }
+
+  public String getHavingExpression()
+  {
+    return getHavingRow().expression;
+  }
+
+  @Override
+  public List<String> getOrderings() 
+  {
+    return super.getOrderings();
+  }
+  
+}
diff --git a/src/java/com/healthmarketscience/jackcess/query/CrossTabQuery.java b/src/java/com/healthmarketscience/jackcess/query/CrossTabQuery.java
new file mode 100644 (file)
index 0000000..e750998
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess.query;
+
+import java.util.List;
+
+import static com.healthmarketscience.jackcess.query.QueryFormat.*;
+
+
+/**
+ * @author James Ahlborn
+ */
+public class CrossTabQuery extends BaseSelectQuery 
+{
+
+  public CrossTabQuery(String name, List<Row> rows, int objectId) {
+    super(name, rows, objectId, Type.CROSS_TAB);
+  }
+
+  protected Row getTransformRow() {
+    return getUniqueRow(
+        filterRowsByNotFlag(super.getColumnRows(), 
+                            (short)(CROSSTAB_PIVOT_FLAG | 
+                                    CROSSTAB_NORMAL_FLAG)));
+  }
+
+  @Override
+  protected List<Row> getColumnRows() {
+    return filterRowsByFlag(super.getColumnRows(), CROSSTAB_NORMAL_FLAG);
+  }
+
+  @Override
+  protected List<Row> getGroupByRows() {
+    return filterRowsByFlag(super.getGroupByRows(), CROSSTAB_NORMAL_FLAG);
+  }
+
+  protected Row getPivotRow() {
+    return getUniqueRow(filterRowsByFlag(super.getColumnRows(), 
+                                         CROSSTAB_PIVOT_FLAG));
+  }
+
+  public String getTransformExpression() {
+    Row row = getTransformRow();
+    if(row.expression == null) {
+      return null;
+    }
+    // note column expression are always quoted appropriately
+    StringBuilder builder = new StringBuilder(row.expression);
+    return toAlias(builder, row.name1).toString();
+  }
+
+  public String getPivotExpression() {
+    return getPivotRow().expression;
+  }
+
+  @Override
+  protected void toSQLString(StringBuilder builder)
+  {
+    String transformExpr = getTransformExpression();
+    if(transformExpr != null) {
+      builder.append("TRANSFORM ").append(transformExpr).append(NEWLINE);
+    }
+
+    toSQLSelectString(builder, true);
+
+    builder.append(NEWLINE).append("PIVOT ")
+      .append(getPivotExpression());
+  }
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/query/DataDefinitionQuery.java b/src/java/com/healthmarketscience/jackcess/query/DataDefinitionQuery.java
new file mode 100644 (file)
index 0000000..282a6a0
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess.query;
+
+import java.util.List;
+
+
+/**
+ * SQL query which represents a DDL query.
+ * 
+ * @author James Ahlborn
+ */
+public class DataDefinitionQuery extends Query 
+{
+
+  public DataDefinitionQuery(String name, List<Row> rows, int objectId) {
+    super(name, rows, objectId, Type.DATA_DEFINITION);
+  }
+
+  public String getDDLString() {
+    return getTypeRow().expression;
+  }
+
+  @Override
+  protected boolean supportsStandardClauses() {
+    return false;
+  }
+
+  @Override
+  protected void toSQLString(StringBuilder builder)
+  {
+    String ddl = getDDLString();
+    if(ddl != null) {
+      builder.append(ddl);
+    }
+  }
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/query/DeleteQuery.java b/src/java/com/healthmarketscience/jackcess/query/DeleteQuery.java
new file mode 100644 (file)
index 0000000..b2d0bc9
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess.query;
+
+import java.util.List;
+
+
+/**
+ * @author James Ahlborn
+ */
+public class DeleteQuery extends BaseSelectQuery 
+{
+
+  public DeleteQuery(String name, List<Row> rows, int objectId) {
+    super(name, rows, objectId, Type.DELETE);
+  }
+
+  @Override
+  protected void toSQLString(StringBuilder builder)
+  {
+    builder.append("DELETE ");
+    toSQLSelectString(builder, false);
+  }
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/query/MakeTableQuery.java b/src/java/com/healthmarketscience/jackcess/query/MakeTableQuery.java
new file mode 100644 (file)
index 0000000..4d62bba
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess.query;
+
+import java.util.List;
+
+import static com.healthmarketscience.jackcess.query.QueryFormat.*;
+
+
+/**
+ * @author James Ahlborn
+ */
+public class MakeTableQuery extends BaseSelectQuery 
+{
+
+  public MakeTableQuery(String name, List<Row> rows, int objectId) {
+    super(name, rows, objectId, Type.MAKE_TABLE);
+  }
+
+  public String getTargetTable() {
+    return getTypeRow().name1;
+  }
+
+  public String getRemoteDb() {
+    return getTypeRow().name2;
+  }
+
+  @Override
+  protected void toSelectInto(StringBuilder builder) 
+  {
+    builder.append(" INTO ").append(getTargetTable());
+    toRemoteDb(builder, getRemoteDb());
+  }
+
+  @Override
+  protected void toSQLString(StringBuilder builder)
+  {
+    toSQLSelectString(builder, true);
+  }  
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/query/PassthroughQuery.java b/src/java/com/healthmarketscience/jackcess/query/PassthroughQuery.java
new file mode 100644 (file)
index 0000000..fc2fc61
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess.query;
+
+import java.util.List;
+
+
+/**
+ * SQL query which represents a query which will be executed via ODBC.
+ * 
+ * @author James Ahlborn
+ */
+public class PassthroughQuery extends Query 
+{
+
+  public PassthroughQuery(String name, List<Row> rows, int objectId) {
+    super(name, rows, objectId, Type.PASSTHROUGH);
+  }
+
+  public String getConnectionString() {
+    return getTypeRow().name1;
+  }
+
+  public String getPassthroughString() {
+    return getTypeRow().expression;
+  }
+
+  @Override
+  protected boolean supportsStandardClauses() {
+    return false;
+  }
+
+  @Override
+  protected void toSQLString(StringBuilder builder)
+  {
+    String pt = getPassthroughString();
+    if(pt != null) {
+      builder.append(pt);
+    }
+  }
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/query/Query.java b/src/java/com/healthmarketscience/jackcess/query/Query.java
new file mode 100644 (file)
index 0000000..7a78d79
--- /dev/null
@@ -0,0 +1,636 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess.query;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import static com.healthmarketscience.jackcess.query.QueryFormat.*;
+
+
+/**
+ * @author James Ahlborn
+ */
+public abstract class Query 
+{
+  protected static final Log LOG = LogFactory.getLog(Query.class);  
+
+  private static final Row EMPTY_ROW = 
+    new Row(Collections.<String,Object>emptyMap());
+
+  public enum Type 
+  {
+    SELECT(SELECT_QUERY_OBJECT_FLAG, 1),
+    MAKE_TABLE(MAKE_TABLE_QUERY_OBJECT_FLAG, 2),
+    APPEND(APPEND_QUERY_OBJECT_FLAG, 3),
+    UPDATE(UPDATE_QUERY_OBJECT_FLAG, 4),
+    DELETE(DELETE_QUERY_OBJECT_FLAG, 5),
+    CROSS_TAB(CROSS_TAB_QUERY_OBJECT_FLAG, 6),
+    DATA_DEFINITION(DATA_DEF_QUERY_OBJECT_FLAG, 7),
+    PASSTHROUGH(PASSTHROUGH_QUERY_OBJECT_FLAG, 8),
+    UNION(UNION_QUERY_OBJECT_FLAG, 9),
+    UNKNOWN(-1, -1);
+
+    private final int _objectFlag; 
+    private final short _value;
+
+    private Type(int objectFlag, int value) {
+      _objectFlag = objectFlag;
+      _value = (short)value;
+    }
+
+    public int getObjectFlag() {
+      return _objectFlag;
+    }
+
+    public short getValue() {
+      return _value;
+    }
+  }
+
+  private final String _name;
+  private final List<Row> _rows;
+  private final int _objectId;
+  private final Type _type;
+
+  protected Query(String name, List<Row> rows, int objectId, Type type) {
+    _name = name;
+    _rows = rows;
+    _objectId = objectId;
+    _type = type;
+
+    if(type != Type.UNKNOWN) {
+      short foundType = getShortValue(getQueryType(rows),
+                                      _type.getValue());
+      if(foundType != _type.getValue()) {
+        throw new IllegalStateException("Unexpected query type " + foundType);
+      }
+    }
+  }
+
+  public String getName() {
+    return _name;
+  }
+
+  public Type getType() {
+    return _type;
+  }
+
+  public int getObjectId() {
+    return _objectId;
+  }
+
+  public int getObjectFlag() {
+    return getType().getObjectFlag();
+  }
+
+  public List<Row> getRows() {
+    return _rows;
+  }
+
+  protected List<Row> getRowsByAttribute(Byte attribute) {
+    return getRowsByAttribute(getRows(), attribute);
+  }
+
+  protected Row getRowByAttribute(Byte attribute) {
+    return getUniqueRow(getRowsByAttribute(getRows(), attribute));
+  }
+
+  protected Row getTypeRow() {
+    return getRowByAttribute(TYPE_ATTRIBUTE);
+  }
+
+  protected List<Row> getParameterRows() {
+    return getRowsByAttribute(PARAMETER_ATTRIBUTE);
+  }
+
+  protected Row getFlagRow() {
+    return getRowByAttribute(FLAG_ATTRIBUTE);
+  }
+
+  protected Row getRemoteDatabaseRow() {
+    return getRowByAttribute(REMOTEDB_ATTRIBUTE);
+  }
+
+  protected List<Row> getTableRows() {
+    return getRowsByAttribute(TABLE_ATTRIBUTE);
+  }
+
+  protected List<Row> getColumnRows() {
+    return getRowsByAttribute(COLUMN_ATTRIBUTE);
+  }
+
+  protected List<Row> getJoinRows() {
+    return getRowsByAttribute(JOIN_ATTRIBUTE);
+  }
+
+  protected Row getWhereRow() {
+    return getRowByAttribute(WHERE_ATTRIBUTE);
+  }
+
+  protected List<Row> getGroupByRows() {
+    return getRowsByAttribute(GROUPBY_ATTRIBUTE);
+  }
+
+  protected Row getHavingRow() {
+    return getRowByAttribute(HAVING_ATTRIBUTE);
+  }
+
+  protected List<Row> getOrderByRows() {
+    return getRowsByAttribute(ORDERBY_ATTRIBUTE);
+  }
+
+  protected abstract void toSQLString(StringBuilder builder);
+
+  protected void toSQLParameterString(StringBuilder builder) {
+    // handle any parameters
+    List<String> params = getParameters();
+    if(!params.isEmpty()) {
+      builder.append("PARAMETERS ").append(params)
+        .append(";").append(NEWLINE);
+    }
+  }
+
+  public List<String> getParameters() 
+  {
+    return (new RowFormatter(getParameterRows()) {
+        @Override protected void format(StringBuilder builder, Row row) {
+          String name = row.name1;
+          String typeName = PARAM_TYPE_MAP.get(row.flag);
+          if(typeName == null) {
+            throw new IllegalStateException("Unknown param type " + row.flag);
+          }
+              
+          builder.append(name).append(" ").append(typeName);
+          if((TEXT_FLAG.equals(row.flag)) && (getIntValue(row.extra, 0) > 0)) {
+            builder.append("(").append(row.extra).append(")");
+          }
+        }
+      }).format();
+  }
+
+  protected List<String> getFromTables() 
+  {
+    List<Join> joinExprs = new ArrayList<Join>();
+    for(Row table : getTableRows()) {
+      StringBuilder builder = new StringBuilder();
+
+      if(table.expression != null) {
+        toQuotedExpr(builder, table.expression).append(IDENTIFIER_SEP_CHAR);
+      }
+      if(table.name1 != null) {
+        toOptionalQuotedExpr(builder, table.name1, true);
+      }
+      toAlias(builder, table.name2);
+
+      String key = ((table.name2 != null) ? table.name2 : table.name1);
+      joinExprs.add(new Join(key, builder.toString()));
+    }
+
+
+    List<Row> joins = getJoinRows();
+    if(!joins.isEmpty()) {
+
+      for(Row join : joins) {
+
+        String fromTable = join.name1;
+        String toTable = join.name2;
+      
+        Join fromExpr = getJoinExpr(fromTable, joinExprs);
+        Join toExpr = getJoinExpr(toTable, joinExprs);
+        String joinType = JOIN_TYPE_MAP.get(join.flag);
+        if(joinType == null) {
+            throw new IllegalStateException("Unknown join type " + join.flag);
+        }
+
+        String expr = new StringBuilder().append(fromExpr)
+          .append(joinType).append(toExpr).append(" ON ")
+          .append(join.expression).toString();
+
+        fromExpr.join(toExpr, expr);
+        joinExprs.add(fromExpr);
+      }
+    }
+
+    List<String> result = new AppendableList<String>();
+    for(Join joinExpr : joinExprs) {
+      result.add(joinExpr.expression);
+    }
+
+    return result;
+  }
+
+  private Join getJoinExpr(String table, List<Join> joinExprs)
+  {
+    for(Iterator<Join> iter = joinExprs.iterator(); iter.hasNext(); ) {
+      Join joinExpr = iter.next();
+      if(joinExpr.tables.contains(table)) {
+        iter.remove();
+        return joinExpr;
+      }
+    }
+    throw new IllegalStateException("Cannot find join table " + table);
+  }
+
+  protected String getFromRemoteDb() 
+  {
+    return getRemoteDatabaseRow().name1;
+  }
+
+  protected String getWhereExpression()
+  {
+    return getWhereRow().expression;
+  }
+
+  protected List<String> getOrderings() 
+  {
+    return (new RowFormatter(getOrderByRows()) {
+        @Override protected void format(StringBuilder builder, Row row) {
+          builder.append(row.expression);
+          if(DESCENDING_FLAG.equalsIgnoreCase(row.name1)) {
+            builder.append(" DESC");
+          }
+        }
+      }).format();
+  }
+
+  public String getOwnerAccessType() {
+    return(hasFlag(OWNER_ACCESS_SELECT_TYPE) ?
+           "WITH OWNERACCESS OPTION" : DEFAULT_TYPE);
+  }
+
+  protected boolean hasFlag(int flagMask)
+  {
+    return hasFlag(getFlagRow(), flagMask);
+  }
+
+  protected boolean supportsStandardClauses() {
+    return true;
+  }
+
+  public String toSQLString() 
+  {
+    StringBuilder builder = new StringBuilder();
+    if(supportsStandardClauses()) {
+      toSQLParameterString(builder);
+    }
+
+    toSQLString(builder);
+
+    if(supportsStandardClauses()) {
+
+      String accessType = getOwnerAccessType();
+      if(!DEFAULT_TYPE.equals(accessType)) {
+        builder.append(NEWLINE).append(accessType);
+      }
+      
+      builder.append(";");
+    }
+    return builder.toString();
+  }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
+
+  public static Query create(int objectFlag, String name, List<Row> rows, 
+                             int objectId)
+  {
+    try {
+      switch(objectFlag) {
+      case SELECT_QUERY_OBJECT_FLAG:
+        return new SelectQuery(name, rows, objectId);
+      case MAKE_TABLE_QUERY_OBJECT_FLAG:
+        return new MakeTableQuery(name, rows, objectId);
+      case APPEND_QUERY_OBJECT_FLAG:
+        return new AppendQuery(name, rows, objectId);
+      case UPDATE_QUERY_OBJECT_FLAG:
+        return new UpdateQuery(name, rows, objectId);
+      case DELETE_QUERY_OBJECT_FLAG:
+        return new DeleteQuery(name, rows, objectId);
+      case CROSS_TAB_QUERY_OBJECT_FLAG:
+        return new CrossTabQuery(name, rows, objectId);
+      case DATA_DEF_QUERY_OBJECT_FLAG:
+        return new DataDefinitionQuery(name, rows, objectId);
+      case PASSTHROUGH_QUERY_OBJECT_FLAG:
+        return new PassthroughQuery(name, rows, objectId);
+      case UNION_QUERY_OBJECT_FLAG:
+        return new UnionQuery(name, rows, objectId);
+      default:
+        // unknown querytype
+        throw new IllegalStateException(
+            "unknown query object flag " + objectFlag);
+      }
+    } catch(IllegalStateException e) {
+      LOG.warn("Failed parsing query", e);
+    }
+
+    // return unknown query
+    return new UnknownQuery(name, rows, objectId, objectFlag);
+  }
+
+  private static Short getQueryType(List<Row> rows)
+  {
+    return getUniqueRow(getRowsByAttribute(rows, TYPE_ATTRIBUTE)).flag;
+  }
+
+  private static List<Row> getRowsByAttribute(List<Row> rows, Byte attribute) {
+    List<Row> result = new ArrayList<Row>();
+    for(Row row : rows) {
+      if(attribute.equals(row.attribute)) {
+        result.add(row);
+      }
+    }
+    return result;
+  }
+
+  protected static Row getUniqueRow(List<Row> rows) {
+    if(rows.size() == 1) {
+      return rows.get(0);
+    }
+    if(rows.isEmpty()) {
+      return EMPTY_ROW;
+    }
+    throw new IllegalStateException("Unexpected number of rows for" + rows);
+  }
+
+  protected static List<Row> filterRowsByFlag(
+      List<Row> rows, final short flag) 
+  {
+    return new RowFilter() {
+        @Override protected boolean keep(Row row) {
+          return hasFlag(row, flag);
+        }
+      }.filter(rows);
+  }
+
+  protected static List<Row> filterRowsByNotFlag(
+      List<Row> rows, final short flag) 
+  {
+    return new RowFilter() {
+        @Override protected boolean keep(Row row) {
+          return !hasFlag(row, flag);
+        }
+      }.filter(rows);
+  }
+
+  protected static boolean hasFlag(Row row, int flagMask)
+  {
+    return((getShortValue(row.flag, 0) & flagMask) != 0);
+  }
+
+  protected static short getShortValue(Short s, int def) {
+    return ((s != null) ? (short)s : (short)def);
+  }
+
+  protected static int getIntValue(Integer i, int def) {
+    return ((i != null) ? (int)i : def);
+  }
+
+  protected static StringBuilder toOptionalQuotedExpr(StringBuilder builder, 
+                                                      String fullExpr,
+                                                      boolean isIdentifier)
+  {
+    String[] exprs = (isIdentifier ? 
+                      IDENTIFIER_SEP_PAT.split(fullExpr) : 
+                      new String[]{fullExpr});
+    for(int i = 0; i < exprs.length; ++i) {
+      String expr = exprs[i];
+      if(QUOTABLE_CHAR_PAT.matcher(expr).find()) {
+        toQuotedExpr(builder, expr);
+      } else {
+        builder.append(expr);
+      }
+      if(i < (exprs.length - 1)) {
+        builder.append(IDENTIFIER_SEP_CHAR);
+      }
+    }
+    return builder;
+  }
+
+  protected static StringBuilder toQuotedExpr(StringBuilder builder, 
+                                              String expr)
+  {
+    return builder.append("[").append(expr).append("]");
+  }
+
+  protected static StringBuilder toRemoteDb(StringBuilder builder,
+                                            String remoteDb) {
+    if(remoteDb != null) {
+      builder.append(" IN ").append(remoteDb);
+    }
+    return builder;
+  }
+
+  protected static StringBuilder toAlias(StringBuilder builder,
+                                         String alias) {
+    if(alias != null) {
+      toOptionalQuotedExpr(builder.append(" AS "), alias, false);
+    }
+    return builder;
+  }
+
+  private static class UnknownQuery extends Query
+  {
+    private final int _objectFlag;
+
+    private UnknownQuery(String name, List<Row> rows, int objectId, 
+                         int objectFlag) 
+    {
+      super(name, rows, objectId, Type.UNKNOWN);
+      _objectFlag = objectFlag;
+    }
+
+    @Override
+    public int getObjectFlag() {
+      return _objectFlag;
+    }
+
+    @Override
+    protected void toSQLString(StringBuilder builder) {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  public static class Row
+  {
+    public final Byte attribute;
+    public final String expression;
+    public final Short flag;
+    public final Integer extra;
+    public final String name1;
+    public final String name2;
+    public final Integer objectId;
+    public final byte[] order;
+
+    public Row(Map<String,Object> tableRow) {
+      this((Byte)tableRow.get(COL_ATTRIBUTE),
+           (String)tableRow.get(COL_EXPRESSION),
+           (Short)tableRow.get(COL_FLAG),
+           (Integer)tableRow.get(COL_EXTRA),
+           (String)tableRow.get(COL_NAME1),
+           (String)tableRow.get(COL_NAME2),
+           (Integer)tableRow.get(COL_OBJECTID),
+           (byte[])tableRow.get(COL_ORDER));
+    }
+
+    public Row(Byte attribute, String expression, Short flag,
+               Integer extra, String name1, String name2,
+               Integer objectId, byte[] order)
+    {
+      this.attribute = attribute;
+      this.expression = expression;
+      this.flag = flag;
+      this.extra = extra;
+      this.name1 = name1;
+      this.name2= name2;
+      this.objectId = objectId;
+      this.order = order;
+    }
+
+    public Map<String,Object> toTableRow()
+    {
+      Map<String,Object> tableRow = new HashMap<String,Object>();
+
+      tableRow.put(COL_ATTRIBUTE, attribute);
+      tableRow.put(COL_EXPRESSION, expression);
+      tableRow.put(COL_FLAG, flag);
+      tableRow.put(COL_EXTRA, extra);
+      tableRow.put(COL_NAME1, name1);
+      tableRow.put(COL_NAME2, name2);
+      tableRow.put(COL_OBJECTID, objectId);
+      tableRow.put(COL_ORDER, order);
+
+      return tableRow;
+    }
+
+    @Override
+    public String toString() {
+      return ToStringBuilder.reflectionToString(this);
+    }
+  }
+
+  protected static abstract class RowFormatter
+  {
+    private final List<Row> _list;
+
+    protected RowFormatter(List<Row> list) {
+      _list = list;
+    }
+
+    public List<String> format() {
+      return format(new AppendableList<String>());
+    }
+
+    public List<String> format(List<String> strs) {
+      for(Row row : _list) {
+        StringBuilder builder = new StringBuilder();
+        format(builder, row);
+        strs.add(builder.toString());
+      }
+      return strs;
+    }
+
+    protected abstract void format(StringBuilder builder, Row row);
+  }
+
+  protected static abstract class RowFilter
+  {
+    protected RowFilter() {
+    }
+
+    public List<Row> filter(List<Row> list) {
+      for(Iterator<Row> iter = list.iterator(); iter.hasNext(); ) {
+        if(!keep(iter.next())) {
+          iter.remove();
+        }
+      }
+      return list;
+    }
+
+    protected abstract boolean keep(Row row);
+  }
+
+  protected static class AppendableList<E> extends ArrayList<E>
+  {
+    private static final long serialVersionUID = 0L;
+
+    protected AppendableList() {
+    }
+
+    protected AppendableList(Collection<? extends E> c) {
+      super(c);
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder builder = new StringBuilder();
+      for(Iterator<E> iter = iterator(); iter.hasNext(); ) {
+        builder.append(iter.next().toString());
+        if(iter.hasNext()) {
+          builder.append(", ");
+        }
+      }
+      return builder.toString();
+    }
+  }
+
+  private static class Join
+  {
+    public final List<String> tables = new ArrayList<String>();
+    public boolean isJoin;
+    public String expression;
+
+    private Join(String table, String expr) {
+      tables.add(table);
+      expression = expr;
+    }
+
+    public void join(Join other, String newExpr) {
+      tables.addAll(other.tables);
+      isJoin = true;
+      expression = newExpr;
+    }
+
+    @Override
+    public String toString() {
+      return (isJoin ? ("(" + expression + ")") : expression);
+    }
+  }
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/query/QueryFormat.java b/src/java/com/healthmarketscience/jackcess/query/QueryFormat.java
new file mode 100644 (file)
index 0000000..ac58727
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess.query;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import com.healthmarketscience.jackcess.DataType;
+import org.apache.commons.lang.SystemUtils;
+
+/**
+ * @author James Ahlborn
+ */
+public class QueryFormat 
+{
+
+  private QueryFormat() {}
+
+  public static final int SELECT_QUERY_OBJECT_FLAG = 0;
+  public static final int MAKE_TABLE_QUERY_OBJECT_FLAG = 80;
+  public static final int APPEND_QUERY_OBJECT_FLAG = 64;
+  public static final int UPDATE_QUERY_OBJECT_FLAG = 48;
+  public static final int DELETE_QUERY_OBJECT_FLAG = 32;
+  public static final int CROSS_TAB_QUERY_OBJECT_FLAG = 16;
+  public static final int DATA_DEF_QUERY_OBJECT_FLAG = 96;
+  public static final int PASSTHROUGH_QUERY_OBJECT_FLAG = 112;
+  public static final int UNION_QUERY_OBJECT_FLAG = 128;
+
+  public static final String COL_ATTRIBUTE = "Attribute";
+  public static final String COL_EXPRESSION = "Expression";
+  public static final String COL_FLAG = "Flag";
+  public static final String COL_EXTRA = "LvExtra";
+  public static final String COL_NAME1 = "Name1";
+  public static final String COL_NAME2 = "Name2";
+  public static final String COL_OBJECTID = "ObjectId";
+  public static final String COL_ORDER = "Order";
+
+  public static final Byte START_ATTRIBUTE = 0;
+  public static final Byte TYPE_ATTRIBUTE = 1;
+  public static final Byte PARAMETER_ATTRIBUTE = 2;
+  public static final Byte FLAG_ATTRIBUTE = 3;
+  public static final Byte REMOTEDB_ATTRIBUTE = 4;
+  public static final Byte TABLE_ATTRIBUTE = 5;
+  public static final Byte COLUMN_ATTRIBUTE = 6;
+  public static final Byte JOIN_ATTRIBUTE = 7;
+  public static final Byte WHERE_ATTRIBUTE = 8;
+  public static final Byte GROUPBY_ATTRIBUTE = 9;
+  public static final Byte HAVING_ATTRIBUTE = 10;
+  public static final Byte ORDERBY_ATTRIBUTE = 11;
+  public static final Byte END_ATTRIBUTE = (byte)255;
+
+  public static final short UNION_FLAG = 0x02;
+
+  public static final Short TEXT_FLAG = (short)DataType.TEXT.getValue();
+
+  public static final String DESCENDING_FLAG = "D";
+
+  public static final short SELECT_STAR_SELECT_TYPE = 0x01;
+  public static final short DISTINCT_SELECT_TYPE = 0x02;
+  public static final short OWNER_ACCESS_SELECT_TYPE = 0x04;
+  public static final short DISTINCT_ROW_SELECT_TYPE = 0x08;
+  public static final short TOP_SELECT_TYPE = 0x10;
+  public static final short PERCENT_SELECT_TYPE = 0x20;
+
+  public static final short APPEND_VALUE_FLAG = (short)0x8000;
+
+  public static final short CROSSTAB_PIVOT_FLAG = 0x01;
+  public static final short CROSSTAB_NORMAL_FLAG = 0x02;  
+
+  public static final String UNION_PART1 = "X7YZ_____1";
+  public static final String UNION_PART2 = "X7YZ_____2";
+
+  public static final String DEFAULT_TYPE = "";
+
+  public static final Pattern QUOTABLE_CHAR_PAT = Pattern.compile("\\W");
+
+  public static final Pattern IDENTIFIER_SEP_PAT = Pattern.compile("\\.");
+  public static final char IDENTIFIER_SEP_CHAR = '.';
+
+  public static final String NEWLINE = SystemUtils.LINE_SEPARATOR;
+
+
+  public static final Map<Short,String> PARAM_TYPE_MAP = 
+    new HashMap<Short,String>();
+  static {
+    PARAM_TYPE_MAP.put((short)0, "Value");
+    PARAM_TYPE_MAP.put((short)DataType.BOOLEAN.getValue(), "Bit");
+    PARAM_TYPE_MAP.put((short)DataType.TEXT.getValue(), "Text");
+    PARAM_TYPE_MAP.put((short)DataType.BYTE.getValue(), "Byte");
+    PARAM_TYPE_MAP.put((short)DataType.INT.getValue(), "Short");
+    PARAM_TYPE_MAP.put((short)DataType.LONG.getValue(), "Long");
+    PARAM_TYPE_MAP.put((short)DataType.MONEY.getValue(), "Currency");
+    PARAM_TYPE_MAP.put((short)DataType.FLOAT.getValue(), "IEEESingle");
+    PARAM_TYPE_MAP.put((short)DataType.DOUBLE.getValue(), "IEEEDouble");
+    PARAM_TYPE_MAP.put((short)DataType.SHORT_DATE_TIME.getValue(), "DateTime");
+    PARAM_TYPE_MAP.put((short)DataType.BINARY.getValue(), "Binary");
+    PARAM_TYPE_MAP.put((short)DataType.OLE.getValue(), "LongBinary");
+    PARAM_TYPE_MAP.put((short)DataType.GUID.getValue(), "Guid");
+  }
+
+  public static final Map<Short,String> JOIN_TYPE_MAP = 
+    new HashMap<Short,String>();
+  static {
+    JOIN_TYPE_MAP.put((short)1, " INNER JOIN ");
+    JOIN_TYPE_MAP.put((short)2, " LEFT OUTER JOIN ");
+    JOIN_TYPE_MAP.put((short)3, " RIGHT OUTER JOIN ");
+  }
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/query/SelectQuery.java b/src/java/com/healthmarketscience/jackcess/query/SelectQuery.java
new file mode 100644 (file)
index 0000000..c639286
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess.query;
+
+import java.util.List;
+
+
+/**
+ * @author James Ahlborn
+ */
+public class SelectQuery extends BaseSelectQuery 
+{
+
+  public SelectQuery(String name, List<Row> rows, int objectId) {
+    super(name, rows, objectId, Type.SELECT);
+  }
+
+  @Override
+  protected void toSQLString(StringBuilder builder)
+  {
+    toSQLSelectString(builder, true);
+  }  
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/query/UnionQuery.java b/src/java/com/healthmarketscience/jackcess/query/UnionQuery.java
new file mode 100644 (file)
index 0000000..e323b83
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess.query;
+
+import java.util.List;
+
+import static com.healthmarketscience.jackcess.query.QueryFormat.*;
+
+
+/**
+ * SQL query which represents a UNION query.
+ * 
+ * @author James Ahlborn
+ */
+public class UnionQuery extends Query 
+{
+  public UnionQuery(String name, List<Row> rows, int objectId) {
+    super(name, rows, objectId, Type.UNION);
+  }
+
+  public String getUnionType() {
+    return(hasFlag(UNION_FLAG) ? DEFAULT_TYPE : "ALL");
+  }
+
+  public String getUnionString1() {
+    return getUnionString(UNION_PART1);
+  }
+
+  public String getUnionString2() {
+    return getUnionString(UNION_PART2);
+  }
+
+  @Override
+  public List<String> getOrderings() {
+    return super.getOrderings();
+  }
+
+  private String getUnionString(String id) {
+    for(Row row : getTableRows()) {
+      if(id.equals(row.name2)) {
+        return row.expression;
+      }
+    }
+    throw new IllegalStateException(
+        "Could not find union query with id " + id);
+  }
+
+  @Override
+  protected void toSQLString(StringBuilder builder)
+  {
+    builder.append(getUnionString1()).append(NEWLINE)
+      .append("UNION ");
+    String unionType = getUnionType();
+    if(!DEFAULT_TYPE.equals(unionType)) {
+      builder.append(unionType).append(" ");
+    }
+    builder.append(getUnionString2());
+    List<String> orderings = getOrderings();
+    if(!orderings.isEmpty()) {
+      builder.append(NEWLINE).append("ORDER BY ").append(orderings);
+    }
+  }
+
+}
diff --git a/src/java/com/healthmarketscience/jackcess/query/UpdateQuery.java b/src/java/com/healthmarketscience/jackcess/query/UpdateQuery.java
new file mode 100644 (file)
index 0000000..30c7769
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+Copyright (c) 2008 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess.query;
+
+import java.util.List;
+
+import static com.healthmarketscience.jackcess.query.QueryFormat.*;
+
+
+/**
+ * @author James Ahlborn
+ */
+public class UpdateQuery extends Query 
+{
+
+  public UpdateQuery(String name, List<Row> rows, int objectId) {
+    super(name, rows, objectId, Type.UPDATE);
+  }
+
+  public List<String> getTargetTables() 
+  {
+    return super.getFromTables();
+  }
+
+  public String getRemoteDb() 
+  {
+    return super.getFromRemoteDb();
+  }
+
+  public List<String> getNewValues()
+  {
+    return (new RowFormatter(getColumnRows()) {
+        @Override protected void format(StringBuilder builder, Row row) {
+          toOptionalQuotedExpr(builder, row.name2, true)
+            .append(" = ").append(row.expression);
+        }
+      }).format();
+  }
+
+  @Override
+  public String getWhereExpression()
+  {
+    return super.getWhereExpression();
+  }
+
+  @Override
+  protected void toSQLString(StringBuilder builder)
+  {
+    builder.append("UPDATE ").append(getTargetTables());
+    toRemoteDb(builder, getRemoteDb());
+
+    builder.append(NEWLINE).append("SET ").append(getNewValues());
+
+    String whereExpr = getWhereExpression();
+    if(whereExpr != null) {
+      builder.append(NEWLINE).append("WHERE ").append(whereExpr);
+    }
+  }  
+
+}
diff --git a/test/src/java/com/healthmarketscience/jackcess/query/QueryTest.java b/test/src/java/com/healthmarketscience/jackcess/query/QueryTest.java
new file mode 100644 (file)
index 0000000..bdba1d2
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+Copyright (c) 2007 Health Market Science, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
+USA
+
+You can contact Health Market Science at info@healthmarketscience.com
+or at the following address:
+
+Health Market Science
+2700 Horizon Drive
+Suite 200
+King of Prussia, PA 19406
+*/
+
+package com.healthmarketscience.jackcess.query;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import com.healthmarketscience.jackcess.query.Query.Row;
+import junit.framework.TestCase;
+import org.apache.commons.lang.StringUtils;
+
+import static org.apache.commons.lang.SystemUtils.LINE_SEPARATOR;
+import static com.healthmarketscience.jackcess.query.QueryFormat.*;
+
+
+/**
+ * @author James Ahlborn
+ */
+public class QueryTest extends TestCase
+{
+
+  public QueryTest(String name) throws Exception {
+    super(name);
+  }
+
+  public void testUnionQuery() throws Exception
+  {
+    String expr1 = "Select * from Table1";
+    String expr2 = "Select * from Table2";
+
+    UnionQuery query = (UnionQuery)newQuery(
+        Query.Type.UNION, 
+        newRow(TABLE_ATTRIBUTE, expr1, null, UNION_PART1),
+        newRow(TABLE_ATTRIBUTE, expr2, null, UNION_PART2));
+    setFlag(query, 3);
+
+    assertEquals(multiline("Select * from Table1",
+                           "UNION Select * from Table2;"), 
+                 query.toSQLString());
+
+    setFlag(query, 1);
+
+    assertEquals(multiline("Select * from Table1",
+                           "UNION ALL Select * from Table2;"), 
+                 query.toSQLString());
+
+    query.getRows().add(newRow(ORDERBY_ATTRIBUTE, "Table1.id", 
+                               null, null));
+
+    assertEquals(multiline("Select * from Table1",
+                           "UNION ALL Select * from Table2",
+                           "ORDER BY Table1.id;"),
+                 query.toSQLString());
+
+  }
+
+  public void testPassthroughQuery() throws Exception
+  {
+    String expr = "Select * from Table1";
+    String constr = "ODBC;";
+
+    PassthroughQuery query = (PassthroughQuery)newQuery(
+        Query.Type.PASSTHROUGH, expr, constr);
+
+    assertEquals(expr, query.toSQLString());
+    assertEquals(constr, query.getConnectionString());
+  }
+
+  public void testDataDefinitionQuery() throws Exception
+  {
+    String expr = "Drop table Table1";
+
+    DataDefinitionQuery query = (DataDefinitionQuery)newQuery(
+        Query.Type.DATA_DEFINITION, expr, null);
+
+    assertEquals(expr, query.toSQLString());
+  }
+
+  public void testUpdateQuery() throws Exception
+  {
+    UpdateQuery query = (UpdateQuery)newQuery(
+        Query.Type.UPDATE, 
+        newRow(TABLE_ATTRIBUTE, null, "Table1", null),
+        newRow(COLUMN_ATTRIBUTE, "\"some string\"", null, "Table1.id"),
+        newRow(COLUMN_ATTRIBUTE, "42", null, "Table1.col1"));
+
+    assertEquals(
+        multiline("UPDATE Table1",
+                  "SET Table1.id = \"some string\", Table1.col1 = 42;"),
+        query.toSQLString());
+
+    query.getRows().add(newRow(WHERE_ATTRIBUTE, "(Table1.col2 < 13)", 
+                               null, null));
+
+    assertEquals(
+        multiline("UPDATE Table1",
+                  "SET Table1.id = \"some string\", Table1.col1 = 42",
+                  "WHERE (Table1.col2 < 13);"),
+        query.toSQLString());
+  }
+
+  private static Query newQuery(Query.Type type, Row... rows)
+  {
+    return newQuery(type, null, null, rows);
+  }
+
+  private static Query newQuery(Query.Type type, String typeExpr,
+                                String typeName1, Row... rows)
+  {
+    List<Row> rowList = new ArrayList<Row>();
+    rowList.add(newRow(TYPE_ATTRIBUTE, typeExpr, type.getValue(),
+                       null, typeName1, null));
+    rowList.addAll(Arrays.asList(rows));
+    return Query.create(type.getObjectFlag(), "TestQuery", rowList, 13);
+  }
+
+  private static Row newRow(Byte attr, String expr, String name1, String name2)
+  {
+    return newRow(attr, expr, null, null, name1, name2);
+  }
+
+  private static Row newRow(Byte attr, String expr, Number flagNum,
+                            String name1, String name2)
+  {
+    return newRow(attr, expr, flagNum, null, name1, name2);
+  }
+
+  private static Row newRow(Byte attr, String expr, Number flagNum,
+                            Number extraNum, String name1, String name2)
+  {
+    Short flag = ((flagNum != null) ? flagNum.shortValue() : null);
+    Integer extra = ((extraNum != null) ? extraNum.intValue() : null);
+    return new Row(attr, expr, flag, extra, name1, name2, null, null);
+  }
+
+  private static void setFlag(Query query, Number newFlagNum)
+  {
+    removeRows(query, FLAG_ATTRIBUTE);
+    query.getRows().add(
+        newRow(FLAG_ATTRIBUTE, null, newFlagNum, null, null, null));
+  }
+
+  private static void removeRows(Query query, Byte attr)
+  {
+    for(Iterator<Row> iter = query.getRows().iterator(); iter.hasNext(); ) {
+      if(attr.equals(iter.next().attribute)) {
+        iter.remove();
+      }
+    }
+  }
+
+  private static String multiline(String... strs)
+  {
+    return StringUtils.join(strs, LINE_SEPARATOR);
+  }
+
+}