]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Initial implementation of LEFT OUTER JOIN including the expanded SQL92 join
authordrh <drh@noemail.net>
Fri, 24 May 2002 20:31:36 +0000 (20:31 +0000)
committerdrh <drh@noemail.net>
Fri, 24 May 2002 20:31:36 +0000 (20:31 +0000)
syntax. The basic functionality is there but there is still a lot of testing
to do. (CVS 587)

FossilOrigin-Name: 99bd1f5b9a1a20bfeefe15c00d96a34a5f40923e

manifest
manifest.uuid
src/build.c
src/select.c
src/sqliteInt.h
src/trigger.c
src/vdbe.c
src/where.c
test/join.test [new file with mode: 0644]
test/select1.test

index 621f884f6e06e7f1ba8bb24a67e5bf4684cf2c42..3845b14f97fee327da0ad6b80cde715a482f78c6 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\ssupport\sfor\sthe\sfull\sSQL\sjoin\ssyntax.\s\sThis\sis\sjust\sa\sparser\senhancement.\nWe\snow\srecognize\sall\skinds\sof\sjoins,\sbut\swe\sdon't\sactually\sdo\sanything\swith\nthem\syet.\s(CVS\s586)
-D 2002-05-24T16:14:15
+C Initial\simplementation\sof\sLEFT\sOUTER\sJOIN\sincluding\sthe\sexpanded\sSQL92\sjoin\nsyntax.\sThe\sbasic\sfunctionality\sis\sthere\sbut\sthere\sis\sstill\sa\slot\sof\stesting\nto\sdo.\s(CVS\s587)
+D 2002-05-24T20:31:37
 F Makefile.in 6291a33b87d2a395aafd7646ee1ed562c6f2c28c
 F Makefile.template 4e11752e0b5c7a043ca50af4296ec562857ba495
 F README a4c0ba11354ef6ba0776b400d057c59da47a4cc0
@@ -20,7 +20,7 @@ F sqlite.1 83f4a9d37bdf2b7ef079a82d54eaf2e3509ee6ea
 F src/TODO af7f3cab0228e34149cf98e073aa83d45878e7e6
 F src/btree.c c01b404b9373ae1c0daf7d1f9211c72ead67638e
 F src/btree.h 8abeabfe6e0b1a990b64fa457592a6482f6674f3
-F src/build.c 48bb306676b77711919adc3578655e3ebf2ac8f0
+F src/build.c 36e42718a7a94f554ea39508993378482f5335c7
 F src/delete.c a2b098cbbf518e6b641847e26de85827793bc523
 F src/encode.c 346b12b46148506c32038524b95c4631ab46d760
 F src/expr.c 818a702ba93e444813b8935a7ab509f6e3352b49
@@ -37,11 +37,11 @@ F src/pager.h 6fddfddd3b73aa8abc081b973886320e3c614f0e
 F src/parse.y c681da701bf142967325b8791f22418e2d81552d
 F src/printf.c d8032ee18b860c812eeff596c9bebfdacb7930fd
 F src/random.c 19e8e00fe0df32a742f115773f57651be327cabe
-F src/select.c 6e9f995908a98c21486e25010408ab2fc2e84517
+F src/select.c 8f0ec9de36a22d167402af6ff8936e142ada4a11
 F src/shell.c 1d22fe870ee852cfb975fd000dbe3973713d0a15
 F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e
 F src/sqlite.h.in 0038faa6d642de06b91143ee65a131bd831d020b
-F src/sqliteInt.h 179d1562e272d3c038a8f46cabfbfd74be46a1dc
+F src/sqliteInt.h 5b71407c8546514168ae6984c18c5d035a2643ce
 F src/table.c eed2098c9b577aa17f8abe89313a9c4413f57d63
 F src/tclsqlite.c 9300c9606a38bc0c75d6c0bc8a6197ab979353d1
 F src/test1.c 09d95048b66ce6dcd2bae90f443589043d7d631e
@@ -49,12 +49,12 @@ F src/test2.c 669cc22781c6461a273416ec1a7414d25c081730
 F src/test3.c 4e52fff8b01f08bd202f7633feda5639b7ba2b5e
 F src/threadtest.c 81f0598e0f031c1bd506af337fdc1b7e8dff263f
 F src/tokenize.c facec7dc0b4a13e17ad67702f548dac2f7c6a732
