pg 1.1.3 → 1.3.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.appveyor.yml +36 -0
- data/.gems +6 -0
- data/.github/workflows/binary-gems.yml +86 -0
- data/.github/workflows/source-gem.yml +129 -0
- data/.gitignore +13 -0
- data/.hgsigs +34 -0
- data/.hgtags +41 -0
- data/.irbrc +23 -0
- data/.pryrc +23 -0
- data/.tm_properties +21 -0
- data/.travis.yml +49 -0
- data/Gemfile +14 -0
- data/History.rdoc +210 -6
- data/Manifest.txt +3 -3
- data/README-Windows.rdoc +4 -4
- data/README.ja.rdoc +1 -2
- data/README.rdoc +51 -15
- data/Rakefile +31 -140
- data/Rakefile.cross +60 -56
- data/certs/ged.pem +24 -0
- data/certs/larskanis-2022.pem +26 -0
- data/ext/errorcodes.def +76 -0
- data/ext/errorcodes.txt +21 -2
- data/ext/extconf.rb +101 -26
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +23 -0
- data/ext/pg.c +190 -98
- data/ext/pg.h +42 -17
- data/ext/pg_binary_decoder.c +20 -16
- data/ext/pg_binary_encoder.c +13 -12
- data/ext/pg_coder.c +95 -29
- data/ext/pg_connection.c +1043 -769
- data/ext/pg_copy_coder.c +50 -18
- data/ext/pg_record_coder.c +519 -0
- data/ext/pg_result.c +326 -142
- data/ext/pg_text_decoder.c +15 -9
- data/ext/pg_text_encoder.c +185 -53
- data/ext/pg_tuple.c +61 -27
- data/ext/pg_type_map.c +42 -9
- data/ext/pg_type_map_all_strings.c +19 -5
- data/ext/pg_type_map_by_class.c +54 -24
- data/ext/pg_type_map_by_column.c +73 -34
- data/ext/pg_type_map_by_mri_type.c +48 -19
- data/ext/pg_type_map_by_oid.c +55 -25
- data/ext/pg_type_map_in_ruby.c +51 -20
- data/ext/{util.c → pg_util.c} +7 -7
- data/ext/{util.h → pg_util.h} +0 -0
- data/lib/pg/basic_type_map_based_on_result.rb +47 -0
- data/lib/pg/basic_type_map_for_queries.rb +193 -0
- data/lib/pg/basic_type_map_for_results.rb +81 -0
- data/lib/pg/basic_type_registry.rb +296 -0
- data/lib/pg/binary_decoder.rb +1 -0
- data/lib/pg/coder.rb +23 -2
- data/lib/pg/connection.rb +589 -59
- data/lib/pg/constants.rb +1 -0
- data/lib/pg/exceptions.rb +1 -0
- data/lib/pg/result.rb +13 -1
- data/lib/pg/text_decoder.rb +2 -3
- data/lib/pg/text_encoder.rb +8 -18
- data/lib/pg/type_map_by_column.rb +2 -1
- data/lib/pg/version.rb +4 -0
- data/lib/pg.rb +48 -33
- data/misc/openssl-pg-segfault.rb +31 -0
- data/misc/postgres/History.txt +9 -0
- data/misc/postgres/Manifest.txt +5 -0
- data/misc/postgres/README.txt +21 -0
- data/misc/postgres/Rakefile +21 -0
- data/misc/postgres/lib/postgres.rb +16 -0
- data/misc/ruby-pg/History.txt +9 -0
- data/misc/ruby-pg/Manifest.txt +5 -0
- data/misc/ruby-pg/README.txt +21 -0
- data/misc/ruby-pg/Rakefile +21 -0
- data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
- data/pg.gemspec +32 -0
- data/rakelib/task_extension.rb +46 -0
- data/sample/array_insert.rb +20 -0
- data/sample/async_api.rb +106 -0
- data/sample/async_copyto.rb +39 -0
- data/sample/async_mixed.rb +56 -0
- data/sample/check_conn.rb +21 -0
- data/sample/copydata.rb +71 -0
- data/sample/copyfrom.rb +81 -0
- data/sample/copyto.rb +19 -0
- data/sample/cursor.rb +21 -0
- data/sample/disk_usage_report.rb +177 -0
- data/sample/issue-119.rb +94 -0
- data/sample/losample.rb +69 -0
- data/sample/minimal-testcase.rb +17 -0
- data/sample/notify_wait.rb +72 -0
- data/sample/pg_statistics.rb +285 -0
- data/sample/replication_monitor.rb +222 -0
- data/sample/test_binary_values.rb +33 -0
- data/sample/wal_shipper.rb +434 -0
- data/sample/warehouse_partitions.rb +311 -0
- data.tar.gz.sig +0 -0
- metadata +94 -237
- metadata.gz.sig +0 -0
- data/ChangeLog +0 -6595
- data/lib/pg/basic_type_mapping.rb +0 -459
- data/spec/data/expected_trace.out +0 -26
- data/spec/data/random_binary_data +0 -0
- data/spec/helpers.rb +0 -381
- data/spec/pg/basic_type_mapping_spec.rb +0 -508
- data/spec/pg/connection_spec.rb +0 -1849
- data/spec/pg/connection_sync_spec.rb +0 -41
- data/spec/pg/result_spec.rb +0 -491
- data/spec/pg/tuple_spec.rb +0 -280
- data/spec/pg/type_map_by_class_spec.rb +0 -138
- data/spec/pg/type_map_by_column_spec.rb +0 -222
- data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
- data/spec/pg/type_map_by_oid_spec.rb +0 -149
- data/spec/pg/type_map_in_ruby_spec.rb +0 -164
- data/spec/pg/type_map_spec.rb +0 -22
- data/spec/pg/type_spec.rb +0 -949
- data/spec/pg_spec.rb +0 -50
data/ext/pg_text_decoder.c
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* pg_text_decoder.c - PG::TextDecoder module
|
|
3
|
-
* $Id
|
|
3
|
+
* $Id$
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
#include "ruby/version.h"
|
|
32
32
|
#include "pg.h"
|
|
33
|
-
#include "
|
|
33
|
+
#include "pg_util.h"
|
|
34
34
|
#ifdef HAVE_INTTYPES_H
|
|
35
35
|
#include <inttypes.h>
|
|
36
36
|
#endif
|
|
@@ -89,7 +89,7 @@ pg_text_dec_boolean(t_pg_coder *conv, const char *val, int len, int tuple, int f
|
|
|
89
89
|
VALUE
|
|
90
90
|
pg_text_dec_string(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx)
|
|
91
91
|
{
|
|
92
|
-
VALUE ret =
|
|
92
|
+
VALUE ret = rb_str_new( val, len );
|
|
93
93
|
PG_ENCODING_SET_NOCHECK( ret, enc_idx );
|
|
94
94
|
return ret;
|
|
95
95
|
}
|
|
@@ -204,7 +204,12 @@ struct pg_blob_initialization {
|
|
|
204
204
|
|
|
205
205
|
static VALUE pg_create_blob(VALUE v) {
|
|
206
206
|
struct pg_blob_initialization *bi = (struct pg_blob_initialization *)v;
|
|
207
|
-
return
|
|
207
|
+
return rb_str_new(bi->blob_string, bi->length);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
static VALUE pg_pq_freemem(VALUE mem) {
|
|
211
|
+
PQfreemem((void *)mem);
|
|
212
|
+
return Qfalse;
|
|
208
213
|
}
|
|
209
214
|
|
|
210
215
|
/*
|
|
@@ -223,7 +228,7 @@ pg_text_dec_bytea(t_pg_coder *conv, const char *val, int len, int tuple, int fie
|
|
|
223
228
|
if (bi.blob_string == NULL) {
|
|
224
229
|
rb_raise(rb_eNoMemError, "PQunescapeBytea failure: probably not enough memory");
|
|
225
230
|
}
|
|
226
|
-
return rb_ensure(pg_create_blob, (VALUE)&bi,
|
|
231
|
+
return rb_ensure(pg_create_blob, (VALUE)&bi, pg_pq_freemem, (VALUE)bi.blob_string);
|
|
227
232
|
}
|
|
228
233
|
|
|
229
234
|
/*
|
|
@@ -558,7 +563,7 @@ pg_text_dec_from_base64(t_pg_coder *conv, const char *val, int len, int tuple, i
|
|
|
558
563
|
t_pg_coder_dec_func dec_func = pg_coder_dec_func(this->elem, this->comp.format);
|
|
559
564
|
int decoded_len;
|
|
560
565
|
/* create a buffer of the expected decoded length */
|
|
561
|
-
VALUE out_value =
|
|
566
|
+
VALUE out_value = rb_str_new(NULL, BASE64_DECODED_SIZE(len));
|
|
562
567
|
|
|
563
568
|
decoded_len = base64_decode( RSTRING_PTR(out_value), val, len );
|
|
564
569
|
rb_str_set_len(out_value, decoded_len);
|
|
@@ -610,7 +615,7 @@ static int parse_year(const char **str) {
|
|
|
610
615
|
* This is a decoder class for conversion of PostgreSQL text timestamps
|
|
611
616
|
* to Ruby Time objects.
|
|
612
617
|
*
|
|
613
|
-
* The following flags can be used to specify timezone
|
|
618
|
+
* The following flags can be used to specify time interpretation when no timezone is given:
|
|
614
619
|
* * +PG::Coder::TIMESTAMP_DB_UTC+ : Interpret timestamp as UTC time (default)
|
|
615
620
|
* * +PG::Coder::TIMESTAMP_DB_LOCAL+ : Interpret timestamp as local time
|
|
616
621
|
* * +PG::Coder::TIMESTAMP_APP_UTC+ : Return timestamp as UTC time (default)
|
|
@@ -619,6 +624,7 @@ static int parse_year(const char **str) {
|
|
|
619
624
|
* Example:
|
|
620
625
|
* deco = PG::TextDecoder::Timestamp.new(flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL)
|
|
621
626
|
* deco.decode("2000-01-01 00:00:00") # => 2000-01-01 01:00:00 +0100
|
|
627
|
+
* deco.decode("2000-01-01 00:00:00.123-06") # => 2000-01-01 00:00:00 -0600
|
|
622
628
|
*/
|
|
623
629
|
static VALUE pg_text_dec_timestamp(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx)
|
|
624
630
|
{
|
|
@@ -848,7 +854,7 @@ pg_text_dec_inet(t_pg_coder *conv, const char *val, int len, int tuple, int fiel
|
|
|
848
854
|
|
|
849
855
|
ip_int_native = read_nbo32(dst);
|
|
850
856
|
|
|
851
|
-
/* Work around broken IPAddr behavior of
|
|
857
|
+
/* Work around broken IPAddr behavior of converting portion
|
|
852
858
|
of address after netmask to 0 */
|
|
853
859
|
switch (mask) {
|
|
854
860
|
case 0:
|
|
@@ -961,7 +967,7 @@ init_pg_text_decoder()
|
|
|
961
967
|
pg_define_coder( "Integer", pg_text_dec_integer, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder );
|
|
962
968
|
/* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Float", rb_cPG_SimpleDecoder ); */
|
|
963
969
|
pg_define_coder( "Float", pg_text_dec_float, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder );
|
|
964
|
-
/* dummy = rb_define_class_under( rb_mPG_TextDecoder, "
|
|
970
|
+
/* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Numeric", rb_cPG_SimpleDecoder ); */
|
|
965
971
|
pg_define_coder( "Numeric", pg_text_dec_numeric, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder );
|
|
966
972
|
/* dummy = rb_define_class_under( rb_mPG_TextDecoder, "String", rb_cPG_SimpleDecoder ); */
|
|
967
973
|
pg_define_coder( "String", pg_text_dec_string, rb_cPG_SimpleDecoder, rb_mPG_TextDecoder );
|
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;
|
|
@@ -167,7 +191,7 @@ pg_text_enc_integer(t_pg_coder *this, VALUE value, char *out, VALUE *intermediat
|
|
|
167
191
|
if (neg)
|
|
168
192
|
*out++ = '-';
|
|
169
193
|
|
|
170
|
-
len = out - start;
|
|
194
|
+
len = (int)(out - start);
|
|
171
195
|
|
|
172
196
|
/* Reverse string. */
|
|
173
197
|
out--;
|
|
@@ -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 computation 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*/;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
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
|
+
}
|
|
254
371
|
}
|
|
255
372
|
}
|
|
256
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
|
|
@@ -286,11 +403,11 @@ pg_text_enc_bytea(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate,
|
|
|
286
403
|
*optr++ = hextab[c >> 4];
|
|
287
404
|
*optr++ = hextab[c & 0xf];
|
|
288
405
|
}
|
|
289
|
-
return optr - out;
|
|
406
|
+
return (int)(optr - out);
|
|
290
407
|
}else{
|
|
291
408
|
*intermediate = rb_obj_as_string(value);
|
|
292
409
|
/* The output starts with "\x" and each character is converted to hex. */
|
|
293
|
-
return 2 +
|
|
410
|
+
return 2 + RSTRING_LENINT(*intermediate) * 2;
|
|
294
411
|
}
|
|
295
412
|
}
|
|
296
413
|
|
|
@@ -493,8 +610,8 @@ quote_identifier( VALUE value, VALUE out_string, char *current_out ){
|
|
|
493
610
|
static char *
|
|
494
611
|
pg_text_enc_array_identifier(VALUE value, VALUE string, char *out, int enc_idx)
|
|
495
612
|
{
|
|
496
|
-
|
|
497
|
-
|
|
613
|
+
long i;
|
|
614
|
+
long nr_elems;
|
|
498
615
|
|
|
499
616
|
Check_Type(value, T_ARRAY);
|
|
500
617
|
nr_elems = RARRAY_LEN(value);
|
|
@@ -520,7 +637,8 @@ pg_text_enc_array_identifier(VALUE value, VALUE string, char *out, int enc_idx)
|
|
|
520
637
|
*
|
|
521
638
|
* This is the encoder class for PostgreSQL identifiers.
|
|
522
639
|
*
|
|
523
|
-
* 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:
|
|
524
642
|
* PG::TextEncoder::Identifier.new.encode(['schema', 'table', 'column'])
|
|
525
643
|
* => '"schema"."table"."column"'
|
|
526
644
|
*
|
|
@@ -588,7 +706,13 @@ quote_literal_buffer( void *_this, char *p_in, int strlen, char *p_out ){
|
|
|
588
706
|
*
|
|
589
707
|
* This is the encoder class for PostgreSQL literals.
|
|
590
708
|
*
|
|
591
|
-
* 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.
|
|
592
716
|
*
|
|
593
717
|
*/
|
|
594
718
|
static int
|
|
@@ -655,6 +779,12 @@ init_pg_text_encoder()
|
|
|
655
779
|
{
|
|
656
780
|
s_id_encode = rb_intern("encode");
|
|
657
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
|
+
|
|
658
788
|
|
|
659
789
|
/* This module encapsulates all encoder classes with text output format */
|
|
660
790
|
rb_mPG_TextEncoder = rb_define_module_under( rb_mPG, "TextEncoder" );
|
|
@@ -666,6 +796,8 @@ init_pg_text_encoder()
|
|
|
666
796
|
pg_define_coder( "Integer", pg_text_enc_integer, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder );
|
|
667
797
|
/* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Float", rb_cPG_SimpleEncoder ); */
|
|
668
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 );
|
|
669
801
|
/* dummy = rb_define_class_under( rb_mPG_TextEncoder, "String", rb_cPG_SimpleEncoder ); */
|
|
670
802
|
pg_define_coder( "String", pg_coder_enc_to_s, rb_cPG_SimpleEncoder, rb_mPG_TextEncoder );
|
|
671
803
|
/* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Bytea", rb_cPG_SimpleEncoder ); */
|
data/ext/pg_tuple.c
CHANGED
|
@@ -52,57 +52,83 @@ typedef struct {
|
|
|
52
52
|
VALUE values[0];
|
|
53
53
|
} t_pg_tuple;
|
|
54
54
|
|
|
55
|
-
static inline VALUE
|
|
56
|
-
|
|
55
|
+
static inline VALUE *
|
|
56
|
+
pg_tuple_get_field_names_ptr( t_pg_tuple *this )
|
|
57
57
|
{
|
|
58
58
|
if( this->num_fields != (int)RHASH_SIZE(this->field_map) ){
|
|
59
|
-
return this->values[this->num_fields];
|
|
59
|
+
return &this->values[this->num_fields];
|
|
60
60
|
} else {
|
|
61
|
-
|
|
61
|
+
static VALUE f = Qfalse;
|
|
62
|
+
return &f;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static inline VALUE
|
|
67
|
+
pg_tuple_get_field_names( t_pg_tuple *this )
|
|
68
|
+
{
|
|
69
|
+
return *pg_tuple_get_field_names_ptr(this);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static void
|
|
73
|
+
pg_tuple_gc_mark( void *_this )
|
|
74
|
+
{
|
|
75
|
+
t_pg_tuple *this = (t_pg_tuple *)_this;
|
|
76
|
+
int i;
|
|
77
|
+
|
|
78
|
+
if( !this ) return;
|
|
79
|
+
rb_gc_mark_movable( this->result );
|
|
80
|
+
rb_gc_mark_movable( this->typemap );
|
|
81
|
+
rb_gc_mark_movable( this->field_map );
|
|
82
|
+
|
|
83
|
+
for( i = 0; i < this->num_fields; i++ ){
|
|
84
|
+
rb_gc_mark_movable( this->values[i] );
|
|
62
85
|
}
|
|
86
|
+
rb_gc_mark_movable( pg_tuple_get_field_names(this) );
|
|
63
87
|
}
|
|
64
88
|
|
|
65
89
|
static void
|
|
66
|
-
|
|
90
|
+
pg_tuple_gc_compact( void *_this )
|
|
67
91
|
{
|
|
92
|
+
t_pg_tuple *this = (t_pg_tuple *)_this;
|
|
68
93
|
int i;
|
|
69
94
|
|
|
70
95
|
if( !this ) return;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
96
|
+
pg_gc_location( this->result );
|
|
97
|
+
pg_gc_location( this->typemap );
|
|
98
|
+
pg_gc_location( this->field_map );
|
|
74
99
|
|
|
75
100
|
for( i = 0; i < this->num_fields; i++ ){
|
|
76
|
-
|
|
101
|
+
pg_gc_location( this->values[i] );
|
|
77
102
|
}
|
|
78
|
-
|
|
103
|
+
pg_gc_location( *pg_tuple_get_field_names_ptr(this) );
|
|
79
104
|
}
|
|
80
105
|
|
|
81
106
|
static void
|
|
82
|
-
pg_tuple_gc_free(
|
|
107
|
+
pg_tuple_gc_free( void *_this )
|
|
83
108
|
{
|
|
109
|
+
t_pg_tuple *this = (t_pg_tuple *)_this;
|
|
84
110
|
if( !this ) return;
|
|
85
111
|
xfree(this);
|
|
86
112
|
}
|
|
87
113
|
|
|
88
114
|
static size_t
|
|
89
|
-
pg_tuple_memsize(
|
|
115
|
+
pg_tuple_memsize( const void *_this )
|
|
90
116
|
{
|
|
117
|
+
const t_pg_tuple *this = (const t_pg_tuple *)_this;
|
|
91
118
|
if( this==NULL ) return 0;
|
|
92
119
|
return sizeof(*this) + sizeof(*this->values) * this->num_fields;
|
|
93
120
|
}
|
|
94
121
|
|
|
95
122
|
static const rb_data_type_t pg_tuple_type = {
|
|
96
|
-
"
|
|
123
|
+
"PG::Tuple",
|
|
97
124
|
{
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
125
|
+
pg_tuple_gc_mark,
|
|
126
|
+
pg_tuple_gc_free,
|
|
127
|
+
pg_tuple_memsize,
|
|
128
|
+
pg_compact_callback(pg_tuple_gc_compact),
|
|
101
129
|
},
|
|
102
130
|
0, 0,
|
|
103
|
-
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
|
|
104
131
|
RUBY_TYPED_FREE_IMMEDIATELY,
|
|
105
|
-
#endif
|
|
106
132
|
};
|
|
107
133
|
|
|
108
134
|
/*
|
|
@@ -132,7 +158,6 @@ pg_tuple_new(VALUE result, int row_num)
|
|
|
132
158
|
sizeof(*this) +
|
|
133
159
|
sizeof(*this->values) * num_fields +
|
|
134
160
|
sizeof(*this->values) * (dup_names ? 1 : 0));
|
|
135
|
-
RTYPEDDATA_DATA(self) = this;
|
|
136
161
|
|
|
137
162
|
this->result = result;
|
|
138
163
|
this->typemap = p_result->typemap;
|
|
@@ -151,6 +176,8 @@ pg_tuple_new(VALUE result, int row_num)
|
|
|
151
176
|
this->values[num_fields] = rb_obj_freeze(rb_ary_new4(num_fields, p_result->fnames));
|
|
152
177
|
}
|
|
153
178
|
|
|
179
|
+
RTYPEDDATA_DATA(self) = this;
|
|
180
|
+
|
|
154
181
|
return self;
|
|
155
182
|
}
|
|
156
183
|
|
|
@@ -171,7 +198,7 @@ pg_tuple_materialize_field(t_pg_tuple *this, int col)
|
|
|
171
198
|
VALUE value = this->values[col];
|
|
172
199
|
|
|
173
200
|
if( value == Qundef ){
|
|
174
|
-
t_typemap *p_typemap =
|
|
201
|
+
t_typemap *p_typemap = RTYPEDDATA_DATA( this->typemap );
|
|
175
202
|
|
|
176
203
|
pgresult_get(this->result); /* make sure we have a valid PGresult object */
|
|
177
204
|
value = p_typemap->funcs.typecast_result_value(p_typemap, this->result, this->row_num, col);
|
|
@@ -211,7 +238,7 @@ pg_tuple_materialize(t_pg_tuple *this)
|
|
|
211
238
|
* An integer +key+ is interpreted as column index.
|
|
212
239
|
* Negative values of index count from the end of the array.
|
|
213
240
|
*
|
|
214
|
-
*
|
|
241
|
+
* Depending on Result#field_name_type= a string or symbol +key+ is interpreted as column name.
|
|
215
242
|
*
|
|
216
243
|
* If the key can't be found, there are several options:
|
|
217
244
|
* With no other arguments, it will raise a IndexError exception;
|
|
@@ -264,9 +291,16 @@ pg_tuple_fetch(int argc, VALUE *argv, VALUE self)
|
|
|
264
291
|
|
|
265
292
|
/*
|
|
266
293
|
* call-seq:
|
|
267
|
-
*
|
|
294
|
+
* tup[ key ] -> value
|
|
295
|
+
*
|
|
296
|
+
* Returns a field value by either column index or column name.
|
|
297
|
+
*
|
|
298
|
+
* An integer +key+ is interpreted as column index.
|
|
299
|
+
* Negative values of index count from the end of the array.
|
|
268
300
|
*
|
|
269
|
-
*
|
|
301
|
+
* Depending on Result#field_name_type= a string or symbol +key+ is interpreted as column name.
|
|
302
|
+
*
|
|
303
|
+
* If the key can't be found, it returns +nil+ .
|
|
270
304
|
*/
|
|
271
305
|
static VALUE
|
|
272
306
|
pg_tuple_aref(VALUE self, VALUE key)
|
|
@@ -457,7 +491,6 @@ pg_tuple_load(VALUE self, VALUE a)
|
|
|
457
491
|
int dup_names;
|
|
458
492
|
|
|
459
493
|
rb_check_frozen(self);
|
|
460
|
-
rb_check_trusted(self);
|
|
461
494
|
|
|
462
495
|
TypedData_Get_Struct(self, t_pg_tuple, &pg_tuple_type, this);
|
|
463
496
|
if (this)
|
|
@@ -472,9 +505,9 @@ pg_tuple_load(VALUE self, VALUE a)
|
|
|
472
505
|
rb_obj_freeze(field_names);
|
|
473
506
|
values = RARRAY_AREF(a, 1);
|
|
474
507
|
Check_Type(values, T_ARRAY);
|
|
475
|
-
num_fields =
|
|
508
|
+
num_fields = RARRAY_LENINT(values);
|
|
476
509
|
|
|
477
|
-
if (
|
|
510
|
+
if (RARRAY_LENINT(field_names) != num_fields)
|
|
478
511
|
rb_raise(rb_eTypeError, "different number of fields and values");
|
|
479
512
|
|
|
480
513
|
field_map = rb_hash_new();
|
|
@@ -489,7 +522,6 @@ pg_tuple_load(VALUE self, VALUE a)
|
|
|
489
522
|
sizeof(*this) +
|
|
490
523
|
sizeof(*this->values) * num_fields +
|
|
491
524
|
sizeof(*this->values) * (dup_names ? 1 : 0));
|
|
492
|
-
RTYPEDDATA_DATA(self) = this;
|
|
493
525
|
|
|
494
526
|
this->result = Qnil;
|
|
495
527
|
this->typemap = Qnil;
|
|
@@ -508,6 +540,8 @@ pg_tuple_load(VALUE self, VALUE a)
|
|
|
508
540
|
this->values[num_fields] = field_names;
|
|
509
541
|
}
|
|
510
542
|
|
|
543
|
+
RTYPEDDATA_DATA(self) = this;
|
|
544
|
+
|
|
511
545
|
if (FL_TEST(a, FL_EXIVAR)) {
|
|
512
546
|
rb_copy_generic_ivar(self, a);
|
|
513
547
|
FL_SET(self, FL_EXIVAR);
|