[mapguide-commits] r6239 - in trunk/MgDev: Oem/FDO/ProviderList/ServerCommunity Server/src/PostBuild Server/src/Services/Feature Server/src/UnitTesting UnitTest/TestData/FeatureService/SQLite

svn_mapguide at osgeo.org svn_mapguide at osgeo.org
Mon Nov 21 06:00:25 EST 2011


Author: jng
Date: 2011-11-21 03:00:25 -0800 (Mon, 21 Nov 2011)
New Revision: 6239

Added:
   trunk/MgDev/Server/src/Services/Feature/ExtendedSelectCommand.cpp
   trunk/MgDev/Server/src/Services/Feature/ExtendedSelectCommand.h
   trunk/MgDev/Server/src/Services/Feature/FdoForcedOneToOneFeatureReader.cpp
   trunk/MgDev/Server/src/Services/Feature/FdoForcedOneToOneFeatureReader.h
   trunk/MgDev/UnitTest/TestData/FeatureService/SQLite/JoinTest.sqlite
   trunk/MgDev/UnitTest/TestData/FeatureService/SQLite/ParcelsJoinTest.sqlite
   trunk/MgDev/UnitTest/TestData/FeatureService/SQLite/UT_FdoJoin.FeatureSource
   trunk/MgDev/UnitTest/TestData/FeatureService/SQLite/UT_Parcels_SQLite_Join.FeatureSource
Modified:
   trunk/MgDev/Oem/FDO/ProviderList/ServerCommunity/providers.xml
   trunk/MgDev/Server/src/PostBuild/PostBuild.mak
   trunk/MgDev/Server/src/Services/Feature/FeatureServiceCommand.cpp
   trunk/MgDev/Server/src/Services/Feature/SelectAggregateCommand.cpp
   trunk/MgDev/Server/src/Services/Feature/SelectAggregateCommand.h
   trunk/MgDev/Server/src/Services/Feature/ServerFeatureService.vcproj
   trunk/MgDev/Server/src/Services/Feature/ServerFeatureServiceBuild.cpp
   trunk/MgDev/Server/src/Services/Feature/ServerFeatureUtil.cpp
   trunk/MgDev/Server/src/Services/Feature/ServerFeatureUtil.h
   trunk/MgDev/Server/src/Services/Feature/ServerSelectFeatures.cpp
   trunk/MgDev/Server/src/Services/Feature/ServerSelectFeatures.h
   trunk/MgDev/Server/src/UnitTesting/TestFeatureService.cpp
   trunk/MgDev/Server/src/UnitTesting/TestFeatureService.h
Log:
#1854: Implement RFC123. This submission takes advantage of recently introduced FDO Join APIs to provide a high-performance optimization shortcut for Feature Joins if:

 a) The extended feature class joins against another feature class from the same feature source
 b) The feature source supports the FDO Join APIs. As of FDO 3.7, SQLite and SQL Server satisfy this condition.

Although use of the FDO expression engine is generally avoided where possible due to performance, we have no choice but to use the FdoExpressionEngineUtilDataReader here in order to support SpatialExtents(). The reason we need to support SpatialExtents() on extended feature classes is in order for the Feature Source Preview to generate a usable MBR for previewing data.

Also included are unit tests to benchmark and test some SQLite-based feature sources set up to take advantage of this optimization shortcut. The 2 SQLite data stores used (and included in this submission) are:

 1. The Sheboygan Parcels data set, split into 2 parts:
   - The autogenerated id, geometry and join key (ParcelFeatures)
   - The remaining attributes, including the join key (Parcels)
 2. A hand-built data set of US/Australian countries/states/cities

See the RFC123 link for full details.

Building in VS2010 will be momentarily broken (as I do not have access to VS2010 on hand to update its vcxproj files)

Modified: trunk/MgDev/Oem/FDO/ProviderList/ServerCommunity/providers.xml
===================================================================
--- trunk/MgDev/Oem/FDO/ProviderList/ServerCommunity/providers.xml	2011-11-21 02:09:23 UTC (rev 6238)
+++ trunk/MgDev/Oem/FDO/ProviderList/ServerCommunity/providers.xml	2011-11-21 11:00:25 UTC (rev 6239)
@@ -1,21 +1,30 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
 <FeatureProviderRegistry>
   <FeatureProvider>
-    <Name>OSGeo.SDF.3.5</Name>
+    <Name>OSGeo.SDF.3.7</Name>
     <DisplayName>OSGeo FDO Provider for SDF</DisplayName>
     <Description>Read/write access to Autodesk's spatial database format, a file-based personal geodatabase that supports multiple features/attributes, spatial indexing, and file-locking.</Description>
     <IsManaged>False</IsManaged>
-    <Version>3.5.0.0</Version>
-    <FeatureDataObjectsVersion>3.5.0.0</FeatureDataObjectsVersion>
+    <Version>3.7.0.0</Version>
+    <FeatureDataObjectsVersion>3.7.0.0</FeatureDataObjectsVersion>
     <LibraryPath>SDFProvider.dll</LibraryPath>
   </FeatureProvider>
   <FeatureProvider>
-    <Name>OSGeo.SHP.3.5</Name>
+    <Name>OSGeo.SHP.3.7</Name>
     <DisplayName>OSGeo FDO Provider for SHP</DisplayName>
     <Description>Read/write access to spatial and attribute data in an ESRI SHP file.</Description>
     <IsManaged>False</IsManaged>
-    <Version>3.5.0.0</Version>
-    <FeatureDataObjectsVersion>3.5.0.0</FeatureDataObjectsVersion>
+    <Version>3.7.0.0</Version>
+    <FeatureDataObjectsVersion>3.7.0.0</FeatureDataObjectsVersion>
     <LibraryPath>ShpProvider.dll</LibraryPath>
   </FeatureProvider>
+  <FeatureProvider>
+    <Name>OSGeo.SQLite.3.7</Name>
+    <DisplayName>OSGeo FDO Provider for SQLite</DisplayName>
+    <Description>Read/write access to spatial and attribute data in an SQLite file.</Description>
+    <IsManaged>False</IsManaged>
+    <Version>3.7.0.0</Version>
+    <FeatureDataObjectsVersion>3.7.0.0</FeatureDataObjectsVersion>
+    <LibraryPath>SQLiteProvider.dll</LibraryPath>
+  </FeatureProvider>
 </FeatureProviderRegistry>

Modified: trunk/MgDev/Server/src/PostBuild/PostBuild.mak
===================================================================
--- trunk/MgDev/Server/src/PostBuild/PostBuild.mak	2011-11-21 02:09:23 UTC (rev 6238)
+++ trunk/MgDev/Server/src/PostBuild/PostBuild.mak	2011-11-21 11:00:25 UTC (rev 6239)
@@ -243,6 +243,10 @@
         ..\..\bin\UnitTestFiles\UT_Annotation3.mdf \
         ..\..\bin\UnitTestFiles\SavePointTest.sqlite \
         ..\..\bin\UnitTestFiles\SavePointTest.FeatureSource \
+        ..\..\bin\UnitTestFiles\UT_Parcels_SQLite_Join.FeatureSource \
+        ..\..\bin\UnitTestFiles\UT_FdoJoin.FeatureSource \
+        ..\..\bin\UnitTestFiles\ParcelsJoinTest.sqlite \
+        ..\..\bin\UnitTestFiles\JoinTest.sqlite \
         CopyFdoComponentsDebug \
         CopyFdoProvidersDebug \
         CopySchemaDebug \
@@ -496,6 +500,10 @@
         ..\..\bin\UnitTestFiles\UT_Annotation3.mdf \
         ..\..\bin\UnitTestFiles\SavePointTest.sqlite \
         ..\..\bin\UnitTestFiles\SavePointTest.FeatureSource \
+        ..\..\bin\UnitTestFiles\UT_Parcels_SQLite_Join.FeatureSource \
+        ..\..\bin\UnitTestFiles\UT_FdoJoin.FeatureSource \
+        ..\..\bin\UnitTestFiles\ParcelsJoinTest.sqlite \
+        ..\..\bin\UnitTestFiles\JoinTest.sqlite \
         CopyFdoComponentsDebug64 \
         CopyFdoProvidersDebug64 \
         CopySchemaDebug64 \
@@ -749,6 +757,10 @@
           ..\..\bin\UnitTestFiles\UT_Annotation3.mdf \
           ..\..\bin\UnitTestFiles\SavePointTest.sqlite \
           ..\..\bin\UnitTestFiles\SavePointTest.FeatureSource \
+          ..\..\bin\UnitTestFiles\UT_Parcels_SQLite_Join.FeatureSource \
+          ..\..\bin\UnitTestFiles\UT_FdoJoin.FeatureSource \
+          ..\..\bin\UnitTestFiles\ParcelsJoinTest.sqlite \
+          ..\..\bin\UnitTestFiles\JoinTest.sqlite \
           CopyFdoComponentsRelease \
           CopyFdoProvidersRelease \
           CopySchemaRelease \
@@ -1002,6 +1014,10 @@
           ..\..\bin\UnitTestFiles\UT_Annotation3.mdf \
           ..\..\bin\UnitTestFiles\SavePointTest.sqlite \
           ..\..\bin\UnitTestFiles\SavePointTest.FeatureSource \
+          ..\..\bin\UnitTestFiles\UT_Parcels_SQLite_Join.FeatureSource \
+          ..\..\bin\UnitTestFiles\UT_FdoJoin.FeatureSource \
+          ..\..\bin\UnitTestFiles\ParcelsJoinTest.sqlite \
+          ..\..\bin\UnitTestFiles\JoinTest.sqlite \
           CopyFdoComponentsRelease64 \
           CopyFdoProvidersRelease64 \
           CopySchemaRelease64 \
@@ -1128,6 +1144,10 @@
     if EXIST ..\..\bin\UnitTestFiles\UT_Annotation3.mdf             del /F ..\..\bin\UnitTestFiles\UT_Annotation3.mdf
     if EXIST ..\..\bin\UnitTestFiles\SavePointTest.sqlite           del /F ..\..\bin\UnitTestFiles\SavePointTest.sqlite
     if EXIST ..\..\bin\UnitTestFiles\SavePointTest.FeatureSource    del /F ..\..\bin\UnitTestFiles\SavePointTest.FeatureSource
+    if EXIST ..\..\bin\UnitTestFiles\UT_Parcels_SQLite_Join.FeatureSource    del /F ..\..\bin\UnitTestFiles\UT_Parcels_SQLite_Join.FeatureSource
+    if EXIST ..\..\bin\UnitTestFiles\UT_FdoJoin.FeatureSource		del /F ..\..\bin\UnitTestFiles\UT_FdoJoin.FeatureSource
+    if EXIST ..\..\bin\UnitTestFiles\ParcelsJoinTest.sqlite         del /F ..\..\bin\UnitTestFiles\ParcelsJoinTest.sqlite
+    if EXIST ..\..\bin\UnitTestFiles\JoinTest.sqlite                del /F ..\..\bin\UnitTestFiles\JoinTest.FeatureSource
     del /F   ..\..\bin\debug\*.ilk
 
 
@@ -1249,6 +1269,10 @@
     if EXIST ..\..\bin\UnitTestFiles\UT_Annotation3.mdf             del /F ..\..\bin\UnitTestFiles\UT_Annotation3.mdf
     if EXIST ..\..\bin\UnitTestFiles\SavePointTest.sqlite           del /F ..\..\bin\UnitTestFiles\SavePointTest.sqlite
     if EXIST ..\..\bin\UnitTestFiles\SavePointTest.FeatureSource    del /F ..\..\bin\UnitTestFiles\SavePointTest.FeatureSource
+    if EXIST ..\..\bin\UnitTestFiles\UT_Parcels_SQLite_Join.FeatureSource    del /F ..\..\bin\UnitTestFiles\UT_Parcels_SQLite_Join.FeatureSource
+    if EXIST ..\..\bin\UnitTestFiles\UT_FdoJoin.FeatureSource		del /F ..\..\bin\UnitTestFiles\UT_FdoJoin.FeatureSource
+    if EXIST ..\..\bin\UnitTestFiles\ParcelsJoinTest.sqlite         del /F ..\..\bin\UnitTestFiles\ParcelsJoinTest.sqlite
+    if EXIST ..\..\bin\UnitTestFiles\JoinTest.sqlite                del /F ..\..\bin\UnitTestFiles\JoinTest.FeatureSource
     del /F   ..\..\bin\debug64\*.ilk
 
 
@@ -1370,8 +1394,11 @@
     if EXIST ..\..\bin\UnitTestFiles\UT_Annotation3.mdf             del /F ..\..\bin\UnitTestFiles\UT_Annotation3.mdf
     if EXIST ..\..\bin\UnitTestFiles\SavePointTest.sqlite           del /F ..\..\bin\UnitTestFiles\SavePointTest.sqlite
     if EXIST ..\..\bin\UnitTestFiles\SavePointTest.FeatureSource    del /F ..\..\bin\UnitTestFiles\SavePointTest.FeatureSource
+	if EXIST ..\..\bin\UnitTestFiles\UT_Parcels_SQLite_Join.FeatureSource    del /F ..\..\bin\UnitTestFiles\UT_Parcels_SQLite_Join.FeatureSource
+    if EXIST ..\..\bin\UnitTestFiles\UT_FdoJoin.FeatureSource		del /F ..\..\bin\UnitTestFiles\UT_FdoJoin.FeatureSource
+    if EXIST ..\..\bin\UnitTestFiles\ParcelsJoinTest.sqlite         del /F ..\..\bin\UnitTestFiles\ParcelsJoinTest.sqlite
+    if EXIST ..\..\bin\UnitTestFiles\JoinTest.sqlite                del /F ..\..\bin\UnitTestFiles\JoinTest.FeatureSource
 
-
 cleanrelease64:
     if EXIST ..\..\bin\release64\MgFoundation.dll              del /F ..\..\bin\release64\MgFoundation.dll
     if EXIST ..\..\bin\release64\MgFoundation.pdb              del /F ..\..\bin\release64\MgFoundation.pdb
@@ -1490,6 +1517,10 @@
     if EXIST ..\..\bin\UnitTestFiles\UT_Annotation3.mdf             del /F ..\..\bin\UnitTestFiles\UT_Annotation3.mdf
     if EXIST ..\..\bin\UnitTestFiles\SavePointTest.sqlite           del /F ..\..\bin\UnitTestFiles\SavePointTest.sqlite
     if EXIST ..\..\bin\UnitTestFiles\SavePointTest.FeatureSource    del /F ..\..\bin\UnitTestFiles\SavePointTest.FeatureSource
+    if EXIST ..\..\bin\UnitTestFiles\UT_Parcels_SQLite_Join.FeatureSource    del /F ..\..\bin\UnitTestFiles\UT_Parcels_SQLite_Join.FeatureSource
+    if EXIST ..\..\bin\UnitTestFiles\UT_FdoJoin.FeatureSource		del /F ..\..\bin\UnitTestFiles\UT_FdoJoin.FeatureSource
+    if EXIST ..\..\bin\UnitTestFiles\ParcelsJoinTest.sqlite         del /F ..\..\bin\UnitTestFiles\ParcelsJoinTest.sqlite
+    if EXIST ..\..\bin\UnitTestFiles\JoinTest.sqlite                del /F ..\..\bin\UnitTestFiles\JoinTest.FeatureSource
 
 
 "..\..\..\UnitTest\TestData\ResourceService\LibraryRepositoryContent.xml" :
@@ -1575,6 +1606,10 @@
 "..\..\..\UnitTest\TestData\Symbology\UT_Annotation2.mdf" :
 "..\..\..\UnitTest\TestData\Symbology\UT_Annotation3.ldf" :
 "..\..\..\UnitTest\TestData\Symbology\UT_Annotation3.mdf" :
+"..\..\..\UnitTest\TestData\Symbology\UT_Parcels_SQLite_Join.FeatureSource" :
+"..\..\..\UnitTest\TestData\Symbology\UT_FdoJoin.FeatureSource" :
+"..\..\..\UnitTest\TestData\Symbology\ParcelsJoinTest.sqlite" :
+"..\..\..\UnitTest\TestData\Symbology\JoinTest.sqlite" :
 
 ..\..\bin\UnitTestFiles\LibraryRepositoryContent.xml : "..\..\..\UnitTest\TestData\ResourceService\LibraryRepositoryContent.xml"
     if NOT EXIST ..\..\bin\UnitTestFiles\nul mkdir ..\..\bin\UnitTestFiles
@@ -1919,3 +1954,19 @@
 ..\..\bin\UnitTestFiles\SavePointTest.FeatureSource : "..\..\..\UnitTest\TestData\FeatureService\SQLite\SavePointTest.FeatureSource"
     if NOT EXIST ..\..\bin\UnitTestFiles\nul mkdir ..\..\bin\UnitTestFiles
     if EXIST "..\..\..\UnitTest\TestData\FeatureService\SQLite\SavePointTest.FeatureSource" xcopy /r /d /y "..\..\..\UnitTest\TestData\FeatureService\SQLite\SavePointTest.FeatureSource" ..\..\bin\UnitTestFiles\
