This chapter describes Oracle extensions to standard Java Database Connectivity (JDBC) that let you access and manipulate Oracle collections, which map to Java arrays, and their data. The following topics are discussed:
An Oracle collection, either a variable array (VARRAY) or a nested table in the database, maps to an array in Java. JDBC 2.0 arrays are used to materialize Oracle collections in Java. The terms collection and array are sometimes used interchangeably. However, collection is more appropriate on the database side and array is more appropriate on the JDBC application side.
Oracle supports onlynamed collections, where you specify a SQL type name to describe a type of collection. JDBC enables you to use arrays as any of the following:
Columns in aSELECT clause
IN orOUT bind variables
Attributes in an Oracle object
Elements of other arrays
This section covers the following topics:
In your application, you have the choice of materializing a collection as an instance of theoracle.sql.ARRAY class, which is weakly typed, or materializing it as an instance of a custom Java class that you have created in advance, which is strongly typed. Custom Java classes used for collections are referred to as custom collection classes. A custom collection class must implement the Oracleoracle.sql.ORAData interface. In addition, the custom class or a companion class must implementoracle.sql.ORADataFactory. The standardjava.sql.SQLData interface is for mapping SQL object types only.
Theoracle.sql.ARRAY class implements the standardjava.sql.Array interface.
TheARRAY class includes functionality to retrieve the array as a whole, retrieve a subset of the array elements, and retrieve the SQL base type name of the array elements. However, you cannot write to the array, because there are no setter methods.
Custom collection classes, as with theARRAY class, enable you to retrieve all or part of the array and get the SQL base type name. They also have the advantage of being strongly typed, which can help you find coding errors during compilation that might not otherwise be discovered until run time.
Furthermore, custom collection classes produced by JPublisher offer the feature of being writable, with individually accessible elements.
Note:
There is no difference in the code between accessing VARRAYs and accessing nested tables.ARRAY class methods can determine if they are being applied to a VARRAY or nested table, and respond by taking the appropriate actions.See Also:
For more information about custom collection classes, see"Custom Collection Classes with JPublisher".Because Oracle supports only named collections, you must declare a particularVARRAY type name or nested table type name. VARRAY and nested table are not types themselves, but categories of types.
A SQL type name is assigned to a collection when you create it using the SQLCREATE TYPE statement:
CREATE TYPE <sql_type_name> AS <datatype>;
A VARRAY is an array of varying size. It has an ordered set of data elements, and all the elements are of the same data type. Each element has an index, which is a number corresponding to the position of the element in the VARRAY. The number of elements in a VARRAY is the size of the VARRAY. You must specify a maximum size when you declare theVARRAY type. For example:
CREATE TYPE myNumType AS VARRAY(10) OF NUMBER;
This statement definesmyNumType as a SQL type name that describes a VARRAY ofNUMBER values that can contain no more than 10 elements.
A nested table is an unordered set of data elements, all of the same data type. The database stores a nested table in a separate table which has a single column, and the type of that column is a built-in type or an object type. If the table is an object type, then it can also be viewed as a multi-column table, with a column for each attribute of the object type. You can create a nested table as follows:
CREATE TYPE myNumList AS TABLE OF integer;
This statement identifiesmyNumList as a SQL type name that defines the table type used for the nested tables of the typeINTEGER.
The most common way to create a new multilevel collection type in JDBC is to pass the SQLCREATE TYPE statement to theexecute method of thejava.sql.Statement class. The following code creates a one-level nested table,first_level, and a two- levels nested table,second_level:
Connection conn = .... // make a database // connection Statement stmt = conn.createStatement(); // open a database // cursor stmt.execute("CREATE TYPE first_level AS TABLE OF NUMBER"); // create a nested // table of number stmt.execute("CREATE second_level AS TABLE OF first_level"); // create a // two-levels nested table... // other operations herestmt.close(); // release the // resource conn.close(); // close the // database connectionOnce the multilevel collection types have been created, they can be used as both columns of a base table as well as attributes of a object type.
You can obtain collection data in an array instance through a result set or callable statement and pass it back as a bind variable in a prepared statement or callable statement.
Theoracle.sql.ARRAY class, which implements the standardjava.sql.Array interface, provides the necessary functionality to access and update the data of an Oracle collection.
This section covers Array Getter and Setter Methods. Use the following result set, callable statement, and prepared statement methods to retrieve and pass collections as Java arrays.
Result Set and Callable Statement Getter Methods
TheOracleResultSet andOracleCallableStatement classes supportgetARRAY andgetArray methods to retrieveARRAY objects as output parameters, either asoracle.sql.ARRAY instances orjava.sql.Array instances. You can also use thegetObject method. These methods take as input aString column name orint column index.
Prepared and Callable Statement Setter Methods
TheOraclePreparedStatement andOracleCallableStatement classes supportsetARRAY andsetArray methods to take updatedARRAY objects as bind variables and pass them to the database. You can also use thesetObject method. These methods take as input aString parameter name orint parameter index as well as anoracle.sql.ARRAY instance or ajava.sql.Array instance.
This section discusses the following topics:
Theoracle.sql.ARRAY class contains methods that return array elements as Java primitive types. These methods allow you to access collection elements more efficiently than accessing them asDatum instances and then converting eachDatum instance to its Java primitive value.
Note:
These specialized methods of theoracle.sql.ARRAY class are restricted to numeric collections.Each method using the first signature returns collection elements as anXXX[], whereXXX is a Java primitive type. Each method using the second signature returns a slice of the collection containing the number of elements specified bycount, starting at theindex location.
Oracle JDBC driver provides public methods to enable and disable buffering ofARRAY contents.
The following methods are included with theoracle.sql.ARRAY class:
It is advisable to enable auto-buffering in a JDBC application when theARRAY elements will be accessed more than once by thegetAttributes andgetArray methods, presuming theARRAY data is able to fit into the Java Virtual Machine (JVM) memory without overflow.
Important:
Buffering the converted elements may cause the JDBC application to consume a significant amount of memory.When you enable auto-buffering, theoracle.sql.ARRAY object keeps a local copy of all the converted elements. This data is retained so that a second access of this information does not require going through the data format conversion process.
If an array is in auto-indexing mode, then the array object maintains an index table to hasten array element access.
Theoracle.sql.ARRAY class contains the following methods to support automatic array-indexing:
By default, auto-indexing is not enabled. For a JDBC application, enable auto-indexing forARRAY objects if random access of array elements may occur through thegetArray andgetResultSet methods.
This section discusses how to create array objects and how to retrieve and pass collections as array objects, including the following topics.
Note:
Oracle JDBC does not support the JDBC 4.0 methodcreateArrayOf method ofjava.sql.Connection interface. This method only allows anonymous array types, while all Oracle array types are named. Use the Oracle specific methodcreateArray oforacle.jdbc.OracleConnection instead.This section describes how to createARRAY objects. This section covers the following topics:
Steps in Creating ARRAY Objects
Starting from Oracle Database 11g Release 1 (11.1), you can use thecreateArray factory method oforacle.jdbc.OracleConnection interface to create an array object. The factory method for creating arrays has been defined as follows:
public ARRAY createARRAY(java.lang.String typeName,java.lang.Object elements)throws SQLException
where,typeName is the name of the SQL type of the created object andelements is the elements of the created object.
Perform the following to create an array:
Create a collection with theCREATE TYPE statement as follows:
CREATE TYPE elements AS varray(22) OF NUMBER(5,2);
The two possibilities for the contents ofelements are:
An array of Java primitives. For example,int[].
An array of Java objects, such asxxx[], wherexxx is the name of a Java class. For example,Integer[].
Construct theARRAY objectby passing the Java string specifying the user-defined SQL type name of the array and a Java object containing the individual elements you want the array to contain.
ARRAY array = oracle.jdbc.OracleConnection.createARRAY(sql_type_name, elements);
Creating Multilevel Collections
As with single-level collections, the JDBC application can create anoracle.sql.ARRAY instance to represent a multilevel collection, and then send the instance to the database. The samecreateArray factory method you use to create single-level collections, can be used to create multilevel collections as well. To create a single-level collection, the elements are a one dimensional Java array, while to create a multilevel collection, the elements can be either an array oforacle.sql.ARRAY[] elements or a nested Java array or the combinations.
The following code shows how to create collection types with a nested Java array:
// prepare the multi level collection elements as a nested Java arrayint[][][] elements = { {{1}, {1, 2}}, {{2}, {2, 3}}, {{3}, {3, 4}} };// create the ARRAY using the factory methodARRAY array = oracle.jdbc.OracleConnection.createARRAY(sql_type_name, elements);This section first discusses how to retrieve anARRAY instance as a whole from a result set, and then how to retrieve the elements from theARRAY instance. This section covers the following topics:
You can retrieve a SQL array from a result set by casting the result set toOracleResultSet and using thegetARRAY method, which returns anoracle.sql.ARRAY object. If you want to avoid casting the result set, then you can get the data with the standardgetObject method specified by thejava.sql.ResultSet interface and cast the output tooracle.sql.ARRAY.
Once you have anARRAY object, you can retrieve the data using one of these three overloaded methods of theoracle.sql.ARRAY class:
Oracle also provides methods that enable you to retrieve all the elements of an array, or a subset.
Note:
In case you are working with an array of structured objects, Oracle provides versions of these three methods that enable you to specify a type map so that you can choose how to map the objects to Java.ThegetOracleArray method is an Oracle-specific extension that is not specified in the standardArray interface. ThegetOracleArray method retrieves the element values of the array into aDatum[] array. The elements are of theoracle.sql.* data type corresponding to the SQL type of the data in the original array.
For an array of structured objects, this method will useoracle.sql.STRUCT instances for the elements.
Oracle also provides agetOracleArray(index,count)method to get a subset of the array elements.
ThegetResultSet method returns a result set that contains elements of the array designated by theARRAY object. The result set contains one row for each array element, with two columns in each row. The first column stores the index into the array for that element, and the second column stores the element value. In the case of VARRAYs, the index represents the position of the element in the array. In the case of nested tables, which are by definition unordered, the index reflects only the return order of the elements in the particular query.
Oracle recommends usinggetResultSet when getting data from nested tables. Nested tables can have an unlimited number of elements. TheResultSet object returned by the method initially points at the first row of data. You get the contents of the nested table by using thenext method and the appropriategetXXX method. In contrast,getArray returns the entire contents of the nested table at one time.
ThegetResultSet method uses the default type map of the connection to determine the mapping between the SQL type of the Oracle object and its corresponding Java data type. If you do not want to use the default type map of the connection, another version of the method,getResultSet(map), enables you to specify an alternate type map.
Oracle also provides thegetResultSet(index,count) andgetResultSet(index,count,map) methods to retrieve a subset of the array elements.
ThegetArray method is a standard JDBC method that returns the array elements as ajava.lang.Object, which you can cast as appropriate. The elements are converted to the Java types corresponding to the SQL type of the data in the original array.
Oracle also provides agetArray(index,count) method to retrieve a subset of the array elements.
If you usegetOracleArray to return the array elements, then the use by that method oforacle.sql.Datum instances avoids the expense of data conversion from SQL to Java. The non-character data inside the instance of aDatum class or any of its subclass remains in raw SQL format.
If you usegetResultSet to return an array of primitive data types, then the JDBC driver returns aResultSet object that contains, for each element, the index into the array for the element and the element value. For example:
ResultSet rset = intArray.getResultSet();
In this case, the result set contains one row for each array element, with two columns in each row. The first column stores the index into the array and the second column stores the element value.
If the elements of an array are of a SQL type that maps to a Java type, thengetArray returns an array of elements of this Java type. The return type of thegetArray method isjava.lang.Object. Therefore, the result must be cast before it can be used.
BigDecimal[] values = (BigDecimal[]) intArray.getArray();
HereintArray is anoracle.sql.ARRAY, corresponding to a VARRAY of typeNUMBER. Thevalues array contains an array of elements of typejava.math.BigDecimal, because the SQLNUMBER data type maps to JavaBigDecimal, by default, according to Oracle JDBC drivers.
Note:
UsingBigDecimal is a resource-intensive operation in Java. Because Oracle JDBC maps numeric SQL data toBigDecimal by default, usinggetArray may impact performance, and is not recommended for numeric collections.By default, if you are working with an array whose elements are structured objects, and you usegetArray orgetResultSet, then the Oracle objects in the array will be mapped to their corresponding Java data types according to the default mapping. This is because these methods use the defaulttype map of the connection to determine the mapping.
However, if you do not want default behavior, then you can use thegetArray(map) orgetResultSet(map) method to specify a type map that contains alternate mappings. If there are entries in the type map corresponding to the Oracle objects in the array, then each object in the array is mapped to the corresponding Java type specified in the type map. For example:
Object[] object = (Object[])objArray.getArray(map);
WhereobjArray is anoracle.sql.ARRAY object andmap is ajava.util.Map object.
If the type map does not contain an entry for a particular Oracle object, then the element is returned as anoracle.sql.STRUCT object.
ThegetResultSet(map) method behaves similarly to thegetArray(map) method.
If you do not want to retrieve the entire contents of an array, then you can use signatures ofgetArray,getResultSet, andgetOracleArray that let you retrieve a subset. To retrieve a subset of thearray, pass in an index and a count to indicate where in the array you want to start and how many elements you want to retrieve. As previously described, you can specify a type map or use the default type map for your connection to convert to Java types. For example:
Object object = arr.getArray(index,count,map);Object object = arr.getArray(index,count);
Similar examples usinggetResultSet are:
ResultSet rset = arr.getResultSet(index,count,map);ResultSet rset = arr.getResultSet(index,count);
A similar example usinggetOracleArray is:
Datum[] arr = arr.getOracleArray(index,count);
Wherearr is anoracle.sql.ARRAY object,index is typelong,count is typeint, andmap is ajava.util.Map object.
Note:
There is no performance advantage in retrieving a subset of an array, as opposed to the entire array.UsegetOracleArray to return anoracle.sql.Datum[] array. The elements of the returned array will be of theoracle.sql.* type that correspond to the SQL data type of the elements of the original array. For example:
Datum arraydata[] = arr.getOracleArray();
arr is anoracle.sql.ARRAY object.
Thefollowing example assumes that a connection objectconn and a statement objectstmt have already been created. In the example, an array with the SQL type nameNUM_ARRAY is created to store a VARRAY ofNUMBER data. TheNUM_ARRAY is in turn stored in a tableVARRAY_TABLE.
A query selects the contents of theVARRAY_TABLE. The result set is cast toOracleResultSet;getARRAY is applied to it to retrieve the array data intomy_array, which is anoracle.sql.ARRAY object.
Becausemy_array is of typeoracle.sql.ARRAY, you can apply the methodsgetSQLTypeName andgetBaseType to it to return the name of the SQL type of each element in the array and its integer code.
The program then prints the contents of the array. Because the contents ofNUM_ARRAY are of the SQL data typeNUMBER, the elements ofmy_array are of the type,BigDecimal. Before you can use the elements, they must first be cast toBigDecimal. In thefor loop, the individual values of the array are cast toBigDecimal and printed to standard output.
stmt.execute ("CREATE TYPE num_varray AS VARRAY(10) OF NUMBER(12, 2)");stmt.execute ("CREATE TABLE varray_table (col1 num_varray)");stmt.execute ("INSERT INTO varray_table VALUES (num_varray(100, 200))");ResultSet rs = stmt.executeQuery("SELECT * FROM varray_table");ARRAY my_array = ((OracleResultSet)rs).getARRAY(1);// return the SQL type names, integer codes, // and lengths of the columnsSystem.out.println ("Array is of type " + array.getSQLTypeName());System.out.println ("Array element is of type code " + array.getBaseType());System.out.println ("Array is of length " + array.length());// get Array elements BigDecimal[] values = (BigDecimal[]) my_array.getArray();for (int i=0; i<values.length; i++) { BigDecimal out_value = (BigDecimal) values[i]; System.out.println(">> index " + i + " = " + out_value.intValue());}Note that if you usegetResultSet to obtain the array, then you must would first get the result set object, and then use thenext method to iterate through it. Notice the use of the parameter indexes in thegetInt method to retrieve the element index and the element value.
ResultSet rset = my_array.getResultSet();while (rset.next()){ // The first column contains the element index and the // second column contains the element value System.out.println(">> index " + rset.getInt(1)+" = " + rset.getInt(2));}Theoracle.sql.ARRAY class provides three methods, which are overloaded, to access collection elements. The JDBC drivers extend these methods to support multilevel collections. These methods are:
getArray method
getOracleArray method
getResultSet method
ThegetArray method returns a Java array that holds the collection elements. The array element type is determined by the collection element type and the JDBC default conversion matrix.
For example, thegetArray method returns ajava.math.BigDecimal array for collection of SQLNUMBER. ThegetOracleArray method returns aDatum array that holds the collection elements inDatum format. For multilevel collections, thegetArray andgetOracleArray methods both return a Java array oforacle.sql.ARRAY elements.
ThegetResultSet method returns aResultSet object that wraps the multilevel collection elements. For multilevel collections, the JDBC applications use thegetObject,getARRAY, orgetArray method of theResultSet class to access the collection elements as instances oforacle.sql.ARRAY.
The following code shows how to use thegetOracleArray,getArray, andgetResultSet methods:
Connection conn = ...; // make a JDBC connection Statement stmt = conn.createStatement (); ResultSet rset = stmt.executeQuery ("select col2 from tab2 where idx=1"); while (rset.next()) { ARRAY varray3 = (ARRAY) rset.getObject (1); Object varrayElems = varray3.getArray (1); // access array elements of "varray3" Datum[] varray3Elems = (Datum[]) varrayElems; for (int i=0; i<varray3Elems.length; i++) { ARRAY varray2 = (ARRAY) varray3Elems[i]; Datum[] varray2Elems = varray2.getOracleArray(); // access array elements of "varray2" for (int j=0; j<varray2Elems.length; j++) { ARRAY varray1 = (ARRAY) varray2Elems[j]; ResultSet varray1Elems = varray1.getResultSet(); // access array elements of "varray1" while (varray1Elems.next()) System.out.println ("idx="+varray1Elems.getInt(1)+" value="+varray1Elems.getInt(2)); } } } rset.close (); stmt.close (); conn.close ();This section discusses how to pass arrays to prepared statement objects or callable statement objects.
Passing an Array to a Prepared Statement
Pass an array to a prepared statement as follows.
Note:
you can use arrays as eitherIN orOUT bind variables.Define the array that you want to pass to the prepared statement as anoracle.sql.ARRAY object.
ARRAY array = oracle.jdbc.OracleConnection.createARRAY(sql_type_name, elements);
sql_type_name is a Java string specifying the user-defined SQL type name of the array andelements is ajava.lang.Object containing a Java array of the elements.
Create ajava.sql.PreparedStatement object containing the SQL statement to be run.
Cast your prepared statement toOraclePreparedStatement, and usesetARRAY to pass the array to the prepared statement.
(OraclePreparedStatement)stmt.setARRAY(parameterIndex,array);
parameterIndex is the parameter index andarray is theoracle.sql.ARRAY object you constructed previously.
Run the prepared statement.
Passing an Array to a Callable Statement
To retrieve acollection as anOUT parameter in PL/SQL blocks, perform the following to register the bind type for yourOUT parameter.
Cast your callable statement toOracleCallableStatement, as follows:
OracleCallableStatement ocs = (OracleCallableStatement)conn.prepareCall("{? = call func()}");Register theOUT parameter with the following form of theregisterOutParameter method:
ocs.registerOutParameter (intparam_index, intsql_type, stringsql_type_name);
param_index is the parameter index,sql_type is the SQL type code, andsql_type_name is the name of the array type. In this case, thesql_type isOracleTypes.ARRAY.
Run the call, as follows:
ocs.execute();
Get the value, as follows:
oracle.sql.ARRAY array = ocs.getARRAY(1);
If yourarray contains Oracle objects, then you can use a type map to associate the objects in the array with the corresponding Java class. If you do not specify a type map, or if the type map does not contain an entry for a particular Oracle object, then each element is returned as anoracle.sql.STRUCT object.
If you want the type map to determine the mapping between the Oracle objects in the array and their associated Java classes, then you must add an appropriate entry to the map.
The following example illustrates how you can use a type map to map the elements of an array to a custom Java object class. In this case, the array is a nested table. The example begins by defining anEMPLOYEE object that has a name attribute and employee number attribute.EMPLOYEE_LIST is a nested table type ofEMPLOYEE objects. Then anEMPLOYEE_TABLE is created to store the names of departments within a corporation and the employees associated with each department. In theEMPLOYEE_TABLE, the employees are stored in the form ofEMPLOYEE_LIST tables.
stmt.execute("CREATE TYPE EMPLOYEE AS OBJECT (EmpName VARCHAR2(50),EmpNo INTEGER))");stmt.execute("CREATE TYPE EMPLOYEE_LIST AS TABLE OF EMPLOYEE");stmt.execute("CREATE TABLE EMPLOYEE_TABLE (DeptName VARCHAR2(20), Employees EMPLOYEE_LIST) NESTED TABLE Employees STORE AS ntable1");stmt.execute("INSERT INTO EMPLOYEE_TABLE VALUES ("SALES", EMPLOYEE_LIST (EMPLOYEE('Susan Smith', 123), EMPLOYEE('Scott Tiger', 124)))");If you want to retrieve all the employees belonging to theSALES department into an array of instances of the custom object classEmployeeObj, then you must add an entry to the type map to specify mapping between theEMPLOYEE SQL type and theEmployeeObj custom object class.
To do this, first create your statement and result set objects, then select theEMPLOYEE_LIST associated with theSALES department into the result set. Cast the result set toOracleResultSet so you can use thegetARRAY method to retrieve theEMPLOYEE_LIST into anARRAY object (employeeArray in the following example).
TheEmployeeObj custom object class in this example implements theSQLData interface.
Statement s = conn.createStatement();OracleResultSet rs = (OracleResultSet)s.executeQuery ("SELECT Employees FROM employee_table WHERE DeptName = 'SALES'");// get the array object ARRAY employeeArray = ((OracleResultSet)rs).getARRAY(1);Now that you have theEMPLOYEE_LIST object, get the existing type map and add an entry that maps theEMPLOYEE SQL type to theEmployeeObj Java type.
// add type map entry to map SQL type // "EMPLOYEE" to Java type "EmployeeObj" Map map = conn.getTypeMap();map.put("EMPLOYEE", Class.forName("EmployeeObj"));Next, retrieve the SQLEMPLOYEE objects from theEMPLOYEE_LIST. To do this, call thegetArray method of theemployeeArray array object. This method returns an array of objects. ThegetArray method returns theEMPLOYEE objects into theemployees object array.
// Retrieve array elements Object[] employees = (Object[]) employeeArray.getArray();
Finally, create a loop to assign each of theEMPLOYEE SQL objects to theEmployeeObj Java objectemp.
// Each array element is mapped to EmployeeObj object.for (int i=0; i<employees.length; i++){ EmployeeObj emp = (EmployeeObj) employees[i]; ...}This chapter primarily describes the functionality of theoracle.sql.ARRAY class, but it is also possible to access Oracle collections through custom Java classes or, more specifically, custom collection classes.
You can create custom collection classes yourself, but the most convenient way is to use the Oracle JPublisher utility. Custom collection classes generated by JPublisher offer all the functionality described earlier in this chapter, as well as the following advantages:
They are strongly typed. This can help you find coding errors during compilation that might not otherwise be discovered until run time.
They can be changeable, or mutable. Custom collection classes produced by JPublisher, unlike theARRAY class, allow you to get and set individual elements using thegetElement andsetElement methods.
A custom collection class must satisfy three requirements:
It must implement theoracle.sql.ORAData interface. Note that the standard JDBCSQLData interface, which is an alternative for custom object classes, is not intended for custom collection classes.
It, or a companion class, must implement theoracle.sql.ORADataFactory interface, for creating instances of the custom collection class.
It must have a means of storing the collection data. Typically it will directly or indirectly include anoracle.sql.ARRAY attribute for this purpose.
A JPublisher-generated customcollection class implementsORAData andORADataFactory and indirectly includes anoracle.sql.ARRAY attribute. The custom collection class will have anoracle.jpub.runtime.MutableArray attribute. TheMutableArray class has anoracle.sql.ARRAY attribute.
Note:
When you use JPublisher to create a custom collection class, you must use theORAData implementation. This will be true if the JPublisher-usertypes mapping option is set tooracle, which is the default.You cannot use aSQLData implementation for a custom collection class. Setting the-usertypes mapping option tojdbc is invalid.
As an example of custom collection classes being strongly typed, if you define an Oracle collectionMYVARRAY, then JPublisher can generate aMyVarray custom collection class. UsingMyVarray instances, instead of genericoracle.sql.ARRAY instances, makes it easier to catch errors during compilation instead of at run time. For example, if you accidentally assign some other kind of array into aMyVarray variable.
If you do not use custom collection classes, then you would use standardjava.sql.Array instances, ororacle.sql.ARRAY instances, to map to your collections.