-F src/trigger.c bf1a4f6653e482be0052bc9ade9261cf814c705b
+F src/trigger.c 75dd64808c56ff1b20ee6c6620f5d61487712d74
 F src/update.c f68375173bf5338cae3e97012708e10f206aedd9
 F src/util.c 707c30f8c13cddace7c08556ac450c0b786660b3
-F src/vdbe.c 46fd7cbefdb788195c978e5d2f480d86ea1416e5
+F src/vdbe.c bde1dad84ea4b0de4ac590d0d29522e45bfd1470
 F src/vdbe.h def669b9f2728589aabcb5db756429db02465c9a
-F src/where.c 1516eb1c06ca6d15cd5cf982ae974cf58e5431ed
+F src/where.c 9030d188139f4de73c4b238706afeae8bc4e2f26
 F test/all.test e4d3821eeba751829b419cd47814bd20af4286d1
 F test/bigrow.test 8ab252dba108f12ad64e337b0f2ff31a807ac578
 F test/btree.test bf326f546a666617367a7033fa2c07451bd4f8e1
@@ -71,6 +71,7 @@ F test/insert.test 58d44c19b3557f67f4aeb5110ed9ef02038c3684
 F test/insert2.test eb8481878a7f52ccb4e3346f87550f5afdd77f76
 F test/intpkey.test 31b5f28b2c44273e6695cf36ab2e4133aee7753c
 F test/ioerr.test 57d9bffaca18b34f9e976f786eadc2591d6efc6a
+F test/join.test 905f4b13f8505f6b5b25af82ef11180860e6b180
 F test/limit.test 6f98bcefc92209103bb3764c81975a6ec21d6702
 F test/lock.test 3fcfd46a73119f6a18094673328a32c7b3047a8f
 F test/main.test c66b564554b770ee7fdbf6a66c0cd90329bc2c85
@@ -85,7 +86,7 @@ F test/printf.test 3cb415073754cb8ff076f26173143c3cd293a9da
 F test/quick.test 6f023c7a73fc413e6d65b7a1879c79764038dc05
 F test/quote.test 08f23385c685d3dc7914ec760d492cacea7f6e3d
 F test/rowid.test 4c55943300cddf73dd0f88d40a268cab14c83274
-F test/select1.test c19617be69fb1322c71e100b5882c469729c4bf1
+F test/select1.test 6ba20b52d563b7fb917d8a61a7560d02f90a1a52
 F test/select2.test aceea74fd895b9d007512f72499db589735bd8e4
 F test/select3.test 9469c332250a75a0ef1771fb5da62dc04ec77f18
 F test/select4.test c2313f8c16ca298b0b1ce9cc3c0cfed0939ffea9
@@ -134,7 +135,7 @@ F www/speed.tcl da8afcc1d3ccc5696cfb388a68982bc3d9f7f00f
 F www/sqlite.tcl 8b5884354cb615049aed83039f8dfe1552a44279
 F www/tclsqlite.tcl 1db15abeb446aad0caf0b95b8b9579720e4ea331
 F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218
-P ffc49e56b13096b35e6cbb1a2f7d546843d4a91d
-R f47635ed868634887aa46a05f1852f4d
+P e238643efdbe1394c7ff85e34e486f7c6082b6cc
+R b59d515c126e5f1a822d5f6777b2906b
 U drh
-Z 43e7677c78bedc42f512941bca78b9ad
+Z 38ee23869a9185b6bcb4ab87628c02b5
index 347fc39f659a88ee9282508841419ac6174091ca..b7468024c651387bb7b5a8f0a204a203f4825060 100644 (file)
@@ -1 +1 @@
-e238643efdbe1394c7ff85e34e486f7c6082b6cc
\ No newline at end of file
+99bd1f5b9a1a20bfeefe15c00d96a34a5f40923e
\ No newline at end of file
index 76e22e1d6870af884190cc22e68b8a6187d29fe8..4b2da5b7bce0a67b540510acc4df5085e522bae4 100644 (file)
@@ -25,7 +25,7 @@
 **     ROLLBACK
 **     PRAGMA
 **
