home *** CD-ROM | disk | FTP | other *** search
/ OpenStep (Enterprise) / OpenStepENTCD.toast / OEDEV / EODEV.Z / ODBCSQLExpression.m < prev    next >
Encoding:
Text File  |  1996-09-10  |  13.4 KB  |  386 lines

  1. /*
  2.    ODBCSQLExpression.m
  3.  
  4.    Copyright (c) 1996 NeXT Software, Inc.  All rights reserved.
  5.  
  6.    IMPORTANT:  This NeXT software is supplied to you in consideration of your agreement
  7.    to the terms of the NeXT Software License Agreement stated in the file ODBC_LICENSE.rtf
  8.    provided with the software.  Your use of the software is governed by such terms.
  9.    Do not use, install, or make copies of the software if you do not agree to such terms.
  10.  
  11.    THIS SOFTWARE IS FURNISHED ON AN "AS-IS" BASIS. NeXT MAKES NO WARRANTIES OF ANY KIND,
  12.    EITHER EXPRESS OR IMPLIED, AS TO ANY MATTER WHATSOEVER, INCLUDING WITHOUT LIMITATION
  13.    THE CONDITION, MERCHANTABILITY, OR FITNESS FOR ANY PARTICULAR PURPOSE OF THIS SOFTWARE.
  14.    NeXT DOES NOT ASSUME ANY LIABILITY REGARDING USE OF, OR ANY DEFECT IN, THIS SOFTWARE.
  15.    IN NO EVENT SHALL NeXT BE LIABLE FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
  16.    DAMAGES, EVEN IF IT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  17. */
  18.  
  19. #import "ODBCPrivate.h"
  20.  
  21. /* ODBC SQL notes:
  22.  
  23.    ***
  24.    The create statement includes the referential integrity constraints, there
  25.    is no portable ways to add such constraints later
  26.  
  27.    create table  EMPLOYEE ( EMP_ID int NOT NULL, DEPT_ID int, LAST_NAME VARCHAR(40) NOT NULL, PHONE VARCHAR(12), HIRE_DATE timestamp  null, SALARY numeric(7,2), PRIMARY KEY (EMP_ID), FOREIGN KEY (DEPT_ID) REFERENCES DEPARTMENT (DEPT_ID))
  28.  
  29.    I have no clue on how to specify cross table foreign key ! You need to create a table before the other...
  30.  
  31.    ***
  32.    the locking mechanism is weird, it needs the complete list of columns:
  33.  
  34.    SELECT [DISTINCT] xx.foo, yy.bar FROM xx, yy WHERE xx.foo = yy.foo
  35.    FOR UPDATE OF xx.foo, yy.bar;
  36.  
  37.    ***
  38.  
  39.    Outer joins are also funny:
  40.  
  41.      SELECT EMPLOYEE.NAME, DEPT.DEPTNAME
  42.      FROM --(*vendor(Microsoft),product(ODBC) oj
  43.           EMPLOYEE LEFT OUTER JOIN DEPT ON EMPLOYEE.DEPTID=DEPT.DEPTID*)--
  44.      WHERE EMPLOYEE.PROJID=544
  45.  
  46.    'LEFT' could be replaced by 'RIGHT' or 'FULL'.
  47.  
  48.    ***
  49.    - There is no support for primary keys. 
  50.  
  51.    ***
  52.    It's easier to implement bindings than to deal with stuff like this:
  53.  
  54.    UPDATE EMPLOYEE
  55.      SET BIRTHDAY=--(*vendor(Microsoft),product(ODBC) d '1967-01-15' *)--
  56.      WHERE NAME='Smith, John'
  57.  
  58.  
  59.    'd' (date format "yyyy-mm-dd") could be replaced by 't' (time, format "hh:mm:ss")
  60.    or 'ts' (timestamp, format Óyyyy-mm-dd hh:mm:ss[.f...]Ô). This is a simple and
  61.    efficient way to solve the formatting issues !
  62.  
  63.    ***
  64.    BTW, one could replace --(*vendor... by '{' and *)-- by "}"...
  65.  
  66. */
  67.  
  68. //
  69. // The UPDATE, DELETE and INSERT statements are implemented on the superclass
  70. // and there is no need to alter those. The only change is to format the
  71. // attributes nicely.
  72. //
  73. // The SELECT statement is another story !
  74. //
  75. @implementation ODBCSQLExpression
  76.  
  77. + (BOOL)useBindVariables { return YES; }
  78. - (BOOL)shouldUseBindVariableForAttribute:(EOAttribute *)att { return YES; }
  79. - (BOOL)mustUseBindVariableForAttribute:(EOAttribute *)att { return YES; }
  80.  
  81. - (NSString *)lockClause
  82. {
  83.     if(_sybaseLocking)
  84.         return @"HOLDLOCK";
  85.     else
  86.         return @"FOR UPDATE"; // Add OF + columns names...
  87. }
  88.  
  89. - (NSString *)tableListWithRootEntity:(EOEntity *)entity
  90. {
  91.     //
  92.     // This method build the table list in a select statement
  93.     //
  94.     // We are overiding this method because ODBC is expressing outer joins with
  95.     // a unique syntax. If there is outer joins in the expression we
  96.     // are currently evaluating, we need to generate some weird SQL.
  97.     //
  98.     NSString *outerJoinName = nil;
  99.     NSMutableString *string = [NSMutableString string];
  100.     NSMutableDictionary *backup = _aliasesByRelationshipPath;
  101.     
  102.     //
  103.     // First we check if there are some outer joins.
  104.     //
  105.     // We need at least two tables to check the joins !
  106.     //
  107.     if ([_aliasesByRelationshipPath count] > 1) {
  108.         NSEnumerator *enumerator = [_aliasesByRelationshipPath keyEnumerator];
  109.         NSString *destPath;
  110.         EORelationship *relationship;
  111.  
  112.         //
  113.         // Look at all the paths
  114.         //
  115.         while (destPath = [enumerator nextObject]) {
  116.             if ([destPath isEqualToString:@""])
  117.                 continue; // No join
  118.  
  119.             relationship = [entity relationshipForPath:destPath];
  120.  
  121.             if([relationship joinSemantic] != EOInnerJoin) {
  122.                 if(outerJoinName) {
  123.                     [NSException raise:EOGeneralAdaptorException format:@"Multiple outer joins in a select statement is not supported (entity: %@)", [entity name]];
  124.                 }
  125.                 outerJoinName = destPath;
  126.             }
  127.         }
  128.     }
  129.  
  130.     if(outerJoinName) {
  131.         EOEntity *sourceEntity;
  132.         EOEntity *destinationEntity;
  133.         NSString *sourceAlias;
  134.         NSString *destinationAlias;
  135.         EORelationship *rel;
  136.         NSString *sourcePath;
  137.         NSArray *joins;
  138.         unsigned i,c;
  139.         BOOL first = YES;
  140.         NSString *outer;
  141.         
  142.         if(!_inSelect) {
  143.             [NSException raise:EOGeneralAdaptorException format:@"Impossible to use an outer joins outside a select statement"];
  144.         }
  145.  
  146.         //
  147.         // Lookup for all the necessary info
  148.         //
  149.  
  150.         // First, the followed relationship
  151.         rel = [entity relationshipForPath:outerJoinName];
  152.  
  153.         // Find the source information: entity and alias
  154.         sourcePath = [outerJoinName relationshipPathByDeletingLastComponent];
  155.         if (!sourcePath)
  156.             sourcePath = @"";
  157.  
  158.         sourceEntity = [rel entity];
  159.         sourceAlias = [_aliasesByRelationshipPath objectForKey:sourcePath];
  160.  
  161.         // Find the destination information
  162.         destinationEntity = [rel destinationEntity];
  163.         destinationAlias = [_aliasesByRelationshipPath objectForKey:outerJoinName];
  164.  
  165.         // Check if it's a LEFT, FULL or RIGHT Outer join
  166.         switch([rel joinSemantic]) {
  167.             case EOFullOuterJoin: outer = @"FULL"; break;
  168.             case EOLeftOuterJoin: outer = @"LEFT"; break;
  169.             case EORightOuterJoin: outer = @"RIGHT"; break;
  170.             default: outer = nil; break; // should not happen !
  171.         }
  172.  
  173.         //
  174.         // Build the beginning of the string
  175.         //
  176.         [string appendFormat:@" { oj %@ %@ %@ OUTER JOIN %@ %@ ON ", [sourceEntity externalName], sourceAlias, outer, [destinationEntity externalName], destinationAlias];
  177.  
  178.         //
  179.         // Add the joins
  180.         //
  181.         joins = [rel joins];
  182.         for (i = 0, c = [joins count]; i < c; i++) {
  183.             id join = [joins objectAtIndex:i];
  184.             id sourceAttr = [join sourceAttribute];
  185.             id destAttr = [join destinationAttribute];
  186.  
  187.             // Build the attributes names with aliases in front
  188.             id sourceName = [self _aliasForRelatedAttribute:sourceAttr relationshipPath:sourcePath];
  189.             id destName = [self _aliasForRelatedAttribute:destAttr relationshipPath:outerJoinName];
  190.  
  191.             if(first) {
  192.                 first = NO;
  193.             } else {
  194.                 [string appendFormat:@" AND"];
  195.             }
  196.             [string appendFormat:@" %@", [self assembleJoinClauseWithLeftName:sourceName rightName:destName joinSemantic:EOInnerJoin]];
  197.         }
  198.         //
  199.         // Close the string
  200.         //
  201.         [string appendFormat:@" } "];
  202.  
  203.         //
  204.         // We are going to let the superclass perform the work for all
  205.         // the non outer joins. But before we need to remove the source and
  206.         // destination used by the outer join from the list
  207.         //
  208.         // remove sourcePath and destPath from the list
  209.         // 
  210.         _aliasesByRelationshipPath = [NSMutableDictionary dictionaryWithDictionary:_aliasesByRelationshipPath];
  211.         [_aliasesByRelationshipPath removeObjectForKey:sourcePath];
  212.         [_aliasesByRelationshipPath removeObjectForKey:outerJoinName];
  213.         
  214.     }
  215.  
  216.     {
  217.         NSEnumerator *enumerator;
  218.         NSString *key, *tableName;
  219.  
  220.         if (![self useAliases]) {
  221.             [string appendFormat:@"%@ %@", [entity externalName], _sybaseLocking ? [self lockClause] : @""];
  222.         } else {
  223.             enumerator = [_aliasesByRelationshipPath keyEnumerator];
  224.             while (key = [enumerator nextObject]) {
  225.                 if ([key isEqual:@""])
  226.                     tableName = [entity externalName];
  227.                 else
  228.                     tableName = [[[entity relationshipForPath:key] destinationEntity] externalName];
  229.  
  230.                 if ([string length]) [string appendString:@", "];
  231.                 [string appendFormat:@"%@ %@ %@", tableName, [_aliasesByRelationshipPath objectForKey:key], _sybaseLocking ? [self lockClause] : @""];
  232.             }
  233.         }
  234.     }
  235.  
  236.     //
  237.     // Restore the list
  238.     //
  239.     _aliasesByRelationshipPath = backup;
  240.  
  241.     return string;
  242. }
  243.  
  244. - (void)addJoinClauseWithLeftName:(NSString *)leftName rightName:(NSString *)rightName joinSemantic:(EOJoinSemantic)semantic
  245. {
  246.     if(semantic == EOInnerJoin) {
  247.         return [super addJoinClauseWithLeftName:leftName rightName:rightName joinSemantic:semantic];
  248.     }
  249.     //
  250.     // The outer join generation is done in the table list generation.
  251.     //
  252. }
  253. - (NSMutableDictionary *)bindVariableDictionaryForAttribute:(EOAttribute *)att value:value
  254. {
  255.     return [NSMutableDictionary dictionaryWithObjectsAndKeys:[att name], EOBindVariableNameKey, @"?", EOBindVariablePlaceHolderKey, att, EOBindVariableAttributeKey, value, EOBindVariableValueKey, nil];
  256. }
  257.  
  258. + (NSString *)formatValue:(id)value forAttribute:(EOAttribute *)attribute
  259. {
  260.     return @"NULL"; // This SQL expression is using bindings everywhere.
  261.                     // There is no support for unbound arguments...
  262. }
  263.  
  264. - (NSString *)description
  265. {
  266.     if ([_bindings count]) {
  267.         NSMutableString *string = [NSMutableString string];
  268.         unsigned i,c;
  269.  
  270.         [string appendFormat:@"("];
  271.         for(i = 0, c = [_bindings count]; i < c; i++) {
  272.             id curr = [_bindings objectAtIndex:i];
  273.  
  274.             if(i != 0) [string appendFormat:@", "];
  275.             [string appendFormat:@"%d:%@(%@)", i+1, [curr valueForKey:EOBindVariableValueKey], [curr valueForKey:EOBindVariableNameKey]];
  276.         }
  277.         [string appendFormat:@"("];
  278.  
  279.         return [NSString stringWithFormat:@"<%@: \"%@\" withBindings:%@>", [self class], [self statement], string];
  280.     } else {
  281.         return [NSString stringWithFormat:@"<%@: \"%@\">", [self class], [self statement]];
  282.     }
  283. }
  284.  
  285.  
  286. - (void)prepareSelectExpressionWithAttributes:(NSArray *)attributes lock:(BOOL)lock fetchSpecification:(EOFetchSpecification *)fetchSpec
  287. {
  288.     if(lock) {
  289.         NSDictionary *driverInfo = [ODBCAdaptor driverInfoForModel:[[[attributes objectAtIndex:0] parent] model]];
  290.         NSString *databaseName = [driverInfo objectForKey:@"DBMS_NAME"];
  291.         NSString *driverName = [driverInfo objectForKey:@"DRIVER_NAME"];
  292.  
  293.         if([databaseName isEqual:@"Microsoft SQL Server"]) {
  294.             _sybaseLocking = 1;
  295.         } else if([driverName isEqual:@"odbcjt32.dll"]) {
  296.             lock = NO;
  297.         }
  298.     }
  299.  
  300.     _inSelect = YES;
  301.     [super prepareSelectExpressionWithAttributes:attributes lock:lock fetchSpecification:fetchSpec];
  302.     _inSelect = NO;
  303.  
  304.     _sybaseLocking = 0;
  305. }
  306.  
  307.  
  308. - (NSString *)assembleSelectStatementWithAttributes:(NSArray *)attributes lock:(BOOL)lock qualifier:(EOQualifier *)qualifier fetchOrder:(NSArray *)fetchOrder selectString:(NSString *)selectString columnList:(NSString *)columnList tableList:(NSString *)tableList whereClause:(NSString *)whereClause joinClause:(NSString *)joinClause orderByClause:(NSString *)orderByClause lockClause:(NSString *)lockClause
  309. {
  310.     NSMutableString *string;
  311.  
  312.     string = [NSMutableString stringWithFormat:@"%@ %@ %@ %@", selectString, columnList, @"FROM", tableList];
  313.  
  314.     if (lock) {
  315.         if(!_sybaseLocking)
  316.             [string appendFormat:@" %@", lockClause];
  317.     }
  318.  
  319.     if (whereClause && [whereClause length])
  320.         [string appendFormat:@" %@ %@", @"WHERE", whereClause];
  321.  
  322.     if (joinClause && [joinClause length]) {
  323.         if (whereClause && [whereClause length])
  324.             [string appendFormat:@" %@ %@", @"AND", joinClause];
  325.         else
  326.             [string appendFormat:@" %@ %@", @"WHERE", joinClause];
  327.     }
  328.  
  329.     if (orderByClause && [orderByClause length])
  330.         [string appendFormat:@" %@ %@", @"ORDER BY", orderByClause];
  331.  
  332.     return string;
  333. }
  334.  
  335.  
  336. @end
  337.  
  338. @implementation ODBCSQLExpression(EOSchemaGeneration)
  339.  
  340. + (NSArray *)primaryKeyConstraintStatementsForEntityGroup:(NSArray *)entityGroup
  341. {
  342.     return [NSArray array]; // No primary key constraints for now
  343. }
  344.  
  345. + (NSArray *)primaryKeySupportStatementsForEntityGroup:(NSArray *)entityGroup
  346. {
  347.     return [NSArray array];
  348. }
  349.  
  350. + (NSArray *)dropPrimaryKeySupportStatementsForEntityGroup:(NSArray *)entityGroup
  351. {
  352.     return [NSArray array];
  353. }
  354.  
  355. - (NSString *)columnTypeStringForAttribute:(EOAttribute *)attribute
  356. {
  357.     NSString *externalType = [attribute externalType];
  358.     NSDictionary *typeInfo = [[ODBCAdaptor typeInfoForModel:[[attribute parent] model]] objectForKey:externalType];
  359.     int createParams = [[typeInfo objectForKey:@"createParams"] intValue];
  360.     
  361.     // Should provide the external type with the correct number of arguments
  362.     //
  363.     // 3 possible cases:
  364.     switch(createParams) {
  365.         case 2:
  366.             // scale + precision
  367.             return [NSString stringWithFormat:@"%@(%d,%d)", [attribute externalType], [attribute precision], [attribute scale]];
  368.  
  369.         case 1:
  370.             // Width
  371.             return [NSString stringWithFormat:@"%@(%d)", [attribute externalType], [attribute width]];
  372.  
  373.         default:
  374.             // Nothing
  375.             return [attribute externalType];
  376.     }
  377.     // not reachable
  378. }
  379.  
  380. - (NSString *)allowsNullClauseForConstraint:(BOOL)allowsNull
  381. {
  382.     return @"";
  383.     // return (allowsNull) ? @"" : @"NOT NULL";
  384. }
  385. @end
  386.