+
+..\..\bin\UnitTestFiles\UT_Parcels_SQLite_Join.FeatureSource : "..\..\..\UnitTest\TestData\FeatureService\SQLite\UT_Parcels_SQLite_Join.FeatureSource"
+    if NOT EXIST ..\..\bin\UnitTestFiles\nul mkdir ..\..\bin\UnitTestFiles
+    if EXIST "..\..\..\UnitTest\TestData\FeatureService\SQLite\UT_Parcels_SQLite_Join.FeatureSource" xcopy /r /d /y "..\..\..\UnitTest\TestData\FeatureService\SQLite\UT_Parcels_SQLite_Join.FeatureSource" ..\..\bin\UnitTestFiles\
+
+..\..\bin\UnitTestFiles\UT_FdoJoin.FeatureSource : "..\..\..\UnitTest\TestData\FeatureService\SQLite\UT_FdoJoin.FeatureSource"
+    if NOT EXIST ..\..\bin\UnitTestFiles\nul mkdir ..\..\bin\UnitTestFiles
+    if EXIST "..\..\..\UnitTest\TestData\FeatureService\SQLite\UT_FdoJoin.FeatureSource" xcopy /r /d /y "..\..\..\UnitTest\TestData\FeatureService\SQLite\UT_FdoJoin.FeatureSource" ..\..\bin\UnitTestFiles\
+
+..\..\bin\UnitTestFiles\ParcelsJoinTest.sqlite : "..\..\..\UnitTest\TestData\FeatureService\SQLite\ParcelsJoinTest.sqlite"
+    if NOT EXIST ..\..\bin\UnitTestFiles\nul mkdir ..\..\bin\UnitTestFiles
+    if EXIST "..\..\..\UnitTest\TestData\FeatureService\SQLite\ParcelsJoinTest.sqlite" xcopy /r /d /y "..\..\..\UnitTest\TestData\FeatureService\SQLite\ParcelsJoinTest.sqlite" ..\..\bin\UnitTestFiles\
+
+..\..\bin\UnitTestFiles\JoinTest.sqlite : "..\..\..\UnitTest\TestData\FeatureService\SQLite\JoinTest.sqlite"
+    if NOT EXIST ..\..\bin\UnitTestFiles\nul mkdir ..\..\bin\UnitTestFiles
+    if EXIST "..\..\..\UnitTest\TestData\FeatureService\SQLite\JoinTest.sqlite" xcopy /r /d /y "..\..\..\UnitTest\TestData\FeatureService\SQLite\JoinTest.sqlite" ..\..\bin\UnitTestFiles\
\ No newline at end of file

Added: trunk/MgDev/Server/src/Services/Feature/ExtendedSelectCommand.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Feature/ExtendedSelectCommand.cpp	                        (rev 0)
+++ trunk/MgDev/Server/src/Services/Feature/ExtendedSelectCommand.cpp	2011-11-21 11:00:25 UTC (rev 6239)
@@ -0,0 +1,417 @@
+//
+//  Copyright (C) 2004-2011 by Autodesk, Inc.
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of version 2.1 of the GNU Lesser
+//  General Public License as published by the Free Software Foundation.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+//
+
+#include "ServerFeatureServiceDefs.h"
+#include "MapGuideCommon.h"
+#include "Services/FeatureService.h"
+#include "FeatureServiceCommand.h"
+#include "ExtendedSelectCommand.h"
+#include "SelectAggregateCommand.h"
+#include "ServerFeatureReader.h"
+#include "ServerFeatureConnection.h"
+#include "FdoFeatureReader.h"
+#include "FdoFilterCollection.h"
+#include "FdoReaderCollection.h"
+#include "Util/FdoExpressionEngineUtilDataReader.h"
+#include "FdoForcedOneToOneFeatureReader.h"
+
+// The maximum size of the subfilter for a selection query.  Tune this value for optimal selection perfomance.
+#define MG_MAX_SUBFILTER_SIZE  250
+
+MgExtendedSelectCommand::MgExtendedSelectCommand(MgResourceIdentifier* resource)
+{
+    CHECKNULL((MgResourceIdentifier*)resource, L"MgExtendedSelectCommand.MgSelectCommand");
+
+    // Connect to provider
+    m_connection = new MgServerFeatureConnection(resource);
+    if ((NULL != m_connection.p) && ( m_connection->IsConnectionOpen() ))
+    {
+        m_providerName = m_connection->GetProviderName();
+    }
+    else
+    {
+        throw new MgConnectionFailedException(L"MgExtendedSelectCommand.MgSelectCommand", __LINE__, __WFILE__, NULL, L"", NULL);
+    }
+    // Create FdoISelect command
+    FdoPtr<FdoIConnection> fdoConn = m_connection->GetConnection();
+    m_command = (FdoIExtendedSelect*)fdoConn->CreateCommand(FdoCommandType_ExtendedSelect);
+    CHECKNULL((FdoIExtendedSelect*)m_command, L"MgExtendedSelectCommand.MgSelectCommand");
+}
+
+MgExtendedSelectCommand::~MgExtendedSelectCommand()
+{
+    m_command = NULL;
+    m_filter = NULL;
+}
+
+FdoIdentifierCollection* MgExtendedSelectCommand::GetPropertyNames()
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.GetPropertyNames");
+    return m_command->GetPropertyNames();
+}
+
+FdoJoinCriteriaCollection* MgExtendedSelectCommand::GetJoinCriteria()
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.GetJoinCriteria");
+    return m_command->GetJoinCriteria();
+}
+
+void MgExtendedSelectCommand::SetAlias(FdoString* alias)
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.SetDistinct");
+    m_command->SetAlias(alias);
+}
+
+void MgExtendedSelectCommand::SetDistinct(bool value)
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.SetDistinct");
+    // This operation is not supported by FdoISelect
+    // m_command->SetDistinct(value);
+
+    // throw new MgInvalidOperationException(L"MgExtendedSelectCommand.SetDistinct", __LINE__, __WFILE__, NULL, L"", NULL);
+}
+
+bool MgExtendedSelectCommand::GetDistinct()
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.GetDistinct");
+    // This operation is not supported by FdoISelect
+    // return m_command->GetDistinct();
+
+    // throw new MgInvalidOperationException(L"MgExtendedSelectCommand.GetDistinct", __LINE__, __WFILE__, NULL, L"", NULL);
+
+    return false;
+}
+
+void MgExtendedSelectCommand::SetFetchSize(FdoInt32 fetchSize)
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.SetFetchSize");
+    m_command->SetFetchSize(fetchSize);
+}
+
+FdoInt32 MgExtendedSelectCommand::GetFetchSize()
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.GetFetchSize");
+    return m_command->GetFetchSize();
+}
+
+FdoIdentifierCollection* MgExtendedSelectCommand::GetOrdering()
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.GetOrdering");
+    return m_command->GetOrdering();
+}
+
+void MgExtendedSelectCommand::SetOrderingOption(FdoOrderingOption option)
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.SetOrderingOption");
+    ((FdoIBaseSelect*)m_command)->SetOrderingOption(option);
+}
+
+FdoOrderingOption MgExtendedSelectCommand::GetOrderingOption()
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.GetOrderingOption");
+    return ((FdoIBaseSelect*)m_command)->GetOrderingOption();
+}
+
+FdoIdentifierCollection* MgExtendedSelectCommand::GetGrouping()
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.GetGrouping");
+    // This operation is not supported by FdoISelect
+    // return m_command->GetGrouping();
+
+    // throw new MgInvalidOperationException(L"MgExtendedSelectCommand.GetGrouping", __LINE__, __WFILE__, NULL, L"", NULL);
+    return NULL;
+}
+
+void MgExtendedSelectCommand::SetGroupingFilter(FdoFilter* filter)
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.SetGroupingFilter");
+    // This operation is not supported by FdoISelect
+    // m_command->SetGroupingFilter(filter);
+
+    // throw new MgInvalidOperationException(L"MgExtendedSelectCommand.SetGroupingFilter", __LINE__, __WFILE__, NULL, L"", NULL);
+}
+
+FdoFilter* MgExtendedSelectCommand::GetGroupingFilter()
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.GetGroupingFilter");
+    // This operation is not supported by FdoISelect
+    // return m_command->GetGroupingFilter(filter);
+
+    // throw new MgInvalidOperationException(L"MgExtendedSelectCommand.GetGroupingFilter", __LINE__, __WFILE__, NULL, L"", NULL);
+    return NULL;
+}
+
+void MgExtendedSelectCommand::SetFeatureClassName(FdoString* value)
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.SetFeatureClassName");
+    m_command->SetFeatureClassName(value);
+}
+
+void MgExtendedSelectCommand::SetFilter(FdoString* value)
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.SetFilter");
+    m_command->SetFilter(value);
+#ifdef DEBUG_FDO_JOIN
+    ACE_DEBUG((LM_INFO, ACE_TEXT("\n\t(%t) [FDO Join] Set Filter: %W"), value));
+#endif
+}
+
+void MgExtendedSelectCommand::SetFilter(FdoFilter* value)
+{
+    CHECKNULL((FdoISelect*)m_command, L"MgExtendedSelectCommand.SetFilter");
+    m_command->SetFilter(value);
+#ifdef DEBUG_FDO_JOIN
+    ACE_DEBUG((LM_INFO, ACE_TEXT("\n\t(%t) [FDO Join] Set Filter: %W"), value->ToString()));
+#endif
+    m_filter = FDO_SAFE_ADDREF(value);
+}
+
+MgReader* MgExtendedSelectCommand::Execute()
+{
+    throw new MgNotImplementedException(L"MgExtendedSelectCommand.Execute", __LINE__, __WFILE__, NULL, L"", NULL);
+}
+
+MgReader* MgExtendedSelectCommand::ExecuteJoined(MgStringCollection* idPropNames, bool bForceOneToOne)
+{
+    Ptr<MgReader> ret;
+
+    MG_FEATURE_SERVICE_TRY()
+
+    FdoPtr<FdoIFeatureReader> fdoReader = m_command->Execute();
+    if (bForceOneToOne)
+    {
+        FdoPtr<FdoStringCollection> names = MgServerFeatureUtil::MgToFdoStringCollection(idPropNames, false);
+        FdoPtr<FdoIFeatureReader> forcedReader = new MgFdoForcedOneToOneFeatureReader(fdoReader, names); 
+        ret = new MgServerFeatureReader(m_connection, forcedReader);
+    }
+    else
+    {
+        ret = new MgServerFeatureReader(m_connection, fdoReader);
+    }
+    MG_FEATURE_SERVICE_CATCH_AND_THROW(L"MgExtendedSelectCommand.ExecuteJoined")
+
+    return ret.Detach();
+}
+
+bool MgExtendedSelectCommand::IsSupportedFunction(FdoFunction* fdoFunc)
+{
+    FdoPtr<FdoIConnection> fdoConn = m_connection->GetConnection();
+    return this->IsFdoSupportedFunction(fdoConn, fdoFunc);
+}
+
+bool MgExtendedSelectCommand::SupportsSelectGrouping()
+{
+    FdoPtr<FdoIConnection> fdoConn = m_connection->GetConnection();
+    return MgFeatureServiceCommand::SupportsSelectGrouping(fdoConn);
+}
+
+bool MgExtendedSelectCommand::SupportsSelectOrdering()
+{
+    FdoPtr<FdoIConnection> fdoConn = m_connection->GetConnection();
+    return MgFeatureServiceCommand::SupportsSelectOrdering(fdoConn);
+}
+
+bool MgExtendedSelectCommand::SupportsSelectDistinct()
+{
+    FdoPtr<FdoIConnection> fdoConn = m_connection->GetConnection();
+    return MgFeatureServiceCommand::SupportsSelectDistinct(fdoConn);
+}
+
+FdoFilter* MgExtendedSelectCommand::GetFilter()
+{
+    return FDO_SAFE_ADDREF(m_filter.p);
+}
+
+MgFdoFilterCollection* MgExtendedSelectCommand::GetSubFilters()
+{
+    // Break up a filter into a bunch of smaller filters
+
+    // For now we just reduce a simple case with datastore limitations in handling the number of OR conditions.
+    // This is the case where a filter has only OR spatial conditions that can be broken up into a collection
+    // of smaller OR filters.
+
+    class FdoCommonFilterFragmenter :  public virtual FdoIFilterProcessor
+    {
+    private:
+        FdoPtr<FdoFilter>    m_newFilter;
+        FdoPtr<FdoIGeometry> m_geomRight;
+        FdoPtr<FdoIGeometry> m_geomLeft;
+
+        int m_OrCount;
+        std::vector<FdoFilter*> m_filters;
+        bool m_isFragmented;
+
+    protected:
+        void HandleFilter( FdoFilter *filter )
+        {
+            filter->Process( this );
+        }
+    public:
+
+        FdoCommonFilterFragmenter( ):m_isFragmented(true)
+        {
+            m_OrCount = 0;
+            m_filters.clear();
+        }
+
+        int GetOrCount() { return m_OrCount; }
+        std::vector<FdoFilter*>& GetFilters() { return m_filters; }
+        bool IsFragmented() { return m_isFragmented; }
+        FdoFilter* GetNewFilter() { return m_newFilter ? FDO_SAFE_ADDREF(m_newFilter.p) : NULL; }
+
+        virtual void Dispose()
+        {
+            delete this;
+        }
+
+        virtual void ProcessBinaryLogicalOperator(FdoBinaryLogicalOperator& filter)
+        {
+            if( filter.GetOperation() != FdoBinaryLogicalOperations_Or )
+            {
+                m_isFragmented = false;
+                return;
+            }
+
+            HandleFilter( FdoPtr<FdoFilter>(filter.GetRightOperand()) );
+            m_newFilter = filter.GetLeftOperand();
+
+            m_OrCount++;
+        }
+        virtual void ProcessComparisonCondition(FdoComparisonCondition& filter)
+        {
+            // Add filter to collection
+            m_filters.push_back(&filter);
+            return;
+        }
+        virtual void ProcessDistanceCondition(FdoDistanceCondition& filter)
+        {
+            m_isFragmented = false;
+            return;
+        }
+
+        virtual void ProcessInCondition(FdoInCondition& filter)
+        {
+            m_isFragmented = false;
+            return;
+        }
+        virtual void ProcessNullCondition(FdoNullCondition& filter)
+        {
+            m_isFragmented = false;
+            return;
+        }
+        virtual void ProcessSpatialCondition(FdoSpatialCondition& filter)
+        {
+            m_isFragmented = false;
+            return;
+        }
+
+        virtual void ProcessUnaryLogicalOperator(FdoUnaryLogicalOperator& filter)
+        {
+            m_isFragmented = false;
+            return;
+        }
+    };
+
+
+    FdoCommonFilterFragmenter  fragmenter;
+    if (m_filter)
+        m_filter->Process( &fragmenter );
+
+    FdoPtr<FdoFilter> newFilter = fragmenter.GetNewFilter();
+    while (newFilter != NULL)
+    {
+        newFilter->Process( &fragmenter );
+        FdoPtr<FdoFilter> tempFilter = fragmenter.GetNewFilter();
+        if (tempFilter != newFilter)
+        {
+            newFilter = tempFilter;
+        }
+        else
+        {
+            newFilter = NULL;
+        }
+    }
+
+#ifdef _DEBUG
+    int nCount = fragmenter.GetOrCount();
+    if ( nCount > 0)
+    {
+        char temp[1024];
+        sprintf(temp, "Or_Count = %d", nCount);  // NOXLATE
+        printf("%s\n", temp);
+    }
+#endif
+
+    FdoPtr<MgFdoFilterCollection> filters = MgFdoFilterCollection::Create();
+
+    if (fragmenter.IsFragmented() && fragmenter.GetOrCount() > 0)
+    {
+        int nSelectionCount = 0;
+
+        std::vector<FdoFilter*>::iterator filterIter;
+        bool bFirst = true;
+
+        FdoStringP filterString;
+        std::vector<FdoFilter*>& fragmentedFilters = fragmenter.GetFilters();
+
+
+        bool bIsAddedToCollection = false;
+
+        for (filterIter = fragmentedFilters.begin(); filterIter != fragmentedFilters.end(); filterIter++)
+        {
+            FdoStringP tempString = (*filterIter)->ToString();
+            FdoStringP orString = L" OR ";  // NOXLATE
+            if (bFirst)
+            {
+                filterString = tempString;
+                bFirst = false;
+            }
+            else
+            {
+                filterString = filterString + orString + tempString;
+                nSelectionCount++;
+            }
+
+            if (nSelectionCount >= MG_MAX_SUBFILTER_SIZE)
+            {
+                FdoPtr<FdoFilter> filter = FdoFilter::Parse(filterString);
+                filters->Add(filter);
+                bFirst = true;
+                filterString = L"";
+                nSelectionCount = 0;
+                bIsAddedToCollection = true;
+            }
+            else
+            {
+                bIsAddedToCollection = false;
+            }
+        }
+
+        if ( !bIsAddedToCollection )
+        {
+            FdoPtr<FdoFilter> filter = FdoFilter::Parse(filterString);
+            filters->Add(filter);
+        }
+
+    }
+    else
+    {
+        filters->Add(m_filter);
+    }
+
+    return filters.Detach();
+}

Added: trunk/MgDev/Server/src/Services/Feature/ExtendedSelectCommand.h
===================================================================
--- trunk/MgDev/Server/src/Services/Feature/ExtendedSelectCommand.h	                        (rev 0)
+++ trunk/MgDev/Server/src/Services/Feature/ExtendedSelectCommand.h	2011-11-21 11:00:25 UTC (rev 6239)
@@ -0,0 +1,83 @@
+//
+//  Copyright (C) 2004-2011 by Autodesk, Inc.
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of version 2.1 of the GNU Lesser
+//  General Public License as published by the Free Software Foundation.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+//
+
+#ifndef _MGEXTENDEDSELECTCOMMAND_H_
+#define _MGEXTENDEDSELECTCOMMAND_H_
+
+class MgFdoFeatureReader;
+class MgFdoReaderCollection;
+class MgFdoFilterCollection;
+
+class MgExtendedSelectCommand : public MgFeatureServiceCommand
+{
+    DECLARE_CLASSNAME(MgExtendedSelectCommand)
+
+public:
+    MgExtendedSelectCommand(MgResourceIdentifier* resource);
+    virtual ~MgExtendedSelectCommand();
+
+    virtual FdoIdentifierCollection* GetPropertyNames();
+    
+    virtual void SetDistinct( bool value );
+    virtual bool GetDistinct( );
+
+    virtual void SetFetchSize(FdoInt32 fetchSize);
+    virtual FdoInt32 GetFetchSize();
+
+    virtual FdoIdentifierCollection* GetOrdering();
+    virtual void SetOrderingOption( FdoOrderingOption  option );
+    virtual FdoOrderingOption GetOrderingOption( );
+
+    virtual FdoIdentifierCollection* GetGrouping();
+    virtual void SetGroupingFilter( FdoFilter* filter );
+    virtual FdoFilter* GetGroupingFilter( );
+
+    virtual void SetFeatureClassName(FdoString* value);
+    virtual void SetFilter(FdoString* value);
+    virtual void SetFilter(FdoFilter* value);
+
+    virtual FdoFilter* GetFilter();
+
+    virtual MgReader* Execute(); //Not used
+    MgReader* ExecuteJoined(MgStringCollection* idPropNames, bool bForceOneToOne);
+
+    virtual bool IsSupportedFunction(FdoFunction* fdoFunc);
+
+    virtual bool SupportsSelectGrouping();
+    virtual bool SupportsSelectOrdering();
+    virtual bool SupportsSelectDistinct();
+
+    virtual void Dispose()
+    {
+        delete this;
+    }
+
+    virtual FdoJoinCriteriaCollection* GetJoinCriteria();
+
+    virtual void SetAlias(FdoString* alias);
+
+private:
+    Ptr<MgServerFeatureConnection> m_connection;
+    STRING m_providerName;
+    FdoPtr<FdoIExtendedSelect> m_command;
+
+    FdoPtr<FdoFilter> m_filter;
+
+    MgFdoFilterCollection* GetSubFilters();
+};
+
+#endif