-** $Id: build.c,v 1.94 2002/05/24 02:04:33 drh Exp $
+** $Id: build.c,v 1.95 2002/05/24 20:31:37 drh Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -1594,6 +1594,19 @@ void sqliteIdListDelete(IdList *pList){
   sqliteFree(pList);
 }
 
+/*
+** Return the index in pList of the identifier named zId.  Return -1
+** if not found.
+*/
+int sqliteIdListIndex(IdList *pList, const char *zName){
+  int i;
+  if( pList==0 ) return -1;
+  for(i=0; i<pList->nId; i++){
+    if( sqliteStrICmp(pList->a[i].zName, zName)==0 ) return i;
+  }
+  return -1;
+}
+
 /*
 ** Delete an entire SrcList including all its substructure.
 */
index 9541ef114fa5787b54dfa717ede811bf80db717a..f6b719984f89f27233df494acff54a377ec7d476 100644 (file)
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle SELECT statements in SQLite.
 **
-** $Id: select.c,v 1.83 2002/05/24 16:14:15 drh Exp $
+** $Id: select.c,v 1.84 2002/05/24 20:31:37 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -106,7 +106,11 @@ int sqliteJoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){
       break;
     }
   }
-  if( (jointype & ~JT_INNER)!=0 ){
+  if(
+     (jointype & (JT_INNER|JT_OUTER))==(JT_INNER|JT_OUTER) ||
+     (jointype & JT_ERROR)!=0 ||
+     (jointype & JT_RIGHT)==JT_RIGHT
+  ){
     static Token dummy = { 0, 0 };
     char *zSp1 = " ", *zSp2 = " ";
     if( pB==0 ){ pB = &dummy; zSp1 = 0; }
@@ -119,6 +123,137 @@ int sqliteJoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){
   return jointype;
 }
 
+/*
+** Return the index of a column in a table.  Return -1 if the column
+** is not contained in the table.
+*/
+static int columnIndex(Table *pTab, const char *zCol){
+  int i;
+  for(i=0; i<pTab->nCol; i++){
+    if( sqliteStrICmp(pTab->aCol[i].zName, zCol)==0 ) return i;
+  }
+  return -1;
+}
+
+/*
+** Add a term to the WHERE expression in *ppExpr that requires the
+** zCol column to be equal in the two tables pTab1 and pTab2.
+*/
+static void addWhereTerm(
+  const char *zCol,        /* Name of the column */
+  const Table *pTab1,      /* First table */
+  const Table *pTab2,      /* Second table */
+  Expr **ppExpr            /* Add the equality term to this expression */
+){
+  Token dummy;
+  Expr *pE1a, *pE1b, *pE1c;
+  Expr *pE2a, *pE2b, *pE2c;
+  Expr *pE;
+
+  dummy.z = zCol;
+  dummy.n = strlen(zCol);
+  pE1a = sqliteExpr(TK_ID, 0, 0, &dummy);
+  pE2a = sqliteExpr(TK_ID, 0, 0, &dummy);
+  dummy.z = pTab1->zName;
+  dummy.n = strlen(dummy.z);
+  pE1b = sqliteExpr(TK_ID, 0, 0, &dummy);
+  dummy.z = pTab2->zName;
+  dummy.n = strlen(dummy.z);
+  pE2b = sqliteExpr(TK_ID, 0, 0, &dummy);
+  pE1c = sqliteExpr(TK_DOT, pE1b, pE1a, 0);
+  pE2c = sqliteExpr(TK_DOT, pE2b, pE2a, 0);
+  pE = sqliteExpr(TK_EQ, pE1c, pE2c, 0);
+  if( *ppExpr ){
+    *ppExpr = sqliteExpr(TK_AND, *ppExpr, pE, 0);
+  }else{
+    *ppExpr = pE;
+  }
+}
+
+/*
+** This routine processes the join information for a SELECT statement.
+** ON and USING clauses are converted into extra terms of the WHERE clause.
+** NATURAL joins also create extra WHERE clause terms.
+**
+** This routine returns the number of errors encountered.
+*/
+static int sqliteProcessJoin(Parse *pParse, Select *p){
+  SrcList *pSrc;
+  int i, j;
+  pSrc = p->pSrc;
+  for(i=0; i<pSrc->nSrc-1; i++){
+    struct SrcList_item *pTerm = &pSrc->a[i];
+    struct SrcList_item *pOther = &pSrc->a[i+1];
+
+    if( pTerm->pTab==0 || pOther->pTab==0 ) continue;
+
+    /* When the NATURAL keyword is present, add WHERE clause terms for
+    ** every column that the two tables have in common.
+    */
+    if( pTerm->jointype & JT_NATURAL ){
+      Table *pTab;
+      if( pTerm->pOn || pTerm->pUsing ){
+        sqliteSetString(&pParse->zErrMsg, "a NATURAL join may not have "
+           "an ON or USING clause", 0);
+        pParse->nErr++;
+        return 1;
+      }
+      pTab = pTerm->pTab;
+      for(j=0; j<pTab->nCol; j++){
+        if( columnIndex(pOther->pTab, pTab->aCol[j].zName)>=0 ){
+          addWhereTerm(pTab->aCol[j].zName, pTab, pOther->pTab, &p->pWhere);
+        }
+      }
+    }
+
+    /* Disallow both ON and USING clauses in the same join
+    */
+    if( pTerm->pOn && pTerm->pUsing ){
+      sqliteSetString(&pParse->zErrMsg, "cannot have both ON and USING "
+        "clauses in the same join", 0);
+      pParse->nErr++;
+      return 1;
+    }
+
+    /* Add the ON clause to the end of the WHERE clause, connected by
+    ** and AND operator.
+    */
+    if( pTerm->pOn ){
+      if( p->pWhere==0 ){
+        p->pWhere = pTerm->pOn;
+      }else{
+        p->pWhere = sqliteExpr(TK_AND, p->pWhere, pTerm->pOn, 0);
+      }
+      pTerm->pOn = 0;
+    }
+
+    /* Create extra terms on the WHERE clause for each column named
+    ** in the USING clause.  Example: If the two tables to be joined are 
+    ** A and B and the USING clause names X, Y, and Z, then add this
+    ** to the WHERE clause:    A.X=B.X AND A.Y=B.Y AND A.Z=B.Z
+    ** Report an error if any column mentioned in the USING clause is
+    ** not contained in both tables to be joined.
+    */
+    if( pTerm->pUsing ){
+      IdList *pList;
+      int j;
+      assert( i<pSrc->nSrc-1 );
+      pList = pTerm->pUsing;
+      for(j=0; j<pList->nId; j++){
+        if( columnIndex(pTerm->pTab, pList->a[i].zName)<0 ||
+            columnIndex(pOther->pTab, pList->a[i].zName)<0 ){
+          sqliteSetString(&pParse->zErrMsg, "cannot join using column ",
+            pList->a[i].zName, " - column not present in both tables", 0);
+          pParse->nErr++;
+          return 1;
+        }
+        addWhereTerm(pList->a[i].zName, pTerm->pTab, pOther->pTab, &p->pWhere);
+      }
+    }
+  }
+  return 0;
+}
+
 /*
 ** Delete the given Select structure and all of its substructures.
 */
@@ -414,12 +549,15 @@ Table *sqliteResultSetOfSelect(Parse *pParse, char *zTabName, Select *pSelect){
 }
 
 /*
-** For the given SELECT statement, do two things.
+** For the given SELECT statement, do three things.
 **
 **    (1)  Fill in the pTabList->a[].pTab fields in the SrcList that 
 **         defines the set of tables that should be scanned. 
 **
-**    (2)  Scan the list of columns in the result set (pEList) looking
+**    (2)  Add terms to the WHERE clause to accomodate the NATURAL keyword
+**         on joins and the ON and USING clause of joins.
+**
+**    (3)  Scan the list of columns in the result set (pEList) looking
 **         for instances of the "*" operator or the TABLE.* operator.
 **         If found, expand each "*" to be every column in every table
 **         and TABLE.* to be every column in TABLE.
@@ -447,6 +585,12 @@ static int fillInColumnList(Parse *pParse, Select *p){
     if( pTabList->a[i].zName==0 ){
       /* A sub-query in the FROM clause of a SELECT */
       assert( pTabList->a[i].pSelect!=0 );
+      if( pTabList->a[i].zAlias==0 ){
+        char zFakeName[60];
+        sprintf(zFakeName, "sqlite_subquery_%p_",
+           (void*)pTabList->a[i].pSelect);
+        sqliteSetString(&pTabList->a[i].zAlias, zFakeName, 0);
+      }
       pTabList->a[i].pTab = pTab = 
         sqliteResultSetOfSelect(pParse, pTabList->a[i].zAlias,
                                         pTabList->a[i].pSelect);
@@ -473,6 +617,10 @@ static int fillInColumnList(Parse *pParse, Select *p){
     }
   }
 
