--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.iapi.store.access.RowUtil\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.iapi.store.access;\r
+\r
+import org.apache.derby.iapi.error.StandardException; \r
+import org.apache.derby.iapi.services.io.FormatableBitSet;\r
+import org.apache.derby.iapi.services.io.Storable;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.store.raw.FetchDescriptor;\r
+\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+import org.apache.derby.iapi.types.DataValueFactory;\r
+\r
+import java.util.Enumeration;\r
+import java.util.Hashtable;\r
+import java.util.Vector;\r
+\r
+/**\r
+ A set of static utility methods to work with rows.\r
+ <P>\r
+ A row or partial row is described by two or three parameters.\r
+ <OL>\r
+ <LI>DataValueDescriptor[] row - an array of objects, one per column.\r
+ <LI>FormatableBitSet validColumns - \r
+ an indication of which objects in row map to which columns\r
+ </OL>\r
+ These objects can describe a complete row or a partial row. A partial row is \r
+ one where a sub-set (e.g. columns 0, 4 and 7) of the columns are supplied \r
+ for update, or requested to be fetched on a read. Here's an example\r
+ of code to set up a partial column list to fetch the 0th (type FOO), \r
+ 4th (type BAR), and 7th (type MMM) columns from a row with 10 columns, note\r
+ that the format for a partial row changed from a "packed" representation\r
+ in the 3.0 release to a "sparse" representation in later releases:\r
+\r
+ <blockquote><pre>\r
+\r
+ // allocate/initialize the row \r
+ DataValueDescriptor row = new DataValueDescriptor[10]\r
+ row[0] = new FOO();\r
+ row[4] = new BAR();\r
+ row[7] = new MMM();\r
+ \r
+ // allocate/initialize the bit set \r
+ FormatableBitSet FormatableBitSet = new FormatableBitSet(10);\r
+ \r
+ FormatableBitSet.set(0);\r
+ FormatableBitSet.set(4);\r
+ FormatableBitSet.set(7);\r
+ </blockquote></pre>\r
+\r
+\r
+ <BR><B>Column mapping<B><BR>\r
+ When validColumns is null:\r
+ <UL>\r
+ <LI> The number of columns is given by row.length\r
+ <LI> Column N maps to row[N], where column numbers start at zero.\r
+ </UL>\r
+ <BR>\r
+ When validColumns is not null, then\r
+ <UL>\r
+ <LI> The number of requested columns is given by the number of bits set in \r
+ validColumns.\r
+ <LI> Column N is not in the partial row if validColumns.isSet(N) \r
+ returns false.\r
+ <LI> Column N is in the partial row if validColumns.isSet(N) returns true.\r
+ <LI> If column N is in the partial row then it maps to row[N].\r
+ If N >= row.length then the column is taken as non existent for an\r
+ insert or update, and not fetched on a fetch.\r
+ </UL>\r
+ If row.length is greater than the number of columns indicated by validColumns\r
+ the extra entries are ignored.\r
+\r
+**/\r
+public class RowUtil\r
+{\r
+ private RowUtil() {}\r
+\r
+ /**\r
+ An object that can be used on a fetch to indicate no fields\r
+ need to be fetched.\r
+ */\r
+ public static final DataValueDescriptor[] EMPTY_ROW = \r
+ new DataValueDescriptor[0];\r
+\r
+ /**\r
+ An object that can be used on a fetch as a FormatableBitSet to indicate no fields\r
+ need to be fetched.\r
+ */\r
+ public static final FormatableBitSet EMPTY_ROW_BITSET = \r
+ new FormatableBitSet(0);\r
+\r
+ /**\r
+ An object that can be used on a fetch as a FormatableBitSet to indicate no fields\r
+ need to be fetched.\r
+ */\r
+ public static final FetchDescriptor EMPTY_ROW_FETCH_DESCRIPTOR = \r
+ new FetchDescriptor(0);\r
+\r
+ private static final FetchDescriptor[] ROWUTIL_FETCH_DESCRIPTOR_CONSTANTS =\r
+ {EMPTY_ROW_FETCH_DESCRIPTOR,\r
+ new FetchDescriptor(1, 1),\r
+ new FetchDescriptor(2, 2),\r
+ new FetchDescriptor(3, 3),\r
+ new FetchDescriptor(4, 4),\r
+ new FetchDescriptor(5, 5),\r
+ new FetchDescriptor(6, 6),\r
+ new FetchDescriptor(7, 7)};\r
+\r
+\r
+ /**\r
+ Get the object for a column identifer (0 based) from a complete or \r
+ partial row.\r
+\r
+ @param row the row\r
+ @param columnList valid columns in the row\r
+ @param columnId which column to return (0 based)\r
+\r
+ @return the obejct for the column, or null if the column is not represented.\r
+ */\r
+ public static DataValueDescriptor getColumn(\r
+ DataValueDescriptor[] row, \r
+ FormatableBitSet columnList, \r
+ int columnId) \r
+ {\r
+\r
+ if (columnList == null)\r
+ return columnId < row.length ? row[columnId] : null;\r
+\r
+\r
+ if (!(columnList.getLength() > columnId && columnList.isSet(columnId)))\r
+ return null;\r
+\r
+ return columnId < row.length ? row[columnId] : null;\r
+\r
+ }\r
+\r
+ public static Object getColumn(\r
+ Object[] row, \r
+ FormatableBitSet columnList, \r
+ int columnId) \r
+ {\r
+\r
+ if (columnList == null)\r
+ return columnId < row.length ? row[columnId] : null;\r
+\r
+\r
+ if (!(columnList.getLength() > columnId && columnList.isSet(columnId)))\r
+ return null;\r
+\r
+ return columnId < row.length ? row[columnId] : null;\r
+\r
+ }\r
+\r
+ /**\r
+ Get a FormatableBitSet representing all the columns represented in\r
+ a qualifier list.\r
+\r
+ @return a FormatableBitSet describing the valid columns.\r
+ */\r
+ public static FormatableBitSet getQualifierBitSet(Qualifier[][] qualifiers) \r
+ {\r
+ FormatableBitSet qualifierColumnList = new FormatableBitSet();\r
+\r
+ if (qualifiers != null) \r
+ {\r
+ for (int i = 0; i < qualifiers.length; i++)\r
+ {\r
+ for (int j = 0; j < qualifiers[i].length; j++)\r
+ {\r
+ int colId = qualifiers[i][j].getColumnId();\r
+\r
+ // we are about to set bit colId, need length to be colId+1\r
+ qualifierColumnList.grow(colId+1);\r
+ qualifierColumnList.set(colId);\r
+ }\r
+ }\r
+ }\r
+\r
+ return qualifierColumnList;\r
+ }\r
+\r
+ /**\r
+ * Get the number of columns represented by a FormatableBitSet.\r
+ * <p>\r
+ * This is simply a count of the number of bits set in the FormatableBitSet.\r
+ * <p>\r
+ *\r
+ * @param maxColumnNumber Because the FormatableBitSet.size() can't be used as\r
+ * the number of columns, allow caller to tell\r
+ * the maximum column number if it knows. \r
+ * -1 means caller does not know.\r
+ * >=0 number is the largest column number.\r
+ * \r
+ * @param columnList valid columns in the row\r
+ *\r
+ * @return The number of columns represented in the FormatableBitSet.\r
+ **/\r
+ public static int getNumberOfColumns(\r
+ int maxColumnNumber,\r
+ FormatableBitSet columnList)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(columnList != null);\r
+\r
+ int max_col_number = columnList.getLength();\r
+\r
+ if (maxColumnNumber > 0 && maxColumnNumber < max_col_number)\r
+ max_col_number = maxColumnNumber;\r
+\r
+ int ret_num_cols = 0;\r
+\r
+ for (int i = 0; i < max_col_number; i++)\r
+ {\r
+ if (columnList.isSet(i))\r
+ ret_num_cols++;\r
+ }\r
+\r
+ return(ret_num_cols);\r
+ }\r
+\r
+ /**\r
+ See if a row actually contains no columns.\r
+ Returns true if row is null or row.length is zero.\r
+\r
+ @return true if row is empty.\r
+ */\r
+ public static boolean isRowEmpty(\r
+ DataValueDescriptor[] row) \r
+ {\r
+\r
+ if (row == null)\r
+ return true;\r
+\r
+ if (row.length == 0)\r
+ return true;\r
+\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ Return the column number of the first column out of range, or a number\r
+ less than zero if all columns are in range.\r
+ */\r
+ public static int columnOutOfRange(\r
+ DataValueDescriptor[] row, \r
+ FormatableBitSet columnList, \r
+ int maxColumns) \r
+ {\r
+\r
+ if (columnList == null) {\r
+ if (row.length > maxColumns)\r
+ return maxColumns;\r
+\r
+ return -1;\r
+ }\r
+\r
+ int size = columnList.getLength();\r
+ for (int i = maxColumns; i < size; i++) {\r
+ if (columnList.isSet(i))\r
+ return i;\r
+ }\r
+\r
+ return -1;\r
+ }\r
+\r
+ /**\r
+ Get the next valid column after or including start column.\r
+ Returns -1 if no valid columns exist after startColumn\r
+ */\r
+ public static int nextColumn(\r
+ Object[] row, \r
+ FormatableBitSet columnList, \r
+ int startColumn) \r
+ {\r
+\r
+ if (columnList != null) {\r
+\r
+ int size = columnList.getLength();\r
+\r
+ for (; startColumn < size; startColumn++) {\r
+ if (columnList.isSet(startColumn)) {\r
+ return startColumn;\r
+ }\r
+ }\r
+\r
+ return -1;\r
+ }\r
+\r
+ if (row == null)\r
+ return -1;\r
+\r
+ return startColumn < row.length ? startColumn : -1;\r
+ }\r
+\r
+ /**\r
+ * Return a FetchDescriptor which describes a single column set.\r
+ * <p>\r
+ * This routine returns one of a set of constant FetchDescriptor's, and\r
+ * should not be altered by the caller.\r
+ **/\r
+ public static final FetchDescriptor getFetchDescriptorConstant(\r
+ int single_column_number)\r
+ {\r
+ if (single_column_number < ROWUTIL_FETCH_DESCRIPTOR_CONSTANTS.length)\r
+ {\r
+ return(ROWUTIL_FETCH_DESCRIPTOR_CONSTANTS[single_column_number]);\r
+ }\r
+ else\r
+ {\r
+ return(\r
+ new FetchDescriptor(\r
+ single_column_number, single_column_number));\r
+ }\r
+ }\r
+\r
+ /**************************************************************************\r
+ * Public Methods dealing with cloning and row copying util functions\r
+ **************************************************************************\r
+ */\r
+\r
+ /**\r
+ * Generate a template row of DataValueDescriptor's\r
+ * <p>\r
+ * Generate an array of DataValueDescriptor objects which will be used to \r
+ * make calls to newRowFromClassInfoTemplate(), to repeatedly and\r
+ * efficiently generate new rows. This is important for certain \r
+ * applications like the sorter and fetchSet which generate large numbers\r
+ * of "new" empty rows.\r
+ * <p>\r
+ *\r
+ * @return The new row.\r
+ *\r
+ * @param column_list A bit set indicating which columns to include in row.\r
+ * @param format_ids an array of format id's, one per column in row.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ public static DataValueDescriptor[] newTemplate(\r
+ DataValueFactory dvf,\r
+ FormatableBitSet column_list,\r
+ int[] format_ids,\r
+ int[] collation_ids) \r
+ throws StandardException\r
+ {\r
+ int num_cols = format_ids.length;\r
+ DataValueDescriptor[] ret_row = new DataValueDescriptor[num_cols];\r
+\r
+ int column_listSize = \r
+ (column_list == null) ? 0 : column_list.getLength();\r
+\r
+ for (int i = 0; i < num_cols; i++)\r
+ {\r
+ // does caller want this column?\r
+ if ((column_list != null) && \r
+ !((column_listSize > i) && \r
+ (column_list.isSet(i))))\r
+ {\r
+ // no - column should be skipped.\r
+ }\r
+ else\r
+ {\r
+ // yes - create the column \r
+\r
+ // get empty instance of object identified by the format id.\r
+\r
+ ret_row[i] = dvf.getNull(format_ids[i], collation_ids[i]);\r
+ }\r
+ }\r
+\r
+ return(ret_row);\r
+ }\r
+\r
+\r
+ private static void newRowFromClassInfoTemplateError()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.THROWASSERT(\r
+ "unexpected error in newRowFromClassInfoTemplate()");\r
+ }\r
+\r
+ /**\r
+ * Generate an "empty" row from an array of DataValueDescriptor objects.\r
+ * <p>\r
+ * Generate an array of new'd objects by using the getNewNull()\r
+ * method on each of the DataValueDescriptor objects. \r
+ * <p>\r
+ *\r
+ * @return The new row.\r
+ *\r
+ * @param template An array of DataValueDescriptor objects \r
+ * each of which can be used to create a new \r
+ * instance of the appropriate type to build a \r
+ * new empty template row.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ public static DataValueDescriptor[] newRowFromTemplate(\r
+ DataValueDescriptor[] template) \r
+ throws StandardException\r
+ {\r
+\r
+ DataValueDescriptor[] columns = \r
+ new DataValueDescriptor[template.length];\r
+\r
+ \r
+ for (int column_index = template.length; column_index-- > 0;)\r
+ {\r
+ if (template[column_index] != null)\r
+ {\r
+ // get empty instance of DataValueDescriptor identified by \r
+ // the format id.\r
+ columns[column_index] = template[column_index].getNewNull();\r
+ }\r
+ }\r
+\r
+ return columns;\r
+ }\r
+\r
+\r
+ /**\r
+ * return string version of row.\r
+ * <p>\r
+ * For debugging only. \r
+ *\r
+ * @return The string version of row.\r
+ *\r
+ * @param row The row.\r
+ *\r
+ **/\r
+ public static String toString(Object[] row)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+\r
+ String str = new String();\r
+\r
+ if (row != null)\r
+ {\r
+ if (row.length == 0)\r
+ {\r
+ str = "empty row";\r
+ }\r
+ else\r
+ {\r
+ for (int i = 0; i < row.length; i++)\r
+ str += "col[" + i + "]=" + row[i];\r
+ }\r
+ }\r
+ else\r
+ {\r
+ str = "row is null";\r
+ }\r
+\r
+ return(str);\r
+ }\r
+ else\r
+ {\r
+ return(null);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * return string version of a HashTable returned from a FetchSet.\r
+ * <p>\r
+ *\r
+ * @return The string version of row.\r
+ *\r
+ *\r
+ **/\r
+\r
+ // For debugging only. \r
+ public static String toString(Hashtable hash_table)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ String str = new String();\r
+\r
+ Object row_or_vector;\r
+\r
+ for (Enumeration e = hash_table.elements(); e.hasMoreElements();)\r
+ {\r
+ row_or_vector = e.nextElement();\r
+\r
+ if (row_or_vector instanceof Object[])\r
+ {\r
+ // it's a row\r
+ str += RowUtil.toString((Object[]) row_or_vector);\r
+ str += "\n";\r
+ }\r
+ else if (row_or_vector instanceof Vector)\r
+ {\r
+ // it's a vector\r
+ Vector vec = (Vector) row_or_vector;\r
+\r
+ for (int i = 0; i < vec.size(); i++)\r
+ {\r
+ str += \r
+ "vec[" + i + "]:" + \r
+ RowUtil.toString((Object[]) vec.elementAt(i));\r
+\r
+ str += "\n";\r
+ }\r
+ }\r
+ else\r
+ {\r
+ str += "BAD ENTRY\n";\r
+ }\r
+ }\r
+ return(str);\r
+ }\r
+ else\r
+ {\r
+ return(null);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Process the qualifier list on the row, return true if it qualifies.\r
+ * <p>\r
+ * A two dimensional array is to be used to pass around a AND's and OR's in\r
+ * conjunctive normal form. The top slot of the 2 dimensional array is \r
+ * optimized for the more frequent where no OR's are present. The first \r
+ * array slot is always a list of AND's to be treated as described above \r
+ * for single dimensional AND qualifier arrays. The subsequent slots are \r
+ * to be treated as AND'd arrays or OR's. Thus the 2 dimensional array \r
+ * qual[][] argument is to be treated as the following, note if \r
+ * qual.length = 1 then only the first array is valid and it is and an \r
+ * array of and clauses:\r
+ *\r
+ * (qual[0][0] and qual[0][0] ... and qual[0][qual[0].length - 1])\r
+ * and\r
+ * (qual[1][0] or qual[1][1] ... or qual[1][qual[1].length - 1])\r
+ * and\r
+ * (qual[2][0] or qual[2][1] ... or qual[2][qual[2].length - 1])\r
+ * ...\r
+ * and\r
+ * (qual[qual.length - 1][0] or qual[1][1] ... or qual[1][2])\r
+ *\r
+ * \r
+ * @return true if the row qualifies.\r
+ *\r
+ * @param row The row being qualified.\r
+ * @param qual_list 2 dimensional array representing conjunctive\r
+ * normal form of simple qualifiers.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ public static final boolean qualifyRow(\r
+ Object[] row, \r
+ Qualifier[][] qual_list)\r
+ throws StandardException\r
+ {\r
+ boolean row_qualifies = true;\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(row != null);\r
+ }\r
+\r
+ // First do the qual[0] which is an array of qualifer terms.\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ // routine should not be called if there is no qualifier\r
+ SanityManager.ASSERT(qual_list != null);\r
+ SanityManager.ASSERT(qual_list.length > 0);\r
+ }\r
+\r
+ for (int i = 0; i < qual_list[0].length; i++)\r
+ {\r
+ // process each AND clause \r
+\r
+ row_qualifies = false;\r
+\r
+ // process each OR clause.\r
+\r
+ Qualifier q = qual_list[0][i];\r
+\r
+ // Get the column from the possibly partial row, of the \r
+ // q.getColumnId()'th column in the full row.\r
+ DataValueDescriptor columnValue = \r
+ (DataValueDescriptor) row[q.getColumnId()];\r
+\r
+ row_qualifies =\r
+ columnValue.compare(\r
+ q.getOperator(),\r
+ q.getOrderable(),\r
+ q.getOrderedNulls(),\r
+ q.getUnknownRV());\r
+\r
+ if (q.negateCompareResult())\r
+ row_qualifies = !row_qualifies;\r
+\r
+ // Once an AND fails the whole Qualification fails - do a return!\r
+ if (!row_qualifies)\r
+ return(false);\r
+ }\r
+\r
+ // all the qual[0] and terms passed, now process the OR clauses\r
+\r
+ for (int and_idx = 1; and_idx < qual_list.length; and_idx++)\r
+ {\r
+ // loop through each of the "and" clause.\r
+\r
+ row_qualifies = false;\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ // Each OR clause must be non-empty.\r
+ SanityManager.ASSERT(qual_list[and_idx].length > 0);\r
+ }\r
+\r
+ for (int or_idx = 0; or_idx < qual_list[and_idx].length; or_idx++)\r
+ {\r
+ // Apply one qualifier to the row.\r
+ Qualifier q = qual_list[and_idx][or_idx];\r
+ int col_id = q.getColumnId();\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(\r
+ (col_id < row.length),\r
+ "Qualifier is referencing a column not in the row.");\r
+ }\r
+\r
+ // Get the column from the possibly partial row, of the \r
+ // q.getColumnId()'th column in the full row.\r
+ DataValueDescriptor columnValue = \r
+ (DataValueDescriptor) row[q.getColumnId()];\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (columnValue == null)\r
+ SanityManager.THROWASSERT(\r
+ "1:row = " + RowUtil.toString(row) +\r
+ "row.length = " + row.length +\r
+ ";q.getColumnId() = " + q.getColumnId());\r
+ }\r
+\r
+ // do the compare between the column value and value in the\r
+ // qualifier.\r
+ row_qualifies = \r
+ columnValue.compare(\r
+ q.getOperator(),\r
+ q.getOrderable(),\r
+ q.getOrderedNulls(),\r
+ q.getUnknownRV());\r
+\r
+ if (q.negateCompareResult())\r
+ row_qualifies = !row_qualifies;\r
+\r
+ // SanityManager.DEBUG_PRINT("StoredPage.qual", "processing qual[" + and_idx + "][" + or_idx + "] = " + qual_list[and_idx][or_idx] );\r
+\r
+ // SanityManager.DEBUG_PRINT("StoredPage.qual", "value = " + row_qualifies);\r
+\r
+ // processing "OR" clauses, so as soon as one is true, break\r
+ // to go and process next AND clause.\r
+ if (row_qualifies)\r
+ break;\r
+\r
+ }\r
+\r
+ // The qualifier list represented a set of "AND'd" \r
+ // qualifications so as soon as one is false processing is done.\r
+ if (!row_qualifies)\r
+ break;\r
+ }\r
+\r
+ return(row_qualifies);\r
+ }\r
+\r
+}\r