2a2979f92a8db7c10c477f150de952b57be9deff
[pspp-builds.git] / src / data / sys-file-private.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2006 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <data/sys-file-private.h>
20
21 #include <data/dictionary.h>
22 #include <data/value.h>
23 #include <data/variable.h>
24 #include <libpspp/assertion.h>
25
26 #include "minmax.h"
27 #include "xalloc.h"
28
29 /* Number of bytes really stored in each segment of a very long
30    string variable. */
31 #define REAL_VLS_CHUNK 255
32
33 /* Number of bytes per segment by which the amount of space for
34    very long string variables is allocated. */
35 #define EFFECTIVE_VLS_CHUNK 252
36
37 /* Returns true if WIDTH is a very long string width,
38    false otherwise. */
39 static bool
40 is_very_long (int width)
41 {
42   return width >= 256;
43 }
44
45 /* Returns the smaller of A or B.
46    (Defined as a function to avoid evaluating A or B more than
47    once.) */
48 static int
49 min_int (int a, int b)
50 {
51   return MIN (a, b);
52 }
53
54 /* Returns the larger of A or B.
55    (Defined as a function to avoid evaluating A or B more than
56    once.) */
57 static int
58 max_int (int a, int b)
59 {
60   return MAX (a, b);
61 }
62
63 /* Returns the number of bytes of uncompressed case data used for
64    writing a variable of the given WIDTH to a system file.  All
65    required space is included, including trailing padding and
66    internal padding. */
67 static int
68 sfm_width_to_bytes (int width)
69 {
70   int bytes;
71
72   assert (width >= 0);
73
74   if (width == 0)
75     bytes = 8;
76   else if (!is_very_long (width))
77     bytes = width;
78   else
79     {
80       int chunks = width / EFFECTIVE_VLS_CHUNK;
81       int remainder = width % EFFECTIVE_VLS_CHUNK;
82       bytes = remainder + (chunks * ROUND_UP (REAL_VLS_CHUNK, 8));
83     }
84   return ROUND_UP (bytes, 8);
85 }
86
87 /* Returns the number of 8-byte units (octs) used to write data
88    for a variable of the given WIDTH. */
89 int
90 sfm_width_to_octs (int width)
91 {
92   return sfm_width_to_bytes (width) / 8;
93 }
94
95 /* Returns the number of "segments" used for writing case data
96    for a variable of the given WIDTH.  A segment is a physical
97    variable in the system file that represents some piece of a
98    logical variable as seen by a PSPP user.  Only very long
99    string variables have more than one segment. */
100 int
101 sfm_width_to_segments (int width)
102 {
103   assert (width >= 0);
104
105   return !is_very_long (width) ? 1 : DIV_RND_UP (width, EFFECTIVE_VLS_CHUNK);
106 }
107
108 /* Returns the width to allocate to the given SEGMENT within a
109    variable of the given WIDTH.  A segment is a physical variable
110    in the system file that represents some piece of a logical
111    variable as seen by a PSPP user. */
112 int
113 sfm_segment_alloc_width (int width, int segment)
114 {
115   assert (segment < sfm_width_to_segments (width));
116
117   return (!is_very_long (width) ? width
118           : segment < sfm_width_to_segments (width) - 1 ? 255
119           : width - segment * EFFECTIVE_VLS_CHUNK);
120 }
121
122 /* Returns the number of bytes to allocate to the given SEGMENT
123    within a variable of the given width.  This is the same as
124    sfm_segment_alloc_width, except that a numeric value takes up
125    8 bytes despite having a width of 0. */
126 static int
127 sfm_segment_alloc_bytes (int width, int segment)
128 {
129   assert (segment < sfm_width_to_segments (width));
130   return (width == 0 ? 8
131           : ROUND_UP (sfm_segment_alloc_width (width, segment), 8));
132 }
133
134 /* Returns the number of bytes in the given SEGMENT within a
135    variable of the given WIDTH that are actually used to store
136    data.  For a numeric value (WIDTH of 0), this is 8 bytes; for
137    a string value less than 256 bytes wide, it is WIDTH bytes.
138    For very long string values, the calculation is more
139    complicated and ranges between 255 bytes for the first segment
140    to as little as 0 bytes for final segments. */
141 static int
142 sfm_segment_used_bytes (int width, int segment)
143 {
144   assert (segment < sfm_width_to_segments (width));
145   return (width == 0 ? 8
146           : !is_very_long (width) ? width
147           : max_int (0, min_int (width - REAL_VLS_CHUNK * segment,
148                                  REAL_VLS_CHUNK)));
149 }
150
151 /* Returns the number of bytes at the end of the given SEGMENT
152    within a variable of the given WIDTH that are not used for
153    data; that is, the number of bytes that must be padded with
154    data that a reader ignores. */
155 static int
156 sfm_segment_padding (int width, int segment)
157 {
158   return (sfm_segment_alloc_bytes (width, segment)
159           - sfm_segment_used_bytes (width, segment));
160 }
161
162 /* Returns the byte offset of the start of the given SEGMENT
163    within a variable of the given WIDTH.  The first segment
164    starts at offset 0; only very long string variables have any
165    other segments. */
166 static int
167 sfm_segment_offset (int width, int segment)
168 {
169   assert (segment < sfm_width_to_segments (width));
170   return min_int (REAL_VLS_CHUNK * segment, width);
171 }
172
173 /* Returns the byte offset of the start of the given SEGMENT
174    within a variable of the given WIDTH, given the (incorrect)
175    assumption that there are EFFECTIVE_VLS_CHUNK bytes per
176    segment.  (Use of this function is questionable at best.) */
177 int
178 sfm_segment_effective_offset (int width, int segment)
179 {
180   assert (segment < sfm_width_to_segments (width));
181   return EFFECTIVE_VLS_CHUNK * segment;
182 }
183
184 /* Creates and initializes an array of struct sfm_vars that
185    describe how a case drawn from dictionary DICT is laid out in
186    a system file.  Returns the number of segments in a case.  A
187    segment is a physical variable in the system file that
188    represents some piece of a logical variable as seen by a PSPP
189    user.
190
191    The array is allocated with malloc and stored in *SFM_VARS,
192    and its number of elements is stored in *SFM_VAR_CNT.  The
193    caller is responsible for freeing it when it is no longer
194    needed. */
195 int
196 sfm_dictionary_to_sfm_vars (const struct dictionary *dict,
197                             struct sfm_var **sfm_vars, size_t *sfm_var_cnt)
198 {
199   size_t var_cnt = dict_get_var_cnt (dict);
200   size_t segment_cnt;
201   size_t i;
202
203   /* Estimate the number of sfm_vars that will be needed.
204      We might not need all of these, because very long string
205      variables can have segments that are all padding, which do
206      not need sfm_vars of their own. */
207   segment_cnt = 0;
208   for (i = 0; i < var_cnt; i++)
209     {
210       const struct variable *v = dict_get_var (dict, i);
211       segment_cnt += sfm_width_to_segments (var_get_width (v));
212     }
213
214   /* Compose the sfm_vars. */
215   *sfm_vars = xnmalloc (segment_cnt, sizeof **sfm_vars);
216   *sfm_var_cnt = 0;
217   for (i = 0; i < var_cnt; i++)
218     {
219       const struct variable *dv = dict_get_var (dict, i);
220       int width = var_get_width (dv);
221       int j;
222
223       for (j = 0; j < sfm_width_to_segments (width); j++)
224         {
225           int used_bytes = sfm_segment_used_bytes (width, j);
226           int padding = sfm_segment_padding (width, j);
227           struct sfm_var *sv;
228           if (used_bytes != 0)
229             {
230               sv = &(*sfm_vars)[(*sfm_var_cnt)++];
231               sv->width = width == 0 ? 0 : used_bytes;
232               sv->case_index = var_get_case_index (dv);
233               sv->offset = sfm_segment_offset (width, j);
234               sv->padding = padding;
235             }
236           else
237             {
238               /* Segment is all padding.  Just add it to the
239                  previous segment.  (Otherwise we'd have an
240                  ambiguity whether ->width of 0 indicates a
241                  numeric variable or an all-padding segment.) */
242               sv = &(*sfm_vars)[*sfm_var_cnt - 1];
243               sv->padding += padding;
244             }
245           assert ((sv->width + sv->padding) % 8 == 0);
246         }
247     }
248
249   return segment_cnt;
250 }