bug-recutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[COMMITTED] src,torture,doc: support for singular fields


From: Jose E. Marchesi
Subject: [COMMITTED] src,torture,doc: support for singular fields
Date: Sat, 16 Apr 2022 13:17:42 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/28.0.50 (gnu/linux)

2022-04-16  Jose E. Marchesi  <jemarch@gnu.org>

        * src/rec.h (enum rec_std_field_e): New entry REC_FIELD_SINGULAR.
        * src/rec-field-name.c (fnames): Add "singular".
        * src/rec-rset.c (rec_rset_rename_field): Handle
        REC_FIELD_SINGULAR.
        * src/rec-int.c (rec_int_check_record_singular): Define.
        * torture/utils/recfix.sh: New tests.
        * doc/recutils.texi: Document singular fields.
---
 ChangeLog               | 10 ++++++
 doc/recutils.texi       | 17 ++++++++++
 src/rec-field-name.c    |  3 +-
 src/rec-int.c           | 86 ++++++++++++++++++++++++++++++++++++++++++++++++-
 src/rec-rset.c          |  3 +-
 src/rec.h               |  3 +-
 torture/utils/recfix.sh | 22 ++++++++++++-
 7 files changed, 139 insertions(+), 5 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 0f42fd4..0d71572 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+2022-04-16  Jose E. Marchesi  <jemarch@gnu.org>
+
+       * src/rec.h (enum rec_std_field_e): New entry REC_FIELD_SINGULAR.
+       * src/rec-field-name.c (fnames): Add "singular".
+       * src/rec-rset.c (rec_rset_rename_field): Handle
+       REC_FIELD_SINGULAR.
+       * src/rec-int.c (rec_int_check_record_singular): Define.
+       * torture/utils/recfix.sh: New tests.
+       * doc/recutils.texi: Document singular fields.
+
 2022-01-20  Liliana Marie Prikler  <liliana.prikler@ist.tugraz.at>
 
        * utils/recfmt.c (recfmt_apply_template): Add open brace as excluded
diff --git a/doc/recutils.texi b/doc/recutils.texi
index 837adf5..a32ea81 100644
--- a/doc/recutils.texi
+++ b/doc/recutils.texi
@@ -828,6 +828,8 @@ Restricting the size of your database.  @xref{Size 
Constraints}.
 Enforcing arbitrary constraints.  @xref{Arbitrary Constraints}.
 @item %confidential
 Storing confidential information.  @xref{Encryption}.
+@item %singular
+Fields without repeating values.
 @end table
 
 @node Querying Recfiles
@@ -2851,6 +2853,7 @@ structure of the records.
 * Prohibited Fields::        Forbidding the presence of fields.
 * Allowed Fields::           Restricting the presence of fields.
 * Keys and Unique Fields::   Fields characterizing records.
+* Singular Fields::          Fields with unique contents.
 * Size Constraints::         Constraints on the number of records in a set.
 * Arbitrary Constraints::    Constraints records must comply with.
 @end menu
@@ -3084,6 +3087,20 @@ violations, and will be reported by a checking tool.
 Elsewhere, we discuss how primary keys can be used to link one record set to 
 another using primary keys together with foreign keys. @xref{Queries which 
Join Records}.
 
+@node Singular Fields
+@section Singular Fields
+
+Sometimes we require certain fields with a given name to not appear in
+a record set featuring the same contents, but we don't want (or we
+can't) declare such fields as the key of the record set.
+
+In these circumstances we can use @dfn{singular fields}, which are
+declared as such in the record descriptor using the @code{%singular}
+special field:
+
+@example
+%singular: @var{field}
+@end example
 
 @node Size Constraints
 @section Size Constraints
diff --git a/src/rec-field-name.c b/src/rec-field-name.c
index d063fe9..2a92606 100644
--- a/src/rec-field-name.c
+++ b/src/rec-field-name.c
@@ -49,7 +49,8 @@ static const char *fnames[] =
     "%typedef",
     "%unique",
     "%constraint",
-    "%allowed"
+    "%allowed",
+    "%singular",
   };
 
 const char *
diff --git a/src/rec-int.c b/src/rec-int.c
index d0a69b9..6449474 100644
--- a/src/rec-int.c
+++ b/src/rec-int.c
@@ -487,6 +487,89 @@ rec_int_check_record_key (rec_rset_t rset,
   return res;
 }
 
