From 2080d5ecfeb80769756fc162afdf1a2dbba8c601 Mon Sep 17 00:00:00 2001
From: Tim Chase <recutils@tim.thechases.com>
Date: Thu, 15 Oct 2020 18:02:45 -0500
Subject: [PATCH 1/2] Add OUTER JOIN functionality

---
 src/rec-db.c   | 36 ++++++++++++++++++++++++++++++++----
 src/rec.h      | 22 +++++++++++++++++-----
 utils/recsel.c | 18 ++++++++++++++----
 3 files changed, 63 insertions(+), 13 deletions(-)

diff --git a/src/rec-db.c b/src/rec-db.c
index 26229be..f9ad806 100644
--- a/src/rec-db.c
+++ b/src/rec-db.c
@@ -72,7 +72,7 @@ static bool rec_db_set_act_set (rec_rset_t rset, rec_record_t record, rec_fex_t
 static bool rec_db_set_act_add (rec_rset_t rset, rec_record_t record, rec_fex_t fex, const char *arg);
 static bool rec_db_set_act_delete (rec_rset_t rset, rec_record_t record, rec_fex_t fex, bool comment_out);
 
-static rec_rset_t rec_db_join (rec_db_t db, const char *type1, const char *field, const char *type2);
+static rec_rset_t rec_db_join (rec_db_t db, const char *type1, const char *field, const char *type2, enum rec_join_type_e join_type);
 static rec_record_t rec_db_merge_records (rec_record_t record1, rec_record_t record2, const char *prefix);
 
 /*
@@ -268,6 +268,7 @@ rec_rset_t
 rec_db_query (rec_db_t     db,
               const char  *type,
               const char  *join,
+              enum rec_join_type_e join_type,
               size_t       *index,
               rec_sex_t    sex,
               const char  *fast_string,
@@ -332,7 +333,11 @@ rec_db_query (rec_db_t     db,
 
               if (rec_db_get_rset_by_type (db, referred_type))
                 {
-                  rset = rec_db_join (db, type, join, referred_type);
+                  rset = rec_db_join (db,
+                                      type,
+                                      join,
+                                      referred_type,
+                                      join_type);
                   if (!rset)
                     {
                       /* Out of memory.  */
@@ -1040,7 +1045,9 @@ static rec_rset_t
 rec_db_join (rec_db_t db,
              const char *type1,
              const char *field,
-             const char *type2)
+             const char *type2,
+             enum rec_join_type_e join_type
+             )
 {
   /* Note that this function is inefficient like hell.  */
 
@@ -1055,7 +1062,12 @@ rec_db_join (rec_db_t db,
   rec_rset_t rset2 = rec_db_get_rset_by_type (db, type2);
   
 
-  if (!rset1 || !rset2)
+  if (!rset1)
+    {
+      return NULL;
+    }
+
+  if (join_type == REC_JOIN_INNER && !rset2)
     {
       return NULL;
     }
@@ -1124,6 +1136,22 @@ rec_db_join (rec_db_t db,
                   if (!found)
                     {
                       /* Skip this combination record.  */
+                      if (join_type == REC_JOIN_OUTER)
+                        {
+                          /* but this is an OUTER JOIN so keep the original */
+                          rec_record_t record = NULL;
+                          record = rec_record_dup (record1);
+                          if (!record)
+                            {
+                              return NULL;
+                            }
+                          rec_record_set_container (record, join);
+                          if (!rec_mset_append (rec_rset_mset (join), MSET_RECORD, (void *) record, MSET_ANY))
+                            {
+                              /* Out of memory.  */
+                              return NULL;
+                            }
+                        }
                       continue;
                     }
                   
diff --git a/src/rec.h b/src/rec.h
index 3d6168b..1fb23c7 100644
--- a/src/rec.h
+++ b/src/rec.h
@@ -1391,11 +1391,14 @@ rec_aggregate_reg_t rec_db_aggregates (rec_db_t db);
 
    JOIN
    
-      If not NULL, this argument must be a string denoting a field
-      name.  This field name must be a foreign key (field of type
-      'rec') defined in the selected record set.  The query operation
-      will do an inner join using T1.Field = T2.Field as join
-      criteria.
+      If not NULL, this argument must be a string denoting a field name.
+      This field name must be a foreign key (field of type 'rec')
+      defined in the selected record set.  The query operation will do
+      an join using T1.Field = T2.Field as join criteria.
+
+   JOINTYPE
+
+      Either JOIN_INNER or JOIN_OUTER to control how the JOIN happens
 
    INDEX
 
@@ -1484,9 +1487,18 @@ rec_aggregate_reg_t rec_db_aggregates (rec_db_t db);
 
 #define REC_Q_NOINDEX ((size_t)-1)
 
+enum rec_join_type_e
+  {
+    /* An inner join.  */
+    REC_JOIN_INNER = 0,
+    /* An outer join.  */
+    REC_JOIN_OUTER
+  };
+
 rec_rset_t rec_db_query (rec_db_t     db,
                          const char  *type,
                          const char  *join,
+			 enum rec_join_type_e join_type,
                          size_t      *index,
                          rec_sex_t    sex,
                          const char  *fast_string,
diff --git a/utils/recsel.c b/utils/recsel.c
index a914bd1..4a62646 100644
--- a/utils/recsel.c
+++ b/utils/recsel.c
@@ -61,6 +61,7 @@ char      *recsel_password     = NULL;
 bool       recsel_uniq         = false;
 size_t     recutl_random       = 0;
 char      *recsel_join         = NULL;
+int        recsel_join_type    = 0;
 
 /*
  * Command line options management.
@@ -83,7 +84,8 @@ enum
 #endif
   UNIQ_ARG,
   GROUP_BY_ARG,
-  JOIN_ARG
+  JOIN_INNER_ARG,
+  JOIN_OUTER_ARG
 };
 
 static const struct option GNU_longOptions[] =
@@ -103,7 +105,9 @@ static const struct option GNU_longOptions[] =
 #endif
     {"uniq", no_argument, NULL, UNIQ_ARG},
     {"group-by", required_argument, NULL, GROUP_BY_ARG},
-    {"join", required_argument, NULL, JOIN_ARG},
+    {"join", required_argument, NULL, JOIN_INNER_ARG},
+    {"inner-join", required_argument, NULL, JOIN_INNER_ARG},
+    {"outer-join", required_argument, NULL, JOIN_OUTER_ARG},
     {NULL, 0, NULL, 0}
   };
 
@@ -189,7 +193,7 @@ recsel_parse_args (int argc,
                              argv,
                              RECORD_SELECTION_SHORT_ARGS
                              ENCRYPTION_SHORT_ARGS
-                             "S:Cdcp:P:R:UG:j:",
+                             "S:Cdcp:P:R:UG:j:J:",
                              GNU_longOptions,
                              NULL)) != -1)
     {
@@ -251,8 +255,10 @@ recsel_parse_args (int argc,
 
             break;
           }
-        case JOIN_ARG:
+        case JOIN_INNER_ARG:
+        case JOIN_OUTER_ARG:
         case 'j':
+        case 'J':
           {
             if (recsel_join)
               {
@@ -265,6 +271,9 @@ recsel_parse_args (int argc,
               }
 
             recsel_join = xstrdup (optarg);
+            recsel_join_type = ('j' == c || JOIN_INNER_ARG == c)
+              ? REC_JOIN_INNER
+              : REC_JOIN_OUTER;
             break;
           }
         case GROUP_BY_ARG:
@@ -465,6 +474,7 @@ recsel_process_data (rec_db_t db)
     rset = rec_db_query (db,
                          recutl_type,
                          recsel_join,
+                         recsel_join_type,
                          recutl_index(),
                          recutl_sex,
                          recutl_quick_str,
-- 
2.25.1