+  /* Process NATURAL keywords, and ON and USING clauses of joins.
+  */
+  if( sqliteProcessJoin(pParse, p) ) return 1;
+
   /* For every "*" that occurs in the column list, insert the names of
   ** all columns in all tables.  And for every TABLE.* insert the names
   ** of all columns in TABLE.  The parser inserted a special expression
@@ -531,10 +679,23 @@ static int fillInColumnList(Parse *pParse, Select *p){
           tableSeen = 1;
           for(j=0; j<pTab->nCol; j++){
             Expr *pExpr, *pLeft, *pRight;
+            char *zName = pTab->aCol[j].zName;
+
+            if( i>0 && (pTabList->a[i-1].jointype & JT_NATURAL)!=0 &&
+                columnIndex(pTabList->a[i-1].pTab, zName)>=0 ){
+              /* In a NATURAL join, omit the join columns from the 
+              ** table on the right */
+              continue;
+            }
+            if( i>0 && sqliteIdListIndex(pTabList->a[i-1].pUsing, zName)>=0 ){
+              /* In a join with a USING clause, omit columns in the
+              ** using clause from the table on the right. */
+              continue;
+            }
             pRight = sqliteExpr(TK_ID, 0, 0, 0);
             if( pRight==0 ) break;
-            pRight->token.z = pTab->aCol[j].zName;
-            pRight->token.n = strlen(pTab->aCol[j].zName);
+            pRight->token.z = zName;
+            pRight->token.n = strlen(zName);
             if( zTabName ){
               pLeft = sqliteExpr(TK_ID, 0, 0, 0);
               if( pLeft==0 ) break;
@@ -1295,6 +1456,7 @@ int sqliteSelect(
   if( fillInColumnList(pParse, p) ){
     goto select_end;
   }
+  pWhere = p->pWhere;
   pEList = p->pEList;
   if( pEList==0 ) goto select_end;
 
index c413b5298e8e2b5927418b0f59246e0187c1fd11..d323174e7ee784a4dff2b13025f5d04ddc61e802 100644 (file)
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.114 2002/05/24 16:14:15 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.115 2002/05/24 20:31:37 drh Exp $
 */
 #include "sqlite.h"
 #include "hash.h"
@@ -481,6 +481,8 @@ struct WhereLevel {
   int brk;             /* Jump here to break out of the loop */
   int cont;            /* Jump here to continue with the next loop cycle */
   int op, p1, p2;      /* Opcode used to terminate the loop */
+  int iLeftJoin;       /* Memory cell used to implement LEFT OUTER JOIN */
+  int top;             /* First instruction of interior of the loop */
 };
 
 /*
@@ -797,6 +799,7 @@ void sqliteDropTable(Parse*, Token*, int);
 void sqliteDeleteTable(sqlite*, Table*);
 void sqliteInsert(Parse*, Token*, ExprList*, Select*, IdList*, int);
 IdList *sqliteIdListAppend(IdList*, Token*);
+int sqliteIdListIndex(IdList*,const char*);
 SrcList *sqliteSrcListAppend(SrcList*, Token*);
 void sqliteSrcListAddAlias(SrcList*, Token*);
 void sqliteIdListDelete(IdList*);
index d53dff219f158bf06324404f6d7ce5a6cc0647fa..48e7ecf792ee0b0004c47bbf0c6df5d11f685eb2 100644 (file)
@@ -388,18 +388,11 @@ void sqliteDropTrigger(Parse *pParse, Token *pName, int nested)
 ** if there is no match.
 */
 static int checkColumnOverLap(IdList *pIdList, ExprList *pEList){
-  int i, e;
-  if( !pIdList )return 1;
-  if( !pEList )return 1;
-
-  for(i = 0; i < pIdList->nId; i++){ 
-    for(e = 0; e < pEList->nExpr; e++){ 
-      if( !sqliteStrICmp(pIdList->a[i].zName, pEList->a[e].zName) ){
-        return 1;
-      }
-    }
+  int e;
+  if( !pIdList || !pEList ) return 1;
+  for(e=0; e<pEList->nExpr; e++){
+    if( sqliteIdListIndex(pIdList, pEList->a[e].zName)>=0 ) return 1;
   }
-
   return 0; 
 }
 
index d12e55687ceb43c35f7e895cf378eff388376caa..af42f9b52f3a41f99ff503b5093b053f320d8fe6 100644 (file)
@@ -30,7 +30,7 @@
 ** But other routines are also provided to help in building up
 ** a program instruction by instruction.
 **
-** $Id: vdbe.c,v 1.147 2002/05/24 02:04:34 drh Exp $
+** $Id: vdbe.c,v 1.148 2002/05/24 20:31:37 drh Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -3452,8 +3452,12 @@ case OP_Next: {
 
   if( VERIFY( i>=0 && i<p->nCursor && ) (pCrsr = p->aCsr[i].pCursor)!=0 ){
     int res;
-    rc = sqliteBtreeNext(pCrsr, &res);
-    p->aCsr[i].nullRow = res;
+    if( p->aCsr[i].nullRow ){
+      res = 1;
+    }else{
+      rc = sqliteBtreeNext(pCrsr, &res);
+      p->aCsr[i].nullRow = res;
+    }
     if( res==0 ){
       pc = pOp->p2 - 1;
       sqlite_search_count++;
index 2082d6aec2b251d0622d245553f9c83afffec0da..a3576cae441afaf9ec73e9890bababb7abd193cc 100644 (file)
@@ -13,7 +13,7 @@
 ** the WHERE clause of SQL statements.  Also found here are subroutines
 ** to generate VDBE code to evaluate expressions.
 **
-** $Id: where.c,v 1.46 2002/05/24 02:04:34 drh Exp $
+** $Id: where.c,v 1.47 2002/05/24 20:31:38 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -455,6 +455,17 @@ WhereInfo *sqliteWhereBegin(
     Index *pIdx;
     WhereLevel *pLevel = &pWInfo->a[i];
 
+    /* If this is the right table of a LEFT OUTER JOIN, allocate and
+    ** initialize a memory cell that record if this table matches any
+    ** row of the left table in the join.
+    */
+    if( i>0 && (pTabList->a[i-1].jointype & JT_LEFT)!=0 ){
+      if( !pParse->nMem ) pParse->nMem++;
+      pLevel->iLeftJoin = pParse->nMem++;
+      sqliteVdbeAddOp(v, OP_String, 0, 0);
+      sqliteVdbeAddOp(v, OP_MemStore, pLevel->iLeftJoin, 1);
+    }
+
     pIdx = pLevel->pIdx;
     if( i<ARRAYSIZE(iDirectEq) && iDirectEq[i]>=0 ){
       /* Case 1:  We can directly reference a single row using an
@@ -788,6 +799,15 @@ WhereInfo *sqliteWhereBegin(
       aExpr[j].p = 0;
     }
     brk = cont;
+
+    /* For a LEFT OUTER JOIN, generate code that will record the fact that
+    ** at least one row of the right table has matched the left table.  
+    */
+    if( pLevel->iLeftJoin ){
+      pLevel->top = sqliteVdbeCurrentAddr(v);
+      sqliteVdbeAddOp(v, OP_Integer, 1, 0);
+      sqliteVdbeAddOp(v, OP_MemStore, pLevel->iLeftJoin, 1);
+    }
   }
   pWInfo->iContinue = cont;
   if( pushKey && !haveKey ){
@@ -814,6 +834,13 @@ void sqliteWhereEnd(WhereInfo *pWInfo){
       sqliteVdbeAddOp(v, pLevel->op, pLevel->p1, pLevel->p2);
     }
     sqliteVdbeResolveLabel(v, pLevel->brk);
+    if( pLevel->iLeftJoin ){
+      int addr;
+      addr = sqliteVdbeAddOp(v, OP_MemLoad, pLevel->iLeftJoin, 0);
+      sqliteVdbeAddOp(v, OP_NotNull, 0, addr+4);
+      sqliteVdbeAddOp(v, OP_NullRow, base+i, 0);
+      sqliteVdbeAddOp(v, OP_Goto, 0, pLevel->top);
+    }
   }
   sqliteVdbeResolveLabel(v, pWInfo->iBreak);
   for(i=0; i<pTabList->nSrc; i++){
diff --git a/test/join.test b/test/join.test
new file mode 100644 (file)
index 0000000..6aa70ea
--- /dev/null
@@ -0,0 +1,71 @@
+# 2002 May 24
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.
+#
+# This file implements tests for joins, including outer joins.
+#
+# $Id: join.test,v 1.1 2002/05/24 20:31:38 drh Exp $
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+do_test join-1.1 {
+  execsql {
+    CREATE TABLE t1(a,b,c);
+    INSERT INTO t1 VALUES(1,2,3);
+    INSERT INTO t1 VALUES(2,3,4);
+    INSERT INTO t1 VALUES(3,4,5);
+    SELECT * FROM t1;
+  }  
+} {1 2 3 2 3 4 3 4 5}
+do_test join-1.2 {
+  execsql {
+    CREATE TABLE t2(b,c,d);
+    INSERT INTO t2 VALUES(1,2,3);
+    INSERT INTO t2 VALUES(2,3,4);
+    INSERT INTO t2 VALUES(3,4,5);
+    SELECT * FROM t2;
+  }  
+} {1 2 3 2 3 4 3 4 5}
+
+do_test join-1.3 {
+  execsql2 {
+    SELECT * FROM t1 NATURAL JOIN t2;
+  }
+} {t1.a 1 t1.b 2 t1.c 3 t2.d 4 t1.a 2 t1.b 3 t1.c 4 t2.d 5}
+do_test join-1.4 {
+  execsql2 {
+    SELECT * FROM t1 INNER JOIN t2 USING(b,c);
+  }
+} {t1.a 1 t1.b 2 t1.c 3 t2.d 4 t1.a 2 t1.b 3 t1.c 4 t2.d 5}
+do_test join-1.5 {
+  execsql2 {
+    SELECT * FROM t1 INNER JOIN t2 USING(b);
+  }
+} {t1.a 1 t1.b 2 t1.c 3 t2.c 3 t2.d 4 t1.a 2 t1.b 3 t1.c 4 t2.c 4 t2.d 5}
+do_test join-1.6 {
+  execsql2 {
+    SELECT * FROM t1 INNER JOIN t2 USING(c);
+  }
+} {t1.a 1 t1.b 2 t1.c 3 t2.b 2 t2.d 4 t1.a 2 t1.b 3 t1.c 4 t2.b 3 t2.d 5}
+do_test join-1.7 {
+  execsql2 {
+    SELECT * FROM t1 INNER JOIN t2 USING(c,b);
+  }
+} {t1.a 1 t1.b 2 t1.c 3 t2.d 4 t1.a 2 t1.b 3 t1.c 4 t2.d 5}
+
+do_test join-2.1 {
+  execsql {
+    SELECT * FROM t1 NATURAL LEFT JOIN t2;
+  }
+} {1 2 3 4 2 3 4 5 3 4 5 {}}
+
+finish_test
index adf67993ecc8a410a3fdf5cad3359175d66e1151..304181bf474fa3d391c58910599fcb2bded2408e 100644 (file)
@@ -11,7 +11,7 @@
 # This file implements regression tests for SQLite library.  The
 # focus of this file is testing the SELECT statement.
 #
-# $Id: select1.test,v 1.25 2002/05/08 21:46:16 drh Exp $
+# $Id: select1.test,v 1.26 2002/05/24 20:31:38 drh Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -617,9 +617,9 @@ do_test select1-11.13 {
 } {t3.a 1 t3.b 2}
 do_test select1-11.14 {
   execsql2 {
-    SELECT * FROM t3, (SELECT max(a), max(b) FROM t4)
+    SELECT * FROM t3, (SELECT max(a), max(b) FROM t4) AS 'tx'
   }
-} {t3.a 1 t3.b 2 max(a) 3 max(b) 4}
+} {t3.a 1 t3.b 2 tx.max(a) 3 tx.max(b) 4}
 do_test select1-11.15 {
   execsql2 {
     SELECT y.*, t3.* FROM t3, (SELECT max(a), max(b) FROM t4) AS y