OPP 2007

February 28 ± March 1, 2007 San Mateo Marriott San Mateo, California

An ODTUG SP* Oracle PL/SQL Programming Conference
*SP ± Seriously Practical Conference

ODTUG Kaleidoscope
June 18 ± 21, 2007
Pre-conference Hands-on Training - June 16 ± 17

Hilton Daytona Beach Oceanfront Resort Daytona, Florida

WOW-Wide Open World, Wide Open Web!
Copyright 2000-2006 Steven Feuerstein - Page 1

For more information visit www.odtug.com or call 910-452-7444

Everything you need to know about collections, but were afraid to ask
Steven Feuerstein
PL/SQL Evangelist Quest Software steven.feuerstein@quest.com
Copyright 2000-2006 Steven Feuerstein - Page 2

Ten Years Writing Ten Books on the Oracle PL/SQL Language

Copyright 2000-2006 Steven Feuerstein - Page 3

How to benefit most from this seminar 

Watch, listen, ask questions.  Download the training materials and supporting scripts:
± http://oracleplsqlprogramming.com/resources.html ± "Demo zip": all the scripts I run in my class available at http://oracleplsqlprogramming.com/downloads/demo.zip

Use these materials as an accelerator as you venture into new territory and need to apply new techniques.  Play games! Keep your brain fresh and active by mixing hard work with challenging games ± MasterMind and Set (www.setgame.com)
Copyright 2000-2006 Steven Feuerstein - Page 4

 They are an invaluable data structure. similar to 3GL arrays. ± They are not the most straightforward implementation of array-like structures.PL/SQL Collections  Collections are single-dimensioned lists of information.Page 5 . ± Advanced features like string indexes and multilevel collections can be a challenge.  Collections take some getting used to. Copyright 2000-2006 Steven Feuerstein . ± All PL/SQL developers should be very comfortable with collections and use them often.

Page 6 .What we will cover on collections      Review of basic functionality Indexing collections by strings Working with collections of collections MULTISET operators for nested tables Then later in the section on SQL: ± Bulk processing with FORALL and BULK COLLECT ± Table functions and pipelined functions Copyright 2000-2006 Steven Feuerstein .

Copyright 2000-2006 Steven Feuerstein .. sets. but you can create collections of collections to emulate multi-dimensional structures." (PL/SQL User Guide and Reference) ± That's a very general definition. rrr  A collection is an "ordered group of elements. ± Collections are single-dimensional.. all of the same type.Page 7 . ± Each element of a collection may be addressed by a unique subscript.What is a collection? 1 abc 2 def 3 sf 4 q 22 23 swq . arrays and similar data structures are all types of collections. usually an integer but in some cases also a string. lists.

Page 8 . to manipulate in-program-memory lists of information. updating and deleting the contents of tables.  Dramatically improve multi-row querying.  Serve up complex datasets of information to nonPL/SQL host environments using table functions.  Emulate bi-directional cursors. Copyright 2000-2006 Steven Feuerstein . which are not yet supported within PL/SQL. ± Much faster than working through SQL.. inserting.Why use collections?  Generally. Combined with BULK COLLECT and FORALL...

Copyright 2000-2006 Steven Feuerstein . such as table functions ± With Varrays.  Nested tables and Varrays ± Can be used in PL/SQL blocks.Three Types of Collections  Associative arrays (aka index-by tables) ± Can be used only in PL/SQL blocks. ± Part of the object model in PL/SQL. you specify a maximum number of elements in the collection. ± Required for some features. allows you to access elements via arbitrary subscript values. but also can be the datatype of a column in a relational table. at time of definition. ± Similar to hash tables in other languages.Page 9 .

because AAs also are:  Sparse ± Data does not have to be stored in consecutive rows.647.147.147.483.sql . such as the primary key or unique index value. ± This range allows you to employ the row number as an intelligent key. practically speaking.About Associative Arrays  Unbounded.Page 10 assoc_array_example.  Index values can be integers or strings (Oracle9i R2 and above). Copyright 2000-2006 Steven Feuerstein . ± Valid row numbers range from -2. as is required in traditional 3GL arrays and VARRAYs.483.647 to 2.