Added: trunk/MgDev/Server/src/Services/Feature/FdoForcedOneToOneFeatureReader.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Feature/FdoForcedOneToOneFeatureReader.cpp	                        (rev 0)
+++ trunk/MgDev/Server/src/Services/Feature/FdoForcedOneToOneFeatureReader.cpp	2011-11-21 11:00:25 UTC (rev 6239)
@@ -0,0 +1,376 @@
+#include "FdoForcedOneToOneFeatureReader.h"
+
+// constructs a MgFdoForcedOneToOneFeatureReader
+MgFdoForcedOneToOneFeatureReader::MgFdoForcedOneToOneFeatureReader() { }
+
+MgFdoForcedOneToOneFeatureReader::MgFdoForcedOneToOneFeatureReader(FdoIFeatureReader* reader, FdoStringCollection* idPropNames)
+{
+    m_cachedClsDef = NULL;
+    m_reader = FDO_SAFE_ADDREF(reader);
+    m_idPropNames = FDO_SAFE_ADDREF(idPropNames);
+}
+
+// default destructor
+MgFdoForcedOneToOneFeatureReader::~MgFdoForcedOneToOneFeatureReader() 
+{ 
+    m_idValues.clear();
+    FDO_SAFE_RELEASE(m_reader);
+    FDO_SAFE_RELEASE(m_idPropNames);
+    FDO_SAFE_RELEASE(m_cachedClsDef);
+}
+
+void MgFdoForcedOneToOneFeatureReader::Dispose() { delete this; }
+
+FdoClassDefinition* MgFdoForcedOneToOneFeatureReader::GetClassDefinition()
+{
+    return m_reader->GetClassDefinition();
+}
+
+int MgFdoForcedOneToOneFeatureReader::GetDepth()
+{
+    return m_reader->GetDepth();
+}
+
+bool MgFdoForcedOneToOneFeatureReader::GetBoolean(const wchar_t *propertyName)
+{
+    return m_reader->GetBoolean(propertyName);
+}
+
+FdoByte MgFdoForcedOneToOneFeatureReader::GetByte(const wchar_t *propertyName)
+{
+    return m_reader->GetByte(propertyName);
+}
+
+double MgFdoForcedOneToOneFeatureReader::GetDouble(const wchar_t* propertyName)
+{
+    return m_reader->GetDouble(propertyName);
+}
+
+short MgFdoForcedOneToOneFeatureReader::GetInt16(const wchar_t *propertyName)
+{
+    return m_reader->GetInt16(propertyName);
+}
+
+int MgFdoForcedOneToOneFeatureReader::GetInt32(const wchar_t *propertyName)
+{
+    return m_reader->GetInt32(propertyName);
+}
+
+FdoInt64 MgFdoForcedOneToOneFeatureReader::GetInt64(const wchar_t *propertyName)
+{
+    return m_reader->GetInt64(propertyName);
+}
+
+float MgFdoForcedOneToOneFeatureReader::GetSingle(const wchar_t *propertyName)
+{
+    return m_reader->GetSingle(propertyName);
+}
+
+const wchar_t* MgFdoForcedOneToOneFeatureReader::GetString(const wchar_t *propertyName)
+{
+    return m_reader->GetString(propertyName);
+}
+
+FdoLOBValue* MgFdoForcedOneToOneFeatureReader::GetLOB(const wchar_t* propertyName)
+{
+    return m_reader->GetLOB(propertyName);
+}
+
+FdoIStreamReader* MgFdoForcedOneToOneFeatureReader::GetLOBStreamReader(const wchar_t* propertyName)
+{
+    return m_reader->GetLOBStreamReader(propertyName);
+}
+
+bool MgFdoForcedOneToOneFeatureReader::IsNull(const wchar_t *propertyName)
+{
+    return m_reader->IsNull(propertyName);
+}
+
+FdoIFeatureReader* MgFdoForcedOneToOneFeatureReader::GetFeatureObject(const wchar_t* propertyName)
+{
+    return m_reader->GetFeatureObject(propertyName);
+}
+
+FdoByteArray* MgFdoForcedOneToOneFeatureReader::GetGeometry(const wchar_t* propertyName)
+{
+    return m_reader->GetGeometry(propertyName);
+}
+
+const FdoByte * MgFdoForcedOneToOneFeatureReader::GetGeometry(const wchar_t* propertyName, FdoInt32 * count)
+{
+    return m_reader->GetGeometry(propertyName, count);
+}
+
+FdoIRaster* MgFdoForcedOneToOneFeatureReader::GetRaster(const wchar_t* propertyName)
+{
+    return m_reader->GetRaster(propertyName);
+}
+
+bool MgFdoForcedOneToOneFeatureReader::IsNull(FdoInt32 index)
+{
+    return m_reader->IsNull(index);
+}
+
+const wchar_t* MgFdoForcedOneToOneFeatureReader::GetString(FdoInt32 index)
+{
+    return m_reader->GetString(index);
+}
+
+bool MgFdoForcedOneToOneFeatureReader::GetBoolean(FdoInt32 index)
+{
+    return m_reader->GetBoolean(index);
+}
+
+FdoByte MgFdoForcedOneToOneFeatureReader::GetByte(FdoInt32 index)
+{
+    return m_reader->GetByte(index);
+}
+
+FdoDateTime MgFdoForcedOneToOneFeatureReader::GetDateTime(FdoInt32 index)
+{
+    return m_reader->GetDateTime(index);
+}
+
+double MgFdoForcedOneToOneFeatureReader::GetDouble(FdoInt32 index)
+{
+    return m_reader->GetDouble(index);
+}
+
+short MgFdoForcedOneToOneFeatureReader::GetInt16(FdoInt32 index)
+{
+    return m_reader->GetInt16(index);
+}
+
+int MgFdoForcedOneToOneFeatureReader::GetInt32(FdoInt32 index)
+{
+    return m_reader->GetInt32(index);
+}
+
+FdoInt64 MgFdoForcedOneToOneFeatureReader::GetInt64(FdoInt32 index)
+{
+    return m_reader->GetInt64(index);
+}
+
+float MgFdoForcedOneToOneFeatureReader::GetSingle(FdoInt32 index)
+{
+    return m_reader->GetSingle(index);
+}
+
+FdoLOBValue* MgFdoForcedOneToOneFeatureReader::GetLOB(FdoInt32 index)
+{
+    return m_reader->GetLOB(index);
+}
+
+FdoIStreamReader* MgFdoForcedOneToOneFeatureReader::GetLOBStreamReader(FdoInt32 index)
+{
+    return m_reader->GetLOBStreamReader(index);
+}
+
+FdoIRaster* MgFdoForcedOneToOneFeatureReader::GetRaster(FdoInt32 index)
+{
+    return m_reader->GetRaster(index);
+}
+
+const FdoByte* MgFdoForcedOneToOneFeatureReader::GetGeometry(FdoInt32 index, FdoInt32 * count)
+{
+    return m_reader->GetGeometry(index, count);
+}
+
+FdoByteArray* MgFdoForcedOneToOneFeatureReader::GetGeometry(FdoInt32 index)
+{
+    return m_reader->GetGeometry(index);
+}
+
+FdoIFeatureReader* MgFdoForcedOneToOneFeatureReader::GetFeatureObject (FdoInt32 index)
+{
+    return m_reader->GetFeatureObject(index);
+}
+
+FdoString* MgFdoForcedOneToOneFeatureReader::GetPropertyName(FdoInt32 index)
+{
+    return m_reader->GetPropertyName(index);
+}
+
+FdoInt32 MgFdoForcedOneToOneFeatureReader::GetPropertyIndex(FdoString* propertyName)
+{
+    return m_reader->GetPropertyIndex(propertyName);
+}
+
+bool MgFdoForcedOneToOneFeatureReader::ReadNext()
+{
+    bool ret = false;
+
+    ret = m_reader->ReadNext();
+    if (!ret) //End-of-reader
+        return ret;
+
+    //So we're forcing one-to-one, this means we need to keep track of 
+    //identity property values. We hash each one and store in a set, if 
+    //our generated hash already exists, we skip it.
+    STRING hash = GetIdentityHash();
+    while (m_idValues.find(hash) != m_idValues.end()) 
+    {
+        ret = m_reader->ReadNext(); //Read next feature
+        if (!ret) //End-of-reader
+            return ret;
+        hash = GetIdentityHash();
+    }
+    //Add this hash
+    m_idValues.insert(hash);
+
+    return ret;
+}
+
+void MgFdoForcedOneToOneFeatureReader::Close()
+{
+    m_reader->Close();
+}
+
+
+FdoDateTime MgFdoForcedOneToOneFeatureReader::GetDateTime(const wchar_t *propertyName )
+{
+    return m_reader->GetDateTime(propertyName);
+}
+
+STRING MgFdoForcedOneToOneFeatureReader::GetIdentityHash()
+{
+    STRING ret;
+
+    if (NULL == m_cachedClsDef)
+    {
+        m_cachedClsDef = m_reader->GetClassDefinition();
+    }
+
+    //return the current identity values as: <identity 1>|<identity 2>|...|<identity n>
+    const wchar_t* SEPARATOR = L"|";
+
+    FdoPtr<FdoPropertyDefinitionCollection> clsProps = m_cachedClsDef->GetProperties();
+    for (INT32 i = 0; i < m_idPropNames->GetCount(); i++) 
+    {
+        bool append = false;
+        wchar_t tbuff[256];
+        //Don't bother with IsNull() checks. These are identity property values, how can they be null?
+        FdoStringElement* el = m_idPropNames->GetItem(i);
+        FdoStringP str = el->GetString();
+        FdoString* name = (FdoString*)str;
+        
+        FdoPtr<FdoPropertyDefinition> propDef = clsProps->GetItem(name);
+        if (propDef->GetPropertyType() == FdoPropertyType_DataProperty)
+        {
+            FdoDataPropertyDefinition* dataProp = static_cast<FdoDataPropertyDefinition*>(propDef.p);
+            FdoDataType dtype = dataProp->GetDataType();
+            //I don't like to assume, but this is a safe assumption to make. There is no way in hell
+            //these types could ever be identity property types:
+            // - BLOB
+            // - CLOB
+            // - Feature
+            // - Geometry
+            // - Raster
+            //So they can be ignored here.
+            switch(dtype)
+            {
+            case FdoDataType_Boolean:
+                {
+                    bool val = m_reader->GetBoolean(name);
+                    swprintf (tbuff, 256, L"%s", val ? L"TRUE" : L"FALSE");
+                    append = true;
+                }
+                break;
+            case FdoDataType_Byte:
+                {
+                    BYTE val = m_reader->GetByte(name);
+                    swprintf (tbuff, 256, L"%d", val);
+                    append = true;
+                }
+                break;
+            case FdoDataType_DateTime:
+                {
+                    FdoDateTime dt = m_reader->GetDateTime(name);
+                    swprintf (tbuff, 256, L"%d-%d-%d %2d:%2d:%2.4f",
+                                          dt.year,
+                                          dt.month, 
+                                          dt.day, 
+                                          dt.hour, 
+                                          dt.minute, 
+                                          dt.seconds);
+                    append = true;
+                }
+                break;
+            case FdoDataType_Decimal:
+            case FdoDataType_Double:
+                {
+                    double val = m_reader->GetDouble(name);
+                    swprintf (tbuff, 256, L"%f", val);
+                    append = true;
+                }
+                break;
+            case FdoDataType_Int16:
+                {
+                    INT16 val = m_reader->GetInt16(name);
+                    swprintf (tbuff, 256, L"%d", val);
+                    append = true;
+                }
+                break;
+            case FdoDataType_Int32:
+                {
+                    INT32 val = m_reader->GetInt32(name);
+                    swprintf (tbuff, 256, L"%ld", val);
+                    append = true;
+                }
+                break;
+            case FdoDataType_Int64:
+                {
+                    INT64 val = m_reader->GetInt64(name);
+                    #ifdef _WIN32
+                    _i64tow (val, tbuff, 10);
+                    #else
+                    swprintf(tbuff, 256, L"%lli", val);
+                    #endif
+                    append = true;
+                }
+                break;
+            case FdoDataType_Single:
+                {
+                    float val = m_reader->GetSingle(name);
+                    swprintf (tbuff, 256, L"%f", val);
+                    append = true;
+                }
+                break;
+            case FdoDataType_String:
+                {
+                    STRING val = m_reader->GetString(name);
+                    if (ret.empty())
+                    {
+                        ret = val;
+                    }
+                    else
+                    {
+                        ret += SEPARATOR;
+                        ret += val;
+                    }   
+                    append = false; //already appended
+                }
+                break;
+            }
+
+            if (append)
+            {
+                if (ret.empty())
+                {
+                    ret = tbuff;
+                }
+                else
+                {
+                    ret += SEPARATOR;
+                    ret += tbuff;
+                }   
+            }
+        }
+        else
+        {
+            
+        }
+    }
+
+    return ret;
+}
\ No newline at end of file

Added: trunk/MgDev/Server/src/Services/Feature/FdoForcedOneToOneFeatureReader.h
===================================================================
--- trunk/MgDev/Server/src/Services/Feature/FdoForcedOneToOneFeatureReader.h	                        (rev 0)
+++ trunk/MgDev/Server/src/Services/Feature/FdoForcedOneToOneFeatureReader.h	2011-11-21 11:00:25 UTC (rev 6239)
@@ -0,0 +1,94 @@
+//
+//  Copyright (C) 2004-2011 by Autodesk, Inc.
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of version 2.1 of the GNU Lesser
+//  General Public License as published by the Free Software Foundation.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+//
+
+#ifndef _MGFDOFORCEDONETOONEFEATUREREADER_H
+#define _MGFDOFORCEDONETOONEFEATUREREADER_H
+
+#include <set>
+
+class FdoIFeatureReader;
+
+class MgFdoForcedOneToOneFeatureReader: public FdoIFeatureReader
+{
+public:
+    // constructs a MgFdoForcedOneToOneFeatureReader
+    MgFdoForcedOneToOneFeatureReader();
+    MgFdoForcedOneToOneFeatureReader(FdoIFeatureReader* reader, FdoStringCollection* idPropNames);
+    // default destructor
+    virtual ~MgFdoForcedOneToOneFeatureReader();
+
+protected:
+
+    virtual void Dispose();
+
+public:
+
+    virtual FdoClassDefinition* GetClassDefinition();
+    virtual int GetDepth();
+    virtual bool     GetBoolean( const wchar_t *propertyName );
+    virtual FdoByte  GetByte( const wchar_t *propertyName );
+    virtual double   GetDouble(const wchar_t* propertyName);
+    virtual short    GetInt16( const wchar_t *propertyName );
+    virtual int      GetInt32( const wchar_t *propertyName );
+    virtual FdoInt64 GetInt64( const wchar_t *propertyName );
+    virtual float    GetSingle( const wchar_t *propertyName );
+    virtual const wchar_t* GetString( const wchar_t *propertyName );
+
+    virtual FdoLOBValue* GetLOB(const wchar_t* propertyName );
+    virtual FdoIStreamReader* GetLOBStreamReader(const wchar_t* propertyName );
+
+    virtual bool     IsNull( const wchar_t *propertyName );
+    virtual FdoIFeatureReader* GetFeatureObject(const wchar_t* propertyName);
+    virtual FdoByteArray* GetGeometry(const wchar_t* propertyName);
+    virtual const FdoByte * GetGeometry(const wchar_t* propertyName, FdoInt32 * count);
+    virtual FdoIRaster* GetRaster(const wchar_t* propertyName);
+
+    virtual bool                IsNull (FdoInt32 index);
+    virtual const wchar_t*      GetString   (FdoInt32 index);
+    virtual bool                GetBoolean  (FdoInt32 index);
+    virtual FdoByte             GetByte     (FdoInt32 index);
+    virtual FdoDateTime         GetDateTime (FdoInt32 index);
+    virtual double              GetDouble   (FdoInt32 index);
+    virtual short               GetInt16    (FdoInt32 index);
+    virtual int                 GetInt32    (FdoInt32 index);
+    virtual FdoInt64            GetInt64    (FdoInt32 index);
+    virtual float               GetSingle   (FdoInt32 index);
+    virtual FdoLOBValue*        GetLOB      (FdoInt32 index);
+    virtual FdoIStreamReader*   GetLOBStreamReader (FdoInt32 index);
+    virtual FdoIRaster*         GetRaster   (FdoInt32 index);
+    virtual const FdoByte *     GetGeometry (FdoInt32 index, FdoInt32 * count);
+    virtual FdoByteArray*       GetGeometry (FdoInt32 index);
+    virtual FdoIFeatureReader*  GetFeatureObject (FdoInt32 index);
+
+    virtual FdoString*          GetPropertyName(FdoInt32 index);
+    virtual FdoInt32            GetPropertyIndex(FdoString* propertyName);
+
+    virtual bool     ReadNext( );
+    virtual void     Close();
+
+    virtual FdoDateTime GetDateTime(const wchar_t *propertyName );
+private:
+    std::set<STRING> m_idValues;
+    STRING GetIdentityHash();
+
+    FdoIFeatureReader* m_reader;
+    FdoStringCollection* m_idPropNames;
+
+    FdoClassDefinition* m_cachedClsDef;
+};
+
+#endif
\ No newline at end of file

