pg 0.18.0.pre20140820094244 → 0.18.0.pre20141017155815

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +1573 -2
  5. data/History.rdoc +3 -11
  6. data/Manifest.txt +24 -0
  7. data/README.rdoc +51 -4
  8. data/Rakefile +20 -14
  9. data/Rakefile.cross +39 -32
  10. data/ext/extconf.rb +27 -26
  11. data/ext/pg.c +75 -21
  12. data/ext/pg.h +194 -6
  13. data/ext/pg_binary_decoder.c +160 -0
  14. data/ext/pg_binary_encoder.c +160 -0
  15. data/ext/pg_coder.c +454 -0
  16. data/ext/pg_connection.c +815 -518
  17. data/ext/pg_copy_coder.c +557 -0
  18. data/ext/pg_result.c +258 -103
  19. data/ext/pg_text_decoder.c +424 -0
  20. data/ext/pg_text_encoder.c +608 -0
  21. data/ext/pg_type_map.c +113 -0
  22. data/ext/pg_type_map_all_strings.c +113 -0
  23. data/ext/pg_type_map_by_column.c +254 -0
  24. data/ext/pg_type_map_by_mri_type.c +266 -0
  25. data/ext/pg_type_map_by_oid.c +341 -0
  26. data/ext/util.c +121 -0
  27. data/ext/util.h +63 -0
  28. data/lib/pg.rb +11 -1
  29. data/lib/pg/basic_type_mapping.rb +377 -0
  30. data/lib/pg/coder.rb +74 -0
  31. data/lib/pg/connection.rb +38 -5
  32. data/lib/pg/result.rb +13 -3
  33. data/lib/pg/text_decoder.rb +42 -0
  34. data/lib/pg/text_encoder.rb +27 -0
  35. data/lib/pg/type_map_by_column.rb +15 -0
  36. data/spec/helpers.rb +9 -1
  37. data/spec/pg/basic_type_mapping_spec.rb +251 -0
  38. data/spec/pg/connection_spec.rb +232 -13
  39. data/spec/pg/result_spec.rb +52 -0
  40. data/spec/pg/type_map_by_column_spec.rb +135 -0
  41. data/spec/pg/type_map_by_mri_type_spec.rb +122 -0
  42. data/spec/pg/type_map_by_oid_spec.rb +133 -0
  43. data/spec/pg/type_map_spec.rb +39 -0
  44. data/spec/pg/type_spec.rb +620 -0
  45. metadata +40 -4
  46. metadata.gz.sig +0 -0
