Changes between Initial Version and Version 1 of FDORfc50


Ignore:
Timestamp:
Jun 2, 2010, 10:57:23 AM (14 years ago)
Author:
gregboone
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • FDORfc50

    v1 v1  
     1= FDO RFC 50 - Extend FDO API to Select from Multiple Classes and Select Joins =
     2
     3This page contains a request for comments document (RFC) for the FDO Open Source project. 
     4More FDO RFCs can be found on the [wiki:FDORfcs RFCs] page.
     5
     6== Status ==
     7 
     8||RFC Template Version||1.0||
     9||Submission Date||Jun. 02 2010||
     10||Last Modified||Romica Dascalescu [[Timestamp]]||
     11||Author||Romica Dascalescu||
     12||Contibutors||Orest Halustchak, Greg Boone||
     13||RFC Status||Draft||
     14||Implementation Status||Pending||
     15||Proposed Milestone||3.6.0.0||
     16||Assigned PSC guide(s)||Greg Boone||
     17||'''Voting History'''||(vote date)||
     18||+1||||
     19||+0||||
     20||-0||||
     21||-1||||
     22 
     23== Motivation ==
     24
     25It is a common requirement for applications to join classes together so that they can process related information that is spread across more than one class, e.g. properties stored in one class and geometry in another class. This can be for modeling purposes such as when geometry in one class is shared between separate feature classes or when properties from another class are needed for rendering purposes. Currently this is achieved either by using client-side join processing as in MapGuide or for RDBMS sources, by defining views as the physical level and exposing them as feature classes at the FDO API level. While these techniques are useful, the first results in very poor performance compared to a native join if the classes come from the same data store and the data store can do native joins as is possible with RDBMS-based providers. Using views is common as well, but requires the user to do this at the physical data store level. Finally, users could use SQL pass-through for providers that support it but that has issues because again the user is dealing with physical schema rather than FDO classes and the result is not a feature class.
     26
     27This RFC adds the ability to create joined selects using the FDO API directly rather than using the workarounds described above. This adds more power and flexibility to the FDO API for applications that need that capability. This enhancement will be optional for providers and a capabilities function is included for an application to determine if a provider supports it.
     28
     29== Overview ==
     30
     31Implementing this new select is optional for all providers, for now we can support this only for SQLite and in the future we can extend it to other providers as needed. We expect the RDBMS-based provider to support this over time.
     32
     33The idea is to add support in FDO API for selecting from multiple classes and get a feature reader back.
     34
     35We have a few options from which we can chose in order to achieve our goal:
     36
     37 * 1. Add a new select command e.g. FdoIJoinSelect derived from FdoISelect, add a new command type and caller needs to ensure that command exist in the provider command capabilities before calling this interface. A small improvement here we can add two classes FdoIJoinSelect (the interface having only pure methods) and FdoJoinSelect (having a default implementation of those methods). All providers can use the class with a base implementation and in the future in case we want to add a new method we just add a default implementation in FdoJoinSelect, however this adds more complexity to the API since developer would be confused which class should use as base class in his implementation.
     38     *  '''Pro''': will not change the FdoISelect command, all other providers do not have to change anything on their side, and only in case a provider wants to support this command it has to implement it and expose it as supported.
     39     *  '''Cons''': We add a new command and a new type command, we already have a FdoIExtendedSelect for scrollable readers and users can be confused by those two commands. This will add more complexity in the API.
     40
     41 * 2. Enhance FdoISelect, then create a FdoSelect base class and add default implementers for GetFeatureClassNames () and GetJoinCriteria().Then each provider would be changed to base their select command implementation on FdoSelect instead of FdoISelect. Two new capability functions will be added so that applications can determine: if a provider supports the join criteria and supported join types, SupportsJoins() & GetJoinTypes(). Again, if future enhancements are ever made to FdoISelect, the need to change each provider would be eliminated.
     42     *  '''Pro''': this solution will not add more complexity to the API.
     43     *  '''Cons''': We need to change all the providers leading to a big effort from everyone.
     44
     45 * 3. Enhance FdoISelect, add two new methods ‘GetFeatureClassNames ()’ and ‘GetJoinCriteria()’ with a default implementation throwing an exception. Providers which want to implement these new methods just can override then and provide a detailed implementation. Even FdoISelect is declared as ‘interface’ we do not have interfaces in C++ and we can have all other abstract methods and only the new one with a default implementation. A good example here would be locking methods from select command: most of the provider do not support locking and have to provide an empty implementation (usually to throw an exception), so more code on provider side when we could just provide a default implementation in the base class. Following this path we just modify the base class and all other providers will not be changed since will inherit the default implementation. Looking at FdoISelect there are already two methods which provide default implementation: AddRef() and Release(), so adding default implementation for the new methods will not be something totally new.
     46     *  '''Pro''': this solution will not add more complexity to the API, we do not need to change any provider.
     47     *  '''Cons''': None.
     48
     49== Proposed Solution ==
     50
     51The last option seems to be the best solution since will not enforce other providers to add a blank implementation for those new methods and also will not add a new command. We avoid having two select commands one “extended” and one “join” this way we also eliminate a confusion which might show-up in commands naming. Also it opens a different approach to update a base class without changing all providers to add a blank implementation.
     52
     53We need to define supported cases and all necessary classes need to be added:
     54
     55 * A. '''Selecting form multiple classes'''. We should be able to select from multiple classes and have filters applied on any class attributes. In order to be able to achieve that we can use FdoISelect::GetFeatureClassNames () and add all classes we want to select from. The filter must be based on properties from the selected classes and filter can be validated at run time when the whole select is built. In this collection we can have:
     56     *  FdoIdentifier’s in case we just need to pass a class name.
     57     *  FdoComputedIdentifier’s having a name and as expression value a FdoIdentifier, this way we can get aliases, e.g. PropName AS NewPropName. We already use computed identifiers in our API for expressions and we provide a name and an expression value. In this case it will be the same, since an alias is actually an expression: we provide an alias (name) and the real name of the property.
     58     *  FdoClassComputedIdentifier’s which is a FdoISelect having a name. This option might not be supported by all RDBMS providers and it depends of the internal implementation. This class behaves like a computed class which will be evaluated at run time and really depends of the provider implementation. This new class will help to define sub-selects, see below case (C)
     59
     60We will use some SQL examples just to illustrate things we are trying to achieve, each RBDMS-based provider will translate FDO selects in server side SQL depending of the server capabilities.
     61
     62'''SELECT * FROM class1 AS p1, class2 AS p2 WHERE p1.ID=p2.FeatId AND SPATIAL_COND(p1.GEOM, GEOM_VAL);'''
     63
     64Below we added some C++ code on how caller can use this new improvement to achieve above select statement:
     65{{{
     66      FdoPtr<FdoISelect> sel = static_cast<FdoISelect*>(conn->CreateCommand(FdoCommandType_Select));
     67      FdoPtr<FdoIdentifierCollection> fclasscoll = = sel->GetFeatureClassNames();
     68
     69      FdoPtr<FdoIdentifier> fcname1 = FdoIdentifier::Create(L"class1");
     70      FdoPtr<FdoComputedIdentifier> fcp1 = FdoComputedIdentifier::Create(L"p1", fcname1);
     71      fclasscoll->Add(fcp1);
     72
     73      FdoPtr<FdoIdentifier> fcname2 = FdoIdentifier::Create(L"class2");
     74      FdoPtr<FdoComputedIdentifier> fcp2 = FdoComputedIdentifier::Create(L"p2", fcname2);
     75      fclasscoll->Add(fcp2);
     76
     77      FdoPtr<FdoFilter> filter = FdoFilter::Parse(L"p1.ID=p2.FeatId AND Geometry INTERSECTS GeomFromText('CURVESTRING(...)')");
     78      sel->SetFilter(filter);
     79
     80      FdoPtr<FdoIFeatureReader> rdr = sel->Execute();
     81}}}
     82
     83'''Note:''' In the base class we have a method named GetFeatureClassName, in case caller will use it and the FdoIdentifier is not added into GetFeatureClassNames we can add it at Execute time.
     84
     85
     86
     87 * B. '''Handle Joins''' : below we define new classes to define and use a join criteria used in the join select.
     88
     89{{{
     90      class FdoJoinCriteria : public FdoIDisposable
     91      {
     92      /// \cond DOXYGEN-IGNORE
     93      protected:
     94          /// Constructs a default instance of a join criteria.
     95          FdoJoinCriteria();
     96
     97          /// Constructs an instance of a join criteria using the specified arguments.
     98          FdoJoinCriteria(FdoString* name,
     99                          FdoIdentifier* joinClass,
     100                          FdoJoinType joinType,
     101                          FdoFilter* filter);
     102
     103          /// Default destructor for join criteriab.
     104          virtual ~FdoJoinCriteria();
     105
     106          virtual void Dispose();
     107
     108      public:
     109          /// Constructs a default instance of a join criteria.
     110          FDO_API static FdoJoinCriteria* Create();
     111
     112          /// Constructs a full defined instance of a join criteria.
     113          FDO_API static FdoJoinCriteria* Create(FdoString* name,
     114                                                 FdoIdentifier* joinClass,
     115                                                 FdoJoinType joinType,
     116                                                 FdoFilter* filter);
     117
     118          /// Returns the alias name for the join criteria class
     119          FDO_API FdoString* GetName();
     120
     121          /// Sets the alias name for the join criteria class
     122          FDO_API void SetName(FdoString* value);
     123
     124          /// Returns the identifier of the right join class
     125          FDO_API FdoIdentifier* GetJoinClass();
     126
     127          /// Sets the identifier of the right join class
     128          FDO_API void SetJoinClass(FdoIdentifier* value);
     129
     130          /// Returns the join type, e.g. inner join, cross join, left join
     131          FDO_API FdoJoinType GetJoinType();
     132
     133          /// Sets the join type, e.g. inner join, cross join, left join
     134          FDO_API void SetJoinType(FdoJoinType value);
     135
     136          /// Returns the join filter, e.g. LEFT JOIN rclass ON(filter)
     137          FDO_API FdoFilter* GetFilter();
     138
     139          /// Sets the join filter, e.g. LEFT JOIN rclass ON(filter)
     140          FDO_API void SetFilter(FdoFilter* value);
     141
     142      private:
     143          wchar_t*    m_name;
     144          FdoIdentifier* m_JoinClass;
     145          FdoJoinType m_JoinType;
     146          FdoFilter* m_Filter;
     147      };
     148
     149      /// Join type enumeration used to specify the join type
     150      enum FdoJoinType
     151      {
     152          /// default join type.
     153          FdoJoinType_None = 0x01,
     154          /// inner join
     155          FdoJoinType_Inner = 0x02,
     156          /// right outer join
     157          FdoJoinType_RightOuter = 0x04,
     158          /// left outer join
     159          FdoJoinType_LeftOuter = 0x08,
     160          /// full outer join
     161          FdoJoinType_FullOuter = 0x10,
     162          /// cross join
     163          FdoJoinType_Cross = 0x20
     164      };
     165}}}
     166
     167We need to add two new methods in connection capabilities: supports join selects (to avoid an exception when a user will try to use these two new methods from FdoISelect) and also supported join types which can be a collection or a value as OR between values, since all join types cannot be more than we can fit in a int32 using OR e.g.: val1|val2.
     168
     169{{{
     170      class FdoIConnectionCapabilities
     171      {
     172          virtual bool SupportsJoins(){ return false; }
     173          virtual FdoInt32 GetJoinTypes() {return FdoJoinType_None;}
     174      }
     175}}}
     176
     177We also need a collection to be defined for join criteria:
     178
     179{{{
     180      class FdoJoinCriteriaCollection : public FdoCollection<FdoJoinCriteria, FdoCommandException>
     181      {
     182      protected:
     183      /// \cond DOXYGEN-IGNORE
     184          virtual void Dispose();
     185      public:
     186          /// Default constructor
     187          FDO_API static FdoJoinCriteriaCollection* Create();
     188
     189          /// Returns FdoJoinCriteria having the name.
     190          /// This will throw an exception in case item is not found
     191          FDO_API FdoJoinCriteria* GetItem(FdoString* name);
     192       
     193          /// Returns FdoJoinCriteria having the name or NULL.
     194          FDO_API FdoJoinCriteria* FindItem(FdoString* name);
     195
     196          /// Returns FdoJoinCriteria having the index.
     197          FDO_API FdoJoinCriteria* GetItem(FdoInt32 index);
     198      };
     199}}}
     200
     201Join criteria will be used to generate join SQL commands, like:
     202
     203'''SELECT p.FeatId AS xFID,p.Geometry,pxy.Name,pxy.X,pxy.Y,pex.NameB,pex.Link FROM p '''[[BR]]
     204'''INNER JOIN pxy ON(p.FeatId = pxy.FeatId)'''[[BR]]
     205'''INNER JOIN pex ON(pex.FID = pxy.FeatId)'''[[BR]]
     206'''WHERE p.FeatId >= 0;'''[[BR]]
     207
     208Below we added some C++ code on how caller can use this new improvement to achieve above select statement:
     209
     210{{{
     211      FdoPtr<FdoISelect> sel = static_cast<FdoISelect*>(conn->CreateCommand(FdoCommandType_Select));
     212      FdoPtr<FdoIdentifierCollection> fclasscoll = = sel->GetFeatureClassNames();
     213      FdoPtr<FdoJoinCriteriaCollection> jcrit = sel->GetJoinCriteria();
     214
     215      FdoPtr<FdoIdentifier> fcpoint = FdoIdentifier::Create(L"p");
     216      fclasscoll->Add(fcpoint);
     217
     218      FdoPtr<FdoIdentifier> jcCls1 = FdoIdentifier::Create(L"pxy");
     219      FdoPtr<FdoFilter> flcr1 = FdoFilter::Parse(L"p.FeatId=pxy.FeatId");
     220
     221      FdoPtr<FdoJoinCriteria> jc1 = FdoJoinCriteria::Create(L"pxy", jcCls1, FdoJoinType_Inner, flcr1);
     222      jcrits->Add(jc1);
     223
     224      FdoPtr<FdoIdentifier> jcCls2 = FdoIdentifier::Create(L"pex");
     225      FdoPtr<FdoFilter> flcr2 = FdoFilter::Parse(L"pex.FID=pxy.FeatId");
     226
     227      FdoPtr<FdoJoinCriteria> jc2 = FdoJoinCriteria::Create(L"pex", jcCls2, FdoJoinType_Inner, flcr2);
     228      jcrits->Add(jc2);
     229
     230      FdoPtr<FdoIFeatureReader> rdr = sel->Execute();
     231}}}
     232
     233
     234
     235 * C. '''Handle Sub-selects''' : below we define a new class to define sub-select used in the new select.
     236
     237{{{
     238      class FdoComputedClassIdentifier : public FdoIdentifier
     239      {
     240      protected:
     241          /// Constructs a default instance of a computed class identifier.
     242          FdoComputedClassIdentifier();
     243
     244          /// Constructs an instance of a computed class
     245          /// identifier using the specified arguments.
     246          FdoComputedClassIdentifier(FdoString* name, FdoISelect* select);
     247
     248          /// Default destructor for computed class identifier.
     249          virtual ~FdoComputedClassIdentifier();
     250
     251          virtual void Dispose();
     252
     253      public:
     254          /// Constructs a default instance of a computed class identifier.
     255          FDO_API static FdoComputedClassIdentifier* Create();
     256
     257          /// Constructs an instance of a computed class identifier
     258          /// using the specified arguments.
     259          FDO_API static FdoComputedClassIdentifier* Create(
     260                                         FdoString* name,
     261                                         FdoISelect* select);
     262
     263          /// Gets the sub-select of the computed class identifier.
     264          FDO_API FdoISelect* GetSelect();
     265
     266          /// Sets the sub-select of the computed class identifier.
     267          FDO_API void SetSelect(FdoISelect* value);
     268
     269          /// Returns the type of the expression used to identify the
     270          /// type and avoid dynamic casts
     271          FDO_API FdoItemExpressionType GetExpressionType()
     272          {
     273              return FdoItemExpressionType_ComputedClassIdentifier;
     274          }
     275
     276      private:
     277          FdoISelect* m_subSelect;
     278      };
     279}}}
     280
     281Named sub-select will be used to generate SQL commands, like:
     282
     283'''SELECT * FROM point WHERE  FeatId IN (SELECT pt.FeatId FROM pt WHERE pt.FeatId >= 2 AND SPATIAL_COND(pt.GEOM, GEOM_VAL));'''
     284
     285In above case we will have two selects having following filters:
     286    * Main select:  “FeatId IN(:SUBSEL)”
     287    * Sub-select: “pt.FeatId >= 2 AND SPATIAL_COND(pt.GEOM, GEOM_VAL)”
     288
     289Below we added some C++ code on how caller can use this new improvement to achieve above select statement:
     290
     291{{{
     292      FdoPtr<FdoISelect> sel = static_cast<FdoISelect*>(conn->CreateCommand(FdoCommandType_Select));
     293      FdoPtr<FdoIdentifierCollection> fclasscoll = = sel->GetFeatureClassNames();
     294
     295      FdoPtr<FdoIdentifier> fcpoint = FdoIdentifier::Create(L"point");
     296      fclasscoll->Add(fcpoint);
     297
     298      FdoPtr<FdoISelect> subsel = static_cast<FdoISelect*>(conn->CreateCommand(FdoCommandType_Select));
     299      FdoPtr<FdoIdentifierCollection> ssfclasscoll = = subsel->GetFeatureClassNames();
     300      FdoPtr<FdoIdentifier> fcpt = FdoIdentifier::Create(L"pt");
     301      ssfclasscoll->Add(fcpt);
     302      FdoPtr<FdoFilter> ssfilter = FdoFilter::Parse(L"pt.FeatId>=2 AND Geometry INTERSECTS GeomFromText('CURVESTRING(...)')");
     303      subsel->SetFilter(ssfilter);
     304
     305      FdoPtr<FdoComputedClassIdentifier> fcSubSel = FdoComputedClassIdentifier::Create(L"SUBSEL1", subsel);
     306      fclasscoll->Add(fcSubSel);
     307
     308      FdoPtr<FdoFilter> filter = FdoFilter::Parse(L"FeatId IN (:SUBSEL1)");
     309      sel->SetFilter(filter);
     310
     311      FdoPtr<FdoIFeatureReader> rdr = sel->Execute();
     312}}}
     313
     314In order to be able to support sub-selects for '''IN''' operator we need provide a way to pass it through FDO Filter and make the parser to recognize the filter, since FDO Filter parser don’t know SQL syntax. The best way to achieve that is to pass a parameter in the filter, e.g.: '''FeatId IN(:PARAM)'''. This parameter will be replaced by the provider at execution time by the sub-select. This way we improve IN() operator in FDO to handle sub-selects.
     315
     316
     317As new additions to the FDO API we will have three new classes (FdoJoinCriteria, FdoJoinCriteriaCollection and FdoComputedClassIdentifier) and two enumerations (FdoItemExpressionType and FdoJoinType). We also need to add a new two new command capabilities, two new methods SupportsJoins and GetJoinTypes in order to be able to detect if those methods are supported by the provider (otherwise caller might get exceptions from the default implementation).
     318
     319We are planning at execute time to return a feature reader even there might be cases when the class returned by the reader might not have a primary key or a geometry column. In case select do not provide a select property list, all returned columns will be added to the feature class reader built at runtime, however in case user will request certain properties only those properties will be added to the feature class and even returned feature class might have no primary key or no geometry, depending of caller request. A feature reader is way more useful in applications. In the worst case we can add all primary keys from the selected classes to the result primary key of the class. Also in case there is no way to return a primary key user can add one of the property to the primary key collection of the returned class to have a primary key. The returned class will be read-only since there is no way to know what we can update.
     320
     321FdoISelectAggregates interface will be updated in the same manner in order to allow doing aggregates selects having sub-selects in the filter side.
     322
     323Update and delete commands will not be changed to allow such complex filters, however we can update those commands doing a similar RFC in the future if is needed.
     324
     325== Small improvements in FDO ==
     326
     327 * '''Handling FDO expressions to avoid dynamic casts'''
     328
     329In the current API in order to make a difference between FdoIdentifier and FdoComputedIdentifier we need to use dynamic cast and that’s not too fast. There is a simple way to avoid many dynamic casts by adding a new abstract method in FdoExpression named GetExpressionType(). All main derivate classes from FdoExpression need to return a type without adding a new parameter this way we avoid increasing the memory consumption by FDO expression objects. A new enumeration will be added, see below, and classes like FdoIdentifier will have to implement this method.
     330
     331{{{
     332      /// The FdoExpressionType enumeration defines the expression types available.
     333      enum FdoItemExpressionType
     334      {
     335         /// Identifier e.g. PROP
     336         FdoItemExpressionType_Identifier,
     337         /// Identifier e.g. PROP*5 AS EXP
     338         FdoItemExpressionType_ComputedIdentifier,
     339         /// Identifier e.g. SELECT * FROM C1 AS CLS
     340         FdoItemExpressionType_ComputedClassIdentifier,
     341         /// Parameter e.g. :PROP
     342         FdoItemExpressionType_Parameter,
     343         /// Function e.g. AVG()
     344         FdoItemExpressionType_Function,
     345         /// Expression e.g. A+B
     346         FdoItemExpressionType_BinaryExpression,
     347         /// Expression e.g. -A
     348         FdoItemExpressionType_UnaryExpression,
     349         /// Data value e.g. 12, 12.4 'text'
     350         FdoItemExpressionType_DataValue,
     351         /// Geometry e.g. 12, 12.4 'text'
     352         FdoItemExpressionType_GeometryValue
     353      };
     354
     355      class FdoIdentifier : public FdoExpression
     356      {
     357          /// Returns FdoItemExpressionType
     358          FDO_API virtual FdoItemExpressionType GetExpressionType()
     359          {
     360              return FdoItemExpressionType_Identifier;
     361          }
     362      }
     363}}}
     364
     365 * '''Update the FDO Filter to handle custom names'''
     366
     367 FDO filter parser needs to be updated since we support only Class.Property e.g.: "A.BB >=10". We need to support also formats like:
     368        ||Class."Property"||
     369        ||"Class".Property||
     370        ||"Class"."Property"||
     371
     372This change is required in order to be able to support properies names having special characters, like: myClass."My Property" > 10. This change will not invlove any API change and will be done like a bug fix.
     373
     374== Managed FDO API ==
     375
     376The FDO Managed Interfaces will be updated in a similar manner to reflect the proposed changes.
     377
     378== Provider Implementation ==
     379
     380Initially, we need this for SQLite provider. Other provider developers can add it and use it as needed. The providers which decide to use this new select need to implement the new methods from the FdoISelect and return the right command capabilities.
     381
     382== Test Plan ==
     383
     384Run existing FDO core unit tests and expand SQLite unit tests to test the proposed enhancements defined above.
     385
     386== Funding/Resources ==
     387
     388Autodesk to provide resources / funding.
     389