Modified: trunk/MgDev/Server/src/Services/Feature/FeatureServiceCommand.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Feature/FeatureServiceCommand.cpp	2011-11-21 02:09:23 UTC (rev 6238)
+++ trunk/MgDev/Server/src/Services/Feature/FeatureServiceCommand.cpp	2011-11-21 11:00:25 UTC (rev 6239)
@@ -20,6 +20,7 @@
 #include "Services/FeatureService.h"
 #include "FeatureServiceCommand.h"
 #include "SelectCommand.h"
+#include "ExtendedSelectCommand.h"
 #include "SelectAggregateCommand.h"
 
 MgFeatureServiceCommand* MgFeatureServiceCommand::CreateCommand(MgResourceIdentifier* resource, FdoCommandType commandType)
@@ -37,6 +38,11 @@
             command = new MgSelectAggregateCommand(resource);
             break;
         }
+        case FdoCommandType_ExtendedSelect:
+        {
+            command = new MgExtendedSelectCommand(resource);
+            break;
+        }
     }
     return command.Detach();
 }

Modified: trunk/MgDev/Server/src/Services/Feature/SelectAggregateCommand.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Feature/SelectAggregateCommand.cpp	2011-11-21 02:09:23 UTC (rev 6238)
+++ trunk/MgDev/Server/src/Services/Feature/SelectAggregateCommand.cpp	2011-11-21 11:00:25 UTC (rev 6239)
@@ -23,7 +23,12 @@
 #include "SelectAggregateCommand.h"
 #include "ServerDataReader.h"
 #include "ServerFeatureConnection.h"
+#include "FdoForcedOneToOneFeatureReader.h"
+#include "ServerFeatureUtil.h"
+#include "FdoExpressionEngine.h"
+#include "Util/FdoExpressionEngineUtilDataReader.h"
 
+
 MgSelectAggregateCommand::MgSelectAggregateCommand(MgResourceIdentifier* resource)
 {
     CHECKNULL((MgResourceIdentifier*)resource, L"MgSelectAggregateCommand.MgSelectAggregateCommand");
@@ -175,3 +180,161 @@
 {
     return FDO_SAFE_ADDREF(m_filter.p);
 }
+
+FdoJoinCriteriaCollection* MgSelectAggregateCommand::GetJoinCriteria()
+{
+    CHECKNULL((FdoISelectAggregates*)m_command, L"MgSelectAggregateCommand.GetJoinCriteria");
+    return m_command->GetJoinCriteria();
+}
+
+void MgSelectAggregateCommand::SetAlias(FdoString* alias)
+{
+    CHECKARGUMENTNULL(alias, L"MgSelectAggregateCommand.SetAlias");
+    CHECKNULL((FdoISelectAggregates*)m_command, L"MgSelectAggregateCommand.SetAlias");
+    m_command->SetAlias(alias);
+}
+
+MgReader* MgSelectAggregateCommand::ExecuteJoined(MgStringCollection* idPropNames, bool bForceOneToOne)
+{
+    Ptr<MgReader> ret;
+
+    FdoPtr<FdoIConnection> fdoConn = m_connection->GetConnection();
+    FdoPtr<FdoIExtendedSelect> select = static_cast<FdoIExtendedSelect*>(fdoConn->CreateCommand(FdoCommandType_ExtendedSelect));
+
+    FdoPtr<FdoIdentifier> clsName = m_command->GetFeatureClassName();
+    select->SetFeatureClassName(clsName);
+
+    
+    STRING clsNameStr = clsName->GetText();
+    STRING schemaName;
+    STRING className;
+    MgUtil::ParseQualifiedClassName(clsNameStr, schemaName, className);
+
+    FdoPtr<FdoIdentifierCollection> selectedIds = GetPropertyNames();
+    FdoPtr<FdoIdentifierCollection> computedIds = FdoIdentifierCollection::Create();
+    for (FdoInt32 i = 0; i < selectedIds->GetCount(); i++)
+    {
+        FdoPtr<FdoIdentifier> identifier = selectedIds->GetItem(i);
+        if (identifier->GetExpressionType() == FdoExpressionItemType_ComputedIdentifier)
+        {
+            FdoComputedIdentifier* comp = static_cast<FdoComputedIdentifier*>(identifier.p);
+            FdoPtr<FdoExpression> expr = comp->GetExpression();
+            if (expr->GetExpressionType() == FdoExpressionItemType_Function)
+                computedIds->Add(identifier);
+        }
+    }
+    FdoPtr<FdoClassDefinition> originalClassDef;
+
+    FdoPtr<FdoIDescribeSchema> descSchema = static_cast<FdoIDescribeSchema*>(fdoConn->CreateCommand(FdoCommandType_DescribeSchema));
+    if (!schemaName.empty())
+    {
+        descSchema->SetSchemaName(schemaName.c_str());
+    }
+    if (!className.empty())
+    {
+        FdoPtr<FdoStringCollection> classNames = FdoStringCollection::Create();
+        classNames->Add(className.c_str());
+        descSchema->SetClassNames(classNames);
+    }
+
+    FdoPtr<FdoClassDefinition> classDef;
+    FdoPtr<FdoFeatureSchemaCollection> schemas = descSchema->Execute();
+    for (FdoInt32 i = 0; i < schemas->GetCount(); i++)
+    {
+        FdoPtr<FdoFeatureSchema> schema = schemas->GetItem(i);
+        if (wcscmp(schema->GetName(), schemaName.c_str()) == 0) 
+        {
+            FdoPtr<FdoClassCollection> classes = schema->GetClasses();
+            for (FdoInt32 j = 0; j < classes->GetCount(); j++) 
+            {
+                FdoPtr<FdoClassDefinition> klassDef = classes->GetItem(j);
+                if (wcscmp(klassDef->GetName(), className.c_str()) == 0)
+                {
+                    originalClassDef = SAFE_ADDREF(klassDef.p);
+                    break;
+                }
+            }
+        }
+    }
+    
+
+    if (NULL != (FdoFilter*)m_filter)
+        select->SetFilter(m_filter);
+
+    FdoCommonExpressionType exprType;
+    FdoPtr<FdoFunctionDefinitionCollection> functions = FdoExpressionEngine::GetStandardFunctions();
+    FdoPtr<FdoArray<FdoFunction*> > aggrIdents = FdoExpressionEngineUtilDataReader::GetAggregateFunctions(functions, computedIds, exprType);
+
+    FdoPtr<FdoIFeatureReader> reader;
+    FdoPtr<FdoIdentifierCollection> ids;
+    FdoPtr<FdoIdentifierCollection> orderBy = m_command->GetOrdering();
+    FdoOrderingOption orderOpt = m_command->GetOrderingOption();
+
+    //n00bism: String goes in, garbage comes out. Anyway, we know the alias we want to set
+    select->SetAlias(L"primary");
+    //select->SetAlias(m_command->GetAlias());
+
+    FdoPtr<FdoJoinCriteriaCollection> srcCriteria = GetJoinCriteria();
+    FdoPtr<FdoJoinCriteriaCollection> dstCriteria = select->GetJoinCriteria();
+    for (FdoInt32 i = 0; i < srcCriteria->GetCount(); i++)
+    {
+        FdoPtr<FdoJoinCriteria> criteria = srcCriteria->GetItem(i);
+        dstCriteria->Add(criteria);
+    }
+
+    if ((aggrIdents != NULL) && (aggrIdents->GetCount() > 0))
+    {
+        reader = select->Execute();
+    }
+    else
+    {
+        // transfer over the identifiers to the basic select command:
+        ids = select->GetPropertyNames();
+        ids->Clear();
+        if (0 == selectedIds->GetCount())
+        {
+            FdoPtr<FdoPropertyDefinitionCollection> propDefs = originalClassDef->GetProperties();
+            for (int i=0; i<propDefs->GetCount(); i++)
+            {
+                FdoPtr<FdoPropertyDefinition> propDef = propDefs->GetItem(i);
+                FdoPtr<FdoIdentifier> localId = FdoIdentifier::Create(propDef->GetName());
+                ids->Add(localId);
+            }
+            FdoPtr<FdoReadOnlyPropertyDefinitionCollection> basePropDefs = originalClassDef->GetBaseProperties();
+            for (int i=0; i<basePropDefs->GetCount(); i++)
+            {
+                FdoPtr<FdoPropertyDefinition> basePropDef = basePropDefs->GetItem(i);
+                FdoPtr<FdoIdentifier> localId = FdoIdentifier::Create(basePropDef->GetName());
+                ids->Add(localId);
+            }
+        }
+        else
+        {
+            for (int i=0; i<selectedIds->GetCount(); i++)
+            {
+                FdoPtr<FdoIdentifier> localId = selectedIds->GetItem(i);
+                ids->Add(localId);
+            }
+        }
+
+        // Execute the basic select command:
+        reader = select->Execute();
+    }
+
+    if (bForceOneToOne)
+    {
+        FdoPtr<FdoStringCollection> names = MgServerFeatureUtil::MgToFdoStringCollection(idPropNames, false);
+        FdoPtr<FdoIFeatureReader> forcedReader = new MgFdoForcedOneToOneFeatureReader(reader, names);
+        FdoPtr<FdoIDataReader> dataReader = new FdoExpressionEngineUtilDataReader(functions, forcedReader, originalClassDef, computedIds, GetDistinct(), orderBy, orderOpt, ids, aggrIdents);
+
+        ret = new MgServerDataReader(m_connection, dataReader, m_providerName);
+    }
+    else
+    {
+        FdoPtr<FdoIDataReader> dataReader = new FdoExpressionEngineUtilDataReader(functions, reader, originalClassDef, computedIds, GetDistinct(), orderBy, orderOpt, ids, aggrIdents);
+
+        ret = new MgServerDataReader(m_connection, dataReader, m_providerName);
+    }
+
+    return ret.Detach();
+}
\ No newline at end of file

Modified: trunk/MgDev/Server/src/Services/Feature/SelectAggregateCommand.h
===================================================================
--- trunk/MgDev/Server/src/Services/Feature/SelectAggregateCommand.h	2011-11-21 02:09:23 UTC (rev 6238)
+++ trunk/MgDev/Server/src/Services/Feature/SelectAggregateCommand.h	2011-11-21 11:00:25 UTC (rev 6239)
@@ -49,6 +49,7 @@
     virtual FdoFilter* GetFilter();
 
     virtual MgReader* Execute();
+    MgReader* ExecuteJoined(MgStringCollection* idPropNames, bool bForceOneToOne);
     virtual bool IsSupportedFunction(FdoFunction* fdoFunc);
     virtual bool SupportsSelectGrouping();
     virtual bool SupportsSelectOrdering();
@@ -59,6 +60,9 @@
         delete this;
     }
 
+    virtual FdoJoinCriteriaCollection* GetJoinCriteria();
+    virtual void SetAlias(FdoString* alias);
+
 private:
     Ptr<MgServerFeatureConnection> m_connection;
     STRING m_providerName;

Modified: trunk/MgDev/Server/src/Services/Feature/ServerFeatureService.vcproj
===================================================================
--- trunk/MgDev/Server/src/Services/Feature/ServerFeatureService.vcproj	2011-11-21 02:09:23 UTC (rev 6238)
+++ trunk/MgDev/Server/src/Services/Feature/ServerFeatureService.vcproj	2011-11-21 11:00:25 UTC (rev 6239)
@@ -428,7 +428,7 @@
 				>
 			</File>
 			<File
-				RelativePath=".\OpApplySchema.cpp"
+				RelativePath=".\OpAddSavePoint.cpp"
 				>
 				<FileConfiguration
 					Name="Debug|Win32"
@@ -464,11 +464,11 @@
 				</FileConfiguration>
 			</File>
 			<File
-				RelativePath=".\OpApplySchema.h"
+				RelativePath=".\OpAddSavePoint.h"
 				>
- 			</File>               
+			</File>
 			<File
-				RelativePath=".\OpAddSavePoint.cpp"
+				RelativePath=".\OpApplySchema.cpp"
 				>
 				<FileConfiguration
 					Name="Debug|Win32"
@@ -504,7 +504,7 @@
 				</FileConfiguration>
 			</File>
 			<File
-				RelativePath=".\OpAddSavePoint.h"
+				RelativePath=".\OpApplySchema.h"
 				>
 			</File>
 			<File
@@ -1666,7 +1666,7 @@
 			<File
 				RelativePath=".\OpReleaseSavePoint.h"
 				>
-			</File>             
+			</File>
 			<File
 				RelativePath=".\OpRollbackSavePoint.cpp"
 				>
@@ -2209,6 +2209,46 @@
 			>
 		</File>
 		<File
+			RelativePath=".\ExtendedSelectCommand.cpp"
+			>
+			<FileConfiguration
+				Name="Debug|Win32"
+				ExcludedFromBuild="true"
+				>
+				<Tool
+					Name="VCCLCompilerTool"
+				/>
+			</FileConfiguration>
+			<FileConfiguration
+				Name="Debug|x64"
+				ExcludedFromBuild="true"
+				>
+				<Tool
+					Name="VCCLCompilerTool"
+				/>
+			</FileConfiguration>
+			<FileConfiguration
+				Name="Release|Win32"
+				ExcludedFromBuild="true"
+				>
+				<Tool
+					Name="VCCLCompilerTool"
+				/>
+			</FileConfiguration>
+			<FileConfiguration
+				Name="Release|x64"
+				ExcludedFromBuild="true"
+				>
+				<Tool
+					Name="VCCLCompilerTool"
+				/>
+			</FileConfiguration>
+		</File>
+		<File
+			RelativePath=".\ExtendedSelectCommand.h"
+			>
+		</File>
+		<File
 			RelativePath=".\FdoFeatureReader.cpp"
 			>
 			<FileConfiguration
@@ -2289,6 +2329,46 @@
 			>
 		</File>
 		<File
+			RelativePath=".\FdoForcedOneToOneFeatureReader.cpp"
+			>
+			<FileConfiguration
+				Name="Debug|Win32"
+				ExcludedFromBuild="true"
+				>
+				<Tool
+					Name="VCCLCompilerTool"
+				/>
+			</FileConfiguration>
+			<FileConfiguration
+				Name="Debug|x64"
+				ExcludedFromBuild="true"
+				>
+				<Tool
+					Name="VCCLCompilerTool"
+				/>
+			</FileConfiguration>
+			<FileConfiguration
+				Name="Release|Win32"
+				ExcludedFromBuild="true"
+				>
+				<Tool
+					Name="VCCLCompilerTool"
+				/>
+			</FileConfiguration>
+			<FileConfiguration
+				Name="Release|x64"
+				ExcludedFromBuild="true"
+				>
+				<Tool
+					Name="VCCLCompilerTool"
+				/>
+			</FileConfiguration>
+		</File>
+		<File
+			RelativePath=".\FdoForcedOneToOneFeatureReader.h"
+			>
+		</File>
+		<File
 			RelativePath=".\FdoReaderCollection.cpp"
 			>
 			<FileConfiguration

Modified: trunk/MgDev/Server/src/Services/Feature/ServerFeatureServiceBuild.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Feature/ServerFeatureServiceBuild.cpp	2011-11-21 02:09:23 UTC (rev 6238)
+++ trunk/MgDev/Server/src/Services/Feature/ServerFeatureServiceBuild.cpp	2011-11-21 11:00:25 UTC (rev 6239)
@@ -70,6 +70,7 @@
 #include "OpGetDataRows.cpp"
 #include "SelectAggregateCommand.cpp"
 #include "SelectCommand.cpp"
+#include "ExtendedSelectCommand.cpp"
 #include "ServerDataReader.cpp"
 #include "ServerSqlDataReaderPool.cpp"
 #include "FeatureNumericFunctions.cpp"
@@ -100,6 +101,7 @@
 #include "FdoReaderCollection.cpp"
 #include "FdoFilterCollection.cpp"
 #include "FdoFeatureReader.cpp"
+#include "FdoForcedOneToOneFeatureReader.cpp"
 #include "ServerFeatureTransactionPool.cpp"
 #include "ServerFeatureTransaction.cpp"
 #include "OpBeginTransaction.cpp"

Modified: trunk/MgDev/Server/src/Services/Feature/ServerFeatureUtil.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Feature/ServerFeatureUtil.cpp	2011-11-21 02:09:23 UTC (rev 6238)
+++ trunk/MgDev/Server/src/Services/Feature/ServerFeatureUtil.cpp	2011-11-21 11:00:25 UTC (rev 6239)
@@ -3551,3 +3551,72 @@
 
     MG_FEATURE_SERVICE_CATCH_AND_THROW(L"MgServerFeatureUtil.UpdateRasterPropertyDefinition")
 }
