[mapserver-commits] r9058 - sandbox/single-pass/mapserver
svn at osgeo.org
svn at osgeo.org
Fri May 29 00:05:08 EDT 2009
Author: sdlime
Date: 2009-05-29 00:05:07 -0400 (Fri, 29 May 2009)
New Revision: 9058
Modified:
sandbox/single-pass/mapserver/maporaclespatial.c
Log:
Added Jim's second generation patch for Oracle Spatial. (#2933)
Modified: sandbox/single-pass/mapserver/maporaclespatial.c
===================================================================
--- sandbox/single-pass/mapserver/maporaclespatial.c 2009-05-29 03:57:57 UTC (rev 9057)
+++ sandbox/single-pass/mapserver/maporaclespatial.c 2009-05-29 04:05:07 UTC (rev 9058)
@@ -139,9 +139,10 @@
msOracleSpatialDataHandler *oradatahandlers;
/* fetch data */
- int rows_fetched;
- int row_num;
- int row;
+ long row_num; /* current row number within cursor results == shapeindex */
+ long rows_count; /* total number of rows seen so far within the cursor */
+ int rows_fetched; /* number of rows in buffer (note: buffer is of length ARRAY_SIZE )*/
+ int row; /* current row index within buffer */
item_text_array *items; /* items buffer */
item_text_array_query *items_query; /* items buffer */
SDOGeometryObj *obj[ARRAY_SIZE]; /* spatial object buffer */
@@ -1766,7 +1767,7 @@
OCIDefineObject( adtp, hand->errhp, dthand->tdo, (dvoid **)layerinfo->obj, (ub4 *)0, (dvoid **)layerinfo->ind, (ub4 *)0 ) )
&& TRY( hand,
/* execute */
- OCIStmtExecute( hand->svchp, dthand->stmthp, hand->errhp, (ub4)ARRAY_SIZE, (ub4)0, (OCISnapshot *)NULL, (OCISnapshot *)NULL, (ub4)OCI_DEFAULT ) )
+ OCIStmtExecute( hand->svchp, dthand->stmthp, hand->errhp, (ub4)ARRAY_SIZE, (ub4)0, (OCISnapshot *)NULL, (OCISnapshot *)NULL, (ub4)OCI_STMT_SCROLLABLE_READONLY ) )
&& TRY( hand,
/* get rows fetched */
OCIAttrGet( (dvoid *)dthand->stmthp, (ub4)OCI_HTYPE_STMT, (dvoid *)&layerinfo->rows_fetched, (ub4 *)0, (ub4)OCI_ATTR_ROW_COUNT, hand->errhp ) );
@@ -1823,19 +1824,21 @@
return MS_DONE;
do{
- /* is buffer empty? */
- if (layerinfo->row_num >= layerinfo->rows_fetched)
- {
- /* fetch more */
- success = TRY( hand, OCIStmtFetch( dthand->stmthp, hand->errhp, (ub4)ARRAY_SIZE, (ub2)OCI_FETCH_NEXT, (ub4)OCI_DEFAULT ) )
- && TRY( hand, OCIAttrGet( (dvoid *)dthand->stmthp, (ub4)OCI_HTYPE_STMT, (dvoid *)&layerinfo->rows_fetched, (ub4 *)0, (ub4)OCI_ATTR_ROW_COUNT, hand->errhp ) );
+ /* is buffer empty? */
+ if (layerinfo->row >= layerinfo->rows_fetched)
+ {
+ /* fetch more */
+ /* TODO: This would be faster if we could overlap the OCI call while we are processing our buffer so we don't have to wait when we get to the end */
+ success = TRY( hand, OCIStmtFetch2( dthand->stmthp, hand->errhp, (ub4)ARRAY_SIZE, (ub2)OCI_FETCH_NEXT, (sb4)0, (ub4)OCI_DEFAULT ) )
+ && TRY( hand, OCIAttrGet( (dvoid *)dthand->stmthp, (ub4)OCI_HTYPE_STMT, (dvoid *)&layerinfo->rows_fetched, (ub4 *)0, (ub4)OCI_ATTR_ROWS_FETCHED, hand->errhp ) )
+ && TRY( hand, OCIAttrGet( (dvoid *)dthand->stmthp, (ub4)OCI_HTYPE_STMT, (dvoid *)&layerinfo->rows_count, (ub4 *)0, (ub4)OCI_ATTR_ROW_COUNT, hand->errhp ) );
+
+ if (!success || layerinfo->rows_fetched == 0)
+ return MS_DONE;
- if (!success || layerinfo->rows_fetched == 0)
+ if (layerinfo->row_num >= layerinfo->rows_count)
return MS_DONE;
- if (layerinfo->row_num >= layerinfo->rows_fetched)
- return MS_DONE;
-
layerinfo->row = 0; /* reset row index */
}
@@ -1844,7 +1847,7 @@
ind = layerinfo->ind[ layerinfo->row ];
/* get the items for the shape */
- shape->index = atol( (char *)(layerinfo->items[0][ layerinfo->row ]));
+ shape->index = layerinfo->row_num;
shape->numvalues = layer->numitems;
shape->values = (char **)malloc( sizeof(char*) * shape->numvalues );
@@ -1941,10 +1944,16 @@
}
else
{
- dthand = (msOracleSpatialDataHandler *)layerinfo->oradatahandlers;
hand = (msOracleSpatialHandler *)layerinfo->orahandlers;
}
+ /* Use a local statement handle for this function so we don't mess up what WhichShapes setup */
+ dthand = (msOracleSpatialDataHandler *)malloc(sizeof(msOracleSpatialDataHandler));
+ if(dthand == NULL) {
+ msSetError( MS_ORACLESPATIALERR, "msOracleSpatialLayerGetItems cannot allocate dthand", "msOracleSpatialLayerGetItems()" );
+ return MS_FAILURE;
+ }
+
if (!msSplitData(layer->data, geom_column_name, table_name, unique, srid, &function, &version))
{
msSetError( MS_ORACLESPATIALERR,
@@ -1960,7 +1969,8 @@
sprintf( query_str, "SELECT * FROM %s", table_name );
- success = TRY( hand, OCIStmtPrepare( dthand->stmthp, hand->errhp, (text *)query_str, (ub4)strlen(query_str), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DESCRIBE_ONLY) )
+ success = msOCISetDataHandlers(hand, dthand)
+ && TRY( hand, OCIStmtPrepare( dthand->stmthp, hand->errhp, (text *)query_str, (ub4)strlen(query_str), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DESCRIBE_ONLY) )
&& TRY( hand, OCIStmtExecute( hand->svchp, dthand->stmthp, hand->errhp, (ub4)QUERY_SIZE, (ub4)0, (OCISnapshot *)NULL, (OCISnapshot *)NULL, (ub4)OCI_DESCRIBE_ONLY ) )
&& TRY( hand, OCIAttrGet( (dvoid *)dthand->stmthp, (ub4)OCI_HTYPE_STMT, (dvoid *)&layer->numitems, (ub4 *)0, OCI_ATTR_PARAM_COUNT, hand->errhp) );
@@ -2052,268 +2062,135 @@
return MS_FAILURE;
}
+ msOCICloseDataHandlers( dthand );
return msOracleSpatialLayerInitItemInfo( layer );
}
+/* THIS IS A CHANGE FROM THE OLD BEHAVIOR --- No longer search for primary key
+ * Now this looks for a result from the existing (see WhichShapes) result set
+ */
+/* Seek to result set id = offset and return shape found (pointed to/referenced) there -- random access to result set instead of dataset (GetShape) */
+/* Offset is offset into cursor (not the primary key... this is set as such now in NextShape so the MS result sets will contain this id */
+/* this will probably become the new getshape */
+/* New rules: WhichShape needs to be called first. NextShape shouldn't be used after GetShape is called */
+/* This shouldn't be a problem for current use cases. */
int msOracleSpatialLayerGetShape( layerObj *layer, shapeObj *shape, long record )
{
- char query_str[6000], table_name[2000], geom_column_name[100], unique[100], srid[100];
- int success, i;
- int function = 0;
- int version = 0;
- SDOGeometryObj *obj = NULL;
- SDOGeometryInd *ind = NULL;
- sb2 *nullind = NULL;
- /*OCIDefine *adtp = NULL, *items[QUERY_SIZE] = { NULL };*/
- OCIDefine *adtp = NULL;
- OCIDefine **items = NULL;
-
- msOracleSpatialLayerInfo *layerinfo = (msOracleSpatialLayerInfo *)layer->layerinfo;
+ int success, i;
+ long buffer_first_row_num, buffer_last_row_num;
+ SDOGeometryObj *obj;
+ SDOGeometryInd *ind;
msOracleSpatialDataHandler *dthand = NULL;
msOracleSpatialHandler *hand = NULL;
+ msOracleSpatialLayerInfo *layerinfo;
- if (layer->debug)
- msDebug("msOracleSpatialLayerGetShape was called. Using the record = %ld.\n", record);
+ if(layer == NULL) {
+ msSetError( MS_ORACLESPATIALERR, "msOracleSpatialLayerGetShape called on unopened layer","msOracleSpatialLayerGetShape()" );
+ return MS_FAILURE;
+ }
+
+ layerinfo = (msOracleSpatialLayerInfo *)layer->layerinfo;
- if (layerinfo == NULL)
- {
- msSetError( MS_ORACLESPATIALERR, "msOracleSpatialLayerGetShape called on unopened layer","msOracleSpatialLayerGetShape()" );
- return MS_FAILURE;
- }
- else
- {
- dthand = (msOracleSpatialDataHandler *)layerinfo->oradatahandlers;
- hand = (msOracleSpatialHandler *)layerinfo->orahandlers;
- }
+ if (layerinfo == NULL)
+ {
+ msSetError( MS_ORACLESPATIALERR, "msOracleSpatialLayerGetShape called on unopened layer","msOracleSpatialLayerGetShape()" );
+ return MS_FAILURE;
+ }
- /* allocate enough space for items */
- if (layer->numitems > 0)
- {
- layerinfo->items_query = (item_text_array_query *)malloc( sizeof(item_text_array_query) * (layer->numitems) );
+ /* get layerinfo */
+ dthand = (msOracleSpatialDataHandler *)layerinfo->oradatahandlers;
+ hand = (msOracleSpatialHandler *)layerinfo->orahandlers;
- if (layerinfo->items_query == NULL)
- {
- msSetError( MS_ORACLESPATIALERR, "Cannot allocate layerinfo->items_query buffer", "msOracleSpatialLayerGetShape()" );
- return MS_FAILURE;
- }
+ if (layer->resultcache == NULL)
+ {
+ msSetError( MS_ORACLESPATIALERR, "msOracleSpatialLayerGetShape called before msOracleSpatialLayerWhichShapes()","msOracleSpatialLayerGetShape()" );
+ return MS_FAILURE;
+ }
- nullind = (sb2 *)malloc( sizeof(sb2) * (layer->numitems) );
- if (nullind == NULL)
- {
- msSetError( MS_ORACLESPATIALERR, "Cannot allocate nullind buffer", "msOracleSpatialLayerGetShape()" );
- return MS_FAILURE;
- }
- memset(nullind ,0, sizeof(sb2) * (layer->numitems) );
+ if (layer->debug)
+ msDebug("msOracleSpatialLayerGetShape was called. Using the record = %ld of %ld. (shape %ld)\n",
+ record, layer->resultcache->numresults, layer->resultcache->results[record].shapeindex);
- items = (OCIDefine **)malloc(sizeof(OCIDefine *)*layer->numitems);
- if (items == NULL)
- {
- msSetError( MS_ORACLESPATIALERR,"Cannot allocate items buffer","msOracleSpatialLayerWhichShapes()" );
+ if (record >= layerinfo->rows_count || record < 0)
+ {
+ fprintf(stderr, "record: %ld, row_count: %ld\n", record, layerinfo->rows_count);
+ msSetError( MS_ORACLESPATIALERR, "msOracleSpatialLayerGetShape record out of range","msOracleSpatialLayerGetShape()" );
+ return MS_FAILURE;
+ }
+
+ /* NOTE: with the way the resultcache works, we should see items in increasing order, but some may have been filtered out. */
+ /* Best case: item in buffer */
+ /* Next best case: item is in next fetch block */
+ /* Worst case: item is random access */
+ buffer_first_row_num = layerinfo->row_num - layerinfo->row; /* cursor id of first item in buffer */
+ buffer_last_row_num = buffer_first_row_num + layerinfo->rows_fetched - 1; /* cursor id of last item in buffer */
+ if(record >= buffer_first_row_num && record <= buffer_last_row_num) /* Item is in buffer. Calculate position in buffer */
+ {
+ layerinfo->row += record - layerinfo->row_num; /* move layerinfo row an row_num by offset from last call */
+ layerinfo->row_num += record - layerinfo->row_num;
+ }
+ else /* Item is not in buffer. Fetch item from Oracle */
+ {
+ if (layer->debug)
+ msDebug("msOracleSpatialLayerGetShape: Fetching result from DB start: %ld end:%ld record: %ld\n", buffer_first_row_num, buffer_last_row_num, record);
- /* clean nullind */
- free(nullind);
+ success = TRY( hand, OCIStmtFetch2( dthand->stmthp, hand->errhp, (ub4)ARRAY_SIZE, (ub2)OCI_FETCH_ABSOLUTE, (sb4)record+1, (ub4)OCI_DEFAULT ) )
+ && TRY( hand, OCIAttrGet( (dvoid *)dthand->stmthp, (ub4)OCI_HTYPE_STMT, (dvoid *)&layerinfo->rows_fetched, (ub4 *)0, (ub4)OCI_ATTR_ROWS_FETCHED, hand->errhp ) );
+ layerinfo->row_num = record;
+ layerinfo->row = 0; /* reset row index */
+
+ if (!success || layerinfo->rows_fetched == 0) {
+ msSetError( MS_ORACLESPATIALERR, "msOracleSpatialLayerGetShape could not fetch specified record.", "msOracleSpatialLayerGetShape()" );
+ return MS_FAILURE;
+ }
+ }
+
+ /* set obj & ind for current row */
+ obj = layerinfo->obj[ layerinfo->row ];
+ ind = layerinfo->ind[ layerinfo->row ];
- return MS_FAILURE;
- }
- memset(items ,0,sizeof(OCIDefine *)*layer->numitems);
- }
+ /* get the items for the shape */
+ shape->index = record; /* By definition this is what we asked for */
+ shape->numvalues = layer->numitems;
- if (!msSplitData( layer->data, geom_column_name, table_name, unique, srid, &function, &version ))
- {
- msSetError( MS_ORACLESPATIALERR,
- "Error parsing OracleSpatial DATA variable. Must be: "
- "'geometry_column FROM table_name [USING UNIQUE <column> SRID srid# FUNCTION]' or "
- "'geometry_column FROM (SELECT stmt) [USING UNIQUE <column> SRID srid# FUNCTION]'. "
- "If want to set the FUNCTION statement you can use: FILTER, RELATE, GEOMRELATE or NONE. "
- "Your data statement: %s",
- "msOracleSpatialLayerGetShape()", layer->data );
+ shape->values = (char **)malloc( sizeof(char*) * shape->numvalues );
+ if (shape->values == NULL)
+ {
+ msSetError( MS_ORACLESPATIALERR, "No memory avaliable to allocate the values", "msOracleSpatialLayerNextShape()" );
+ return MS_FAILURE;
+ }
- /* clean nullind */
- free(nullind);
+ for( i=0; i < shape->numvalues; ++i )
+ {
+ shape->values[i] = (char *)malloc(strlen((char *)layerinfo->items[i+1][ layerinfo->row ])+1);
+ if (shape->values[i] == NULL)
+ {
+ msSetError( MS_ORACLESPATIALERR, "No memory avaliable to allocate the items", "msOracleSpatialLayerNextShape()" );
+ return MS_FAILURE;
+ }
+ else
+ {
+ strcpy(shape->values[i], (char *)layerinfo->items[i+1][ layerinfo->row ]);
+ shape->values[i][strlen((char *)layerinfo->items[i+1][ layerinfo->row ])] = '\0';
+ }
+ }
- /* clean items */
- free(items);
+ /* fetch a layer->type object */
+ success = osGetOrdinates(dthand, hand, shape, obj, ind);
- return MS_FAILURE;
- }
+ if (success != MS_SUCCESS)
+ {
+ msSetError( MS_ORACLESPATIALERR, "Call to osGetOrdinates failed.", "msOracleSpatialLayerGetShape()" );
+ return MS_FAILURE;
+ }
- /*Define the first query to retrive itens*/
- if (unique[0] == '\0')
- {
- msSetError( MS_ORACLESPATIALERR,
- "Error parsing OracleSpatial DATA variable for query. To execute "
- "query functions you need to define one "
- "unique column [USING UNIQUE <#column>]",
- "msOracleSpatialLayerGetShape()" );
+ osShapeBounds(shape);
+ if(shape->type == MS_SHAPE_NULL) {
+ fprintf(stderr, "\trecord: %ld, row: %d rownum: %ld pkey: %s\n", record, layerinfo->row, layerinfo->row_num, shape->values[0]);
+ msSetError( MS_ORACLESPATIALERR, "Shape type is null... this probably means a record number was requested that could not have beeen in a result set (as returned by NextShape).", "msOracleSpatialLayerGetShape()" );
+ return MS_FAILURE;
+ }
- /* clean nullind */
- free(nullind);
-
- /* clean items */
- free(items);
-
- return MS_FAILURE;
- }
- else
- sprintf( query_str, "SELECT");
-
- /*Define the query*/
- for( i = 0; i < layer->numitems; ++i )
- sprintf( query_str + strlen(query_str), " %s,", layer->items[i] );
-
- sprintf( query_str + strlen(query_str), " %s FROM %s WHERE %s = %ld", geom_column_name, table_name, unique, record);
-
- /*if (layer->filter.string != NULL)
- sprintf( query_str + strlen(query_str), " AND %s", (layer->filter.string));*/
- osFilteritem(layer, FUNCTION_NONE, query_str, 2);
-
- if (layer->debug)
- msDebug("msOracleSpatialLayerGetShape. Sql: %s\n", query_str);
-
- /*Prepare the handlers to the query*/
- success = TRY( hand,OCIStmtPrepare( dthand->stmthp, hand->errhp, (text *)query_str, (ub4)strlen(query_str), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT) );
-
- if (success && layer->numitems > 0)
- {
- for( i = 0; i < layer->numitems && success; i++ )
- success = TRY( hand, OCIDefineByPos( dthand->stmthp, &items[i], hand->errhp, (ub4)i+1, (dvoid *)layerinfo->items_query[i], (sb4)TEXT_SIZE, SQLT_STR, (sb2 *)&nullind[i], (ub2 *)0, (ub2 *)0, (ub4)OCI_DEFAULT ) );
- }
-
- if(!success)
- {
- msSetError( MS_ORACLESPATIALERR,
- "Error: %s . "
- "Query statement: %s . "
- "Check your data statement.",
- "msOracleSpatialLayerGetShape()", hand->last_oci_error, query_str );
-
- /* clean nullind */
- free(nullind);
-
- /* clean items */
- free(items);
-
- return MS_FAILURE;
- }
-
- if (success)
- {
- success = TRY( hand, OCIDefineByPos( dthand->stmthp, &adtp, hand->errhp, (ub4)layer->numitems+1, (dvoid *)0, (sb4)0, SQLT_NTY, (dvoid *)0, (ub2 *)0, (ub2 *)0, (ub4)OCI_DEFAULT) )
- && TRY( hand, OCIDefineObject( adtp, hand->errhp, dthand->tdo, (dvoid **)layerinfo->obj, (ub4 *)0, (dvoid **)layerinfo->ind, (ub4 *)0 ) )
- && TRY (hand, OCIStmtExecute( hand->svchp, dthand->stmthp, hand->errhp, (ub4)QUERY_SIZE, (ub4)0, (OCISnapshot *)NULL, (OCISnapshot *)NULL, (ub4)OCI_DEFAULT ))
- && TRY (hand, OCIAttrGet( (dvoid *)dthand->stmthp, (ub4)OCI_HTYPE_STMT, (dvoid *)&layerinfo->rows_fetched, (ub4 *)0, (ub4)OCI_ATTR_ROW_COUNT, hand->errhp ));
-
- }
-
- if(!success)
- {
- msSetError( MS_ORACLESPATIALERR,
- "Error: %s . "
- "Query statement: %s ."
- "Check your data statement.",
- "msOracleSpatialLayerGetShape()", hand->last_oci_error, query_str );
-
- /* clean nullind */
- free(nullind);
-
- /* clean items */
- free(items);
-
- return MS_FAILURE;
- }
-
- shape->type = MS_SHAPE_NULL;
-
- /* no rows fetched */
- if (layerinfo->rows_fetched == 0)
- {
- /* clean nullind */
- free(nullind);
-
- /* clean items */
- free(items);
-
- return (MS_DONE);
- }
-
- obj = layerinfo->obj[ layerinfo->row ];
- ind = layerinfo->ind[ layerinfo->row ];
-
- /* get the items for the shape */
- shape->numvalues = layer->numitems;
- shape->values = (char **) malloc(sizeof(char *) * layer->numitems);
- if (shape->values == NULL)
- {
- msSetError( MS_ORACLESPATIALERR, "No memory avaliable to allocate the values.", "msOracleSpatialLayerGetShape()" );
-
- /* clean nullind */
- free(nullind);
-
- /* clean items */
- free(items);
-
- return MS_FAILURE;
- }
-
- shape->index = record;
-
- for( i = 0; i < layer->numitems; ++i )
- {
- shape->values[i] = (char *)malloc(strlen((char *)layerinfo->items_query[layerinfo->row][i])+1);
-
- if (shape->values[i] == NULL)
- {
- msSetError( MS_ORACLESPATIALERR, "No memory avaliable to allocate the items buffer.", "msOracleSpatialLayerGetShape()" );
-
- /* clean nullind */
- free(nullind);
-
- /* clean items */
- free(items);
-
- return MS_FAILURE;
- }
- else
- {
- if (nullind[i] != OCI_IND_NULL)
- {
- strcpy(shape->values[i], (char *)layerinfo->items_query[layerinfo->row][i]);
- shape->values[i][strlen((char *)layerinfo->items_query[layerinfo->row][i])] = '\0';
- }
- else
- {
- shape->values[i][0] = '\0';
- }
- }
- }
-
- /* increment for next row */
- layerinfo->row_num++;
- layerinfo->row++;
-
- /* fetch a layer->type object */
- success = osGetOrdinates(dthand, hand, shape, obj, ind);
- if (success != MS_SUCCESS)
- {
- msSetError( MS_ORACLESPATIALERR, "Cannot execute query", "msOracleSpatialLayerGetShape()" );
-
- /* clean nullind */
- free(nullind);
-
- /* clean items */
- free(items);
-
- return MS_FAILURE;
- }
- osShapeBounds(shape);
- layerinfo->row = layerinfo->row_num = 0;
-
- /* clean nullind */
- free(nullind);
-
- /* clean items */
- free(items);
-
return (MS_SUCCESS);
}
@@ -2345,10 +2222,16 @@
}
else
{
- dthand = (msOracleSpatialDataHandler *)layerinfo->oradatahandlers;
hand = (msOracleSpatialHandler *)layerinfo->orahandlers;
}
+ /* Use a local statement handle for this function so we don't mess up what WhichShapes setup */
+ dthand = (msOracleSpatialDataHandler *)malloc(sizeof(msOracleSpatialDataHandler));
+ if(dthand == NULL) {
+ msSetError( MS_ORACLESPATIALERR, "msOracleSpatialLayerGetItems cannot allocate dthand", "msOracleSpatialLayerGetItems()" );
+ return MS_FAILURE;
+ }
+
/* allocate enough space for items */
if (layer->numitems > 0)
{
@@ -2401,7 +2284,8 @@
msDebug("msOracleSpatialLayerGetExtent. Using this Sql to retrieve the extent: %s.\n", query_str);
/*Prepare the handlers to the query*/
- success = TRY( hand,OCIStmtPrepare( dthand->stmthp, hand->errhp, (text *)query_str, (ub4)strlen(query_str), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT) );
+ success = msOCISetDataHandlers(hand, dthand)
+ && TRY( hand,OCIStmtPrepare( dthand->stmthp, hand->errhp, (text *)query_str, (ub4)strlen(query_str), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT) );
if (success && layer->numitems > 0)
{
@@ -2536,7 +2420,7 @@
/* clean items */
free(items);
-
+ msOCICloseDataHandlers( dthand );
return(MS_SUCCESS);
}
More information about the mapserver-commits
mailing list