home *** CD-ROM | disk | FTP | other *** search
- /*
- ODBCSQLExpression.m
-
- Copyright (c) 1996 NeXT Software, Inc. All rights reserved.
-
- IMPORTANT: This NeXT software is supplied to you in consideration of your agreement
- to the terms of the NeXT Software License Agreement stated in the file ODBC_LICENSE.rtf
- provided with the software. Your use of the software is governed by such terms.
- Do not use, install, or make copies of the software if you do not agree to such terms.
-
- THIS SOFTWARE IS FURNISHED ON AN "AS-IS" BASIS. NeXT MAKES NO WARRANTIES OF ANY KIND,
- EITHER EXPRESS OR IMPLIED, AS TO ANY MATTER WHATSOEVER, INCLUDING WITHOUT LIMITATION
- THE CONDITION, MERCHANTABILITY, OR FITNESS FOR ANY PARTICULAR PURPOSE OF THIS SOFTWARE.
- NeXT DOES NOT ASSUME ANY LIABILITY REGARDING USE OF, OR ANY DEFECT IN, THIS SOFTWARE.
- IN NO EVENT SHALL NeXT BE LIABLE FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
- DAMAGES, EVEN IF IT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
- #import "ODBCPrivate.h"
-
- /* ODBC SQL notes:
-
- ***
- The create statement includes the referential integrity constraints, there
- is no portable ways to add such constraints later
-
- 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))
-
- I have no clue on how to specify cross table foreign key ! You need to create a table before the other...
-
- ***
- the locking mechanism is weird, it needs the complete list of columns:
-
- SELECT [DISTINCT] xx.foo, yy.bar FROM xx, yy WHERE xx.foo = yy.foo
- FOR UPDATE OF xx.foo, yy.bar;
-
- ***
-
- Outer joins are also funny:
-
- SELECT EMPLOYEE.NAME, DEPT.DEPTNAME
- FROM --(*vendor(Microsoft),product(ODBC) oj
- EMPLOYEE LEFT OUTER JOIN DEPT ON EMPLOYEE.DEPTID=DEPT.DEPTID*)--
- WHERE EMPLOYEE.PROJID=544
-
- 'LEFT' could be replaced by 'RIGHT' or 'FULL'.
-
- ***
- - There is no support for primary keys.
-
- ***
- It's easier to implement bindings than to deal with stuff like this:
-
- UPDATE EMPLOYEE
- SET BIRTHDAY=--(*vendor(Microsoft),product(ODBC) d '1967-01-15' *)--
- WHERE NAME='Smith, John'
-
-
- 'd' (date format "yyyy-mm-dd") could be replaced by 't' (time, format "hh:mm:ss")
- or 'ts' (timestamp, format Óyyyy-mm-dd hh:mm:ss[.f...]Ô). This is a simple and
- efficient way to solve the formatting issues !
-
- ***
- BTW, one could replace --(*vendor... by '{' and *)-- by "}"...
-
- */
-
- //
- // The UPDATE, DELETE and INSERT statements are implemented on the superclass
- // and there is no need to alter those. The only change is to format the
- // attributes nicely.
- //
- // The SELECT statement is another story !
- //
- @implementation ODBCSQLExpression
-
- + (BOOL)useBindVariables { return YES; }
- - (BOOL)shouldUseBindVariableForAttribute:(EOAttribute *)att { return YES; }
- - (BOOL)mustUseBindVariableForAttribute:(EOAttribute *)att { return YES; }
-
- - (NSString *)lockClause
- {
- if(_sybaseLocking)
- return @"HOLDLOCK";
- else
- return @"FOR UPDATE"; // Add OF + columns names...
- }
-
- - (NSString *)tableListWithRootEntity:(EOEntity *)entity
- {
- //
- // This method build the table list in a select statement
- //
- // We are overiding this method because ODBC is expressing outer joins with
- // a unique syntax. If there is outer joins in the expression we
- // are currently evaluating, we need to generate some weird SQL.
- //
- NSString *outerJoinName = nil;
- NSMutableString *string = [NSMutableString string];
- NSMutableDictionary *backup = _aliasesByRelationshipPath;
-
- //
- // First we check if there are some outer joins.
- //
- // We need at least two tables to check the joins !
- //
- if ([_aliasesByRelationshipPath count] > 1) {
- NSEnumerator *enumerator = [_aliasesByRelationshipPath keyEnumerator];
- NSString *destPath;
- EORelationship *relationship;
-
- //
- // Look at all the paths
- //
- while (destPath = [enumerator nextObject]) {
- if ([destPath isEqualToString:@""])
- continue; // No join
-
- relationship = [entity relationshipForPath:destPath];
-
- if([relationship joinSemantic] != EOInnerJoin) {
- if(outerJoinName) {
- [NSException raise:EOGeneralAdaptorException format:@"Multiple outer joins in a select statement is not supported (entity: %@)", [entity name]];
- }
- outerJoinName = destPath;
- }
- }
- }
-
- if(outerJoinName) {
- EOEntity *sourceEntity;
- EOEntity *destinationEntity;
- NSString *sourceAlias;
- NSString *destinationAlias;
- EORelationship *rel;
- NSString *sourcePath;
- NSArray *joins;
- unsigned i,c;
- BOOL first = YES;
- NSString *outer;
-
- if(!_inSelect) {
- [NSException raise:EOGeneralAdaptorException format:@"Impossible to use an outer joins outside a select statement"];
- }
-
- //
- // Lookup for all the necessary info
- //
-
- // First, the followed relationship
- rel = [entity relationshipForPath:outerJoinName];
-
- // Find the source information: entity and alias
- sourcePath = [outerJoinName relationshipPathByDeletingLastComponent];
- if (!sourcePath)
- sourcePath = @"";
-
- sourceEntity = [rel entity];
- sourceAlias = [_aliasesByRelationshipPath objectForKey:sourcePath];
-
- // Find the destination information
- destinationEntity = [rel destinationEntity];
- destinationAlias = [_aliasesByRelationshipPath objectForKey:outerJoinName];
-
- // Check if it's a LEFT, FULL or RIGHT Outer join
- switch([rel joinSemantic]) {
- case EOFullOuterJoin: outer = @"FULL"; break;
- case EOLeftOuterJoin: outer = @"LEFT"; break;
- case EORightOuterJoin: outer = @"RIGHT"; break;
- default: outer = nil; break; // should not happen !
- }
-
- //
- // Build the beginning of the string
- //
- [string appendFormat:@" { oj %@ %@ %@ OUTER JOIN %@ %@ ON ", [sourceEntity externalName], sourceAlias, outer, [destinationEntity externalName], destinationAlias];
-
- //
- // Add the joins
- //
- joins = [rel joins];
- for (i = 0, c = [joins count]; i < c; i++) {
- id join = [joins objectAtIndex:i];
- id sourceAttr = [join sourceAttribute];
- id destAttr = [join destinationAttribute];
-
- // Build the attributes names with aliases in front
- id sourceName = [self _aliasForRelatedAttribute:sourceAttr relationshipPath:sourcePath];
- id destName = [self _aliasForRelatedAttribute:destAttr relationshipPath:outerJoinName];
-
- if(first) {
- first = NO;
- } else {
- [string appendFormat:@" AND"];
- }
- [string appendFormat:@" %@", [self assembleJoinClauseWithLeftName:sourceName rightName:destName joinSemantic:EOInnerJoin]];
- }
- //
- // Close the string
- //
- [string appendFormat:@" } "];
-
- //
- // We are going to let the superclass perform the work for all
- // the non outer joins. But before we need to remove the source and
- // destination used by the outer join from the list
- //
- // remove sourcePath and destPath from the list
- //
- _aliasesByRelationshipPath = [NSMutableDictionary dictionaryWithDictionary:_aliasesByRelationshipPath];
- [_aliasesByRelationshipPath removeObjectForKey:sourcePath];
- [_aliasesByRelationshipPath removeObjectForKey:outerJoinName];
-
- }
-
- {
- NSEnumerator *enumerator;
- NSString *key, *tableName;
-
- if (![self useAliases]) {
- [string appendFormat:@"%@ %@", [entity externalName], _sybaseLocking ? [self lockClause] : @""];
- } else {
- enumerator = [_aliasesByRelationshipPath keyEnumerator];
- while (key = [enumerator nextObject]) {
- if ([key isEqual:@""])
- tableName = [entity externalName];
- else
- tableName = [[[entity relationshipForPath:key] destinationEntity] externalName];
-
- if ([string length]) [string appendString:@", "];
- [string appendFormat:@"%@ %@ %@", tableName, [_aliasesByRelationshipPath objectForKey:key], _sybaseLocking ? [self lockClause] : @""];
- }
- }
- }
-
- //
- // Restore the list
- //
- _aliasesByRelationshipPath = backup;
-
- return string;
- }
-
- - (void)addJoinClauseWithLeftName:(NSString *)leftName rightName:(NSString *)rightName joinSemantic:(EOJoinSemantic)semantic
- {
- if(semantic == EOInnerJoin) {
- return [super addJoinClauseWithLeftName:leftName rightName:rightName joinSemantic:semantic];
- }
- //
- // The outer join generation is done in the table list generation.
- //
- }
- - (NSMutableDictionary *)bindVariableDictionaryForAttribute:(EOAttribute *)att value:value
- {
- return [NSMutableDictionary dictionaryWithObjectsAndKeys:[att name], EOBindVariableNameKey, @"?", EOBindVariablePlaceHolderKey, att, EOBindVariableAttributeKey, value, EOBindVariableValueKey, nil];
- }
-
- + (NSString *)formatValue:(id)value forAttribute:(EOAttribute *)attribute
- {
- return @"NULL"; // This SQL expression is using bindings everywhere.
- // There is no support for unbound arguments...
- }
-
- - (NSString *)description
- {
- if ([_bindings count]) {
- NSMutableString *string = [NSMutableString string];
- unsigned i,c;
-
- [string appendFormat:@"("];
- for(i = 0, c = [_bindings count]; i < c; i++) {
- id curr = [_bindings objectAtIndex:i];
-
- if(i != 0) [string appendFormat:@", "];
- [string appendFormat:@"%d:%@(%@)", i+1, [curr valueForKey:EOBindVariableValueKey], [curr valueForKey:EOBindVariableNameKey]];
- }
- [string appendFormat:@"("];
-
- return [NSString stringWithFormat:@"<%@: \"%@\" withBindings:%@>", [self class], [self statement], string];
- } else {
- return [NSString stringWithFormat:@"<%@: \"%@\">", [self class], [self statement]];
- }
- }
-
-
- - (void)prepareSelectExpressionWithAttributes:(NSArray *)attributes lock:(BOOL)lock fetchSpecification:(EOFetchSpecification *)fetchSpec
- {
- if(lock) {
- NSDictionary *driverInfo = [ODBCAdaptor driverInfoForModel:[[[attributes objectAtIndex:0] parent] model]];
- NSString *databaseName = [driverInfo objectForKey:@"DBMS_NAME"];
- NSString *driverName = [driverInfo objectForKey:@"DRIVER_NAME"];
-
- if([databaseName isEqual:@"Microsoft SQL Server"]) {
- _sybaseLocking = 1;
- } else if([driverName isEqual:@"odbcjt32.dll"]) {
- lock = NO;
- }
- }
-
- _inSelect = YES;
- [super prepareSelectExpressionWithAttributes:attributes lock:lock fetchSpecification:fetchSpec];
- _inSelect = NO;
-
- _sybaseLocking = 0;
- }
-
-
- - (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
- {
- NSMutableString *string;
-
- string = [NSMutableString stringWithFormat:@"%@ %@ %@ %@", selectString, columnList, @"FROM", tableList];
-
- if (lock) {
- if(!_sybaseLocking)
- [string appendFormat:@" %@", lockClause];
- }
-
- if (whereClause && [whereClause length])
- [string appendFormat:@" %@ %@", @"WHERE", whereClause];
-
- if (joinClause && [joinClause length]) {
- if (whereClause && [whereClause length])
- [string appendFormat:@" %@ %@", @"AND", joinClause];
- else
- [string appendFormat:@" %@ %@", @"WHERE", joinClause];
- }
-
- if (orderByClause && [orderByClause length])
- [string appendFormat:@" %@ %@", @"ORDER BY", orderByClause];
-
- return string;
- }
-
-
- @end
-
- @implementation ODBCSQLExpression(EOSchemaGeneration)
-
- + (NSArray *)primaryKeyConstraintStatementsForEntityGroup:(NSArray *)entityGroup
- {
- return [NSArray array]; // No primary key constraints for now
- }
-
- + (NSArray *)primaryKeySupportStatementsForEntityGroup:(NSArray *)entityGroup
- {
- return [NSArray array];
- }
-
- + (NSArray *)dropPrimaryKeySupportStatementsForEntityGroup:(NSArray *)entityGroup
- {
- return [NSArray array];
- }
-
- - (NSString *)columnTypeStringForAttribute:(EOAttribute *)attribute
- {
- NSString *externalType = [attribute externalType];
- NSDictionary *typeInfo = [[ODBCAdaptor typeInfoForModel:[[attribute parent] model]] objectForKey:externalType];
- int createParams = [[typeInfo objectForKey:@"createParams"] intValue];
-
- // Should provide the external type with the correct number of arguments
- //
- // 3 possible cases:
- switch(createParams) {
- case 2:
- // scale + precision
- return [NSString stringWithFormat:@"%@(%d,%d)", [attribute externalType], [attribute precision], [attribute scale]];
-
- case 1:
- // Width
- return [NSString stringWithFormat:@"%@(%d)", [attribute externalType], [attribute width]];
-
- default:
- // Nothing
- return [attribute externalType];
- }
- // not reachable
- }
-
- - (NSString *)allowsNullClauseForConstraint:(BOOL)allowsNull
- {
- return @"";
- // return (allowsNull) ? @"" : @"NOT NULL";
- }
- @end
-