+static int
+rec_int_check_record_singular (rec_rset_t rset,
+                               rec_record_t orig_record,
+                               rec_record_t record,
+                               rec_buf_t errors)
+{
+  int res = 0;
+  rec_record_t descriptor;
+  size_t i;
+
+  descriptor = rec_rset_descriptor (rset);
+  if (!descriptor)
+    return 0;
+
+  for (i = 0;
+       i < rec_record_get_num_fields_by_name (descriptor,
+                                              FNAME (REC_FIELD_SINGULAR));
+       ++i)
+    {
+      size_t j;
+      bool duplicated_singular = false;
+      rec_field_t field = rec_record_get_field_by_name (descriptor,
+                                                        FNAME 
(REC_FIELD_SINGULAR), i);
+      char *singular_field_name = rec_parse_field_name_str (rec_field_value 
(field));
+
+      /* Ignore invalid %singular entries.  */
+      if (!singular_field_name)
+        continue;
+
+      /* Iterate over all the fields in the record having this name.  */
+      for (j = 0;
+           j < rec_record_get_num_fields_by_name (record, singular_field_name);
+           ++j)
+        {
+          /* Check that the value in this field is singular in the
+             whole record set.  */
+          rec_mset_iterator_t iter;
+          rec_record_t other_record;
+          rec_field_t orig_field = rec_record_get_field_by_name (record,
+                                                                 
singular_field_name,
+                                                                 j);
+
+          iter = rec_mset_iterator (rec_rset_mset (rset));
+          while (rec_mset_iterator_next (&iter, MSET_RECORD, (const void**) 
&other_record, NULL))
+            {
+              size_t k;
+
+              for (k = 0;
+                   k < rec_record_get_num_fields_by_name (other_record,
+                                                          singular_field_name);
+                   ++k)
+                {
+                  rec_field_t f = rec_record_get_field_by_name (other_record,
+                                                                
singular_field_name,
+                                                                k);
+                  if (other_record != orig_record
+                      && strcmp (rec_field_value (f),
+                                 rec_field_value (orig_field)) == 0)
+                                 
+                    {
+                      duplicated_singular = true;
+                      break;
+                    }
+                }
+            }
+          rec_mset_iterator_free (&iter);
+        }
+
+      if (duplicated_singular)
+        {
+          ADD_ERROR (errors,
+                     _("%s:%s: error: duplicated value in singular field '%s' 
in record\n"),
+                     rec_record_source (orig_record),
+                     rec_record_location_str (orig_record),
+                     singular_field_name);
+          res++;
+          break;
+        }
+    }
+
+  return res;
+}
+
 static bool
 rec_int_rec_type_p (const char *str)
 {
@@ -1123,7 +1206,8 @@ rec_int_check_record (rec_db_t db,
 #endif
     + rec_int_check_record_prohibit  (rset, record, errors)
     + rec_int_check_record_sex_constraints (rset, record, errors)
-    + rec_int_check_record_allowed   (rset, record, errors);
+    + rec_int_check_record_allowed   (rset, record, errors)
+    + rec_int_check_record_singular (rset, orig_record, record, errors);
 
   return res;
 }
diff --git a/src/rec-rset.c b/src/rec-rset.c
index 4ff27e8..d96bdb1 100644
--- a/src/rec-rset.c
+++ b/src/rec-rset.c
@@ -1268,7 +1268,8 @@ rec_rset_rename_field (rec_rset_t rset,
 #if defined REC_CRYPT_SUPPORT
                    || rec_field_name_equal_p (rec_field_name (field), 
FNAME(REC_FIELD_CONFIDENTIAL))
 #endif
-                   || rec_field_name_equal_p (rec_field_name (field), 
FNAME(REC_FIELD_SORT)))
+                   || rec_field_name_equal_p (rec_field_name (field), 
FNAME(REC_FIELD_SORT))
+                   || rec_field_name_equal_p (rec_field_name (field), 
FNAME(REC_FIELD_SINGULAR)))
             {
               /* Rename the field in the fex expression that is the
                  value of the field.  Skip invalid entries.  */
diff --git a/src/rec.h b/src/rec.h
index 1a712be..4e03e08 100644
--- a/src/rec.h
+++ b/src/rec.h
@@ -397,7 +397,8 @@ enum rec_std_field_e
   REC_FIELD_TYPEDEF,
   REC_FIELD_UNIQUE,
   REC_FIELD_CONSTRAINT,
-  REC_FIELD_ALLOWED
+  REC_FIELD_ALLOWED,
+  REC_FIELD_SINGULAR
 };
 
 /******************* Field name utilities **********************/
diff --git a/torture/utils/recfix.sh b/torture/utils/recfix.sh
index 0f268cf..a8beccb 100755
--- a/torture/utils/recfix.sh
+++ b/torture/utils/recfix.sh
@@ -1380,6 +1380,21 @@ zzz: 40
 yyy: 30
 '
 
+test_declare_input_file multiple-singulars \
+'%rec: Foo
+%singular: Id
+%unique: Id
+
+Id: 0
+Name: Name1
+
+Id: 2
+Name: Name2
+
+Id: 2
+Name: Name3
+'
+
 test_declare_input_file blanks-before-fex-in-type \
 '%rec: Bug
 %mandatory: Id Title Desc Status Reporter Time
@@ -2446,7 +2461,12 @@ test_tool recfix-blanks-before-fex-in-type ok \
           '--check' \
           blanks-before-fex-in-type \
           ''
-        
+
+test_tool recfix-multiple-singulars xfail \
+          recfix \
+          '--check' \
+          multiple-singulars
+
 #
 # Cleanup.
 #
-- 
2.11.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]