+
+FdoIdentifierCollection* MgServerFeatureUtil::ExtractIdentifiers(FdoExpression* expr)
+{
+    CHECKNULL(expr, L"MgServerFeatureUtil.ExtractIdentifiers");
+
+    FdoPtr<FdoIdentifierCollection> ret;
+    MG_FEATURE_SERVICE_TRY()
+
+    ret = FdoIdentifierCollection::Create();
+
+    switch(expr->GetExpressionType())
+    {
+    case FdoExpressionItemType_ComputedIdentifier:
+        {
+            FdoComputedIdentifier* comp = static_cast<FdoComputedIdentifier*>(expr);
+            FdoPtr<FdoExpression> inner = comp->GetExpression();
+
+            FdoPtr<FdoIdentifierCollection> result = ExtractIdentifiers(inner);
+            for (FdoInt32 i = 0; i < result->GetCount(); i++)
+            {
+                FdoPtr<FdoIdentifier> resultItem = result->GetItem(i);
+                ret->Add(resultItem);
+            }
+        }
+        break;
+    case FdoExpressionItemType_Function:
+        {
+            FdoFunction* func = static_cast<FdoFunction*>(expr);
+            FdoExpressionCollection* funcArgs = func->GetArguments();
+            for (FdoInt32 i = 0; i < funcArgs->GetCount(); i++)
+            {
+                FdoPtr<FdoExpression> arg = funcArgs->GetItem(i);
+                FdoPtr<FdoIdentifierCollection> result = ExtractIdentifiers(arg);
+                for (FdoInt32 j = 0; j < result->GetCount(); j++)
+                {
+                    FdoPtr<FdoIdentifier> resultItem = result->GetItem(j);
+                    ret->Add(resultItem);
+                }
+            }
+        }
+        break;
+    case FdoExpressionItemType_Identifier:
+        ret->Add(static_cast<FdoIdentifier*>(expr));
+        break;
+    case FdoExpressionItemType_UnaryExpression:
+        {
+            FdoUnaryExpression* unexpr = static_cast<FdoUnaryExpression*>(expr);
+            FdoExpression* inner = unexpr->GetExpression();
+
+            FdoPtr<FdoIdentifierCollection> result = ExtractIdentifiers(inner);
+            for (FdoInt32 i = 0; i < result->GetCount(); i++)
+            {
+                FdoPtr<FdoIdentifier> resultItem = result->GetItem(i);
+                ret->Add(resultItem);
+            }
+        }
+        break;
+    case FdoExpressionItemType_SubSelectExpression:
+        {
+            FdoSubSelectExpression* subSelect = static_cast<FdoSubSelectExpression*>(expr);
+            FdoPtr<FdoIdentifier> propName = subSelect->GetPropertyName();
+            ret->Add(propName);
+        }
+        break;
+    }
+
+    MG_FEATURE_SERVICE_CATCH_AND_THROW(L"MgServerFeatureUtil.ExtractIdentifiers")
+    return ret.Detach();
+}
\ No newline at end of file

Modified: trunk/MgDev/Server/src/Services/Feature/ServerFeatureUtil.h
===================================================================
--- trunk/MgDev/Server/src/Services/Feature/ServerFeatureUtil.h	2011-11-21 02:09:23 UTC (rev 6238)
+++ trunk/MgDev/Server/src/Services/Feature/ServerFeatureUtil.h	2011-11-21 11:00:25 UTC (rev 6239)
@@ -118,6 +118,7 @@
     static FdoObjectPropertyDefinition* GetObjectPropertyDefinition(MgObjectPropertyDefinition* objPropDef, FdoClassCollection* fdoClassCol);
     static FdoParameterDirection GetFdoParameterDirection(INT32 paramDirection);
     static INT32 GetMgParameterDirection(FdoParameterDirection fdoParamDirection);
+    static FdoIdentifierCollection* ExtractIdentifiers(FdoExpression* expr);
 
     static void UpdateFdoFeatureSchema(MgFeatureSchema* mgSchema, FdoFeatureSchema* fdoSchema);
     static void UpdateFdoClassCollection(MgClassDefinitionCollection* mgClassDefCol, FdoClassCollection* fdoClassCol);

Modified: trunk/MgDev/Server/src/Services/Feature/ServerSelectFeatures.cpp
===================================================================
--- trunk/MgDev/Server/src/Services/Feature/ServerSelectFeatures.cpp	2011-11-21 02:09:23 UTC (rev 6238)
+++ trunk/MgDev/Server/src/Services/Feature/ServerSelectFeatures.cpp	2011-11-21 11:00:25 UTC (rev 6239)
@@ -24,6 +24,7 @@
 #include "ServerFeatureUtil.h"
 #include "FeatureServiceCommand.h"
 #include "SelectCommand.h"
+#include "ExtendedSelectCommand.h"
 #include "SelectAggregateCommand.h"
 #include "FeatureDistribution.h"
 #include "ServerGwsFeatureReader.h"
@@ -35,6 +36,9 @@
 #include "FdoExpressionEngineCopyFilter.h"
 #include "CacheManager.h"
 
+//Uncomment for extra console chatter to debug FDO joins
+//#define DEBUG_FDO_JOIN
+
 MgServerSelectFeatures::MgServerSelectFeatures()
 {
     m_command = NULL;
@@ -88,25 +92,58 @@
         m_featureSourceCacheItem = cacheManager->GetFeatureSourceCacheItem(resource);
     }
 
+    // Set options (NULL is a valid value)
+    m_options = SAFE_ADDREF(options);
     // Check if a feature join is to be performed by inspecting the resource for join properties
     bool bFeatureJoinProperties = FindFeatureJoinProperties(resource, className);
     // Check if a feature join is only a calculation
     bool bFeatureCalculation = FindFeatureCalculation(resource, className);
+    //Test for the FDO join optimization, because performance is **substantially** better
+    bool bSupportsFdoJoin = SupportsFdoJoin(resource, className, isSelectAggregate);
+#ifdef DEBUG_FDO_JOIN
+    STRING fsIdStr = resource->ToString();
+    ACE_DEBUG((LM_INFO, ACE_TEXT("\n(%t) Testing Feature Source (%W, %W) for FDO join optimization"), fsIdStr.c_str(), className.c_str()));
+#endif  
     if (!isSelectAggregate && bFeatureJoinProperties)
     {
-        // Get the FdoFilter from the options
-        // Create Command
-        CreateCommand(resource, isSelectAggregate);
-        // Set options (NULL is a valid value)
-        m_options = SAFE_ADDREF(options);
-        // Apply options to FDO command
-        ApplyQueryOptions(isSelectAggregate);
+        if (bSupportsFdoJoin)
+        {
+#ifdef DEBUG_FDO_JOIN
+            ACE_DEBUG((LM_INFO, ACE_TEXT("\n(%t) Feature Source (%W) supports FDO join optimization"), fsIdStr.c_str()));
+#endif
+            m_command = MgFeatureServiceCommand::CreateCommand(resource, FdoCommandType_ExtendedSelect);
+            mgReader = SelectFdoJoin(resource, className, false);
+        }
+        else 
+        {
+#ifdef DEBUG_FDO_JOIN
+            ACE_DEBUG((LM_INFO, ACE_TEXT("\n(%t) Feature Source (%W) does not support the FDO join optimization. Using GwsQueryEngine"), fsIdStr.c_str()));
+#endif
+            // Get the FdoFilter from the options
+            // Create Command
+            CreateCommand(resource, isSelectAggregate);
 
-        FdoPtr<FdoFilter> filter = m_command->GetFilter();
-
-        // Perform feature join
-        mgReader = JoinFeatures(resource, className, filter);
+            // Apply options to FDO command
+            ApplyQueryOptions(isSelectAggregate);
+            FdoPtr<FdoFilter> filter = m_command->GetFilter();
+            // Perform feature join
+            mgReader = JoinFeatures(resource, className, filter);
+        }
     }
+    else if (isSelectAggregate && bFeatureJoinProperties && bSupportsFdoJoin)
+    {
+        // NOTE: If this is FDO join capable, but contains functions over prefixed secondary properties
+        // We have to go through the GWS Query Engine because we haven't implemented reverse mapping
+        // of prefixed secondary class properties. Fortunately, the common case for this is distict value
+        // queries (eg. Generating themeable values), and GWS Query Engine is surprisingly performant in this case
+#ifdef DEBUG_FDO_JOIN
+        ACE_DEBUG((LM_INFO, ACE_TEXT("\n(%t) Feature Source (%W) supports aggregate FDO join optimization"), fsIdStr.c_str()));
+#endif
+        // Perform the same select query as above, but route this through the FDO expression engine
+        // Slow maybe, but anything is faster than going via the GWS query engine.
+        m_command = MgFeatureServiceCommand::CreateCommand(resource, FdoCommandType_ExtendedSelect);
+        mgReader = SelectFdoJoin(resource, className, true);
+    }
     else
     {
         // Custom function specified from Select command is not allowed
@@ -140,14 +177,11 @@
             m_command->SetFeatureClassName((FdoString*)className.c_str());
         }
 
-        // Set options (NULL is a valid value)
-        m_options = SAFE_ADDREF(options);
-
         // Apply options to FDO command
         ApplyQueryOptions(isSelectAggregate);
 
         // Check if the specified className is an extension (join) in the feature source
-        if (bFeatureJoinProperties)
+        if (bFeatureJoinProperties && !bSupportsFdoJoin)
         {
             // Perform feature join to obtain the joined properties
             // Note: this gwsFeatureReader is just for temporary use. it will not be returned
@@ -273,6 +307,7 @@
     return mgReader.Detach();
 }
 
+
 void MgServerSelectFeatures::ApplyQueryOptions(bool isSelectAggregate)
 {
     CHECKNULL(m_command, L"MgServerSelectFeatures.ApplyQueryOptions");
@@ -304,6 +339,9 @@
     if (cnt <= 0)
         return; // Nothing to do
 
+    //TODO: Need to check if FDO join optimization is supported, and whether this contains prefixed
+    //secondary properties.
+
     FdoPtr<FdoIdentifierCollection> fic = m_command->GetPropertyNames();
     CHECKNULL((FdoIdentifierCollection*)fic, L"MgServerSelectFeatures.ApplyClassProperties");
 
@@ -334,6 +372,9 @@
     if (cnt <= 0)
         return; // Nothing to do
 
+    //TODO: Need to check if FDO join optimization is supported, and whether this contains prefixed
+    //secondary properties in any expressions
+
     // TODO: Add support for custom functions
 
     for (INT32 i=0; i < cnt; i++)
@@ -1490,3 +1531,669 @@
 
     return secResId.Detach();
 }
