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