647.  Is always dense initially.About Nested Tables  No pre-defined limit on a nested table.  Part of object model. but can become sparse after deletes. ± Valid row numbers range from 1 to 2.147.Page 11 nested_table_example. Copyright 2000-2006 Steven Feuerstein .483.  Can be defined as a schema level type and used as a relational table column type. requiring initialization.sql .

sql Copyright 2000-2006 Steven Feuerstein . you can only remove elements from the end of a varray. associated with its type.  Can be defined as a schema level type and used as a relational table column type. varray_example.  Is always dense. ± Can adjust the size at runtime in Oracle10g R2.About Varrays  Has a maximum size.  Part of object model. requiring initialization.Page 12 .

... ± Access the collection inside SQL (table functions... columns in tables) ± Want to perform set operations  Use varrays when you need to. Copyright 2000-2006 Steven Feuerstein . ± Work within PL/SQL code only ± Sparsely fill and manipulate the collection ± Take advantage of negative index values  Use nested tables when you need to.How to choose your collection type  Use associative arrays when you need to.Page 13 . columns in tables).. ± If you need to specify a maximum size to your collection ± Access the collection inside SQL (table functions.

± EXTEND adds rows to a nested table or VARRAY.Page 14 . Copyright 2000-2006 Steven Feuerstein . ± NEXT/PRIOR return the closest defined row after/before the specified row.  Modify the contents of the collection ± DELETE deletes one or more rows from the index-by table. ± LIMIT tells you the max. ± EXISTS returns TRUE if the specified row is defined.Wide Variety of Collection Methods  Obtain information about the collection ± COUNT returns number of rows currently defined in collection. number of elements allowed in a VARRAY. ± TRIM removes rows from a VARRAY. ± FIRST/LAST return lowest/highest numbers of defined rows.

 Encapsulate or hide details of collection management.* .  Try to read a row that doesn't exist.  Use the NOCOPY hint to reduce overhead of passing collections in and out of program units. Think about how you need to manipulate the contents.sql nocopy*. and Oracle raises NO_DATA_FOUND. Copyright 2000-2006 Steven Feuerstein .Page 15 mysess.Useful reminders for PL/SQL collections  Memory for collections comes out of the PGA or Process Global Area ± One per session. so a program using collections can consume a large amount of memory.pkg sess2.  Don't always fill collections sequentially.

Request data from database Application PGA Function Application Requests Data Subsequent accesses Data found in cache. Database is not needed. Data retrieved from cache Data returned to application Database Application PGA Function Application Requests Data Copyright 2000-2006 Steven Feuerstein .tst .Data Caching with PL/SQL Tables First access Pass Data to Cache Data retrieved from cache Data returned to application Database Not in cache.pkg emplu.Page 16 emplu.

you could only index by BINARY_INTEGER.  This means that you can now index on string values! (and concatenated indexes and..Oracle9i Release 2 New indexing capabilities for associative arrays  Prior to Oracle9iR2.  You can now define the index on your associative array to be: ± Any sub-type derived from BINARY_INTEGER ± VARCHAR2(n).Page 17 . where n is between 1 and 32767 ± %TYPE against a database column that is consistent with the above rules ± A SUBTYPE against any of the above.) Copyright 2000-2006 Steven Feuerstein ..

INDEX BY PLS_INTEGER. INDEX BY VARCHAR2(64).Oracle9i Release 2 Examples of New TYPE Variants  All of the following are now valid TYPE declarations in Oracle9i Release 2 ± You cannot use %TYPE against an INTEGER column. because INTEGER is not a subtype of BINARY_INTEGER. TYPE array_t8 IS TABLE OF NUMBER INDEX BY types_pkg. INDEX BY POSITIVE.subtype_t. INDEX BY NATURAL.Page 18 . DECLARE TYPE TYPE TYPE TYPE TYPE TYPE TYPE INDEX BY BINARY_INTEGER. INDEX BY VARCHAR2(32767). INDEX BY employee.last_name%TYPE. array_t1 array_t2 array_t3 array_t4 array_t5 array_t6 array_t7 IS IS IS IS IS IS IS TABLE TABLE TABLE TABLE TABLE TABLE TABLE OF OF OF OF OF OF OF NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER NUMBER Copyright 2000-2006 Steven Feuerstein .

END.tst  Specifying a row via a string takes some getting used to.Page 19 . howmany := country_population ('Greenland'). continent_population population_type.sql assoc_array_perf. but if offers some very powerful advantages. continent_population ('Australia') := 30000000.Working with string-indexed collections DECLARE TYPE population_type IS TABLE OF NUMBER INDEX BY VARCHAR2(64). assoc_array*. country_population population_type. howmany NUMBER. country_population ('Iceland') := 750000. Copyright 2000-2006 Steven Feuerstein . BEGIN country_population ('Greenland') := 100000.

Rapid access to data via strings  One of the most powerful applications of this features is to construct very fast pathways to static data from within PL/SQL programs.* Copyright 2000-2006 Steven Feuerstein .tst Comparison of performance of different approaches: vocab*.Page 20 . unique indexes) with collections.sql Generate a caching package: genaa. Demonstration package: assoc_array5. ± If you are repeatedly querying the same data from the database.sql genaa. why not cache it in your PGA inside collections?  Emulate the various indexing mechanisms (primary key.

END mark_as_used. CREATE OR REPLACE PACKAGE BODY string_tracker IS TYPE used_aat IS TABLE OF BOOLEAN INDEX BY maxvarchar2_t. PROCEDURE mark_as_used (value_in IN maxvarchar2_t) IS BEGIN g_names_used ( value_in ) := TRUE. Copyright 2000-2006 Steven Feuerstein . g_names_used used_aat.Page 21 string_tracker1.* . END string_in_use. ± Can't declare the same variable twice. END string_tracker.The String Tracker package (V1)  Another example: I need to keep track of the names of variables that I have already used in my test code generation.EXISTS ( value_in ). FUNCTION string_in_use ( value_in IN maxvarchar2_t ) RETURN BOOLEAN IS BEGIN RETURN g_names_used.

± Applies to all three types of collections.  The syntax is non-intuitive and resulting code can be quite complex.Page 22 .  Now you can create collections that contain other collections and complex types. you could have collections of records or objects. Copyright 2000-2006 Steven Feuerstein .Oracle9i Multi-level Collections  Prior to Oracle9i. but only if all fields were scalars. ± A collection containing another collection was not allowed.

multilevel collection. ± What if I need to track multiple lists simultaneously or nested?  Let's extend the first version to support multiple lists by using a string-indexed. ± A list of lists...String Tracker Version 2  The problem with String Tracker V1 is that it only supports a single list of strings.Page 23 .. Copyright 2000-2006 Steven Feuerstein .

The String Tracker package (V2) CREATE OR REPLACE PACKAGE BODY string_tracker IS TYPE used_aat IS TABLE OF BOOLEAN INDEX BY maxvarchar2_t. value_in IN maxvarchar2_t .Page 24 . PROCEDURE mark_as_used ( list_in IN maxvarchar2_t . TYPE list_of_lists_aat IS TABLE OF used_aat INDEX BY maxvarchar2_t. BEGIN g_list_of_lists ( list_in ) ( l_name) := TRUE. case_sensitive_in IN BOOLEAN DEFAULT FALSE ) IS l_name maxvarchar2_t := CASE case_sensitive_in WHEN TRUE THEN value_in ELSE UPPER ( value_in ) END. g_list_of_lists list_of_lists_aat. END string_tracker.* Copyright 2000-2006 Steven Feuerstein . string_tracker2. END mark_as_used.

multilevel_collections. Copyright 2000-2006 Steven Feuerstein . ± Use the UTL_NLA package (10gR2) for complex matrix manipulation. multidim*. ± Automatically analyze ambiguous overloading.sql OTN: OverloadCheck .Page 25 ambig_overloading. but can creates nested collections to get much the same effect.Other multi-level collection examples  Multi-level collections with intermediate records and objects.sql  Emulation of multi-dimensional arrays ± No native support.*  Four-level nested collection used to track arguments for a program unit.

add_new_parameter Copyright 2000-2006 Steven Feuerstein .next_overloading cc_smartargs. ± Work with and through functions to retrieve contents and procedures to set contents.Page 26 . you can easily and rapidly arrive at completely unreadable and un-maintainable code.  What' s a developer to do? ± Hide complexity -.behind small modules.Encapsulate these complex structures!  When working with multi-level collections. cc_smartargs.and all data structures -.pkb: cc_smartargs.

.Oracle10g Nested Tables unveil their MULTISET-edness  Oracle10g introduces high-level set operations on nested tables (only). ± Nested tables are ³multisets.´ meaning that theoretically there is no order to their elements. This makes set operations of critical importance for manipulating nested tables. INTERSECT and MINUS operations ± Check for and remove duplicates Copyright 2000-2006 Steven Feuerstein .Page 27 .  You can now« ± Check for equality and inequality ± Perform UNION.

group3 clientele := clientele ('Customer 3'. 'Customer 3').sql 10g_compare_old. END IF.Page 28 .put_line ('Group 1 != Group 2'). END. group2 clientele := clientele ('Customer 1'. 10g_compare. ELSE DBMS_OUTPUT. END IF. 'Customer 2'). DECLARE TYPE clientele IS TABLE OF VARCHAR2 (64). IF group2 != group3 THEN DBMS_OUTPUT. ELSE DBMS_OUTPUT.Oracle10g Check for equality and inequality  Just use the basic operators«. group1 clientele := clientele ('Customer 1'. 'Customer 1').sql Copyright 2000-2006 Steven Feuerstein .put_line ('Group 1 = Group 2'). BEGIN IF group1 = group2 THEN DBMS_OUTPUT.sql 10g_compare2.put_line ('Group 2 != Group 3').put_line ('Group 2 = Group 3').

Copyright 2000-2006 Steven Feuerstein . MINUS  Straightforward. our_favorites). END.Page 29 10g_setops. our_favorites). show_favorites ('MINE then DAD'.sql 10g*union*.Oracle10g UNION. INTERSECT. with the MULTISET keyword. show_favorites ('ONLY DAD''S'. BEGIN our_favorites := my_favorites MULTISET UNION dad_favorites. our_favorites := my_favorites MULTISET UNION DISTINCT dad_favorites. our_favorites).sql . our_favorites).sql 10g_favorites. show_favorites ('MINE then DAD with DISTINCT'. our_favorites := my_favorites MULTISET INTERSECT dad_favorites. show_favorites ('DAD then MINE'. our_favorites := dad_favorites MULTISET EXCEPT my_favorites. our_favorites := dad_favorites MULTISET UNION my_favorites. show_favorites ('IN COMMON'.sql 10g_string_nt. our_favorites).

employee_id.salary.newsal_in IN employee. END upd_for_dept.salary%TYPE) IS CURSOR emp_cur IS SELECT employee_id. Copyright 2000-2006 Steven Feuerstein .hire_date FROM employee WHERE department_id = dept_in.Turbo-charged SQL with BULK COLLECT and FORALL  Improve the performance of multi-row SQL operations by an order of magnitude or more with bulk/array processing in PL/SQL! CREATE OR REPLACE PROCEDURE upd_for_dept ( dept_in IN employee. END LOOP. BEGIN FOR rec IN emp_cur LOOP UPDATE employee SET salary = newsal_in WHERE employee_id = rec.department_id%TYPE .Page 30 ´Conventional bindsµ (and lots of them!) .

WHERE employee_id = rec. END LOOP.Page 31 . SQL Engine Procedural statement executor SQL statement executor Performance penalty for many ³context switches´ Copyright 2000-2006 Steven Feuerstein ..Conventional Bind Oracle server PL/SQL Runtime Engine PL/SQL block FOR rec IN emp_cur LOOP UPDATE employee SET salary = ..employee_id.

FIRST. list_of_emps. SQL Engine Procedural statement executor SQL statement executor Much less overhead for context switching Copyright 2000-2006 Steven Feuerstein .Enter the ³Bulk Bind´: FORALL Oracle server PL/SQL Runtime Engine PL/SQL block FORALL indx IN list_of_emps...Page 32 .. WHERE employee_id = list_of_emps(indx).LAST UPDATE employee SET salary = .

LAST UPDATE employee SET salary = newsal_in WHERE employee_id = list_of_emps (indx). END. ± Use SAVE EXCEPTIONS to continue past errors. you can write your code like this: PROCEDURE upd_for_dept (.. individual DML statements.sql . list_of_emps. SQL%BULK_EXCEPTIONS..sql bulk_rowcount.FIRST .Page 33 bulktiming.Use the FORALL Bulk Bind Statement  Instead of executing repetitive.... Copyright 2000-2006 Steven Feuerstein . ± New cursor attributes: SQL%BULK_ROWCOUNT returns number of rows affected by each row in array.) IS BEGIN FORALL indx IN list_of_emps.  Things to be aware of: ± You MUST know how to use collections to use this feature! ± Only a single DML statement is allowed per FORALL. ± Prior to Oracle10g. the binding array must be sequentially filled.

sql .COUNT LOOP process_employee (l_employees(indx)). END LOOP. l_employees employees_aat. BEGIN SELECT * BULK COLLECT INTO l_employees FROM employees. Iterate through the collection contents with a loop.. FOR indx IN 1 . END. Copyright 2000-2006 Steven Feuerstein .Page 34 bulkcoll. Use BULK COLLECT to retrieve all rows.Use BULK COLLECT INTO for Queries DECLARE TYPE employees_aat IS TABLE OF employees%ROWTYPE INDEX BY BINARY_INTEGER. Declare a collection of records to hold the queried data. l_employees.

WARNING! BULK COLLECT will not raise NO_DATA_FOUND if no rows are found. Best to check contents of collection to confirm that something was retrieved. END bulk_with_limit. emps emp_tt. LOOP FETCH emps_in_dept_cur BULK COLLECT INTO emps LIMIT 100.Limit the number of rows returned by BULK COLLECT CREATE OR REPLACE PROCEDURE bulk_with_limit (deptno_in IN dept. bulklimit.sql .deptno%TYPE) IS CURSOR emps_in_dept_cur IS SELECT * FROM emp WHERE deptno = deptno_in. EXIT WHEN emps. process_emps (emps). Copyright 2000-2006 Steven Feuerstein . BEGIN OPEN emps_in_dept_cur.COUNT = 0.Page 35 Use the LIMIT clause with the INTO to manage the amount of memory used with the BULK COLLECT operation. END LOOP. TYPE emp_tt IS TABLE OF emps_in_dept_cur%ROWTYPE.

Collection subscripts cannot be expressions.Tips and Fine Points  Use bulk binds in these circumstances: ± Recurring SQL statement in PL/SQL loop.  Bulk collects: ± Can be used with implicit and explicit cursors ± Collection is always filled sequentially. The collections must be densely filled (pre-10gR2).pkg cfl_to_bulk*. Copyright 2000-2006 Steven Feuerstein .Page 36 emplu. Oracle recommended threshold: five rows!  Bulk bind rules: ± Can be used with any kind of collection.* . starting at row 1.

± Not everything can be done in SQL. works very smoothly with cursor variables Copyright 2000-2006 Steven Feuerstein .Page 37 .The Wonder Of Table Functions  A table function is a function that you can call in the FROM clause of a query. for example.  Table functions allow you to perform arbitrarily complex transformations of data and then make that data available through a query. and have it be treated as if it were a relational table.  Combined with REF CURSORs. you can now more easily transfer data from within PL/SQL to host environments. ± Java.

 The function header and the way it is called must be SQL-compatible: all parameters use SQL types. Copyright 2000-2006 Steven Feuerstein . the IN parameter must be a cursor variable -. ± In some cases (streaming and pipelined functions).Building a table function  A table function must return a nested table or varray based on a schema-defined type.a query result set. no named notation. or type defined in a PL/SQL package.Page 38 .

END lotsa_names.. FOR indx IN 1 .. RETURN retval.. BEGIN retval. Steven 100 tabfunc_scalar.Page 39 SELECT column_value FROM TABLE ( lotsa_names ('Steven' . 100)) names.sql . count_in IN INTEGER ) RETURN names_nt IS retval names_nt := names_nt (). count_in LOOP retval (indx) := base_name_in || ' ' || indx. Copyright 2000-2006 Steven Feuerstein .EXTEND (count_in).Simple table function example  Return a list of names as a nested table. END LOOP. CREATE OR REPLACE FUNCTION lotsa_names ( base_name_in IN VARCHAR2. COLUMN_VALUE -----------Steven 1 . and then call that function in the FROM clause.

trade_date DATE. price NUMBER) / tabfunc_streaming.Page 40 . open_price NUMBER. CREATE TABLE stocktable ( ticker VARCHAR2(20).sql Copyright 2000-2006 Steven Feuerstein . close_price NUMBER ) / CREATE TABLE tickertable ( ticker VARCHAR2(20).Streaming data with table functions  You can use table functions to "stream" data through several stages within a single SQL statement. pricedate DATE. ± Example: transform one row in the stocktable to two rows in the tickertable. pricetype VARCHAR2(1).

sql . END refcur_pkg.Page 41 tabfunc_streaming.2  In this example.. BEGIN INSERT INTO tickertable SELECT * FROM TABLE (stockpivot (CURSOR (SELECT * FROM stocktable))). / CREATE OR REPLACE FUNCTION stockpivot (dataset refcur_pkg.refcur_t) RETURN tickertypeset . CREATE OR REPLACE PACKAGE refcur_pkg IS TYPE refcur_t IS REF CURSOR RETURN stocktable%ROWTYPE.. transform each row of the stocktable into two rows in the tickertable.Streaming data with table functions . END. / Copyright 2000-2006 Steven Feuerstein .

 Pipelined functions can be defined to support parallel execution. ± Iterative data processing allows multiple processes to work on that data simultaneously.refcur_t) RETURN TickerTypeSet PIPELINED  Pipelined functions allow you to return data iteratively. it is passed back to the calling process/query. ± As data is produced within the function.Use pipelined functions to enhance performance. Copyright 2000-2006 Steven Feuerstein . CREATE FUNCTION StockPivot (p refcur_pkg. asynchronous to termination of the function.Page 42 .

use the PARALLEL_ENABLE clause to allow your pipelined function to participate fully in a parallelized query. ± Use a pipelined function to "serve up" data to the webpage and allow users to being viewing and browsing.  Improve speed of delivery of data to web pages. even before the function has finished retrieving all of the data. Copyright 2000-2006 Steven Feuerstein .Page 43 . ± In Oracle9i Database Release 2 and above. ± Critical in data warehouse applications.Applications for pipelined functions  Execution functions in parallel.

sql tabfunc_pipelined.. END LOOP. RETURN. END.ticker.nothing at all! Copyright 2000-2006 Steven Feuerstein .sql Pipe a row of data back to calling block or query RETURN. NULL.refcur_t) RETURN tickertypeset PIPELINED IS out_rec tickertype := tickertype (NULL. EXIT WHEN p%NOTFOUND. CLOSE p. out_rec.Page 44 . out_rec. PIPE ROW (out_rec).Piping rows out from a pipelined function Add PIPELINED keyword to header CREATE FUNCTION stockpivot (p refcur_pkg. out_rec. BEGIN LOOP FETCH p INTO in_rec. tabfunc_setup.price := in_rec..ticker := in_rec.pricetype := 'O'. NULL).openprice. in_rec p%ROWTYPE.

Page 45 . ± Choose a partition option that specifies how the function's execution should be partitioned. ± "ANY" means that the results are independent of the order in which the function receives the input rows (through the REF CURSOR).  Include the PARALLEL_ENABLE hint in the program header. {[ORDER | CLUSTER] BY column_list} PARALLEL_ENABLE ({PARTITION p BY [ANY | (HASH | RANGE) column_list]} ) Copyright 2000-2006 Steven Feuerstein .Enabling Parallel Execution  The table function's parameter list must consist only of a single strongly-typed REF CURSOR.

Copyright 2000-2006 Steven Feuerstein .Page 46 . ± Need to pass back complex result sets of data through the SQL layer (a query).Table functions ± Summary  Table functions offer significant new flexibility for PL/SQL developers..  Consider using them when you.. ± Want to call a user defined function inside a query and execute it as part of a parallel query.

taking full advantage of new features.  It is impossible to write modern PL/SQL code. Copyright 2000-2006 Steven Feuerstein .Page 47 . ± Your code will get faster and in many cases much simpler than it might have been (though not always!).Collections ± don't start coding without them. collections are required.  Today I offer this challenge: learn collections thoroughly and apply them throughout your backend code. unless you use collections. ± From array processing to table functions.

Page 48 For more information visit www. California An ODTUG SP* Oracle PL/SQL Programming Conference *SP ± Seriously Practical Conference ODTUG Kaleidoscope June 18 ± 21.OPP 2007 February 28 ± March 1. Wide Open Web! Copyright 2000-2006 Steven Feuerstein .com or call 910-452-7444 .June 16 ± 17 Hilton Daytona Beach Oceanfront Resort Daytona. 2007 San Mateo Marriott San Mateo.odtug. Florida WOW-Wide Open World. 2007 Pre-conference Hands-on Training .

Sign up to vote on this title
UsefulNot useful