@@ -0,0 +1,424 @@
1
+ /*
2
+ * pg_text_decoder.c - PG::TextDecoder module
3
+ * $Id: pg_text_decoder.c,v e64618a44912 2014/10/05 19:56:05 lars $
4
+ *
5
+ */
6
+
7
+ /*
8
+ *
9
+ * Type casts for decoding PostgreSQL string representations to Ruby objects.
10
+ *
11
+ * Decoder classes are defined with pg_define_coder(). This creates a new coder class and
12
+ * assigns a decoder function.
13
+ *
14
+ * Signature of all type cast decoders is:
15
+ * VALUE decoder_function(t_pg_coder *this, char *val, int len, int tuple, int field, int enc_idx)
16
+ *
17
+ * Params:
18
+ * this - The data part of the coder object that belongs to the decoder function.
19
+ * val, len - The text or binary data to decode. The caller ensures, that the data is
20
+ * zero terminated ( that is val[len] = 0 ). The memory should be used read
21
+ * only by the callee.
22
+ * tuple - Row of the value within the result set.
23
+ * field - Column of the value within the result set.
24
+ * enc_idx - Index of the Encoding that any output String should get assigned.
25
+ *
26
+ * Returns:
27
+ * The type casted Ruby object.
28
+ *
29
+ */
30
+
31
+ #include "pg.h"
32
+ #include "util.h"
33
+ #include <inttypes.h>
34
+
35
+ VALUE rb_mPG_TextDecoder;
36
+ static ID s_id_decode;
37
+
38
+
39
+ /*
40
+ * Document-class: PG::TextDecoder::Boolean < PG::SimpleDecoder
41
+ *
42
+ * This is a decoder class for conversion of PostgreSQL boolean type
43
+ * to Ruby true or false values.
44
+ *
45
+ */
46
+ static VALUE
47
+ pg_text_dec_boolean(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx)
48
+ {
49
+ if (len < 1) {
50
+ rb_raise( rb_eTypeError, "wrong data for text boolean converter in tuple %d field %d", tuple, field);
51
+ }
52
+ return *val == 't' ? Qtrue : Qfalse;
53
+ }
54
+
55
+ /*
56
+ * Document-class: PG::TextDecoder::String < PG::SimpleDecoder
57
+ *
58
+ * This is a decoder class for conversion of PostgreSQL text output to
59
+ * to Ruby String object. The output value will have the character encoding
60
+ * set with PG::Connection#internal_encoding= .
61
+ *
62
+ */
63
+ VALUE
64
+ pg_text_dec_string(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx)
65
+ {
66
+ VALUE ret = rb_tainted_str_new( val, len );
67
+ PG_ENCODING_SET_NOCHECK( ret, enc_idx );
68
+ return ret;
69
+ }
70
+
71
+ /*
72
+ * Document-class: PG::TextDecoder::Integer < PG::SimpleDecoder
73
+ *
74
+ * This is a decoder class for conversion of PostgreSQL integer types
75
+ * to Ruby Integer objects.
76
+ *
77
+ */
78
+ static VALUE
79
+ pg_text_dec_integer(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx)
80
+ {
81
+ long i;
82
+ int max_len;
83
+
84
+ if( sizeof(i) >= 8 && FIXNUM_MAX >= 1000000000000000000LL ){
85
+ /* 64 bit system can safely handle all numbers up to 18 digits as Fixnum */
86
+ max_len = 18;
87
+ } else if( sizeof(i) >= 4 && FIXNUM_MAX >= 1000000000LL ){
88
+ /* 32 bit system can safely handle all numbers up to 9 digits as Fixnum */
89
+ max_len = 9;
90
+ } else {
91
+ /* unknown -> don't use fast path for int conversion */
92
+ max_len = 0;
93
+ }
94
+
95
+ if( len <= max_len ){
96
+ /* rb_cstr2inum() seems to be slow, so we do the int conversion by hand.
97
+ * This proved to be 40% faster by the following benchmark:
98
+ *
99
+ * conn.type_mapping_for_results = PG::BasicTypeMapForResults.new conn
100
+ * Benchmark.measure do
101
+ * conn.exec("select generate_series(1,1000000)").values }
102
+ * end
103
+ */
104
+ char *val_pos = val;
105
+ char digit = *val_pos;
106
+ int neg;
107
+ int error = 0;
108
+
109
+ if( digit=='-' ){
110
+ neg = 1;
111
+ i = 0;
112
+ }else if( digit>='0' && digit<='9' ){
113
+ neg = 0;
114
+ i = digit - '0';
115
+ } else {
116
+ error = 1;
117
+ }
118
+
119
+ while (!error && (digit=*++val_pos)) {
120
+ if( digit>='0' && digit<='9' ){
121
+ i = i * 10 + (digit - '0');
122
+ } else {
123
+ error = 1;
124
+ }
125
+ }
126
+
127
+ if( !error ){
128
+ return LONG2FIX(neg ? -i : i);
129
+ }
130
+ }
131
+ /* Fallback to ruby method if number too big or unrecognized. */
132
+ return rb_cstr2inum(val, 10);
133
+ }
134
+
135
+ /*
136
+ * Document-class: PG::TextDecoder::Float < PG::SimpleDecoder
137
+ *
138
+ * This is a decoder class for conversion of PostgreSQL float4 and float8 types
139
+ * to Ruby Float objects.
140
+ *
141
+ */
142
+ static VALUE
143
+ pg_text_dec_float(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx)
144
+ {
145
+ return rb_float_new(strtod(val, NULL));
146
+ }
147
+
148
+ /*
149
+ * Document-class: PG::TextDecoder::Bytea < PG::SimpleDecoder
150
+ *
151
+ * This is a decoder class for conversion of PostgreSQL bytea type
152
+ * to binary String objects.
153
+ *
154
+ */
155
+ static VALUE
156
+ pg_text_dec_bytea(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx)
157
+ {
158
+ unsigned char *to;
159
+ size_t to_len;
160
+ VALUE ret;
161
+
162
+ to = PQunescapeBytea( (unsigned char *)val, &to_len);
163
+
164
+ ret = rb_tainted_str_new((char*)to, to_len);
165
+ PQfreemem(to);
166
+
167
+ return ret;
168
+ }
169
+
170
+ /*
171
+ * Array parser functions are thankfully borrowed from here:
172
+ * https://github.com/dockyard/pg_array_parser
173
+ */
174
+ static VALUE
175
+ read_array(t_pg_composite_coder *this, int *index, char *c_pg_array_string, int array_string_length, char *word, int enc_idx, int tuple, int field, t_pg_coder_dec_func dec_func)
176
+ {
177
+ /* Return value: array */
178
+ VALUE array;
179
+ int word_index = 0;
180
+
181
+ /* The current character in the input string. */
182
+ char c;
183
+
184
+ /* 0: Currently outside a quoted string, current word never quoted
185
+ * 1: Currently inside a quoted string
186
+ * -1: Currently outside a quoted string, current word previously quoted */
187
+ int openQuote = 0;
188
+
189
+ /* Inside quoted input means the next character should be treated literally,
190
+ * instead of being treated as a metacharacter.
191
+ * Outside of quoted input, means that the word shouldn't be pushed to the array,
192
+ * used when the last entry was a subarray (which adds to the array itself). */
193
+ int escapeNext = 0;
194
+
195
+ array = rb_ary_new();
196
+
197
+ /* Special case the empty array, so it doesn't need to be handled manually inside
198
+ * the loop. */
199
+ if(((*index) < array_string_length) && c_pg_array_string[(*index)] == '}')
200
+ {
201
+ return array;
202
+ }
203
+
204
+ for(;(*index) < array_string_length; ++(*index))
205
+ {
206
+ c = c_pg_array_string[*index];
207
+ if(openQuote < 1)
208
+ {
209
+ if(c == this->delimiter || c == '}')
210
+ {
211
+ if(!escapeNext)
212
+ {
213
+ if(openQuote == 0 && word_index == 4 && !strncmp(word, "NULL", word_index))
214
+ {
215
+ rb_ary_push(array, Qnil);
216
+ }
217
+ else
218
+ {
219
+ VALUE val;
220
+ word[word_index] = 0;
221
+ val = dec_func(this->elem, word, word_index, tuple, field, enc_idx);
222
+ rb_ary_push(array, val);
223
+ }
224
+ }
225
+ if(c == '}')
226
+ {
227
+ return array;
228
+ }
229
+ escapeNext = 0;
230
+ openQuote = 0;
231
+ word_index = 0;
232
+ }
233
+ else if(c == '"')
234
+ {
235
+ openQuote = 1;
236
+ }
237
+ else if(c == '{')
238
+ {
239
+ (*index)++;
240
+ rb_ary_push(array, read_array(this, index, c_pg_array_string, array_string_length, word, enc_idx, tuple, field, dec_func));
241
+ escapeNext = 1;
242
+ }
243
+ else
244
+ {
245
+ word[word_index] = c;
246
+ word_index++;
247
+ }
248
+ }
249
+ else if (escapeNext) {
250
+ word[word_index] = c;
251
+ word_index++;
252
+ escapeNext = 0;
253
+ }
254
+ else if (c == '\\')
255
+ {
256
+ escapeNext = 1;
257
+ }
258
+ else if (c == '"')
259
+ {
260
+ openQuote = -1;
261
+ }
262
+ else
263
+ {
264
+ word[word_index] = c;
265
+ word_index++;
266
+ }
267
+ }
268
+
269
+ return array;
270
+ }
271
+
272
+ /*
273
+ * Document-class: PG::TextDecoder::Array < PG::CompositeDecoder
274
+ *
275
+ * This is the decoder class for PostgreSQL array types.
276
+ *
277
+ * All values are decoded according to the #elements_type
278
+ * accessor. Sub-arrays are decoded recursively.
279
+ *
280
+ */
281
+ static VALUE
282
+ pg_text_dec_array(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx)
283
+ {
284
+ t_pg_composite_coder *this = (t_pg_composite_coder *)conv;
285
+ t_pg_coder_dec_func dec_func = pg_coder_dec_func(this->elem, 0);
286
+ /* create a buffer of the same length, as that will be the worst case */
287
+ char *word = xmalloc(len + 1);
288
+ int index = 1;
289
+
290
+ VALUE return_value = read_array(this, &index, val, len, word, enc_idx, tuple, field, dec_func);
291
+ free(word);
292
+ return return_value;
293
+ }
294
+
295
+ /*
296
+ * Document-class: PG::TextDecoder::Identifier < PG::CompositeDecoder
297
+ *
298
+ * This is the decoder class for PostgreSQL identifiers.
299
+ *
300
+ * Returns an Array of identifiers:
301
+ * PG::TextDecoder::Identifier.new.decode('schema."table"."column"')
302
+ * => ["schema", "table", "column"]
303
+ *
304
+ */
305
+ static VALUE
306
+ pg_text_dec_identifier(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx)
307
+ {
308
+ t_pg_composite_coder *this = (t_pg_composite_coder *)conv;
309
+ t_pg_coder_dec_func dec_func = pg_coder_dec_func(this->elem, 0);
310
+
311
+ /* Return value: array */
312
+ VALUE array;
313
+ VALUE elem;
314
+ int word_index = 0;
315
+ int index;
316
+ /* create a buffer of the same length, as that will be the worst case */
317
+ char *word = xmalloc(len + 1);
318
+
319
+ /* The current character in the input string. */
320
+ char c;
321
+
322
+ /* 0: Currently outside a quoted string
323
+ * 1: Currently inside a quoted string, last char was a quote
324
+ * 2: Currently inside a quoted string, last char was no quote */
325
+ int openQuote = 0;
326
+
327
+ array = rb_ary_new();
328
+
329
+ for(index = 0; index < len; ++index) {
330
+ c = val[index];
331
+ if(c == '.' && openQuote < 2 ) {
332
+ word[word_index] = 0;
333
+
334
+ elem = dec_func(conv, word, word_index, tuple, field, enc_idx);
335
+ rb_ary_push(array, elem);
336
+
337
+ openQuote = 0;
338
+ word_index = 0;
339
+ } else if(c == '"') {
340
+ if (openQuote == 1) {
341
+ word[word_index] = c;
342
+ word_index++;
343
+ openQuote = 2;
344
+ } else if (openQuote == 2){
345
+ openQuote = 1;
346
+ } else {
347
+ openQuote = 2;
348
+ }
349
+ } else {
350
+ word[word_index] = c;
351
+ word_index++;
352
+ }
353
+ }
354
+
355
+ word[word_index] = 0;
356
+ elem = dec_func(conv, word, word_index, tuple, field, enc_idx);
357
+ rb_ary_push(array, elem);
358
+
359
+ free(word);
360
+
361
+ return array;
362
+ }
363
+
364
+ /*
365
+ * Document-class: PG::TextDecoder::FromBase64 < PG::CompositeDecoder
366
+ *
367
+ * This is a decoder class for conversion of base64 encoded data
368
+ * to it's binary representation. It outputs a binary Ruby String
369
+ * or some other Ruby object, if a #elements_type decoder was defined.
370
+ *
371
+ */
372
+ static VALUE
373
+ pg_text_dec_from_base64(t_pg_coder *conv, char *val, int len, int tuple, int field, int enc_idx)
374
+ {
375
+ t_pg_composite_coder *this = (t_pg_composite_coder *)conv;
376
+ t_pg_coder_dec_func dec_func = pg_coder_dec_func(this->elem, this->comp.format);
377
+ int decoded_len;
378
+ /* create a buffer of the expected decoded length */
379
+ VALUE out_value = rb_tainted_str_new(NULL, BASE64_DECODED_SIZE(len));
380
+
381
+ decoded_len = base64_decode( RSTRING_PTR(out_value), val, len );
382
+ rb_str_set_len(out_value, decoded_len);
383
+
384
+ /* Is it a pure String conversion? Then we can directly send out_value to the user. */
385
+ if( this->comp.format == 0 && dec_func == pg_text_dec_string ){
386
+ PG_ENCODING_SET_NOCHECK( out_value, enc_idx );
387
+ return out_value;
388
+ }
389
+ if( this->comp.format == 1 && dec_func == pg_bin_dec_bytea ){
390
+ PG_ENCODING_SET_NOCHECK( out_value, rb_ascii8bit_encindex() );
391
+ return out_value;
392
+ }
393
+ out_value = dec_func(this->elem, RSTRING_PTR(out_value), decoded_len, tuple, field, enc_idx);
394
+
395
+ return out_value;
396
+ }
397
+
398
+ void
399
+ init_pg_text_decoder()
400
+ {
401
+ s_id_decode = rb_intern("decode");
402
+
403
+ /* This module encapsulates all decoder classes with text input format */
404
+ rb_mPG_TextDecoder = rb_define_module_under( rb_mPG, "TextDecoder" );
405
+
406
+ /* Make RDoc aware of the decoder classes... */
407
+ /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Boolean", rb_cPG_SimpleDecoder ); */
408
+ pg_define_coder( "Boolean", pg_text_dec_boolean, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder );
409
+ /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Integer", rb_cPG_SimpleDecoder ); */
410
+ pg_define_coder( "Integer", pg_text_dec_integer, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder );
411
+ /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Float", rb_cPG_SimpleDecoder ); */
412
+ pg_define_coder( "Float", pg_text_dec_float, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder );
413
+ /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "String", rb_cPG_SimpleDecoder ); */
414
+ pg_define_coder( "String", pg_text_dec_string, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder );
415
+ /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Bytea", rb_cPG_SimpleDecoder ); */
416
+ pg_define_coder( "Bytea", pg_text_dec_bytea, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder );
417
+
418
+ /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Array", rb_cPG_CompositeDecoder ); */
419
+ pg_define_coder( "Array", pg_text_dec_array, rb_cPG_CompositeDecoder, rb_mPG_TextDecoder );
420
+ /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Identifier", rb_cPG_CompositeDecoder ); */
421
+ pg_define_coder( "Identifier", pg_text_dec_identifier, rb_cPG_CompositeDecoder, rb_mPG_TextDecoder );
422
+ /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "FromBase64", rb_cPG_CompositeDecoder ); */
423
+ pg_define_coder( "FromBase64", pg_text_dec_from_base64, rb_cPG_CompositeDecoder, rb_mPG_TextDecoder );
424
+ }
@@ -0,0 +1,608 @@
1
+ /*
2
+ * pg_text_encoder.c - PG::TextEncoder module
3
+ * $Id: pg_text_encoder.c,v ac23631c96d9 2014/10/14 11:50:21 lars $
4
+ *
5
+ */
6
+
7
+ /*
8
+ *
9
+ * Type casts for encoding Ruby objects to PostgreSQL string representations.
10
+ *
11
+ * Encoder classes are defined with pg_define_coder(). This creates a new coder class and
12
+ * assigns an encoder function. The encoder function can decide between two different options
13
+ * to return the encoded data. It can either return it as a Ruby String object or write the
14
+ * encoded data to a memory space provided by the caller. In the second case, the encoder
15
+ * function is called twice, once for deciding the encoding option and returning the expected
16
+ * data length, and a second time when the requested memory space was made available by the
17
+ * calling function, to do the actual conversion and writing. Parameter intermediate can be
18
+ * used to store data between these two calls.
19
+ *
20
+ * Signature of all type cast encoders is:
21
+ * int encoder_function(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate)
22
+ *
23
+ * Params:
24
+ * this - The data part of the coder object that belongs to the encoder function.
25
+ * value - The Ruby object to cast.
26
+ * out - NULL for the first call,
27
+ * pointer to a buffer with the requested size for the second call.
28
+ * intermediate - Pointer to a VALUE that might be set by the encoding function to some
29
+ * value in the first call that can be retrieved later in the second call.
30
+ * This VALUE is not yet initialized by the caller.
31
+ *
32
+ * Returns:
33
+ * >= 0 - If out==NULL the encoder function must return the expected output buffer size.
34
+ * This can be larger than the size of the second call, but may not be smaller.
35
+ * If out!=NULL the encoder function must return the actually used output buffer size
36
+ * without a termination character.
37
+ * -1 - The encoder function can alternatively return -1 to indicate that no second call
38
+ * is required, but the String value in *intermediate should be used instead.
39
+ */
40
+
41
+
42
+ #include "pg.h"
43
+ #include "util.h"
44
+ #include <inttypes.h>
45
+
46
+ VALUE rb_mPG_TextEncoder;
47
+ static ID s_id_encode;
48
+ static ID s_id_to_i;
49
+
50
+
51
+ VALUE
52
+ pg_obj_to_i( VALUE value )
53
+ {
54
+ switch (TYPE(value)) {
55
+ case T_FIXNUM:
56
+ case T_FLOAT:
57
+ case T_BIGNUM:
58
+ return value;
59
+ default:
60
+ return rb_funcall(value, s_id_to_i, 0);
61
+ }
62
+ }
63
+
64
+ /*
65
+ * Document-class: PG::TextEncoder::Boolean < PG::SimpleEncoder
66
+ *
67
+ * This is the encoder class for the PostgreSQL bool type.
68
+ *
69
+ */
70
+
71
+
72
+ /*
73
+ * Document-class: PG::TextEncoder::String < PG::SimpleEncoder
74
+ *
75
+ * This is the encoder class for the PostgreSQL text types.
76
+ *
77
+ * Non-String values are expected to have method +to_s+ defined.
78
+ *
79
+ */
80
+ int
81
+ pg_coder_enc_to_s(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate)
82
+ {
83
+ *intermediate = rb_obj_as_string(value);
84
+ return -1;
85
+ }
86
+
87
+
88
+ /*
89
+ * Document-class: PG::TextEncoder::Integer < PG::SimpleEncoder
90
+ *
91
+ * This is the encoder class for the PostgreSQL int types.
92
+ *
93
+ * Non-Integer values are expected to have method +to_i+ defined.
94
+ *
95
+ */
96
+ static int
97
+ pg_text_enc_integer(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate)
98
+ {
99
+ if(out){
100
+ if(TYPE(*intermediate) == T_STRING){
101
+ return pg_coder_enc_to_s(this, value, out, intermediate);
102
+ }else{
103
+ char *start = out;
104
+ int len;
105
+ int neg = 0;
106
+ long long ll = NUM2LL(*intermediate);
107
+
108
+ if (ll < 0) {
109
+ /* We don't expect problems with the most negative integer not being representable
110
+ * as a positive integer, because Fixnum is only up to 63 bits.
111
+ */
112
+ ll = -ll;
113
+ neg = 1;
114
+ }
115
+
116
+ /* Compute the result string backwards. */
117
+ do {
118
+ long long remainder;
119
+ long long oldval = ll;
120
+
121
+ ll /= 10;
122
+ remainder = oldval - ll * 10;
123
+ *out++ = '0' + remainder;
124
+ } while (ll != 0);
125
+
126
+ if (neg)
127
+ *out++ = '-';
128
+
129
+ len = out - start;
130
+
131
+ /* Reverse string. */
132
+ out--;
133
+ while (start < out)
134
+ {
135
+ char swap = *start;
136
+
137
+ *start++ = *out;
138
+ *out-- = swap;
139
+ }
140
+
141
+ return len;
142
+ }
143
+ }else{
144
+ *intermediate = pg_obj_to_i(value);
145
+ if(TYPE(*intermediate) == T_FIXNUM){
146
+ int len;
147
+ long long sll = NUM2LL(*intermediate);
148
+ long long ll = sll < 0 ? -sll : sll;
149
+ if( ll < 100000000 ){
150
+ if( ll < 10000 ){
151
+ if( ll < 100 ){
152
+ len = ll < 10 ? 1 : 2;
153
+ }else{
154
+ len = ll < 1000 ? 3 : 4;
155
+ }
156
+ }else{
157
+ if( ll < 1000000 ){
158
+ len = ll < 100000 ? 5 : 6;
159
+ }else{
160
+ len = ll < 10000000 ? 7 : 8;
161
+ }
162
+ }
163
+ }else{
164
+ if( ll < 1000000000000LL ){
165
+ if( ll < 10000000000LL ){
166
+ len = ll < 1000000000LL ? 9 : 10;
167
+ }else{
168
+ len = ll < 100000000000LL ? 11 : 12;
169
+ }
170
+ }else{
171
+ if( ll < 100000000000000LL ){
172
+ len = ll < 10000000000000LL ? 13 : 14;
173
+ }else{
174
+ return pg_coder_enc_to_s(this, *intermediate, NULL, intermediate);
175
+ }
176
+ }
177
+ }
178
+ return sll < 0 ? len+1 : len;
179
+ }else{
180
+ return pg_coder_enc_to_s(this, *intermediate, NULL, intermediate);
181
+ }
182
+ }
183
+ }
184
+
185
+
186
+ /*
187
+ * Document-class: PG::TextEncoder::Float < PG::SimpleEncoder
188
+ *
189
+ * This is the encoder class for the PostgreSQL float types.
190
+ *
191
+ */
192
+ static int
193
+ pg_text_enc_float(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate)
194
+ {
195
+ if(out){
196
+ double dvalue = NUM2DBL(value);
197
+ /* Cast to the same strings as value.to_s . */
198
+ if( isinf(dvalue) ){
199
+ if( dvalue < 0 ){
200
+ memcpy( out, "-Infinity", 9);
201
+ return 9;
202
+ } else {
203
+ memcpy( out, "Infinity", 8);
204
+ return 8;
205
+ }
206
+ } else if (isnan(dvalue)) {
207
+ memcpy( out, "NaN", 3);
208
+ return 3;
209
+ }
210
+ return sprintf( out, "%.16E", dvalue);
211
+ }else{
212
+ return 23;
213
+ }
214
+ }
215
+
216
+ /*
217
+ * Case-independent comparison of two not-necessarily-null-terminated strings.
218
+ * At most n bytes will be examined from each string.
219
+ */
220
+ static int
221
+ pg_strncasecmp(const char *s1, const char *s2, size_t n)
222
+ {
223
+ while (n-- > 0)
224
+ {
225
+ unsigned char ch1 = (unsigned char) *s1++;
226
+ unsigned char ch2 = (unsigned char) *s2++;
227
+
228
+ if (ch1 != ch2){
229
+ if (ch1 >= 'A' && ch1 <= 'Z')
230
+ ch1 += 'a' - 'A';
231
+
232
+ if (ch2 >= 'A' && ch2 <= 'Z')
233
+ ch2 += 'a' - 'A';
234
+
235
+ if (ch1 != ch2)
236
+ return (int) ch1 - (int) ch2;
237
+ }
238
+ if (ch1 == 0)
239
+ break;
240
+ }
241
+ return 0;
242
+ }
243
+
244
+ typedef int (*t_quote_func)( void *_this, char *p_in, int strlen, char *p_out );
245
+
246
+ static int
247
+ quote_array_buffer( void *_this, char *p_in, int strlen, char *p_out ){
248
+ t_pg_composite_coder *this = _this;
249
+ char *ptr1;
250
+ char *ptr2;
251
+ int backslashs = 0;
252
+ int needquote;
253
+
254
+ /* count data plus backslashes; detect chars needing quotes */
255
+ if (strlen == 0)
256
+ needquote = 1; /* force quotes for empty string */
257
+ else if (strlen == 4 && pg_strncasecmp(p_in, "NULL", strlen) == 0)
258
+ needquote = 1; /* force quotes for literal NULL */
259
+ else
260
+ needquote = 0;
261
+
262
+ /* count required backlashs */
263
+ for(ptr1 = p_in; ptr1 != p_in + strlen; ptr1++) {
264
+ char ch = *ptr1;
265
+
266
+ if (ch == '"' || ch == '\\'){
267
+ needquote = 1;
268
+ backslashs++;
269
+ } else if (ch == '{' || ch == '}' || ch == this->delimiter ||
270
+ ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f'){
271
+ needquote = 1;
272
+ }
273
+ }
274
+
275
+ if( needquote ){
276
+ ptr1 = p_in + strlen;
277
+ ptr2 = p_out + strlen + backslashs + 2;
278
+ /* Write end quote */
279
+ *--ptr2 = '"';
280
+
281
+ /* Then store the escaped string on the final position, walking
282
+ * right to left, until all backslashs are placed. */
283
+ while( ptr1 != p_in ) {
284
+ *--ptr2 = *--ptr1;
285
+ if(*ptr2 == '"' || *ptr2 == '\\'){
286
+ *--ptr2 = '\\';
287
+ }
288
+ }
289
+ /* Write start quote */
290
+ *p_out = '"';
291
+ return strlen + backslashs + 2;
292
+ } else {
293
+ if( p_in != p_out )
294
+ memcpy( p_out, p_in, strlen );
295
+ return strlen;
296
+ }
297
+ }
298
+
299
+ static char *
300
+ quote_string(t_pg_coder *this, VALUE value, VALUE string, char *current_out, int with_quote, t_quote_func quote_buffer, void *func_data)
301
+ {
302
+ int strlen;
303
+ VALUE subint;
304
+ t_pg_coder_enc_func enc_func = pg_coder_enc_func(this);
305
+
306
+ strlen = enc_func(this, value, NULL, &subint);
307
+
308
+ if( strlen == -1 ){
309
+ /* we can directly use String value in subint */
310
+ strlen = RSTRING_LENINT(subint);
311
+
312
+ if(with_quote){
313
+ /* size of string assuming the worst case, that every character must be escaped. */
314
+ current_out = pg_rb_str_ensure_capa( string, strlen * 2 + 2, current_out, NULL );
315
+
316
+ current_out += quote_buffer( func_data, RSTRING_PTR(subint), strlen, current_out );
317
+ } else {
318
+ current_out = pg_rb_str_ensure_capa( string, strlen, current_out, NULL );
319
+ memcpy( current_out, RSTRING_PTR(subint), strlen );
320
+ current_out += strlen;
321
+ }
322
+
323
+ } else {
324
+
325
+ if(with_quote){
326
+ /* size of string assuming the worst case, that every character must be escaped
327
+ * plus two bytes for quotation.
328
+ */
329
+ current_out = pg_rb_str_ensure_capa( string, 2 * strlen + 2, current_out, NULL );
330
+
331
+ /* Place the unescaped string at current output position. */
332
+ strlen = enc_func(this, value, current_out, &subint);
333
+
334
+ current_out += quote_buffer( func_data, current_out, strlen, current_out );
335
+ }else{
336
+ /* size of the unquoted string */
337
+ current_out = pg_rb_str_ensure_capa( string, strlen, current_out, NULL );
338
+ current_out += enc_func(this, value, current_out, &subint);
339
+ }
340
+ }
341
+ return current_out;
342
+ }
343
+
344
+ static char *
345
+ write_array(t_pg_composite_coder *this, VALUE value, char *current_out, VALUE string, int quote)
346
+ {
347
+ int i;
348
+
349
+ /* size of "{}" */
350
+ current_out = pg_rb_str_ensure_capa( string, 2, current_out, NULL );
351
+ *current_out++ = '{';
352
+
353
+ for( i=0; i<RARRAY_LEN(value); i++){
354
+ VALUE entry = rb_ary_entry(value, i);
355
+
356
+ if( i > 0 ){
357
+ current_out = pg_rb_str_ensure_capa( string, 1, current_out, NULL );
358
+ *current_out++ = this->delimiter;
359
+ }
360
+
361
+ switch(TYPE(entry)){
362
+ case T_ARRAY:
363
+ current_out = write_array(this, entry, current_out, string, quote);
364
+ break;
365
+ case T_NIL:
366
+ current_out = pg_rb_str_ensure_capa( string, 4, current_out, NULL );
367
+ *current_out++ = 'N';
368
+ *current_out++ = 'U';
369
+ *current_out++ = 'L';
370
+ *current_out++ = 'L';
371
+ break;
372
+ default:
373
+ current_out = quote_string( this->elem, entry, string, current_out, quote, quote_array_buffer, this );
374
+ }
375
+ }
376
+ current_out = pg_rb_str_ensure_capa( string, 1, current_out, NULL );
377
+ *current_out++ = '}';
378
+ return current_out;
379
+ }
380
+
381
+
382
+ /*
383
+ * Document-class: PG::TextEncoder::Array < PG::CompositeEncoder
384
+ *
385
+ * This is the encoder class for PostgreSQL array types.
386
+ *
387
+ * All values are encoded according to the #elements_type
388
+ * accessor. Sub-arrays are encoded recursively.
389
+ *
390
+ */
391
+ static int
392
+ pg_text_enc_array(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate)
393
+ {
394
+ char *end_ptr;
395
+ t_pg_composite_coder *this = (t_pg_composite_coder *)conv;
396
+ *intermediate = rb_str_new(NULL, 0);
397
+
398
+ end_ptr = write_array(this, value, RSTRING_PTR(*intermediate), *intermediate, this->needs_quotation);
399
+
400
+ rb_str_set_len( *intermediate, end_ptr - RSTRING_PTR(*intermediate) );
401
+
402
+ return -1;
403
+ }
404
+
405
+ static int
406
+ quote_identifier_buffer( void *_this, char *p_in, int strlen, char *p_out ){
407
+ char *ptr1;
408
+ char *ptr2;
409
+ int backslashs = 0;
410
+
411
+ /* count required backlashs */
412
+ for(ptr1 = p_in; ptr1 != p_in + strlen; ptr1++) {
413
+ if (*ptr1 == '"'){
414
+ backslashs++;
415
+ }
416
+ }
417
+
418
+ ptr1 = p_in + strlen;
419
+ ptr2 = p_out + strlen + backslashs + 2;
420
+ /* Write end quote */
421
+ *--ptr2 = '"';
422
+
423
+ /* Then store the escaped string on the final position, walking
424
+ * right to left, until all backslashs are placed. */
425
+ while( ptr1 != p_in ) {
426
+ *--ptr2 = *--ptr1;
427
+ if(*ptr2 == '"'){
428
+ *--ptr2 = '"';
429
+ }
430
+ }
431
+ /* Write start quote */
432
+ *p_out = '"';
433
+ return strlen + backslashs + 2;
434
+ }
435
+
436
+ static char *
437
+ pg_text_enc_array_identifier(t_pg_composite_coder *this, VALUE value, VALUE string, char *out)
438
+ {
439
+ int i;
440
+ int nr_elems;
441
+
442
+ Check_Type(value, T_ARRAY);
443
+ nr_elems = RARRAY_LEN(value);
444
+
445
+ for( i=0; i<nr_elems; i++){
446
+ VALUE entry = rb_ary_entry(value, i);
447
+
448
+ out = quote_string(this->elem, entry, string, out, this->needs_quotation, quote_identifier_buffer, this);
449
+ if( i < nr_elems-1 ){
450
+ out = pg_rb_str_ensure_capa( string, 1, out, NULL );
451
+ *out++ = '.';
452
+ }
453
+ }
454
+ return out;
455
+ }
456
+
457
+ /*
458
+ * Document-class: PG::TextEncoder::Identifier < PG::CompositeEncoder
459
+ *
460
+ * This is the encoder class for PostgreSQL identifiers.
461
+ *
462
+ * An Array value can be used for "schema.table.column" type identifiers:
463
+ * PG::TextEncoder::Identifier.new.encode(['schema', 'table', 'column'])
464
+ * => "schema"."table"."column"
465
+ *
466
+ */
467
+ static int
468
+ pg_text_enc_identifier(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate)
469
+ {
470
+ t_pg_composite_coder *this = (t_pg_composite_coder *)conv;
471
+
472
+ *intermediate = rb_str_new(NULL, 0);
473
+ out = RSTRING_PTR(*intermediate);
474
+
475
+ if( TYPE(value) == T_ARRAY){
476
+ out = pg_text_enc_array_identifier(this, value, *intermediate, out);
477
+ } else {
478
+ out = quote_string(this->elem, value, *intermediate, out, this->needs_quotation, quote_identifier_buffer, this);
479
+ }
480
+ rb_str_set_len( *intermediate, out - RSTRING_PTR(*intermediate) );
481
+ return -1;
482
+ }
483
+
484
+
485
+ static int
486
+ quote_literal_buffer( void *_this, char *p_in, int strlen, char *p_out ){
487
+ char *ptr1;
488
+ char *ptr2;
489
+ int backslashs = 0;
490
+
491
+ /* count required backlashs */
492
+ for(ptr1 = p_in; ptr1 != p_in + strlen; ptr1++) {
493
+ if (*ptr1 == '\''){
494
+ backslashs++;
495
+ }
496
+ }
497
+
498
+ ptr1 = p_in + strlen;
499
+ ptr2 = p_out + strlen + backslashs + 2;
500
+ /* Write end quote */
501
+ *--ptr2 = '\'';
502
+
503
+ /* Then store the escaped string on the final position, walking
504
+ * right to left, until all backslashs are placed. */
505
+ while( ptr1 != p_in ) {
506
+ *--ptr2 = *--ptr1;
507
+ if(*ptr2 == '\''){
508
+ *--ptr2 = '\'';
509
+ }
510
+ }
511
+ /* Write start quote */
512
+ *p_out = '\'';
513
+ return strlen + backslashs + 2;
514
+ }
515
+
516
+
517
+ /*
518
+ * Document-class: PG::TextEncoder::QuotedLiteral < PG::CompositeEncoder
519
+ *
520
+ * This is the encoder class for PostgreSQL literals.
521
+ *
522
+ * A literal is quoted and escaped by the +'+ character.
523
+ *
524
+ */
525
+ static int
526
+ pg_text_enc_quoted_literal(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate)
527
+ {
528
+ t_pg_composite_coder *this = (t_pg_composite_coder *)conv;
529
+
530
+ *intermediate = rb_str_new(NULL, 0);
531
+ out = RSTRING_PTR(*intermediate);
532
+ out = quote_string(this->elem, value, *intermediate, out, this->needs_quotation, quote_literal_buffer, this);
533
+ rb_str_set_len( *intermediate, out - RSTRING_PTR(*intermediate) );
534
+ return -1;
535
+ }
536
+
537
+ /*
538
+ * Document-class: PG::TextEncoder::ToBase64 < PG::CompositeEncoder
539
+ *
540
+ * This is an encoder class for conversion of binary to base64 data.
541
+ *
542
+ */
543
+ static int
544
+ pg_text_enc_to_base64(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate)
545
+ {
546
+ int strlen;
547
+ VALUE subint;
548
+ t_pg_composite_coder *this = (t_pg_composite_coder *)conv;
549
+ t_pg_coder_enc_func enc_func = pg_coder_enc_func(this->elem);
550
+
551
+ if(out){
552
+ /* Second encoder pass, if required */
553
+ strlen = enc_func(this->elem, value, out, intermediate);
554
+ base64_encode( out, out, strlen );
555
+
556
+ return BASE64_ENCODED_SIZE(strlen);
557
+ } else {
558
+ /* First encoder pass */
559
+ strlen = enc_func(this->elem, value, NULL, &subint);
560
+
561
+ if( strlen == -1 ){
562
+ /* Encoded string is returned in subint */
563
+ VALUE out_str;
564
+
565
+ strlen = RSTRING_LENINT(subint);
566
+ out_str = rb_str_new(NULL, BASE64_ENCODED_SIZE(strlen));
567
+
568
+ base64_encode( RSTRING_PTR(out_str), RSTRING_PTR(subint), strlen);
569
+ *intermediate = out_str;
570
+
571
+ return -1;
572
+ } else {
573
+ *intermediate = subint;
574
+
575
+ return BASE64_ENCODED_SIZE(strlen);
576
+ }
577
+ }
578
+ }
579
+
580
+
581
+ void
582
+ init_pg_text_encoder()
583
+ {
584
+ s_id_encode = rb_intern("encode");
585
+ s_id_to_i = rb_intern("to_i");
586
+
587
+ /* This module encapsulates all encoder classes with text output format */
588
+ rb_mPG_TextEncoder = rb_define_module_under( rb_mPG, "TextEncoder" );
589
+
590
+ /* Make RDoc aware of the encoder classes... */
591
+ /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Boolean", rb_cPG_SimpleEncoder ); */
592
+ pg_define_coder( "Boolean", pg_coder_enc_to_s, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder );
593
+ /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Integer", rb_cPG_SimpleEncoder ); */
594
+ pg_define_coder( "Integer", pg_text_enc_integer, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder );
595
+ /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Float", rb_cPG_SimpleEncoder ); */
596
+ pg_define_coder( "Float", pg_text_enc_float, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder );
597
+ /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "String", rb_cPG_SimpleEncoder ); */
598
+ pg_define_coder( "String", pg_coder_enc_to_s, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder );
599
+
600
+ /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Array", rb_cPG_CompositeEncoder ); */
601
+ pg_define_coder( "Array", pg_text_enc_array, rb_cPG_CompositeEncoder, rb_mPG_TextEncoder );
602
+ /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Identifier", rb_cPG_CompositeEncoder ); */
603
+ pg_define_coder( "Identifier", pg_text_enc_identifier, rb_cPG_CompositeEncoder, rb_mPG_TextEncoder );
604
+ /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "QuotedLiteral", rb_cPG_CompositeEncoder ); */
605
+ pg_define_coder( "QuotedLiteral", pg_text_enc_quoted_literal, rb_cPG_CompositeEncoder, rb_mPG_TextEncoder );
606
+ /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "ToBase64", rb_cPG_CompositeEncoder ); */
607
+ pg_define_coder( "ToBase64", pg_text_enc_to_base64, rb_cPG_CompositeEncoder, rb_mPG_TextEncoder );
608
+ }