+
+bool MgServerSelectFeatures::SupportsFdoJoin(MgResourceIdentifier* featureSourceId, CREFSTRING extensionName, bool isAggregate)
+{
+    bool bSupported = false;
+
+    MG_FEATURE_SERVICE_TRY()
+
+    //This could be qualified, so parse it to be sure
+    STRING schemaName;
+    STRING extName;
+    MgUtil::ParseQualifiedClassName(extensionName, schemaName, extName);
+
+    CHECKNULL(m_featureSourceCacheItem.p, L"MgServerSelectFeatures.SupportsFdoJoin");
+    MdfModel::FeatureSource* featureSource = m_featureSourceCacheItem->Get();
+    MdfModel::ExtensionCollection* extensions = featureSource->GetExtensions();
+    CHECKNULL(extensions, L"MgServerSelectFeatures.SupportsFdoJoin");
+
+    MdfModel::Extension* extension = NULL;
+    for (INT32 i = 0; i < extensions->GetCount(); i++) 
+    {
+        MdfModel::Extension* ext = extensions->GetAt(i);
+        if (ext->GetName() == extName)
+        {
+            extension = ext;
+            break;
+        }
+    }
+
+    if (NULL == extension) //Extension in question not found
+    {
+#ifdef DEBUG_FDO_JOIN
+        ACE_DEBUG((LM_INFO, ACE_TEXT("\n\t(%t) [FDO Join Test] Could not find extension named: %W"), extensionName.c_str()));
+#endif
+        return false;
+    }
+
+    Ptr<MgServerFeatureConnection> conn = new MgServerFeatureConnection(featureSourceId);
+    {
+        if (!conn->IsConnectionOpen())
+        {
+            throw new MgConnectionFailedException(L"MgServerSelectFeatures.SupportsFdoJoin", __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+
+        FdoPtr<FdoIConnection> fdoConn = conn->GetConnection();
+        FdoPtr<FdoIConnectionCapabilities> connCaps = fdoConn->GetConnectionCapabilities();
+        
+        MdfModel::AttributeRelateCollection* relates = extension->GetAttributeRelates();
+
+        //Get the easy checks out of the way
+        if (!connCaps->SupportsJoins())
+        {
+#ifdef DEBUG_FDO_JOIN
+            ACE_DEBUG((LM_INFO, ACE_TEXT("\n\t(%t) [FDO Join Test] The provider does not support the FDO join APIs")));
+#endif
+            return false;
+        }
+
+        FdoJoinType jtypes = (FdoJoinType)connCaps->GetJoinTypes();
+
+        //Now ascertain if all participating feature classes originate from the same feature source
+        
+        //We've yet to figure out chained joins. TODO: Revisit later
+        if (relates->GetCount() > 1)
+        {
+#ifdef DEBUG_FDO_JOIN
+            ACE_DEBUG((LM_INFO, ACE_TEXT("\n\t(%t) [FDO Join Test] Chained/Multiple FDO Joins not supported yet")));
+#endif
+            return false;
+        }
+
+        //Need to check the filter here to see if it involves secondary class properties and/or
+        //expressions involving such properties. We don't support this yet
+        if (m_options != NULL)
+        {
+            MdfModel::AttributeRelate* relate = relates->GetAt(0);
+            STRING filterText = m_options->GetFilter();
+            STRING qualifiedName = relate->GetAttributeClass();
+            STRING schemaName;
+            STRING clsName;
+            MgUtil::ParseQualifiedClassName(qualifiedName, schemaName, clsName);
+            if (FilterContainsSecondaryProperties(featureSourceId, filterText, schemaName, clsName, relate->GetName()))
+            {
+    #ifdef DEBUG_FDO_JOIN
+                ACE_DEBUG((LM_INFO, ACE_TEXT("\n\t(%t) [FDO Join Test] Filter contains secondary class properties")));
+    #endif
+                return false;
+            }
+        }
+
+        for (INT32 i = 0; i < relates->GetCount(); i++)
+        {
+            MdfModel::AttributeRelate* relate = relates->GetAt(i);
+            const MdfModel::MdfString& fsId = relate->GetResourceId();
+
+            //Different feature sources
+            if (featureSourceId->ToString() != fsId)
+            {
+#ifdef DEBUG_FDO_JOIN
+                ACE_DEBUG((LM_INFO, ACE_TEXT("\n\t(%t) [FDO Join Test] The extension does not join with another class from the same data store")));
+#endif
+                return false;
+            }
+
+            //TODO: Review when we lift the one join limit
+            if (NULL != (MgFeatureQueryOptions*)m_options)
+            {
+                //Is this costly? Should we cache it?
+                //FdoPtr<FdoFunctionDefinitionCollection> functions = FdoExpressionEngine::GetStandardFunctions();
+                Ptr<MgStringPropertyCollection> expressions = m_options->GetComputedProperties();
+                for (INT32 i = 0; i < expressions->GetCount(); i++)
+                {
+                    Ptr<MgStringProperty> compProp = expressions->GetItem(i);
+                    STRING exprText = compProp->GetValue();
+                    FdoPtr<FdoExpression> fdoExpr = FdoExpression::Parse(exprText.c_str());
+
+                    if (fdoExpr->GetExpressionType() == FdoExpressionItemType_Function)
+                    {
+                        FdoFunction* func = static_cast<FdoFunction*>(fdoExpr.p);
+                        FdoString* funcName = func->GetName();
+
+                        // We don't support functions on prefixed secondary properties (yet)
+                        // so we have to check these functions to ensure they are only operating on the 
+                        // primary side properties
+                        STRING primarySchemaName;
+                        STRING primaryClassName;
+                        MgUtil::ParseQualifiedClassName(extension->GetFeatureClass(), primarySchemaName, primaryClassName);
+                        bool bValidFunction = IsFunctionOnPrimaryProperty(func, fdoConn, primarySchemaName, primaryClassName);
+                        if (!bValidFunction)
+                        {
+#ifdef DEBUG_FDO_JOIN
+                            ACE_DEBUG((LM_INFO, ACE_TEXT("\n\t(%t) [FDO Join Test] FDO Function %W contains a non-primary or unrecognised identifier"), funcName));
+#endif
+                            return false;
+                        }
+                    }
+                }
+            }
+
+            MdfModel::RelatePropertyCollection* relProps = relate->GetRelateProperties();
+
+            //Check if the join type is supported. Given FDO exposes more join types than
+            //the ones specified here, the chances are real good that we'll have a match
+            MdfModel::AttributeRelate::RelateType rtype = relate->GetRelateType();
+            switch(rtype)
+            {
+            case MdfModel::AttributeRelate::Inner:
+                if ((jtypes & FdoJoinType_Inner) != FdoJoinType_Inner)
+                {
+#ifdef DEBUG_FDO_JOIN
+                    ACE_DEBUG((LM_INFO, ACE_TEXT("\n\t(%t) [FDO Join Test] The provider does not support Inner Join")));
+#endif
+                    return false;
+                }
+                break;
+            case MdfModel::AttributeRelate::LeftOuter:
+                if ((jtypes & FdoJoinType_LeftOuter) != FdoJoinType_LeftOuter)
+                {
+#ifdef DEBUG_FDO_JOIN
+                    ACE_DEBUG((LM_INFO, ACE_TEXT("\n\t(%t) [FDO Join Test] The provider does not support Left Outer Join")));
+#endif
+                    return false;
+                }
+                break;
+            case MdfModel::AttributeRelate::RightOuter:
+                if ((jtypes & FdoJoinType_RightOuter) != FdoJoinType_RightOuter)
+                {
+#ifdef DEBUG_FDO_JOIN
+                    ACE_DEBUG((LM_INFO, ACE_TEXT("\n\t(%t) [FDO Join Test] The provider does not support Right Outer Join")));
+#endif
+                    return false;
+                }
+                break;
+            default:
+                {
+#ifdef DEBUG_FDO_JOIN
+                    ACE_DEBUG((LM_INFO, ACE_TEXT("\n\t(%t) [FDO Join Test] The join type is not recognised")));
+#endif
+                    return false;
+                }
+            }
+        }
+
+        //Still here? You pass the test
+        bSupported = true;
+    }
+
+    MG_FEATURE_SERVICE_CATCH_AND_THROW(L"MgServerSelectFeatures.SupportsFdoJoin")
+
+    return bSupported;
+}
+
+bool MgServerSelectFeatures::IsFunctionOnPrimaryProperty(FdoFunction* function, FdoIConnection* fdoConn, CREFSTRING schemaName, CREFSTRING className)
+{
+    FdoPtr<FdoIdentifierCollection> identifiers = MgServerFeatureUtil::ExtractIdentifiers(function);
+    if (identifiers->GetCount() == 0)
+        return true; //Inconsequential
+
+    FdoPtr<FdoIDescribeSchema> descSchema = dynamic_cast<FdoIDescribeSchema*>(fdoConn->CreateCommand(FdoCommandType_DescribeSchema));
+    CHECKNULL((FdoIDescribeSchema*)descSchema, L"MgServerSelectFeatures.SelectFdoJoin");
+
+    if (!schemaName.empty())
+    {
+        descSchema->SetSchemaName(schemaName.c_str());
+    }
+    if (!className.empty())
+    {
+        FdoPtr<FdoStringCollection> classNames = FdoStringCollection::Create();
+        classNames->Add(className.c_str());
+        descSchema->SetClassNames(classNames);
+    }
+
+    FdoPtr<FdoClassDefinition> classDef;
+    FdoPtr<FdoFeatureSchemaCollection> schemas = descSchema->Execute();
+    for (FdoInt32 i = 0; i < schemas->GetCount(); i++)
+    {
+        FdoPtr<FdoFeatureSchema> schema = schemas->GetItem(i);
+        if (wcscmp(schema->GetName(), schemaName.c_str()) == 0) 
+        {
+            FdoPtr<FdoClassCollection> classes = schema->GetClasses();
+            for (FdoInt32 j = 0; j < classes->GetCount(); j++) 
+            {
+                FdoPtr<FdoClassDefinition> klassDef = classes->GetItem(j);
+                if (wcscmp(klassDef->GetName(), className.c_str()) == 0)
+                {
+                    classDef = SAFE_ADDREF(klassDef.p);
+                    break;
+                }
+            }
+        }
+    }
+
+    if (NULL == (FdoClassDefinition*)classDef)
+    {
+        //TODO: Refine message if available
+        throw new MgClassNotFoundException(L"MgServerSelectFeatures.SelectFdoJoin", __LINE__, __WFILE__, NULL, L"", NULL);
+    }
+
+    FdoPtr<FdoPropertyDefinitionCollection> properties = classDef->GetProperties();
+    for (FdoInt32 i = 0; i < identifiers->GetCount(); i++)
+    {
+        FdoPtr<FdoIdentifier> identifier = identifiers->GetItem(i);
+        FdoString* name = identifier->GetName();
+        if (properties->IndexOf(name) < 0) //Not in primary class or not recognised
+        {
+#ifdef DEBUG_FDO_JOIN
+            FdoString* funcName = function->GetName();
+            ACE_DEBUG((LM_INFO, ACE_TEXT("\n\t(%t) [FDO Join Test] The aggregate function %W contains an unknown or non-primary identifier: %W"), funcName, name));
+#endif
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool MgServerSelectFeatures::FilterContainsSecondaryProperties(MgResourceIdentifier* featureSourceId, CREFSTRING filter, STRING secondarySchema, STRING secondaryClassName, STRING secondaryPrefix)
+{
+    if (filter.empty())
+        return false;
+
+    //TODO: There's probably a more efficient way to do this without needing to fetch the secondary
+    //class definition. But we're aiming for functionality and simplicity first.
+    Ptr<MgServerFeatureConnection> conn = new MgServerFeatureConnection(featureSourceId);
+    {
+        if (!conn->IsConnectionOpen())
+        {
+            throw new MgConnectionFailedException(L"MgServerSelectFeatures.SupportsFdoJoin", __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+
+        FdoPtr<FdoIConnection> fdoConn = conn->GetConnection();
+
+        FdoPtr<FdoIDescribeSchema> descSchema = dynamic_cast<FdoIDescribeSchema*>(fdoConn->CreateCommand(FdoCommandType_DescribeSchema));
+        CHECKNULL((FdoIDescribeSchema*)descSchema, L"MgServerSelectFeatures.SelectFdoJoin");
+
+        if (!secondarySchema.empty())
+        {
+            descSchema->SetSchemaName(secondarySchema.c_str());
+        }
+        if (!secondaryClassName.empty())
+        {
+            FdoPtr<FdoStringCollection> classNames = FdoStringCollection::Create();
+            classNames->Add(secondaryClassName.c_str());
+            descSchema->SetClassNames(classNames);
+        }
+
+        FdoPtr<FdoClassDefinition> classDef;
+        FdoPtr<FdoFeatureSchemaCollection> schemas = descSchema->Execute();
+        for (FdoInt32 i = 0; i < schemas->GetCount(); i++)
+        {
+            FdoPtr<FdoFeatureSchema> schema = schemas->GetItem(i);
+            if (wcscmp(schema->GetName(), secondarySchema.c_str()) == 0) 
+            {
+                FdoPtr<FdoClassCollection> classes = schema->GetClasses();
+                for (FdoInt32 j = 0; j < classes->GetCount(); j++) 
+                {
+                    FdoPtr<FdoClassDefinition> klassDef = classes->GetItem(j);
+                    if (wcscmp(klassDef->GetName(), secondaryClassName.c_str()) == 0)
+                    {
+                        classDef = SAFE_ADDREF(klassDef.p);
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (NULL == (FdoClassDefinition*)classDef)
+        {
+            //TODO: Refine message if available
+            throw new MgClassNotFoundException(L"MgServerSelectFeatures.SelectFdoJoin", __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+
+        FdoPtr<FdoPropertyDefinitionCollection> propDefs = classDef->GetProperties();
+        for (INT32 i = 0; i < propDefs->GetCount(); i++)
+        {
+            FdoPtr<FdoPropertyDefinition> propDef = propDefs->GetItem(i);
+            STRING findStr = secondaryPrefix;
+            findStr += propDef->GetName();
+
+            if (filter.find(findStr) != STRING::npos)
+                return true; //The filter string contains this extended feature class property
+        }
+    }
+    return false;
+}
+
+MgReader* MgServerSelectFeatures::SelectFdoJoin(MgResourceIdentifier* featureSourceId, CREFSTRING extensionName, bool isAggregate)
+{
+    // TODO: This does not handle filters on the secondary side (yet)
+    // Can GwsQueryEngine do this?
+
+    Ptr<MgReader> ret;
+
+    MG_FEATURE_SERVICE_TRY()
+
+    //This could be qualified, so parse it to be sure
+    STRING schemaName;
+    STRING extName;
+    MgUtil::ParseQualifiedClassName(extensionName, schemaName, extName);
+
+    CHECKNULL(m_featureSourceCacheItem.p, L"MgServerSelectFeatures.SelectFdoJoin");
+    MdfModel::FeatureSource* featureSource = m_featureSourceCacheItem->Get();
+    MdfModel::ExtensionCollection* extensions = featureSource->GetExtensions();
+    CHECKNULL(extensions, L"MgServerSelectFeatures.SelectFdoJoin");
+
+    MdfModel::Extension* extension = NULL;
+    for (INT32 i = 0; i < extensions->GetCount(); i++) 
+    {
+        MdfModel::Extension* ext = extensions->GetAt(i);
+        if (ext->GetName() == extName)
+        {
+            extension = ext;
+            break;
+        }
+    }
+
+    CHECKNULL(extension, L"MgServerSelectFeatures.SelectFdoJoin");
+    m_command->SetFeatureClassName(extension->GetFeatureClass().c_str());
+    MdfModel::AttributeRelateCollection* relates = extension->GetAttributeRelates();
+    CHECKNULL(relates, L"MgServerSelectFeatures.SelectFdoJoin");
+    MdfModel::AttributeRelate* relate = relates->GetAt(0);
+
+    const MdfModel::MdfString& prefix = relate->GetName();
+
+    STRING primaryAlias = L"primary";
+    STRING secondaryAlias = L"secondary";
+
+    FdoPtr<FdoJoinCriteriaCollection> joinCriteria;
+    if (isAggregate)
+    {
+        MgSelectAggregateCommand* cmd = static_cast<MgSelectAggregateCommand*>(m_command.p);
+        cmd->SetAlias(primaryAlias.c_str());
+        joinCriteria = cmd->GetJoinCriteria();
+    }
+    else
+    {
+        MgExtendedSelectCommand* cmd = static_cast<MgExtendedSelectCommand*>(m_command.p);
+        cmd->SetAlias(primaryAlias.c_str());
+        joinCriteria = cmd->GetJoinCriteria();
+    }
+
+    Ptr<MgStringCollection> idPropNames = new MgStringCollection();
+    Ptr<MgServerFeatureConnection> conn = new MgServerFeatureConnection(featureSourceId);
+    {
+        if (!conn->IsConnectionOpen())
+        {
+            throw new MgConnectionFailedException(L"MgServerSelectFeatures.SelectFdoJoin", __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+
+        CHECKNULL(m_command, L"MgServerSelectFeatures.SelectFdoJoin");
+        FdoPtr<FdoIConnection> fdoConn = conn->GetConnection();
+
+        bool bAppliedProperties = false;
+        if (m_options != NULL)
+        {
+            //ApplyClassProperties();
+            ApplyComputedProperties();
+            // TODO: We need to find out if there are any filters involving the secondary side
+            ApplyFilter();
+            // ApplySpatialFilter();
+            ApplyOrderingOptions();
+            // We don't apply aggregate options here because these go through the FDO Expression Engine
+            ApplyAggregateOptions(isAggregate);
+            ApplyFetchSize();
+
+            //If an explicit list is specified, we assume the caller knows about prefixed
+            //extended feature class properties.
+            Ptr<MgStringCollection> props = m_options->GetClassProperties();
+            if (props->GetCount() > 0)
+            {
+                ApplyClassProperties();
+                bAppliedProperties = true;
+            }
+        }
+
+        //We need to fetch full class definitions of primary and secondary classes
+        if (!bAppliedProperties)
+        {
+            //Add primary class properties
+            STRING primaryClass = extension->GetFeatureClass();
+            STRING primarySchemaName;
+            STRING primaryClassName;
+            MgUtil::ParseQualifiedClassName(primaryClass, primarySchemaName, primaryClassName);
+
+            ApplyClassProperties(fdoConn, primarySchemaName, primaryClassName, idPropNames, primaryAlias);
+
+            if (!isAggregate)
+            {
+                //Add secondary class properties
+                STRING joinClass = relate->GetAttributeClass();
+                STRING joinSchemaName;
+                STRING joinClassName;
+                MgUtil::ParseQualifiedClassName(joinClass, joinSchemaName, joinClassName);
+
+                ApplyClassProperties(fdoConn, joinSchemaName, joinClassName, NULL, secondaryAlias, prefix); 
+            }
+        }
+    }
+
+    //Set join type
+    FdoJoinType jtype = FdoJoinType_None;
+    switch(relate->GetRelateType())
+    {
+    case MdfModel::AttributeRelate::Inner:
+        jtype = FdoJoinType_Inner;
+        break;
+    case MdfModel::AttributeRelate::LeftOuter:
+        jtype = FdoJoinType_LeftOuter;
+        break;
+    case MdfModel::AttributeRelate::RightOuter:
+        jtype = FdoJoinType_RightOuter;
+        break;
+    }
+
+    bool bForceOneToOne = relate->GetForceOneToOne();
+    //Set filter for this join
+    STRING secondaryClass = relate->GetAttributeClass();
+    STRING filterText;
+    MdfModel::RelatePropertyCollection* relProps = relate->GetRelateProperties();
+    for (INT32 i = 0; i < relProps->GetCount(); i++)
+    {
+        MdfModel::RelateProperty* prop = relProps->GetAt(i);
+        if (!filterText.empty())
+        {
+            filterText += L" AND ";
+        }
+
+        //[primaryAlias].[PropertyName] = [secondaryAlias].[PropertyName]
+        filterText += primaryAlias;
+        filterText += L".";
+        filterText += prop->GetFeatureClassProperty();
+        filterText += L" = ";
+        filterText += secondaryAlias;
+        filterText += L".";
+        filterText += prop->GetAttributeClassProperty();
+    }
+
+    FdoPtr<FdoJoinCriteria> criteria;
+    FdoPtr<FdoIdentifier> idJoinClass = FdoIdentifier::Create(secondaryClass.c_str());
+    FdoPtr<FdoFilter> filter = FdoFilter::Parse(filterText.c_str());
+    if (prefix.empty())
+        criteria = FdoJoinCriteria::Create(idJoinClass, jtype, filter);
+    else
+        criteria = FdoJoinCriteria::Create(secondaryAlias.c_str(), idJoinClass, jtype, filter);
+
+    joinCriteria->Add(criteria);
+
+    if (isAggregate)
+        ret = ((MgSelectAggregateCommand*)m_command.p)->ExecuteJoined(idPropNames, bForceOneToOne);
+    else
+        ret = ((MgExtendedSelectCommand*)m_command.p)->ExecuteJoined(idPropNames, bForceOneToOne);
+
+    MG_FEATURE_SERVICE_CATCH_AND_THROW(L"MgServerSelectFeatures.SelectFdoJoin")
+
+    return ret.Detach();
+}
+
+void MgServerSelectFeatures::ApplyAggregateCommandJoinFilterAndCriteria(MgResourceIdentifier* featureSourceId, CREFSTRING extensionName)
+{
+#ifdef DEBUG_FDO_JOIN
+    ACE_DEBUG((LM_INFO, ACE_TEXT("\n\t(%t) Applying FDO join criteria and filter to aggregate command")));
+#endif
+
+     //This could be qualified, so parse it to be sure
+    STRING schemaName;
+    STRING extName;
+    MgUtil::ParseQualifiedClassName(extensionName, schemaName, extName);
+
+    CHECKNULL(m_featureSourceCacheItem.p, L"MgServerSelectFeatures.SupportsFdoJoin");
+    MdfModel::FeatureSource* featureSource = m_featureSourceCacheItem->Get();
+    MdfModel::ExtensionCollection* extensions = featureSource->GetExtensions();
+    CHECKNULL(extensions, L"MgServerSelectFeatures.SupportsFdoJoin");
+
+    MdfModel::Extension* extension = NULL;
+    for (INT32 i = 0; i < extensions->GetCount(); i++) 
+    {
+        MdfModel::Extension* ext = extensions->GetAt(i);
+        if (ext->GetName() == extName)
+        {
+            extension = ext;
+            break;
+        }
+    }
+
+    CHECKNULL(extension, L"MgServerSelectFeatures.SelectFdoJoin");
+    m_command->SetFeatureClassName(extension->GetFeatureClass().c_str());
+    MdfModel::AttributeRelateCollection* relates = extension->GetAttributeRelates();
+    CHECKNULL(relates, L"MgServerSelectFeatures.SelectFdoJoin");
+    MdfModel::AttributeRelate* relate = relates->GetAt(0);
+
+    const MdfModel::MdfString& prefix = relate->GetName();
+
+    STRING primaryAlias = L"primary";
+    STRING secondaryAlias = L"secondary";
+
+    MgSelectAggregateCommand* extSelect = static_cast<MgSelectAggregateCommand*>(m_command.p);
+    extSelect->SetAlias(primaryAlias.c_str());
+
+    FdoPtr<FdoJoinCriteriaCollection> joinCriteria = extSelect->GetJoinCriteria();
+
+    //Set join type
+    FdoJoinType jtype = FdoJoinType_None;
+    switch(relate->GetRelateType())
+    {
+    case MdfModel::AttributeRelate::Inner:
+        jtype = FdoJoinType_Inner;
+        break;
+    case MdfModel::AttributeRelate::LeftOuter:
+        jtype = FdoJoinType_LeftOuter;
+        break;
+    case MdfModel::AttributeRelate::RightOuter:
+        jtype = FdoJoinType_RightOuter;
+        break;
+    }
+
+    bool bForceOneToOne = relate->GetForceOneToOne();
+    //Set filter for this join
+    STRING secondaryClass = relate->GetAttributeClass();
+    STRING filterText;
+    MdfModel::RelatePropertyCollection* relProps = relate->GetRelateProperties();
+    for (INT32 i = 0; i < relProps->GetCount(); i++)
+    {
+        MdfModel::RelateProperty* prop = relProps->GetAt(i);
+        if (!filterText.empty())
+        {
+            filterText += L" AND ";
+        }
+
+        //[primaryAlias].[PropertyName] = [secondaryAlias].[PropertyName]
+        filterText += primaryAlias;
+        filterText += L".";
+        filterText += prop->GetFeatureClassProperty();
+        filterText += L" = ";
+        filterText += secondaryAlias;
+        filterText += L".";
+        filterText += prop->GetAttributeClassProperty();
+    }
+
+    FdoPtr<FdoJoinCriteria> criteria;
+    FdoPtr<FdoIdentifier> idJoinClass = FdoIdentifier::Create(secondaryClass.c_str());
+    FdoPtr<FdoFilter> filter = FdoFilter::Parse(filterText.c_str());
+    if (prefix.empty())
+        criteria = FdoJoinCriteria::Create(idJoinClass, jtype, filter);
+    else
+        criteria = FdoJoinCriteria::Create(secondaryAlias.c_str(), idJoinClass, jtype, filter);
+
+    joinCriteria->Add(criteria);
+}
+
+void MgServerSelectFeatures::ApplyClassProperties(FdoIConnection* fdoConn, CREFSTRING schemaName, CREFSTRING className, MgStringCollection* idPropNames, CREFSTRING alias, CREFSTRING prefix)
+{
+    FdoPtr<FdoIDescribeSchema> descSchema = dynamic_cast<FdoIDescribeSchema*>(fdoConn->CreateCommand(FdoCommandType_DescribeSchema));
+    CHECKNULL((FdoIDescribeSchema*)descSchema, L"MgServerSelectFeatures.SelectFdoJoin");
+
+    if (!schemaName.empty())
+    {
+        descSchema->SetSchemaName(schemaName.c_str());
+    }
+    if (!className.empty())
+    {
+        FdoPtr<FdoStringCollection> classNames = FdoStringCollection::Create();
+        classNames->Add(className.c_str());
+        descSchema->SetClassNames(classNames);
+    }
+
+    FdoPtr<FdoClassDefinition> classDef;
+    FdoPtr<FdoFeatureSchemaCollection> schemas = descSchema->Execute();
+    for (FdoInt32 i = 0; i < schemas->GetCount(); i++)
+    {
+        FdoPtr<FdoFeatureSchema> schema = schemas->GetItem(i);
+        if (wcscmp(schema->GetName(), schemaName.c_str()) == 0) 
+        {
+            FdoPtr<FdoClassCollection> classes = schema->GetClasses();
+            for (FdoInt32 j = 0; j < classes->GetCount(); j++) 
+            {
+                FdoPtr<FdoClassDefinition> klassDef = classes->GetItem(j);
+                if (wcscmp(klassDef->GetName(), className.c_str()) == 0)
+                {
+                    classDef = SAFE_ADDREF(klassDef.p);
+                    break;
+                }
+            }
+        }
+    }
+
+    if (NULL == (FdoClassDefinition*)classDef)
+    {
+        //TODO: Refine message if available
+        throw new MgClassNotFoundException(L"MgServerSelectFeatures.SelectFdoJoin", __LINE__, __WFILE__, NULL, L"", NULL);
+    }
+
+    FdoPtr<FdoIdentifierCollection> propNames = m_command->GetPropertyNames();
+
+    //Add primary class properties
+    FdoPtr<FdoPropertyDefinitionCollection> propDefs = classDef->GetProperties();
+    for (FdoInt32 i = 0; i < propDefs->GetCount(); i++) 
+    {
+        FdoPtr<FdoPropertyDefinition> propDef = propDefs->GetItem(i);
+
+        //Skip ones that aren't data/geometry
+        if (propDef->GetPropertyType() != FdoPropertyType_DataProperty &&
+            propDef->GetPropertyType() != FdoPropertyType_GeometricProperty)
+            continue;
+
+        STRING exprText = alias + L".";
+        exprText += propDef->GetName();
+
+        FdoPtr<FdoExpression> expr = FdoExpression::Parse(exprText.c_str());
+        STRING idName = prefix + propDef->GetName();
+        //[alias].[propertyName] AS [prefix][propertyName]
+        FdoPtr<FdoComputedIdentifier> compId = FdoComputedIdentifier::Create(idName.c_str(), expr);
+
+        propNames->Add(compId);
+    }
+
+    if (NULL != idPropNames)
+    {
+        FdoPtr<FdoDataPropertyDefinitionCollection> idPropDefs = classDef->GetIdentityProperties();
+        for (FdoInt32 i = 0; i < idPropDefs->GetCount(); i++)
+        {
+            FdoPtr<FdoDataPropertyDefinition> dp = idPropDefs->GetItem(i);
+            STRING propName = L"";
+            propName += dp->GetName();
+            idPropNames->Add(propName);
+        }
+    }
+}
\ No newline at end of file

Modified: trunk/MgDev/Server/src/Services/Feature/ServerSelectFeatures.h
===================================================================
--- trunk/MgDev/Server/src/Services/Feature/ServerSelectFeatures.h	2011-11-21 02:09:23 UTC (rev 6238)
+++ trunk/MgDev/Server/src/Services/Feature/ServerSelectFeatures.h	2011-11-21 11:00:25 UTC (rev 6239)
@@ -91,6 +91,14 @@
     INT32 m_nJoinQueryBatchSize;
 
     INT32 m_nDataCacheSize;
+
+    // FDO join optimization
+    bool IsFunctionOnPrimaryProperty(FdoFunction* function, FdoIConnection* conn, CREFSTRING schemaName, CREFSTRING className);
+    bool SupportsFdoJoin(MgResourceIdentifier* featureSourceId, CREFSTRING extension, bool isAggregate);
+    MgReader* SelectFdoJoin(MgResourceIdentifier* featureSourceId, CREFSTRING extension, bool isAggregate);
+    void ApplyAggregateCommandJoinFilterAndCriteria(MgResourceIdentifier* featureSourceId, CREFSTRING extension);
+    void ApplyClassProperties(FdoIConnection* fdoConn, CREFSTRING schemaName, CREFSTRING className, MgStringCollection* idPropNames, CREFSTRING alias, CREFSTRING prefix = L"");
+    bool FilterContainsSecondaryProperties(MgResourceIdentifier* featureSourceId, CREFSTRING filter, STRING secondarySchema, STRING secondaryClassName, STRING secondaryPrefix);
 };
 
 #endif

Modified: trunk/MgDev/Server/src/UnitTesting/TestFeatureService.cpp
===================================================================
--- trunk/MgDev/Server/src/UnitTesting/TestFeatureService.cpp	2011-11-21 02:09:23 UTC (rev 6238)
+++ trunk/MgDev/Server/src/UnitTesting/TestFeatureService.cpp	2011-11-21 11:00:25 UTC (rev 6239)
@@ -82,14 +82,18 @@
             MgResourceIdentifier resourceIdentifier5(L"Library://UnitTests/Data/TestChainedInner1ToManyJoin.FeatureSource");
             MgResourceIdentifier resourceIdentifier6(L"Library://UnitTests/Data/Empty.FeatureSource");
             MgResourceIdentifier resourceIdentifier7(L"Library://UnitTests/Data/SavePointTest.FeatureSource");
+            MgResourceIdentifier resourceIdentifier8(L"Library://UnitTests/Data/ParcelsJoinTestSQLite.FeatureSource");
+            MgResourceIdentifier resourceIdentifier9(L"Library://UnitTests/Data/FdoJoin.FeatureSource");
 #ifdef _WIN32
             STRING resourceContentFileName1 = L"..\\UnitTestFiles\\Sheboygan_Parcels.FeatureSource";
             STRING resourceContentFileName2 = L"..\\UnitTestFiles\\Redding_Parcels.FeatureSource";
             STRING resourceContentFileName3 = L"..\\UnitTestFiles\\Sheboygan_BuildingOutlines.FeatureSource";
             STRING resourceContentFileName4 = L"..\\UnitTestFiles\\Sheboygan_VotingDistricts.FeatureSource";
-            STRING resourceContentFileName5=  L"..\\UnitTestFiles\\TESTChainedInner1ToManyJoin.FeatureSource";
-            STRING resourceContentFileName6=  L"..\\UnitTestFiles\\Empty.FeatureSource";
-            STRING resourceContentFileName7=  L"..\\UnitTestFiles\\SavePointTest.FeatureSource";
+            STRING resourceContentFileName5 = L"..\\UnitTestFiles\\TESTChainedInner1ToManyJoin.FeatureSource";
+            STRING resourceContentFileName6 = L"..\\UnitTestFiles\\Empty.FeatureSource";
+            STRING resourceContentFileName7 = L"..\\UnitTestFiles\\SavePointTest.FeatureSource";
+            STRING resourceContentFileName8 = L"..\\UnitTestFiles\\UT_Parcels_SQLite_Join.FeatureSource";
+            STRING resourceContentFileName9 = L"..\\UnitTestFiles\\UT_FdoJoin.FeatureSource";
             STRING dataFileName1 = L"..\\UnitTestFiles\\Sheboygan_Parcels.sdf";
             STRING dataFileName2 = L"..\\UnitTestFiles\\Redding_Parcels.shp";
             STRING dataFileName3 = L"..\\UnitTestFiles\\Redding_Parcels.dbf";
@@ -98,6 +102,8 @@
             STRING dataFileName6 = L"..\\UnitTestFiles\\Sheboygan_VotingDistricts.sdf";
             STRING dataFileName7 = L"..\\UnitTestFiles\\Empty.sdf";
             STRING dataFileName8 = L"..\\UnitTestFiles\\SavePointTest.sqlite";
+            STRING dataFileName9 = L"..\\UnitTestFiles\\JoinTest.sqlite";
+            STRING dataFileName10 = L"..\\UnitTestFiles\\ParcelsJoinTest.sqlite";
 #else
             STRING resourceContentFileName1 = L"../UnitTestFiles/Sheboygan_Parcels.FeatureSource";
             STRING resourceContentFileName2 = L"../UnitTestFiles/Redding_Parcels.FeatureSource";
@@ -106,6 +112,8 @@
             STRING resourceContentFileName5 = L"../UnitTestFiles/TESTChainedInner1ToManyJoin.FeatureSource";
             STRING resourceContentFileName6 = L"../UnitTestFiles/Empty.FeatureSource";
             STRING resourceContentFileName7 = L"../UnitTestFiles/SavePointTest.FeatureSource";
+            STRING resourceContentFileName8 = L"../UnitTestFiles/UT_Parcels_SQLite_Join.FeatureSource";
+            STRING resourceContentFileName9 = L"../UnitTestFiles/UT_FdoJoin.FeatureSource";
             STRING dataFileName1 = L"../UnitTestFiles/Sheboygan_Parcels.sdf";
             STRING dataFileName2 = L"../UnitTestFiles/Redding_Parcels.shp";
             STRING dataFileName3 = L"../UnitTestFiles/Redding_Parcels.dbf";
@@ -113,7 +121,9 @@
             STRING dataFileName5 = L"../UnitTestFiles/Sheboygan_BuildingOutlines.sdf";
             STRING dataFileName6 = L"../UnitTestFiles/Sheboygan_VotingDistricts.sdf";
             STRING dataFileName7 = L"../UnitTestFiles/Empty.sdf";
-             STRING dataFileName8 = L"../UnitTestFiles/SavePointTest.sqlite";
+            STRING dataFileName8 = L"../UnitTestFiles/SavePointTest.sqlite";
+            STRING dataFileName9 = L"../UnitTestFiles/JoinTest.sqlite";
+            STRING dataFileName10 = L"../UnitTestFiles/ParcelsJoinTest.sqlite";
 #endif
 
             //Add a new resource
@@ -145,6 +155,14 @@
             Ptr<MgByteReader> contentReader7 = contentSource7->GetReader();
             pService->SetResource(&resourceIdentifier7, contentReader7, NULL);
 
+            Ptr<MgByteSource> contentSource8 = new MgByteSource(resourceContentFileName8);
+            Ptr<MgByteReader> contentReader8 = contentSource8->GetReader();
+            pService->SetResource(&resourceIdentifier8, contentReader8, NULL);
+
+            Ptr<MgByteSource> contentSource9 = new MgByteSource(resourceContentFileName9);
+            Ptr<MgByteReader> contentReader9 = contentSource9->GetReader();
+            pService->SetResource(&resourceIdentifier9, contentReader9, NULL);
+
             //Set the resource data
             Ptr<MgByteSource> dataSource1 = new MgByteSource(dataFileName1);
             Ptr<MgByteReader> dataReader1 = dataSource1->GetReader();
@@ -181,6 +199,14 @@
             Ptr<MgByteSource> dataSource9 = new MgByteSource(dataFileName8);
             Ptr<MgByteReader> dataReader9 = dataSource9->GetReader();
             pService->SetResourceData(&resourceIdentifier7, L"SavePointTest.sqlite", L"File", dataReader9);
+
+            Ptr<MgByteSource> dataSource10 = new MgByteSource(dataFileName9);
+            Ptr<MgByteReader> dataReader10 = dataSource10->GetReader();
+            pService->SetResourceData(&resourceIdentifier9, L"JoinTest.sqlite", L"File", dataReader10);
+
+            Ptr<MgByteSource> dataSource11 = new MgByteSource(dataFileName10);
+            Ptr<MgByteReader> dataReader11 = dataSource11->GetReader();
+            pService->SetResourceData(&resourceIdentifier8, L"ParcelsJoinTest.sqlite", L"File", dataReader11);
         }
     }
     catch(MgException* e)
@@ -234,6 +260,12 @@
         Ptr<MgResourceIdentifier> fsres6 = new MgResourceIdentifier(L"Library://UnitTests/Data/Empty.FeatureSource");
         pService->DeleteResource(fsres6);
 
+        Ptr<MgResourceIdentifier> fsres7 = new MgResourceIdentifier(L"Library://UnitTests/Data/FdoJoin.FeatureSource");
+        pService->DeleteResource(fsres7);
+
+        Ptr<MgResourceIdentifier> fsres8 = new MgResourceIdentifier(L"Library://UnitTests/Data/ParcelsJoinTestSQLite.FeatureSource");
+        pService->DeleteResource(fsres8);
+
         #ifdef _DEBUG
         ACE_DEBUG((LM_INFO, ACE_TEXT("TestFeatureService::TestEnd()\n")));
         MgFdoConnectionManager* pFdoConnectionManager = MgFdoConnectionManager::GetInstance();
@@ -2354,3 +2386,300 @@
         throw;
     }
 }
+
+///----------------------------------------------------------------------------
+/// Test Case Description:
+///
+/// This test case exercises the FDO join optimization
+///----------------------------------------------------------------------------
+void TestFeatureService::TestCase_JoinFdoFeatures()
+{
+    try
+    {
+        
+        MgServiceManager* serviceManager = MgServiceManager::GetInstance();
+        if(serviceManager == 0)
+        {
+            throw new MgNullReferenceException(L"TestFeatureService.TestCase_JoinFdoFeatures", __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+
+        Ptr<MgFeatureService> pService = dynamic_cast<MgFeatureService*>(serviceManager->RequestService(MgServiceType::FeatureService));
+        if (pService == 0)
+        {
+            throw new MgServiceNotAvailableException(L"TestFeatureService.TestCase_JoinFdoFeatures", __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+
+        Ptr<MgResourceIdentifier> lFeatureSource = new MgResourceIdentifier(L"Library://UnitTests/Data/FdoJoin.FeatureSource");
+
+        Ptr<MgFeatureReader> reader = pService->SelectFeatures(lFeatureSource, L"CitiesCountries", NULL);
+
+        INT32 count1 = 0;
+        while(reader->ReadNext())
+        {
+            CPPUNIT_ASSERT(reader->IsNull(L"ID") == false);
+            CPPUNIT_ASSERT(reader->IsNull(L"CountryCode") == false);
+            CPPUNIT_ASSERT(reader->IsNull(L"StateCode") == false);
+            CPPUNIT_ASSERT(reader->IsNull(L"Name") == false);
+            CPPUNIT_ASSERT(reader->IsNull(L"Population") == false);
+            CPPUNIT_ASSERT(reader->IsNull(L"CNT_CountryCode") == false);
+            CPPUNIT_ASSERT(reader->IsNull(L"CNT_Name") == false);
+            count1++;
+        }
+        reader->Close();
+        CPPUNIT_ASSERT(10 == count1);
+
+        Ptr<MgFeatureReader> reader2 = pService->SelectFeatures(lFeatureSource, L"CitiesStates", NULL);
+
+        INT32 count2 = 0;
+        while(reader2->ReadNext())
+        {
+            CPPUNIT_ASSERT(reader2->IsNull(L"ID") == false);
+            CPPUNIT_ASSERT(reader2->IsNull(L"CountryCode") == false);
+            CPPUNIT_ASSERT(reader2->IsNull(L"StateCode") == false);
+            CPPUNIT_ASSERT(reader2->IsNull(L"Name") == false);
+            CPPUNIT_ASSERT(reader2->IsNull(L"Population") == false);
+            CPPUNIT_ASSERT(reader2->IsNull(L"ST_ID") == false);
+            CPPUNIT_ASSERT(reader2->IsNull(L"ST_CountryCode") == false);
+            CPPUNIT_ASSERT(reader2->IsNull(L"ST_StateCode") == false);
+            CPPUNIT_ASSERT(reader2->IsNull(L"ST_Name") == false);
+            count2++;
+        }
+        reader2->Close();
+        CPPUNIT_ASSERT(10 == count2);
+
+        Ptr<MgFeatureReader> reader3 = pService->SelectFeatures(lFeatureSource, L"CitiesStatesOneToOne", NULL);
+        INT32 count3 = 0;
+        while(reader3->ReadNext())
+        {
+            count3++;
+        }
+        reader3->Close();
+        CPPUNIT_ASSERT(10 == count3);
+    }
+    catch(MgException* e)
+    {
+        STRING message = e->GetDetails(TEST_LOCALE);
+        SAFE_RELEASE(e);
+        CPPUNIT_FAIL(MG_WCHAR_TO_CHAR(message.c_str()));
+    }
+    catch(FdoException* e)
+    {
+        STRING message = L"FdoException occurred: ";
+        message += e->GetExceptionMessage();
+        FDO_SAFE_RELEASE(e);
+        CPPUNIT_FAIL(MG_WCHAR_TO_CHAR(message.c_str()));
+    }
+    catch(...)
+    {
+        throw;
+    }
+}
+
+void TestFeatureService::TestCase_BenchmarkSqliteJoin()
+{
+    try
+    {
+        MgServiceManager* serviceManager = MgServiceManager::GetInstance();
+        if(serviceManager == 0)
+        {
+            throw new MgNullReferenceException(L"TestFeatureService.TestCase_BenchmarkSqliteJoin", __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+
+        Ptr<MgResourceService> pService = dynamic_cast<MgResourceService*>(serviceManager->RequestService(MgServiceType::ResourceService));
+        if (pService == 0)
+        {
+            throw new MgServiceNotAvailableException(L"TestFeatureService.TestCase_BenchmarkSqliteJoin", __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+
+        Ptr<MgFeatureService> featSvc = dynamic_cast<MgFeatureService*>(serviceManager->RequestService(MgServiceType::FeatureService));
+        if (featSvc == 0)
+        {
+            throw new MgServiceNotAvailableException(L"TestFeatureService.TestCase_BenchmarkSqliteJoin",
+                __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+
+        Ptr<MgResourceIdentifier> fsId = new MgResourceIdentifier(L"Library://UnitTests/Data/ParcelsJoinTestSQLite.FeatureSource");
+        CPPUNIT_ASSERT(featSvc->TestConnection(fsId));
+        Ptr<MgFeatureReader> reader;
+
+        // ----- Start the tests ------- //
+        ACE_DEBUG((LM_INFO, ACE_TEXT("\nTestFeatureService::TestCase_BenchmarkSqliteJoin() - Inner Join \n")));
+        long lStart = GetTickCount();
+        long total = 0L;
+
+        reader = featSvc->SelectFeatures(fsId, L"ParcelsInner", NULL);
+        while(reader->ReadNext())
+        {
+            total++;
+        }
+        reader->Close();
+
+        ACE_DEBUG((LM_INFO, ACE_TEXT("  Execution Time (%d results): = %6.4f (s)\n"), total, ((GetTickCount()-lStart)/1000.0) ));
+        ACE_DEBUG((LM_INFO, ACE_TEXT("\nTestFeatureService::TestCase_BenchmarkSqliteJoin() - Left Outer Join \n")));
+        lStart = GetTickCount();
+        total = 0L;
+
+        reader = featSvc->SelectFeatures(fsId, L"ParcelsLeftOuter", NULL);
+        while(reader->ReadNext())
+        {
+            total++;
+        }
+        reader->Close();
+
+        ACE_DEBUG((LM_INFO, ACE_TEXT("  Execution Time (%d results): = %6.4f (s)\n"), total, ((GetTickCount()-lStart)/1000.0) ));
+        ACE_DEBUG((LM_INFO, ACE_TEXT("\nTestFeatureService::TestCase_BenchmarkSqliteJoin() - Inner Join (Forced 1:1) \n")));
+        lStart = GetTickCount();
+        total = 0L;
+
+        reader = featSvc->SelectFeatures(fsId, L"ParcelsInnerOneToOne", NULL);
+        while(reader->ReadNext())
+        {
+            total++;
+        }
+        reader->Close();
+
+        ACE_DEBUG((LM_INFO, ACE_TEXT("  Execution Time (%d results): = %6.4f (s)\n"), total, ((GetTickCount()-lStart)/1000.0) ));
+        ACE_DEBUG((LM_INFO, ACE_TEXT("\nTestFeatureService::TestCase_BenchmarkSqliteJoin() - Left Outer Join (Forced 1:1) \n")));
+        lStart = GetTickCount();
+        total = 0L;
+
+        reader = featSvc->SelectFeatures(fsId, L"ParcelsLeftOuterOneToOne", NULL);
+        while(reader->ReadNext())
+        {
+            total++;
+        }
+        reader->Close();
+
+        ACE_DEBUG((LM_INFO, ACE_TEXT("  Execution Time (%d results): = %6.4f (s)\n"), total, ((GetTickCount()-lStart)/1000.0) ));
+    }
+    catch(MgException* e)
+    {
+        STRING message = e->GetDetails(TEST_LOCALE);
+        SAFE_RELEASE(e);
+        CPPUNIT_FAIL(MG_WCHAR_TO_CHAR(message.c_str()));
+    }
+    catch(FdoException* e)
+    {
+        STRING message = L"FdoException occurred: ";
+        message += e->GetExceptionMessage();
+        FDO_SAFE_RELEASE(e);
+        CPPUNIT_FAIL(MG_WCHAR_TO_CHAR(message.c_str()));
+    }
+    catch(...)
+    {
+        throw;
+    }
+}
+
+void TestFeatureService::TestCase_BenchmarkSqliteAggregateJoin()
+{
+    try
+    {
+        MgServiceManager* serviceManager = MgServiceManager::GetInstance();
+        if(serviceManager == 0)
+        {
+            throw new MgNullReferenceException(L"TestFeatureService.TestCase_BenchmarkSqliteAggregateJoin", __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+
+        Ptr<MgResourceService> pService = dynamic_cast<MgResourceService*>(serviceManager->RequestService(MgServiceType::ResourceService));
+        if (pService == 0)
+        {
+            throw new MgServiceNotAvailableException(L"TestFeatureService.TestCase_BenchmarkSqliteAggregateJoin", __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+
+        Ptr<MgFeatureService> featSvc = dynamic_cast<MgFeatureService*>(serviceManager->RequestService(MgServiceType::FeatureService));
+        if (featSvc == 0)
+        {
+            throw new MgServiceNotAvailableException(L"TestFeatureService.TestCase_BenchmarkSqliteAggregateJoin",
+                __LINE__, __WFILE__, NULL, L"", NULL);
+        }
+
+        Ptr<MgResourceIdentifier> fsId = new MgResourceIdentifier(L"Library://UnitTests/Data/ParcelsJoinTestSQLite.FeatureSource");
+        CPPUNIT_ASSERT(featSvc->TestConnection(fsId));
+        Ptr<MgDataReader> reader;
+
+        // ----- Start the tests ------- //
+        Ptr<MgFeatureAggregateOptions> aggOpts = new MgFeatureAggregateOptions();
+        aggOpts->AddComputedProperty(L"Extents", L"SpatialExtents(Geometry)");
+        aggOpts->AddComputedProperty(L"TotalCount", L"Count(SdfId)");
+
+        ACE_DEBUG((LM_INFO, ACE_TEXT("\nTestFeatureService::TestCase_BenchmarkSqliteAggregateJoin() - Inner Join \n")));
+        long lStart = GetTickCount();
+        long total = 0L;
+        int iterations = 0;
+
+        reader = featSvc->SelectAggregate(fsId, L"ParcelsInner", aggOpts);
+        while(reader->ReadNext())
+        {
+            Ptr<MgByteReader> br = reader->GetGeometry(L"Extents");
+            total = reader->GetInt64(L"TotalCount");
+            iterations++;
+        }
+        reader->Close();
+        CPPUNIT_ASSERT(iterations == 1);
+
+        ACE_DEBUG((LM_INFO, ACE_TEXT("  Execution Time (%d results): = %6.4f (s)\n"), total, ((GetTickCount()-lStart)/1000.0) ));
+        ACE_DEBUG((LM_INFO, ACE_TEXT("\nTestFeatureService::TestCase_BenchmarkSqliteAggregateJoin() - Left Outer Join \n")));
+        lStart = GetTickCount();
+        iterations = 0L;
+
+        reader = featSvc->SelectAggregate(fsId, L"ParcelsLeftOuter", aggOpts);
+        while(reader->ReadNext())
+        {
+            Ptr<MgByteReader> br = reader->GetGeometry(L"Extents");
+            total = reader->GetInt64(L"TotalCount");
+            iterations++;
+        }
+        reader->Close();
+        CPPUNIT_ASSERT(iterations == 1L);
+
+        ACE_DEBUG((LM_INFO, ACE_TEXT("  Execution Time (%d results): = %6.4f (s)\n"), total, ((GetTickCount()-lStart)/1000.0) ));
+        ACE_DEBUG((LM_INFO, ACE_TEXT("\nTestFeatureService::TestCase_BenchmarkSqliteAggregateJoin() - Inner Join (Forced 1:1) \n")));
+        lStart = GetTickCount();
+        iterations = 0L;
+
+        reader = featSvc->SelectAggregate(fsId, L"ParcelsInnerOneToOne", aggOpts);
+        while(reader->ReadNext())
+        {
+            Ptr<MgByteReader> br = reader->GetGeometry(L"Extents");
+            total = reader->GetInt64(L"TotalCount");
+            iterations++;
+        }
+        reader->Close();
+        CPPUNIT_ASSERT(iterations == 1L);
+
+        ACE_DEBUG((LM_INFO, ACE_TEXT("  Execution Time (%d results): = %6.4f (s)\n"), total, ((GetTickCount()-lStart)/1000.0) ));
+        ACE_DEBUG((LM_INFO, ACE_TEXT("\nTestFeatureService::TestCase_BenchmarkSqliteAggregateJoin() - Left Outer Join (Forced 1:1) \n")));
+        lStart = GetTickCount();
+        iterations = 0L;
+
+        reader = featSvc->SelectAggregate(fsId, L"ParcelsLeftOuterOneToOne", aggOpts);
+        while(reader->ReadNext())
+        {
+            Ptr<MgByteReader> br = reader->GetGeometry(L"Extents");
+            total = reader->GetInt64(L"TotalCount");
+            iterations++;
+        }
+        reader->Close();
+        CPPUNIT_ASSERT(iterations == 1L);
+
+        ACE_DEBUG((LM_INFO, ACE_TEXT("  Execution Time (%d results): = %6.4f (s)\n"), total, ((GetTickCount()-lStart)/1000.0) ));
+    }
+    catch(MgException* e)
+    {
+        STRING message = e->GetDetails(TEST_LOCALE);
+        SAFE_RELEASE(e);
+        CPPUNIT_FAIL(MG_WCHAR_TO_CHAR(message.c_str()));
+    }
+    catch(FdoException* e)
+    {
+        STRING message = L"FdoException occurred: ";
+        message += e->GetExceptionMessage();
+        FDO_SAFE_RELEASE(e);
+        CPPUNIT_FAIL(MG_WCHAR_TO_CHAR(message.c_str()));
+    }
+    catch(...)
+    {
+        throw;
+    }
+}
\ No newline at end of file

Modified: trunk/MgDev/Server/src/UnitTesting/TestFeatureService.h
===================================================================
--- trunk/MgDev/Server/src/UnitTesting/TestFeatureService.h	2011-11-21 02:09:23 UTC (rev 6238)
+++ trunk/MgDev/Server/src/UnitTesting/TestFeatureService.h	2011-11-21 11:00:25 UTC (rev 6239)
@@ -58,6 +58,10 @@
     CPPUNIT_TEST(TestCase_BenchmarkSelectFeatures);
     CPPUNIT_TEST(TestCase_ConcurrentAccess);
     CPPUNIT_TEST(TestCase_SavePoint);
+    CPPUNIT_TEST(TestCase_JoinFdoFeatures);
+    CPPUNIT_TEST(TestCase_BenchmarkSqliteJoin);
+    
+    CPPUNIT_TEST(TestCase_BenchmarkSqliteAggregateJoin);
 
     CPPUNIT_TEST(TestEnd); // This must be the very last unit test
     CPPUNIT_TEST_SUITE_END();
@@ -101,6 +105,9 @@
     void TestCase_BenchmarkSelectFeatures();
     void TestCase_ConcurrentAccess();
     void TestCase_SavePoint();
+    void TestCase_JoinFdoFeatures();
+    void TestCase_BenchmarkSqliteJoin();
+    void TestCase_BenchmarkSqliteAggregateJoin();
 };
 
 #endif // _TESTFEATURESERVICE_H

Added: trunk/MgDev/UnitTest/TestData/FeatureService/SQLite/JoinTest.sqlite
===================================================================
(Binary files differ)


Property changes on: trunk/MgDev/UnitTest/TestData/FeatureService/SQLite/JoinTest.sqlite
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/MgDev/UnitTest/TestData/FeatureService/SQLite/ParcelsJoinTest.sqlite
===================================================================
(Binary files differ)


Property changes on: trunk/MgDev/UnitTest/TestData/FeatureService/SQLite/ParcelsJoinTest.sqlite
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/MgDev/UnitTest/TestData/FeatureService/SQLite/UT_FdoJoin.FeatureSource
===================================================================
--- trunk/MgDev/UnitTest/TestData/FeatureService/SQLite/UT_FdoJoin.FeatureSource	                        (rev 0)
+++ trunk/MgDev/UnitTest/TestData/FeatureService/SQLite/UT_FdoJoin.FeatureSource	2011-11-21 11:00:25 UTC (rev 6239)
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<FeatureSource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:noNamespaceSchemaLocation="FeatureSource-1.0.0.xsd">
+  <Provider>OSGeo.SQLite</Provider>
+  <Parameter>
+    <Name>File</Name>
+    <Value>%MG_DATA_FILE_PATH%JoinTest.sqlite</Value>
+  </Parameter>
+  <Parameter>
+    <Name>UseFdoMetadata</Name>
+    <Value>FALSE</Value>
+  </Parameter>
+  <Extension>
+    <AttributeRelate>
+      <RelateProperty>
+        <FeatureClassProperty>CountryCode</FeatureClassProperty>
+        <AttributeClassProperty>CountryCode</AttributeClassProperty>
+      </RelateProperty>
+      <AttributeClass>Default:Countries</AttributeClass>
+      <ResourceId>Library://UnitTests/Data/FdoJoin.FeatureSource</ResourceId>
+      <Name>CNT_</Name>
+      <RelateType>Inner</RelateType>
+      <ForceOneToOne>false</ForceOneToOne>
+    </AttributeRelate>
+    <Name>CitiesCountries</Name>
+    <FeatureClass>Default:Cities</FeatureClass>
+  </Extension>
+  <Extension>
+    <AttributeRelate>
+      <RelateProperty>
+        <FeatureClassProperty>StateCode</FeatureClassProperty>
+        <AttributeClassProperty>StateCode</AttributeClassProperty>
+      </RelateProperty>
+      <AttributeClass>Default:States</AttributeClass>
+      <ResourceId>Library://UnitTests/Data/FdoJoin.FeatureSource</ResourceId>
+      <Name>ST_</Name>
+      <RelateType>Inner</RelateType>
+      <ForceOneToOne>false</ForceOneToOne>
+    </AttributeRelate>
+    <Name>CitiesStates</Name>
+    <FeatureClass>Default:Cities</FeatureClass>
+  </Extension>
+  <Extension>
+    <AttributeRelate>
+      <RelateProperty>
+        <FeatureClassProperty>StateCode</FeatureClassProperty>
+        <AttributeClassProperty>StateCode</AttributeClassProperty>
+      </RelateProperty>
+      <AttributeClass>Default:States</AttributeClass>
+      <ResourceId>Library://UnitTests/Data/FdoJoin.FeatureSource</ResourceId>
+      <Name>ST_</Name>
+      <RelateType>Inner</RelateType>
+      <ForceOneToOne>true</ForceOneToOne>
+    </AttributeRelate>
+    <Name>CitiesStatesOneToOne</Name>
+    <FeatureClass>Default:Cities</FeatureClass>
+  </Extension>
+</FeatureSource>
\ No newline at end of file

Added: trunk/MgDev/UnitTest/TestData/FeatureService/SQLite/UT_Parcels_SQLite_Join.FeatureSource
===================================================================
--- trunk/MgDev/UnitTest/TestData/FeatureService/SQLite/UT_Parcels_SQLite_Join.FeatureSource	                        (rev 0)
+++ trunk/MgDev/UnitTest/TestData/FeatureService/SQLite/UT_Parcels_SQLite_Join.FeatureSource	2011-11-21 11:00:25 UTC (rev 6239)
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FeatureSource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:noNamespaceSchemaLocation="FeatureSource-1.0.0.xsd">
+  <Provider>OSGeo.SQLite</Provider>
+  <Parameter>
+    <Name>File</Name>
+    <Value>%MG_DATA_FILE_PATH%ParcelsJoinTest.sqlite</Value>
+  </Parameter>
+  <Parameter>
+    <Name>UseFdoMetadata</Name>
+    <Value>TRUE</Value>
+  </Parameter>
+  <Extension>
+    <AttributeRelate>
+      <RelateProperty>
+        <FeatureClassProperty>ID</FeatureClassProperty>
+        <AttributeClassProperty>ID</AttributeClassProperty>
+      </RelateProperty>
+      <AttributeClass>Default:Parcels</AttributeClass>
+      <ResourceId>Library://UnitTests/Data/ParcelsJoinTestSQLite.FeatureSource</ResourceId>
+      <Name>DATA</Name>
+      <RelateType>LeftOuter</RelateType>
+      <ForceOneToOne>true</ForceOneToOne>
+    </AttributeRelate>
+    <Name>ParcelsLeftOuterOneToOne</Name>
+    <FeatureClass>Default:ParcelFeatures</FeatureClass>
+  </Extension>
+  <Extension>
+    <AttributeRelate>
+      <RelateProperty>
+        <FeatureClassProperty>ID</FeatureClassProperty>
+        <AttributeClassProperty>ID</AttributeClassProperty>
+      </RelateProperty>
+      <AttributeClass>Default:Parcels</AttributeClass>
+      <ResourceId>Library://UnitTests/Data/ParcelsJoinTestSQLite.FeatureSource</ResourceId>
+      <Name>DATA</Name>
+      <RelateType>Inner</RelateType>
+      <ForceOneToOne>true</ForceOneToOne>
+    </AttributeRelate>
+    <Name>ParcelsInnerOneToOne</Name>
+    <FeatureClass>Default:ParcelFeatures</FeatureClass>
+  </Extension>
+  <Extension>
+    <AttributeRelate>
+      <RelateProperty>
+        <FeatureClassProperty>ID</FeatureClassProperty>
+        <AttributeClassProperty>ID</AttributeClassProperty>
+      </RelateProperty>
+      <AttributeClass>Default:Parcels</AttributeClass>
+      <ResourceId>Library://UnitTests/Data/ParcelsJoinTestSQLite.FeatureSource</ResourceId>
+      <Name>DATA</Name>
+      <RelateType>LeftOuter</RelateType>
+      <ForceOneToOne>false</ForceOneToOne>
+    </AttributeRelate>
+    <Name>ParcelsLeftOuter</Name>
+    <FeatureClass>Default:ParcelFeatures</FeatureClass>
+  </Extension>
+  <Extension>
+    <AttributeRelate>
+      <RelateProperty>
+        <FeatureClassProperty>ID</FeatureClassProperty>
+        <AttributeClassProperty>ID</AttributeClassProperty>
+      </RelateProperty>
+      <AttributeClass>Default:Parcels</AttributeClass>
+      <ResourceId>Library://UnitTests/Data/ParcelsJoinTestSQLite.FeatureSource</ResourceId>
+      <Name>DATA</Name>
+      <RelateType>Inner</RelateType>
+      <ForceOneToOne>false</ForceOneToOne>
+    </AttributeRelate>
+    <Name>ParcelsInner</Name>
+    <FeatureClass>Default:ParcelFeatures</FeatureClass>
+  </Extension>
+</FeatureSource>
\ No newline at end of file



More information about the mapguide-commits mailing list