pg 0.21.0 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +0 -6595
- data/History.rdoc +184 -0
- data/Manifest.txt +8 -3
- data/README-Windows.rdoc +4 -4
- data/README.ja.rdoc +1 -2
- data/README.rdoc +58 -13
- data/Rakefile +10 -9
- data/Rakefile.cross +68 -71
- data/ext/errorcodes.def +76 -0
- data/ext/errorcodes.rb +1 -1
- data/ext/errorcodes.txt +21 -2
- data/ext/extconf.rb +18 -36
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +23 -39
- data/ext/pg.c +154 -144
- data/ext/pg.h +68 -95
- data/ext/pg_binary_decoder.c +82 -15
- data/ext/pg_binary_encoder.c +13 -12
- data/ext/pg_coder.c +73 -12
- data/ext/pg_connection.c +699 -459
- data/ext/pg_copy_coder.c +16 -8
- data/ext/pg_record_coder.c +491 -0
- data/ext/pg_result.c +571 -195
- data/ext/pg_text_decoder.c +606 -40
- data/ext/pg_text_encoder.c +185 -54
- data/ext/pg_tuple.c +549 -0
- data/ext/pg_type_map.c +1 -1
- data/ext/pg_type_map_all_strings.c +4 -4
- data/ext/pg_type_map_by_class.c +9 -4
- data/ext/pg_type_map_by_column.c +7 -6
- data/ext/pg_type_map_by_mri_type.c +1 -1
- data/ext/pg_type_map_by_oid.c +3 -2
- data/ext/pg_type_map_in_ruby.c +1 -1
- data/ext/{util.c → pg_util.c} +10 -10
- data/ext/{util.h → pg_util.h} +2 -2
- data/lib/pg.rb +8 -10
- data/lib/pg/basic_type_mapping.rb +121 -25
- data/lib/pg/binary_decoder.rb +23 -0
- data/lib/pg/coder.rb +23 -2
- data/lib/pg/connection.rb +28 -4
- data/lib/pg/constants.rb +2 -1
- data/lib/pg/exceptions.rb +2 -1
- data/lib/pg/result.rb +14 -2
- data/lib/pg/text_decoder.rb +21 -26
- data/lib/pg/text_encoder.rb +32 -8
- data/lib/pg/tuple.rb +30 -0
- data/lib/pg/type_map_by_column.rb +3 -2
- data/spec/helpers.rb +61 -33
- data/spec/pg/basic_type_mapping_spec.rb +362 -37
- data/spec/pg/connection_spec.rb +602 -329
- data/spec/pg/connection_sync_spec.rb +41 -0
- data/spec/pg/result_spec.rb +242 -17
- data/spec/pg/tuple_spec.rb +333 -0
- data/spec/pg/type_map_by_class_spec.rb +2 -2
- data/spec/pg/type_map_by_column_spec.rb +6 -2
- data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
- data/spec/pg/type_map_by_oid_spec.rb +3 -3
- data/spec/pg/type_map_in_ruby_spec.rb +1 -1
- data/spec/pg/type_map_spec.rb +1 -1
- data/spec/pg/type_spec.rb +364 -18
- data/spec/pg_spec.rb +2 -2
- metadata +48 -43
- metadata.gz.sig +0 -0
- data/lib/pg/deprecated_constants.rb +0 -21
data/ext/pg_text_encoder.c
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/*
|
2
2
|
* pg_text_encoder.c - PG::TextEncoder module
|
3
|
-
* $Id
|
3
|
+
* $Id$
|
4
4
|
*
|
5
5
|
*/
|
6
6
|
|
@@ -41,7 +41,7 @@
|
|
41
41
|
|
42
42
|
|
43
43
|
#include "pg.h"
|
44
|
-
#include "
|
44
|
+
#include "pg_util.h"
|
45
45
|
#ifdef HAVE_INTTYPES_H
|
46
46
|
#include <inttypes.h>
|
47
47
|
#endif
|
@@ -50,6 +50,9 @@
|
|
50
50
|
VALUE rb_mPG_TextEncoder;
|
51
51
|
static ID s_id_encode;
|
52
52
|
static ID s_id_to_i;
|
53
|
+
static ID s_id_to_s;
|
54
|
+
static ID s_cBigDecimal;
|
55
|
+
static VALUE s_str_F;
|
53
56
|
|
54
57
|
static int pg_text_enc_integer(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate, int enc_idx);
|
55
58
|
|
@@ -125,11 +128,29 @@ pg_coder_enc_to_s(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate,
|
|
125
128
|
return -1;
|
126
129
|
}
|
127
130
|
|
131
|
+
static int
|
132
|
+
count_leading_zero_bits(unsigned long long x)
|
133
|
+
{
|
134
|
+
#if defined(__GNUC__) || defined(__clang__)
|
135
|
+
return __builtin_clzll(x);
|
136
|
+
#elif defined(_MSC_VER)
|
137
|
+
DWORD r = 0;
|
138
|
+
_BitScanForward64(&r, x);
|
139
|
+
return (int)r;
|
140
|
+
#else
|
141
|
+
unsigned int a;
|
142
|
+
for(a=0; a < sizeof(unsigned long long) * 8; a++){
|
143
|
+
if( x & (1 << (sizeof(unsigned long long) * 8 - 1))) return a;
|
144
|
+
x <<= 1;
|
145
|
+
}
|
146
|
+
return a;
|
147
|
+
#endif
|
148
|
+
}
|
128
149
|
|
129
150
|
/*
|
130
151
|
* Document-class: PG::TextEncoder::Integer < PG::SimpleEncoder
|
131
152
|
*
|
132
|
-
* This is the encoder class for the PostgreSQL
|
153
|
+
* This is the encoder class for the PostgreSQL integer types.
|
133
154
|
*
|
134
155
|
* Non-Integer values are expected to have method +to_i+ defined.
|
135
156
|
*
|
@@ -144,20 +165,23 @@ pg_text_enc_integer(t_pg_coder *this, VALUE value, char *out, VALUE *intermediat
|
|
144
165
|
char *start = out;
|
145
166
|
int len;
|
146
167
|
int neg = 0;
|
147
|
-
long long
|
168
|
+
long long sll = NUM2LL(*intermediate);
|
169
|
+
unsigned long long ll;
|
148
170
|
|
149
|
-
if (
|
150
|
-
/*
|
151
|
-
* as a positive integer,
|
171
|
+
if (sll < 0) {
|
172
|
+
/* Avoid problems with the most negative integer not being representable
|
173
|
+
* as a positive integer, by using unsigned long long for encoding.
|
152
174
|
*/
|
153
|
-
ll = -
|
175
|
+
ll = -sll;
|
154
176
|
neg = 1;
|
177
|
+
} else {
|
178
|
+
ll = sll;
|
155
179
|
}
|
156
180
|
|
157
181
|
/* Compute the result string backwards. */
|
158
182
|
do {
|
159
|
-
long long remainder;
|
160
|
-
long long oldval = ll;
|
183
|
+
unsigned long long remainder;
|
184
|
+
unsigned long long oldval = ll;
|
161
185
|
|
162
186
|
ll /= 10;
|
163
187
|
remainder = oldval - ll * 10;
|
@@ -184,45 +208,17 @@ pg_text_enc_integer(t_pg_coder *this, VALUE value, char *out, VALUE *intermediat
|
|
184
208
|
}else{
|
185
209
|
*intermediate = pg_obj_to_i(value);
|
186
210
|
if(TYPE(*intermediate) == T_FIXNUM){
|
187
|
-
int len;
|
188
211
|
long long sll = NUM2LL(*intermediate);
|
189
|
-
long long ll = sll < 0 ? -sll : sll;
|
190
|
-
|
191
|
-
|
192
|
-
if( ll < 100 ){
|
193
|
-
len = ll < 10 ? 1 : 2;
|
194
|
-
}else{
|
195
|
-
len = ll < 1000 ? 3 : 4;
|
196
|
-
}
|
197
|
-
}else{
|
198
|
-
if( ll < 1000000 ){
|
199
|
-
len = ll < 100000 ? 5 : 6;
|
200
|
-
}else{
|
201
|
-
len = ll < 10000000 ? 7 : 8;
|
202
|
-
}
|
203
|
-
}
|
204
|
-
}else{
|
205
|
-
if( ll < 1000000000000LL ){
|
206
|
-
if( ll < 10000000000LL ){
|
207
|
-
len = ll < 1000000000LL ? 9 : 10;
|
208
|
-
}else{
|
209
|
-
len = ll < 100000000000LL ? 11 : 12;
|
210
|
-
}
|
211
|
-
}else{
|
212
|
-
if( ll < 100000000000000LL ){
|
213
|
-
len = ll < 10000000000000LL ? 13 : 14;
|
214
|
-
}else{
|
215
|
-
return pg_coder_enc_to_s(this, *intermediate, NULL, intermediate, enc_idx);
|
216
|
-
}
|
217
|
-
}
|
218
|
-
}
|
219
|
-
return sll < 0 ? len+1 : len;
|
212
|
+
unsigned long long ll = sll < 0 ? -sll : sll;
|
213
|
+
int len = (sizeof(unsigned long long) * 8 - count_leading_zero_bits(ll)) / 3;
|
214
|
+
return sll < 0 ? len+2 : len+1;
|
220
215
|
}else{
|
221
216
|
return pg_coder_enc_to_s(this, *intermediate, NULL, intermediate, enc_idx);
|
222
217
|
}
|
223
218
|
}
|
224
219
|
}
|
225
220
|
|
221
|
+
#define MAX_DOUBLE_DIGITS 16
|
226
222
|
|
227
223
|
/*
|
228
224
|
* Document-class: PG::TextEncoder::Float < PG::SimpleEncoder
|
@@ -235,6 +231,12 @@ pg_text_enc_float(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate,
|
|
235
231
|
{
|
236
232
|
if(out){
|
237
233
|
double dvalue = NUM2DBL(value);
|
234
|
+
int len = 0;
|
235
|
+
int neg = 0;
|
236
|
+
int exp2i, exp10i, i;
|
237
|
+
unsigned long long ll, remainder, oldval;
|
238
|
+
VALUE intermediate;
|
239
|
+
|
238
240
|
/* Cast to the same strings as value.to_s . */
|
239
241
|
if( isinf(dvalue) ){
|
240
242
|
if( dvalue < 0 ){
|
@@ -248,12 +250,128 @@ pg_text_enc_float(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate,
|
|
248
250
|
memcpy( out, "NaN", 3);
|
249
251
|
return 3;
|
250
252
|
}
|
251
|
-
|
253
|
+
|
254
|
+
/*
|
255
|
+
* The following computaion is roughly a conversion kind of
|
256
|
+
* sprintf( out, "%.16E", dvalue);
|
257
|
+
*/
|
258
|
+
|
259
|
+
/* write the algebraic sign */
|
260
|
+
if( dvalue < 0 ) {
|
261
|
+
dvalue = -dvalue;
|
262
|
+
*out++ = '-';
|
263
|
+
neg++;
|
264
|
+
}
|
265
|
+
|
266
|
+
/* retrieve the power of 2 exponent */
|
267
|
+
frexp(dvalue, &exp2i);
|
268
|
+
/* compute the power of 10 exponent */
|
269
|
+
exp10i = (int)floor(exp2i * 0.30102999566398114); /* Math.log(2)/Math.log(10) */
|
270
|
+
/* move the decimal point, so that we get an integer of MAX_DOUBLE_DIGITS decimal digits */
|
271
|
+
ll = (unsigned long long)(dvalue * pow(10, MAX_DOUBLE_DIGITS - 1 - exp10i) + 0.5);
|
272
|
+
|
273
|
+
/* avoid leading zeros due to inaccuracy of deriving exp10i from exp2i */
|
274
|
+
/* otherwise we would print "09.0" instead of "9.0" */
|
275
|
+
if( ll < 1000000000000000 ){ /* pow(10, MAX_DOUBLE_DIGITS-1) */
|
276
|
+
exp10i--;
|
277
|
+
ll *= 10;
|
278
|
+
}
|
279
|
+
|
280
|
+
if( exp10i <= -5 || exp10i >= 15 ) {
|
281
|
+
/* Write the float in exponent format (1.23e45) */
|
282
|
+
|
283
|
+
/* write fraction digits from right to left */
|
284
|
+
for( i = MAX_DOUBLE_DIGITS; i > 1; i--){
|
285
|
+
oldval = ll;
|
286
|
+
ll /= 10;
|
287
|
+
remainder = oldval - ll * 10;
|
288
|
+
/* omit trailing zeros */
|
289
|
+
if(remainder != 0 || len ) {
|
290
|
+
out[i] = '0' + remainder;
|
291
|
+
len++;
|
292
|
+
}
|
293
|
+
}
|
294
|
+
|
295
|
+
/* write decimal point */
|
296
|
+
if( len ){
|
297
|
+
out[1] = '.';
|
298
|
+
len++;
|
299
|
+
}
|
300
|
+
|
301
|
+
/* write remaining single digit left to the decimal point */
|
302
|
+
oldval = ll;
|
303
|
+
ll /= 10;
|
304
|
+
remainder = oldval - ll * 10;
|
305
|
+
out[0] = '0' + remainder;
|
306
|
+
len++;
|
307
|
+
|
308
|
+
/* write exponent */
|
309
|
+
out[len++] = 'e';
|
310
|
+
intermediate = INT2NUM(exp10i);
|
311
|
+
|
312
|
+
return neg + len + pg_text_enc_integer(conv, Qnil, out + len, &intermediate, enc_idx);
|
313
|
+
} else {
|
314
|
+
/* write the float in non exponent format (0.001234 or 123450.0) */
|
315
|
+
|
316
|
+
/* write digits from right to left */
|
317
|
+
int lz = exp10i < 0 ? 0 : exp10i;
|
318
|
+
for( i = MAX_DOUBLE_DIGITS - (exp10i < 0 ? exp10i : 0); i >= 0; i-- ){
|
319
|
+
oldval = ll;
|
320
|
+
ll /= 10;
|
321
|
+
remainder = oldval - ll * 10;
|
322
|
+
/* write decimal point */
|
323
|
+
if( i - 1 == lz ){
|
324
|
+
out[i--] = '.';
|
325
|
+
len++;
|
326
|
+
}
|
327
|
+
/* if possible then omit trailing zeros */
|
328
|
+
if(remainder != 0 || len || i - 2 == lz) {
|
329
|
+
out[i] = '0' + remainder;
|
330
|
+
len++;
|
331
|
+
}
|
332
|
+
}
|
333
|
+
return neg + len;
|
334
|
+
}
|
252
335
|
}else{
|
253
|
-
return
|
336
|
+
return 1 /*sign*/ + MAX_DOUBLE_DIGITS + 1 /*dot*/ + 1 /*e*/ + 1 /*exp sign*/ + 3 /*exp digits*/;
|
254
337
|
}
|
255
338
|
}
|
256
339
|
|
340
|
+
|
341
|
+
/*
|
342
|
+
* Document-class: PG::TextEncoder::Numeric < PG::SimpleEncoder
|
343
|
+
*
|
344
|
+
* This is the encoder class for the PostgreSQL numeric types.
|
345
|
+
*
|
346
|
+
* It converts Integer, Float and BigDecimal objects.
|
347
|
+
* All other objects are expected to respond to +to_s+.
|
348
|
+
*/
|
349
|
+
static int
|
350
|
+
pg_text_enc_numeric(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate, int enc_idx)
|
351
|
+
{
|
352
|
+
switch(TYPE(value)){
|
353
|
+
case T_FIXNUM:
|
354
|
+
case T_BIGNUM:
|
355
|
+
return pg_text_enc_integer(this, value, out, intermediate, enc_idx);
|
356
|
+
case T_FLOAT:
|
357
|
+
return pg_text_enc_float(this, value, out, intermediate, enc_idx);
|
358
|
+
default:
|
359
|
+
if(out){ /* second pass */
|
360
|
+
rb_bug("unexpected value type: %d", TYPE(value));
|
361
|
+
} else { /* first pass */
|
362
|
+
if( rb_obj_is_kind_of(value, s_cBigDecimal) ){
|
363
|
+
/* value.to_s('F') */
|
364
|
+
*intermediate = rb_funcall(value, s_id_to_s, 1, s_str_F);
|
365
|
+
return -1; /* no second pass */
|
366
|
+
} else {
|
367
|
+
return pg_coder_enc_to_s(this, value, NULL, intermediate, enc_idx);
|
368
|
+
/* no second pass */
|
369
|
+
}
|
370
|
+
}
|
371
|
+
}
|
372
|
+
}
|
373
|
+
|
374
|
+
|
257
375
|
static const char hextab[] = {
|
258
376
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
|
259
377
|
};
|
@@ -261,8 +379,7 @@ static const char hextab[] = {
|
|
261
379
|
/*
|
262
380
|
* Document-class: PG::TextEncoder::Bytea < PG::SimpleEncoder
|
263
381
|
*
|
264
|
-
* This is an encoder class for the PostgreSQL bytea type
|
265
|
-
* or newer.
|
382
|
+
* This is an encoder class for the PostgreSQL +bytea+ type.
|
266
383
|
*
|
267
384
|
* The binary String is converted to hexadecimal representation for transmission
|
268
385
|
* in text format. For query bind parameters it is recommended to use
|
@@ -468,20 +585,19 @@ pg_text_enc_array(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate,
|
|
468
585
|
static char *
|
469
586
|
quote_identifier( VALUE value, VALUE out_string, char *current_out ){
|
470
587
|
char *p_in = RSTRING_PTR(value);
|
471
|
-
char *ptr1;
|
472
588
|
size_t strlen = RSTRING_LEN(value);
|
589
|
+
char *p_inend = p_in + strlen;
|
473
590
|
char *end_capa = current_out;
|
474
591
|
|
475
592
|
PG_RB_STR_ENSURE_CAPA( out_string, strlen + 2, current_out, end_capa );
|
476
593
|
*current_out++ = '"';
|
477
|
-
for(
|
478
|
-
char c = *
|
594
|
+
for(; p_in != p_inend; p_in++) {
|
595
|
+
char c = *p_in;
|
479
596
|
if (c == '"'){
|
480
|
-
|
481
|
-
PG_RB_STR_ENSURE_CAPA( out_string, p_in - ptr1 + strlen + 1, current_out, end_capa );
|
597
|
+
PG_RB_STR_ENSURE_CAPA( out_string, p_inend - p_in + 2, current_out, end_capa );
|
482
598
|
*current_out++ = '"';
|
483
599
|
} else if (c == 0){
|
484
|
-
|
600
|
+
rb_raise(rb_eArgError, "string contains null byte");
|
485
601
|
}
|
486
602
|
*current_out++ = c;
|
487
603
|
}
|
@@ -521,7 +637,8 @@ pg_text_enc_array_identifier(VALUE value, VALUE string, char *out, int enc_idx)
|
|
521
637
|
*
|
522
638
|
* This is the encoder class for PostgreSQL identifiers.
|
523
639
|
*
|
524
|
-
* An Array value can be used for "schema.table.column"
|
640
|
+
* An Array value can be used for identifiers of the kind "schema.table.column".
|
641
|
+
* This ensures that each element is properly quoted:
|
525
642
|
* PG::TextEncoder::Identifier.new.encode(['schema', 'table', 'column'])
|
526
643
|
* => '"schema"."table"."column"'
|
527
644
|
*
|
@@ -589,7 +706,13 @@ quote_literal_buffer( void *_this, char *p_in, int strlen, char *p_out ){
|
|
589
706
|
*
|
590
707
|
* This is the encoder class for PostgreSQL literals.
|
591
708
|
*
|
592
|
-
* A literal is quoted and escaped by the
|
709
|
+
* A literal is quoted and escaped by the <tt>'</tt> character, so that it can be inserted into SQL queries.
|
710
|
+
* It works equal to PG::Connection#escape_literal, but integrates into the type cast system of ruby-pg.
|
711
|
+
*
|
712
|
+
* Both expressions have the same result:
|
713
|
+
* conn.escape_literal(PG::TextEncoder::Array.new.encode(["v1","v2"])) # => "'{v1,v2}'"
|
714
|
+
* PG::TextEncoder::QuotedLiteral.new(elements_type: PG::TextEncoder::Array.new).encode(["v1","v2"]) # => "'{v1,v2}'"
|
715
|
+
* While escape_literal requires a intermediate ruby string allocation, QuotedLiteral encodes the values directly to the result string.
|
593
716
|
*
|
594
717
|
*/
|
595
718
|
static int
|
@@ -656,6 +779,12 @@ init_pg_text_encoder()
|
|
656
779
|
{
|
657
780
|
s_id_encode = rb_intern("encode");
|
658
781
|
s_id_to_i = rb_intern("to_i");
|
782
|
+
s_id_to_s = rb_intern("to_s");
|
783
|
+
s_str_F = rb_str_freeze(rb_str_new_cstr("F"));
|
784
|
+
rb_global_variable(&s_str_F);
|
785
|
+
rb_require("bigdecimal");
|
786
|
+
s_cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
|
787
|
+
|
659
788
|
|
660
789
|
/* This module encapsulates all encoder classes with text output format */
|
661
790
|
rb_mPG_TextEncoder = rb_define_module_under( rb_mPG, "TextEncoder" );
|
@@ -667,6 +796,8 @@ init_pg_text_encoder()
|
|
667
796
|
pg_define_coder( "Integer", pg_text_enc_integer, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder );
|
668
797
|
/* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Float", rb_cPG_SimpleEncoder ); */
|
669
798
|
pg_define_coder( "Float", pg_text_enc_float, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder );
|
799
|
+
/* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Numeric", rb_cPG_SimpleEncoder ); */
|
800
|
+
pg_define_coder( "Numeric", pg_text_enc_numeric, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder );
|
670
801
|
/* dummy = rb_define_class_under( rb_mPG_TextEncoder, "String", rb_cPG_SimpleEncoder ); */
|
671
802
|
pg_define_coder( "String", pg_coder_enc_to_s, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder );
|
672
803
|
/* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Bytea", rb_cPG_SimpleEncoder ); */
|
data/ext/pg_tuple.c
ADDED
@@ -0,0 +1,549 @@
|
|
1
|
+
#include "pg.h"
|
2
|
+
|
3
|
+
/********************************************************************
|
4
|
+
*
|
5
|
+
* Document-class: PG::Tuple
|
6
|
+
*
|
7
|
+
* The class to represent one query result tuple (row).
|
8
|
+
* An instance of this class can be created by PG::Result#tuple .
|
9
|
+
*
|
10
|
+
* All field values of the tuple are retrieved on demand from the underlying PGresult object and converted to a Ruby object.
|
11
|
+
* Subsequent access to the same field returns the same object, since they are cached when materialized.
|
12
|
+
* Each PG::Tuple holds a reference to the related PG::Result object, but gets detached, when all fields are materialized.
|
13
|
+
*
|
14
|
+
* Example:
|
15
|
+
* require 'pg'
|
16
|
+
* conn = PG.connect(:dbname => 'test')
|
17
|
+
* res = conn.exec('VALUES(1,2), (3,4)')
|
18
|
+
* t0 = res.tuple(0) # => #<PG::Tuple column1: "1", column2: "2">
|
19
|
+
* t1 = res.tuple(1) # => #<PG::Tuple column1: "3", column2: "4">
|
20
|
+
* t1[0] # => "3"
|
21
|
+
* t1["column2"] # => "4"
|
22
|
+
*/
|
23
|
+
|
24
|
+
static VALUE rb_cPG_Tuple;
|
25
|
+
|
26
|
+
typedef struct {
|
27
|
+
/* PG::Result object this tuple was retrieved from.
|
28
|
+
* Qnil when all fields are materialized.
|
29
|
+
*/
|
30
|
+
VALUE result;
|
31
|
+
|
32
|
+
/* Store the typemap of the result.
|
33
|
+
* It's not enough to reference the PG::TypeMap object through the result,
|
34
|
+
* since it could be exchanged after the tuple has been created.
|
35
|
+
*/
|
36
|
+
VALUE typemap;
|
37
|
+
|
38
|
+
/* Hash with maps field names to index into values[]
|
39
|
+
* Shared between all instances retrieved from one PG::Result.
|
40
|
+
*/
|
41
|
+
VALUE field_map;
|
42
|
+
|
43
|
+
/* Row number within the result set. */
|
44
|
+
int row_num;
|
45
|
+
|
46
|
+
/* Number of fields in the result set. */
|
47
|
+
int num_fields;
|
48
|
+
|
49
|
+
/* Materialized values.
|
50
|
+
* And in case of dup column names, a field_names Array subsequently.
|
51
|
+
*/
|
52
|
+
VALUE values[0];
|
53
|
+
} t_pg_tuple;
|
54
|
+
|
55
|
+
static inline VALUE
|
56
|
+
pg_tuple_get_field_names( t_pg_tuple *this )
|
57
|
+
{
|
58
|
+
if( this->num_fields != (int)RHASH_SIZE(this->field_map) ){
|
59
|
+
return this->values[this->num_fields];
|
60
|
+
} else {
|
61
|
+
return Qfalse;
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
static void
|
66
|
+
pg_tuple_gc_mark( t_pg_tuple *this )
|
67
|
+
{
|
68
|
+
int i;
|
69
|
+
|
70
|
+
if( !this ) return;
|
71
|
+
rb_gc_mark( this->result );
|
72
|
+
rb_gc_mark( this->typemap );
|
73
|
+
rb_gc_mark( this->field_map );
|
74
|
+
|
75
|
+
for( i = 0; i < this->num_fields; i++ ){
|
76
|
+
rb_gc_mark( this->values[i] );
|
77
|
+
}
|
78
|
+
rb_gc_mark( pg_tuple_get_field_names(this) );
|
79
|
+
}
|
80
|
+
|
81
|
+
static void
|
82
|
+
pg_tuple_gc_free( t_pg_tuple *this )
|
83
|
+
{
|
84
|
+
if( !this ) return;
|
85
|
+
xfree(this);
|
86
|
+
}
|
87
|
+
|
88
|
+
static size_t
|
89
|
+
pg_tuple_memsize( t_pg_tuple *this )
|
90
|
+
{
|
91
|
+
if( this==NULL ) return 0;
|
92
|
+
return sizeof(*this) + sizeof(*this->values) * this->num_fields;
|
93
|
+
}
|
94
|
+
|
95
|
+
static const rb_data_type_t pg_tuple_type = {
|
96
|
+
"pg",
|
97
|
+
{
|
98
|
+
(void (*)(void*))pg_tuple_gc_mark,
|
99
|
+
(void (*)(void*))pg_tuple_gc_free,
|
100
|
+
(size_t (*)(const void *))pg_tuple_memsize,
|
101
|
+
},
|
102
|
+
0, 0,
|
103
|
+
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
|
104
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
105
|
+
#endif
|
106
|
+
};
|
107
|
+
|
108
|
+
/*
|
109
|
+
* Document-method: allocate
|
110
|
+
*
|
111
|
+
* call-seq:
|
112
|
+
* PG::VeryTuple.allocate -> obj
|
113
|
+
*/
|
114
|
+
static VALUE
|
115
|
+
pg_tuple_s_allocate( VALUE klass )
|
116
|
+
{
|
117
|
+
return TypedData_Wrap_Struct( klass, &pg_tuple_type, NULL );
|
118
|
+
}
|
119
|
+
|
120
|
+
VALUE
|
121
|
+
pg_tuple_new(VALUE result, int row_num)
|
122
|
+
{
|
123
|
+
t_pg_tuple *this;
|
124
|
+
VALUE self = pg_tuple_s_allocate( rb_cPG_Tuple );
|
125
|
+
t_pg_result *p_result = pgresult_get_this(result);
|
126
|
+
int num_fields = p_result->nfields;
|
127
|
+
int i;
|
128
|
+
VALUE field_map = p_result->field_map;
|
129
|
+
int dup_names = num_fields != (int)RHASH_SIZE(field_map);
|
130
|
+
|
131
|
+
this = (t_pg_tuple *)xmalloc(
|
132
|
+
sizeof(*this) +
|
133
|
+
sizeof(*this->values) * num_fields +
|
134
|
+
sizeof(*this->values) * (dup_names ? 1 : 0));
|
135
|
+
|
136
|
+
this->result = result;
|
137
|
+
this->typemap = p_result->typemap;
|
138
|
+
this->field_map = field_map;
|
139
|
+
this->row_num = row_num;
|
140
|
+
this->num_fields = num_fields;
|
141
|
+
|
142
|
+
for( i = 0; i < num_fields; i++ ){
|
143
|
+
this->values[i] = Qundef;
|
144
|
+
}
|
145
|
+
|
146
|
+
if( dup_names ){
|
147
|
+
/* Some of the column names are duplicated -> we need the keys as Array in addition.
|
148
|
+
* Store it behind the values to save the space in the common case of no dups.
|
149
|
+
*/
|
150
|
+
this->values[num_fields] = rb_obj_freeze(rb_ary_new4(num_fields, p_result->fnames));
|
151
|
+
}
|
152
|
+
|
153
|
+
RTYPEDDATA_DATA(self) = this;
|
154
|
+
|
155
|
+
return self;
|
156
|
+
}
|
157
|
+
|
158
|
+
static inline t_pg_tuple *
|
159
|
+
pg_tuple_get_this( VALUE self )
|
160
|
+
{
|
161
|
+
t_pg_tuple *this;
|
162
|
+
TypedData_Get_Struct(self, t_pg_tuple, &pg_tuple_type, this);
|
163
|
+
if (this == NULL)
|
164
|
+
rb_raise(rb_eTypeError, "tuple is empty");
|
165
|
+
|
166
|
+
return this;
|
167
|
+
}
|
168
|
+
|
169
|
+
static VALUE
|
170
|
+
pg_tuple_materialize_field(t_pg_tuple *this, int col)
|
171
|
+
{
|
172
|
+
VALUE value = this->values[col];
|
173
|
+
|
174
|
+
if( value == Qundef ){
|
175
|
+
t_typemap *p_typemap = DATA_PTR( this->typemap );
|
176
|
+
|
177
|
+
pgresult_get(this->result); /* make sure we have a valid PGresult object */
|
178
|
+
value = p_typemap->funcs.typecast_result_value(p_typemap, this->result, this->row_num, col);
|
179
|
+
this->values[col] = value;
|
180
|
+
}
|
181
|
+
|
182
|
+
return value;
|
183
|
+
}
|
184
|
+
|
185
|
+
static void
|
186
|
+
pg_tuple_detach(t_pg_tuple *this)
|
187
|
+
{
|
188
|
+
this->result = Qnil;
|
189
|
+
this->typemap = Qnil;
|
190
|
+
this->row_num = -1;
|
191
|
+
}
|
192
|
+
|
193
|
+
static void
|
194
|
+
pg_tuple_materialize(t_pg_tuple *this)
|
195
|
+
{
|
196
|
+
int field_num;
|
197
|
+
for(field_num = 0; field_num < this->num_fields; field_num++) {
|
198
|
+
pg_tuple_materialize_field(this, field_num);
|
199
|
+
}
|
200
|
+
|
201
|
+
pg_tuple_detach(this);
|
202
|
+
}
|
203
|
+
|
204
|
+
/*
|
205
|
+
* call-seq:
|
206
|
+
* tup.fetch(key) → value
|
207
|
+
* tup.fetch(key, default) → value
|
208
|
+
* tup.fetch(key) { |key| block } → value
|
209
|
+
*
|
210
|
+
* Returns a field value by either column index or column name.
|
211
|
+
*
|
212
|
+
* An integer +key+ is interpreted as column index.
|
213
|
+
* Negative values of index count from the end of the array.
|
214
|
+
*
|
215
|
+
* Depending on Result#field_name_type= a string or symbol +key+ is interpreted as column name.
|
216
|
+
*
|
217
|
+
* If the key can't be found, there are several options:
|
218
|
+
* With no other arguments, it will raise a IndexError exception;
|
219
|
+
* if default is given, then that will be returned;
|
220
|
+
* if the optional code block is specified, then that will be run and its result returned.
|
221
|
+
*/
|
222
|
+
static VALUE
|
223
|
+
pg_tuple_fetch(int argc, VALUE *argv, VALUE self)
|
224
|
+
{
|
225
|
+
VALUE key;
|
226
|
+
long block_given;
|
227
|
+
VALUE index;
|
228
|
+
int field_num;
|
229
|
+
t_pg_tuple *this = pg_tuple_get_this(self);
|
230
|
+
|
231
|
+
rb_check_arity(argc, 1, 2);
|
232
|
+
key = argv[0];
|
233
|
+
|
234
|
+
block_given = rb_block_given_p();
|
235
|
+
if (block_given && argc == 2) {
|
236
|
+
rb_warn("block supersedes default value argument");
|
237
|
+
}
|
238
|
+
|
239
|
+
switch(rb_type(key)){
|
240
|
+
case T_FIXNUM:
|
241
|
+
case T_BIGNUM:
|
242
|
+
field_num = NUM2INT(key);
|
243
|
+
if ( field_num < 0 )
|
244
|
+
field_num = this->num_fields + field_num;
|
245
|
+
if ( field_num < 0 || field_num >= this->num_fields ){
|
246
|
+
if (block_given) return rb_yield(key);
|
247
|
+
if (argc == 1) rb_raise( rb_eIndexError, "Index %d is out of range", field_num );
|
248
|
+
return argv[1];
|
249
|
+
}
|
250
|
+
break;
|
251
|
+
default:
|
252
|
+
index = rb_hash_aref(this->field_map, key);
|
253
|
+
|
254
|
+
if (index == Qnil) {
|
255
|
+
if (block_given) return rb_yield(key);
|
256
|
+
if (argc == 1) rb_raise( rb_eKeyError, "column not found" );
|
257
|
+
return argv[1];
|
258
|
+
}
|
259
|
+
|
260
|
+
field_num = NUM2INT(index);
|
261
|
+
}
|
262
|
+
|
263
|
+
return pg_tuple_materialize_field(this, field_num);
|
264
|
+
}
|
265
|
+
|
266
|
+
/*
|
267
|
+
* call-seq:
|
268
|
+
* tup[ key ] -> value
|
269
|
+
*
|
270
|
+
* Returns a field value by either column index or column name.
|
271
|
+
*
|
272
|
+
* An integer +key+ is interpreted as column index.
|
273
|
+
* Negative values of index count from the end of the array.
|
274
|
+
*
|
275
|
+
* Depending on Result#field_name_type= a string or symbol +key+ is interpreted as column name.
|
276
|
+
*
|
277
|
+
* If the key can't be found, it returns +nil+ .
|
278
|
+
*/
|
279
|
+
static VALUE
|
280
|
+
pg_tuple_aref(VALUE self, VALUE key)
|
281
|
+
{
|
282
|
+
VALUE index;
|
283
|
+
int field_num;
|
284
|
+
t_pg_tuple *this = pg_tuple_get_this(self);
|
285
|
+
|
286
|
+
switch(rb_type(key)){
|
287
|
+
case T_FIXNUM:
|
288
|
+
case T_BIGNUM:
|
289
|
+
field_num = NUM2INT(key);
|
290
|
+
if ( field_num < 0 )
|
291
|
+
field_num = this->num_fields + field_num;
|
292
|
+
if ( field_num < 0 || field_num >= this->num_fields )
|
293
|
+
return Qnil;
|
294
|
+
break;
|
295
|
+
default:
|
296
|
+
index = rb_hash_aref(this->field_map, key);
|
297
|
+
if( index == Qnil ) return Qnil;
|
298
|
+
field_num = NUM2INT(index);
|
299
|
+
}
|
300
|
+
|
301
|
+
return pg_tuple_materialize_field(this, field_num);
|
302
|
+
}
|
303
|
+
|
304
|
+
static VALUE
|
305
|
+
pg_tuple_num_fields_for_enum(VALUE self, VALUE args, VALUE eobj)
|
306
|
+
{
|
307
|
+
t_pg_tuple *this = pg_tuple_get_this(self);
|
308
|
+
return INT2NUM(this->num_fields);
|
309
|
+
}
|
310
|
+
|
311
|
+
static int
|
312
|
+
pg_tuple_yield_key_value(VALUE key, VALUE index, VALUE _this)
|
313
|
+
{
|
314
|
+
t_pg_tuple *this = (t_pg_tuple *)_this;
|
315
|
+
VALUE value = pg_tuple_materialize_field(this, NUM2INT(index));
|
316
|
+
rb_yield_values(2, key, value);
|
317
|
+
return ST_CONTINUE;
|
318
|
+
}
|
319
|
+
|
320
|
+
/*
|
321
|
+
* call-seq:
|
322
|
+
* tup.each{ |key, value| ... }
|
323
|
+
*
|
324
|
+
* Invokes block for each field name and value in the tuple.
|
325
|
+
*/
|
326
|
+
static VALUE
|
327
|
+
pg_tuple_each(VALUE self)
|
328
|
+
{
|
329
|
+
t_pg_tuple *this = pg_tuple_get_this(self);
|
330
|
+
VALUE field_names;
|
331
|
+
|
332
|
+
RETURN_SIZED_ENUMERATOR(self, 0, NULL, pg_tuple_num_fields_for_enum);
|
333
|
+
|
334
|
+
field_names = pg_tuple_get_field_names(this);
|
335
|
+
|
336
|
+
if( field_names == Qfalse ){
|
337
|
+
rb_hash_foreach(this->field_map, pg_tuple_yield_key_value, (VALUE)this);
|
338
|
+
} else {
|
339
|
+
int i;
|
340
|
+
for( i = 0; i < this->num_fields; i++ ){
|
341
|
+
VALUE value = pg_tuple_materialize_field(this, i);
|
342
|
+
rb_yield_values(2, RARRAY_AREF(field_names, i), value);
|
343
|
+
}
|
344
|
+
}
|
345
|
+
|
346
|
+
pg_tuple_detach(this);
|
347
|
+
return self;
|
348
|
+
}
|
349
|
+
|
350
|
+
/*
|
351
|
+
* call-seq:
|
352
|
+
* tup.each_value{ |value| ... }
|
353
|
+
*
|
354
|
+
* Invokes block for each field value in the tuple.
|
355
|
+
*/
|
356
|
+
static VALUE
|
357
|
+
pg_tuple_each_value(VALUE self)
|
358
|
+
{
|
359
|
+
t_pg_tuple *this = pg_tuple_get_this(self);
|
360
|
+
int field_num;
|
361
|
+
|
362
|
+
RETURN_SIZED_ENUMERATOR(self, 0, NULL, pg_tuple_num_fields_for_enum);
|
363
|
+
|
364
|
+
for(field_num = 0; field_num < this->num_fields; field_num++) {
|
365
|
+
VALUE value = pg_tuple_materialize_field(this, field_num);
|
366
|
+
rb_yield(value);
|
367
|
+
}
|
368
|
+
|
369
|
+
pg_tuple_detach(this);
|
370
|
+
return self;
|
371
|
+
}
|
372
|
+
|
373
|
+
|
374
|
+
/*
|
375
|
+
* call-seq:
|
376
|
+
* tup.values -> Array
|
377
|
+
*
|
378
|
+
* Returns the values of this tuple as Array.
|
379
|
+
* +res.tuple(i).values+ is equal to +res.tuple_values(i)+ .
|
380
|
+
*/
|
381
|
+
static VALUE
|
382
|
+
pg_tuple_values(VALUE self)
|
383
|
+
{
|
384
|
+
t_pg_tuple *this = pg_tuple_get_this(self);
|
385
|
+
|
386
|
+
pg_tuple_materialize(this);
|
387
|
+
return rb_ary_new4(this->num_fields, &this->values[0]);
|
388
|
+
}
|
389
|
+
|
390
|
+
static VALUE
|
391
|
+
pg_tuple_field_map(VALUE self)
|
392
|
+
{
|
393
|
+
t_pg_tuple *this = pg_tuple_get_this(self);
|
394
|
+
return this->field_map;
|
395
|
+
}
|
396
|
+
|
397
|
+
static VALUE
|
398
|
+
pg_tuple_field_names(VALUE self)
|
399
|
+
{
|
400
|
+
t_pg_tuple *this = pg_tuple_get_this(self);
|
401
|
+
return pg_tuple_get_field_names(this);
|
402
|
+
}
|
403
|
+
|
404
|
+
/*
|
405
|
+
* call-seq:
|
406
|
+
* tup.length → integer
|
407
|
+
*
|
408
|
+
* Returns number of fields of this tuple.
|
409
|
+
*/
|
410
|
+
static VALUE
|
411
|
+
pg_tuple_length(VALUE self)
|
412
|
+
{
|
413
|
+
t_pg_tuple *this = pg_tuple_get_this(self);
|
414
|
+
return INT2NUM(this->num_fields);
|
415
|
+
}
|
416
|
+
|
417
|
+
/*
|
418
|
+
* call-seq:
|
419
|
+
* tup.index(key) → integer
|
420
|
+
*
|
421
|
+
* Returns the field number which matches the given column name.
|
422
|
+
*/
|
423
|
+
static VALUE
|
424
|
+
pg_tuple_index(VALUE self, VALUE key)
|
425
|
+
{
|
426
|
+
t_pg_tuple *this = pg_tuple_get_this(self);
|
427
|
+
return rb_hash_aref(this->field_map, key);
|
428
|
+
}
|
429
|
+
|
430
|
+
|
431
|
+
static VALUE
|
432
|
+
pg_tuple_dump(VALUE self)
|
433
|
+
{
|
434
|
+
VALUE field_names;
|
435
|
+
VALUE values;
|
436
|
+
VALUE a;
|
437
|
+
t_pg_tuple *this = pg_tuple_get_this(self);
|
438
|
+
|
439
|
+
pg_tuple_materialize(this);
|
440
|
+
|
441
|
+
field_names = pg_tuple_get_field_names(this);
|
442
|
+
if( field_names == Qfalse )
|
443
|
+
field_names = rb_funcall(this->field_map, rb_intern("keys"), 0);
|
444
|
+
|
445
|
+
values = rb_ary_new4(this->num_fields, &this->values[0]);
|
446
|
+
a = rb_ary_new3(2, field_names, values);
|
447
|
+
|
448
|
+
if (FL_TEST(self, FL_EXIVAR)) {
|
449
|
+
rb_copy_generic_ivar(a, self);
|
450
|
+
FL_SET(a, FL_EXIVAR);
|
451
|
+
}
|
452
|
+
|
453
|
+
return a;
|
454
|
+
}
|
455
|
+
|
456
|
+
static VALUE
|
457
|
+
pg_tuple_load(VALUE self, VALUE a)
|
458
|
+
{
|
459
|
+
int num_fields;
|
460
|
+
int i;
|
461
|
+
t_pg_tuple *this;
|
462
|
+
VALUE values;
|
463
|
+
VALUE field_names;
|
464
|
+
VALUE field_map;
|
465
|
+
int dup_names;
|
466
|
+
|
467
|
+
rb_check_frozen(self);
|
468
|
+
|
469
|
+
TypedData_Get_Struct(self, t_pg_tuple, &pg_tuple_type, this);
|
470
|
+
if (this)
|
471
|
+
rb_raise(rb_eTypeError, "tuple is not empty");
|
472
|
+
|
473
|
+
Check_Type(a, T_ARRAY);
|
474
|
+
if (RARRAY_LEN(a) != 2)
|
475
|
+
rb_raise(rb_eTypeError, "expected an array of 2 elements");
|
476
|
+
|
477
|
+
field_names = RARRAY_AREF(a, 0);
|
478
|
+
Check_Type(field_names, T_ARRAY);
|
479
|
+
rb_obj_freeze(field_names);
|
480
|
+
values = RARRAY_AREF(a, 1);
|
481
|
+
Check_Type(values, T_ARRAY);
|
482
|
+
num_fields = RARRAY_LEN(values);
|
483
|
+
|
484
|
+
if (RARRAY_LEN(field_names) != num_fields)
|
485
|
+
rb_raise(rb_eTypeError, "different number of fields and values");
|
486
|
+
|
487
|
+
field_map = rb_hash_new();
|
488
|
+
for( i = 0; i < num_fields; i++ ){
|
489
|
+
rb_hash_aset(field_map, RARRAY_AREF(field_names, i), INT2FIX(i));
|
490
|
+
}
|
491
|
+
rb_obj_freeze(field_map);
|
492
|
+
|
493
|
+
dup_names = num_fields != (int)RHASH_SIZE(field_map);
|
494
|
+
|
495
|
+
this = (t_pg_tuple *)xmalloc(
|
496
|
+
sizeof(*this) +
|
497
|
+
sizeof(*this->values) * num_fields +
|
498
|
+
sizeof(*this->values) * (dup_names ? 1 : 0));
|
499
|
+
|
500
|
+
this->result = Qnil;
|
501
|
+
this->typemap = Qnil;
|
502
|
+
this->row_num = -1;
|
503
|
+
this->num_fields = num_fields;
|
504
|
+
this->field_map = field_map;
|
505
|
+
|
506
|
+
for( i = 0; i < num_fields; i++ ){
|
507
|
+
VALUE v = RARRAY_AREF(values, i);
|
508
|
+
if( v == Qundef )
|
509
|
+
rb_raise(rb_eTypeError, "field %d is not materialized", i);
|
510
|
+
this->values[i] = v;
|
511
|
+
}
|
512
|
+
|
513
|
+
if( dup_names ){
|
514
|
+
this->values[num_fields] = field_names;
|
515
|
+
}
|
516
|
+
|
517
|
+
RTYPEDDATA_DATA(self) = this;
|
518
|
+
|
519
|
+
if (FL_TEST(a, FL_EXIVAR)) {
|
520
|
+
rb_copy_generic_ivar(self, a);
|
521
|
+
FL_SET(self, FL_EXIVAR);
|
522
|
+
}
|
523
|
+
|
524
|
+
return self;
|
525
|
+
}
|
526
|
+
|
527
|
+
void
|
528
|
+
init_pg_tuple()
|
529
|
+
{
|
530
|
+
rb_cPG_Tuple = rb_define_class_under( rb_mPG, "Tuple", rb_cObject );
|
531
|
+
rb_define_alloc_func( rb_cPG_Tuple, pg_tuple_s_allocate );
|
532
|
+
rb_include_module(rb_cPG_Tuple, rb_mEnumerable);
|
533
|
+
|
534
|
+
rb_define_method(rb_cPG_Tuple, "fetch", pg_tuple_fetch, -1);
|
535
|
+
rb_define_method(rb_cPG_Tuple, "[]", pg_tuple_aref, 1);
|
536
|
+
rb_define_method(rb_cPG_Tuple, "each", pg_tuple_each, 0);
|
537
|
+
rb_define_method(rb_cPG_Tuple, "each_value", pg_tuple_each_value, 0);
|
538
|
+
rb_define_method(rb_cPG_Tuple, "values", pg_tuple_values, 0);
|
539
|
+
rb_define_method(rb_cPG_Tuple, "length", pg_tuple_length, 0);
|
540
|
+
rb_define_alias(rb_cPG_Tuple, "size", "length");
|
541
|
+
rb_define_method(rb_cPG_Tuple, "index", pg_tuple_index, 1);
|
542
|
+
|
543
|
+
rb_define_private_method(rb_cPG_Tuple, "field_map", pg_tuple_field_map, 0);
|
544
|
+
rb_define_private_method(rb_cPG_Tuple, "field_names", pg_tuple_field_names, 0);
|
545
|
+
|
546
|
+
/* methods for marshaling */
|
547
|
+
rb_define_private_method(rb_cPG_Tuple, "marshal_dump", pg_tuple_dump, 0);
|
548
|
+
rb_define_private_method(rb_cPG_Tuple, "marshal_load", pg_tuple_load, 1);
|
549
|
+
}
|