pg 1.5.6-x64-mingw32 → 1.5.8-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8edaa42878fbf42973b532a75cc311ba482fb7850f0e922a6a4c9be093ca8d4
4
- data.tar.gz: 6414cf328b39c2163347529d7113fc4106a8f633380ed13dccc9dfe41eca5de0
3
+ metadata.gz: 684692e7caffa706b85a278d95e6df11cf7a539219ce9b038205a29e5596bd11
4
+ data.tar.gz: 4d634e0c170b5c6e1f0977de128fe1066f4c7686bd74a1d973bfe873e5d7ce54
5
5
  SHA512:
6
- metadata.gz: bdde5b8daa6b351a2196d10f0f9d483c9afecee9c22a359af3e11ee98c7edaa64169bfd129ea513cd435ab4cd93762a6e952de0335aa3c49adc7e11d8a8c8af6
7
- data.tar.gz: 7f6360b71e278a031263d0c5c97e59ba27cb357721ac702b8184e05133a57c9cc5a568368969ed28d4a79fc6dc57a8dc3f7f5e621d8ef13e5d79913f475fad7d
6
+ metadata.gz: 2087787f88e8acf47b85e53fdd4ee61dd8593b54ab704b99e698220ef2d22f9110eab88c79c61376ef74a90aed69a5dc77dc6c95942045f76971a4871aa67bf2
7
+ data.tar.gz: bb4946f2d41070f13fa505d2d12cdd3a26b7de5df8afce8648d718b3605b6811d9ad408874efde4c39959735e6ec5254ffe6c93f4b176138e254eab7744163c3
checksums.yaml.gz.sig CHANGED
Binary file
data/.appveyor.yml CHANGED
@@ -38,5 +38,5 @@ on_failure:
38
38
  environment:
39
39
  matrix:
40
40
  - ruby_version: "head"
41
- RUBYDOWNLOAD: x86
41
+ RUBYDOWNLOAD: x64
42
42
  - ruby_version: "30-x64"
@@ -19,7 +19,7 @@ jobs:
19
19
  - platform: "x64-mingw32"
20
20
  - platform: "x86-mingw32"
21
21
  steps:
22
- - uses: actions/checkout@v3
22
+ - uses: actions/checkout@v4
23
23
  - name: Set up Ruby
24
24
  uses: ruby/setup-ruby@v1
25
25
  with:
@@ -37,9 +37,9 @@ jobs:
37
37
  run: bundle exec rake gem:windows:${{ matrix.platform }}
38
38
 
39
39
  - name: Upload binary gem
40
- uses: actions/upload-artifact@v3
40
+ uses: actions/upload-artifact@v4
41
41
  with:
42
- name: binary-gem
42
+ name: binary-gem-${{ matrix.platform }}
43
43
  path: pkg/*.gem
44
44
 
45
45
  job_test_binary:
@@ -66,7 +66,7 @@ jobs:
66
66
  env:
67
67
  PGVERSION: ${{ matrix.PGVERSION }}
68
68
  steps:
69
- - uses: actions/checkout@v3
69
+ - uses: actions/checkout@v4
70
70
  - name: Set up Ruby
71
71
  if: matrix.platform != 'x86-mingw32'
72
72
  uses: ruby/setup-ruby@v1
@@ -85,9 +85,9 @@ jobs:
85
85
  echo "C:/msys64/$env:MSYSTEM_PREFIX/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
86
86
 
87
87
  - name: Download gem from build job
88
- uses: actions/download-artifact@v3
88
+ uses: actions/download-artifact@v4
89
89
  with:
90
- name: binary-gem
90
+ name: binary-gem-${{ matrix.platform }}
91
91
 
92
92
  - name: Download PostgreSQL
93
93
  run: |
@@ -1,18 +1,26 @@
1
1
  name: Source gem
2
-
3
2
  on:
4
- push:
5
- pull_request:
6
3
  workflow_dispatch:
7
4
  schedule:
8
5
  - cron: "0 5 * * 3" # At 05:00 on Wednesday # https://crontab.guru/#0_5_*_*_3
6
+ push:
7
+ branches:
8
+ - master
9
+ tags:
10
+ - "*.*.*"
11
+ pull_request:
12
+ types: [opened, synchronize]
13
+ branches:
14
+ - "*"
15
+ permissions:
16
+ contents: read
9
17
 
10
18
  jobs:
11
19
  job_build_gem:
12
20
  name: Build
13
21
  runs-on: ubuntu-latest
14
22
  steps:
15
- - uses: actions/checkout@v3
23
+ - uses: actions/checkout@v4
16
24
  - name: Set up Ruby
17
25
  uses: ruby/setup-ruby@v1
18
26
  with:
@@ -22,7 +30,7 @@ jobs:
22
30
  run: gem build pg.gemspec
23
31
 
24
32
  - name: Upload source gem
25
- uses: actions/upload-artifact@v3
33
+ uses: actions/upload-artifact@v4
26
34
  with:
27
35
  name: source-gem
28
36
  path: "*.gem"
@@ -74,14 +82,14 @@ jobs:
74
82
  MAKE: make -j2 V=1
75
83
 
76
84
  steps:
77
- - uses: actions/checkout@v3
85
+ - uses: actions/checkout@v4
78
86
  - name: Set up Ruby
79
87
  uses: ruby/setup-ruby@v1
80
88
  with:
81
89
  ruby-version: ${{ matrix.ruby }}
82
90
 
83
91
  - name: Download gem from build job
84
- uses: actions/download-artifact@v3
92
+ uses: actions/download-artifact@v4
85
93
  with:
86
94
  name: source-gem
87
95
 
@@ -120,6 +128,7 @@ jobs:
120
128
  wget https://get.enterprisedb.com/postgresql/postgresql-$PGVERSION-binaries.zip && \
121
129
  sudo mkdir -p /Library/PostgreSQL && \
122
130
  sudo unzip postgresql-$PGVERSION-binaries.zip -d /Library/PostgreSQL/$PGVER && \
131
+ sudo mv /Library/PostgreSQL/$PGVER/pgsql/* /Library/PostgreSQL/$PGVER/ && \
123
132
  echo /Library/PostgreSQL/$PGVER/bin >> $GITHUB_PATH
124
133
 
125
134
  - run: gem update --system 3.3.26
@@ -130,6 +139,8 @@ jobs:
130
139
  - name: Run specs
131
140
  env:
132
141
  PG_DEBUG: 0
142
+ # Temprary fix only for Truffleruby-24.0.0:
143
+ TRUFFLERUBYOPT: --experimental-options --keep-handles-alive
133
144
  run: ruby -rpg -S rspec spec/**/*_spec.rb -cfdoc
134
145
 
135
146
  - name: Print logs if job failed
data/Gemfile CHANGED
@@ -11,7 +11,10 @@ group :development, :test do
11
11
  gem "rake-compiler-dock", "~> 1.0"
12
12
  gem "rdoc", "~> 6.4"
13
13
  gem "rspec", "~> 3.5"
14
+ gem "ostruct", "~> 0.5" # for Rakefile.cross
14
15
  # "bigdecimal" is a gem on ruby-3.4+ and it's optional for ruby-pg.
15
16
  # Specs should succeed without it, but 4 examples are then excluded.
17
+ # With bigdecimal commented out here, corresponding tests are omitted on ruby-3.4+ but are executed on ruby < 3.4.
18
+ # That way we can check both situations in CI.
16
19
  # gem "bigdecimal", "~> 3.0"
17
20
  end
data/History.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## v1.5.8 [2024-09-06] Lars Kanis <lars@greiz-reinsdorf.de>
2
+
3
+ - Fix host list duplication every time conn.reset is used. [#586](https://github.com/ged/ruby-pg/pull/586)
4
+ - Add default decoder for anonymous record types to BasicTypeRegistry [#579](https://github.com/ged/ruby-pg/pull/579)
5
+ - Update Windows fat binary gem to OpenSSL-3.3.2 and PostgreSQL-16.4.
6
+
7
+
8
+ ## v1.5.7 [2024-07-28] Lars Kanis <lars@greiz-reinsdorf.de>
9
+
10
+ - Remove deprecated use of fptr->fd.[#562](https://github.com/ged/ruby-pg/pull/562)
11
+ Direct access is disallowed since ruby-3.4.
12
+ - Make `pgconn_connect_poll` close the socket prior to calling `PQconnectPoll`. [#564](https://github.com/ged/ruby-pg/pull/564)
13
+ This could result in an exception while connecting when used multi threaded.
14
+ - Fix several typos and improve spelling in documentation and code. [#566](https://github.com/ged/ruby-pg/pull/566)
15
+ - Add missing PG::RollbackTransaction as an option to exit conn.transaction. [#560](https://github.com/ged/ruby-pg/pull/560)
16
+ Usage like in rails: https://api.rubyonrails.org/classes/ActiveRecord/Rollback.html
17
+ - Don't print a warning when bigdecimal is required on ruby-3.4+ [#574](https://github.com/ged/ruby-pg/pull/574)
18
+ - Update Windows fat binary gem to OpenSSL-3.3.1 and PostgreSQL-16.3.
19
+
20
+
1
21
  ## v1.5.6 [2024-03-01] Lars Kanis <lars@greiz-reinsdorf.de>
2
22
 
3
23
  - Renew address resolution (DNS) in conn.reset. [#558](https://github.com/ged/ruby-pg/pull/558)
@@ -92,7 +112,7 @@ Removed:
92
112
  Repository:
93
113
 
94
114
  - `rake test` tries to find PostgreSQL server commands by pg_config [#503](https://github.com/ged/ruby-pg/pull/503)
95
- So there's no need to set the PATH manuelly any longer.
115
+ So there's no need to set the PATH manually any longer.
96
116
 
97
117
 
98
118
  ## v1.4.6 [2023-02-26] Lars Kanis <lars@greiz-reinsdorf.de>
@@ -161,7 +181,7 @@ Added:
161
181
  Bugfixes:
162
182
 
163
183
  - Try IPv6 and IPv4 addresses, if DNS resolves to both. [#452](https://github.com/ged/ruby-pg/pull/452)
164
- - Re-add block-call semantics to PG::Connection.new accidently removed in pg-1.3.0. [#454](https://github.com/ged/ruby-pg/pull/454)
184
+ - Re-add block-call semantics to PG::Connection.new accidentally removed in pg-1.3.0. [#454](https://github.com/ged/ruby-pg/pull/454)
165
185
  - Handle client error after all data consumed in #copy_data for output. [#455](https://github.com/ged/ruby-pg/pull/455)
166
186
  - Avoid spurious keyword argument warning on Ruby 2.7. [#456](https://github.com/ged/ruby-pg/pull/456)
167
187
  - Change connection setup to respect connect_timeout parameter. [#459](https://github.com/ged/ruby-pg/pull/459)
@@ -200,7 +220,7 @@ Bugfixes:
200
220
 
201
221
  - Don't leak IO in case of connection errors. [#439](https://github.com/ged/ruby-pg/pull/439)
202
222
  Previously it was kept open until the PG::Connection was garbage collected.
203
- - Fix a performance regession in conn.get_result noticed in single row mode. [#442](https://github.com/ged/ruby-pg/pull/442)
223
+ - Fix a performance regression in conn.get_result noticed in single row mode. [#442](https://github.com/ged/ruby-pg/pull/442)
204
224
  - Fix occasional error Errno::EBADF (Bad file descriptor) while connecting. [#444](https://github.com/ged/ruby-pg/pull/444)
205
225
  - Fix compatibility of res.stream_each* methods with Fiber.scheduler. [#446](https://github.com/ged/ruby-pg/pull/446)
206
226
  - Remove FL_TEST and FL_SET, which are MRI-internal. [#437](https://github.com/ged/ruby-pg/pull/437)
@@ -286,7 +306,7 @@ Type cast enhancements:
286
306
  - Add PG::BasicTypeMapForQueries::BinaryData for encoding of bytea columns. [#348](https://github.com/ged/ruby-pg/pull/348)
287
307
  - Reduce time to build coder maps and permit to reuse them for several type maps per PG::BasicTypeRegistry::CoderMapsBundle.new(conn) . [#376](https://github.com/ged/ruby-pg/pull/376)
288
308
  - Make BasicTypeRegistry a class and use a global default instance of it.
289
- Now a local type registry can be instanciated and given to the type map, to avoid changing shared global states.
309
+ Now a local type registry can be instantiated and given to the type map, to avoid changing shared global states.
290
310
  - Allow PG::BasicTypeMapForQueries to take a Proc as callback for undefined types.
291
311
 
292
312
  Other Enhancements:
data/Rakefile.cross CHANGED
@@ -31,8 +31,8 @@ class CrossLibrary < OpenStruct
31
31
  self.host_platform = toolchain
32
32
 
33
33
  # Cross-compilation constants
34
- self.openssl_version = ENV['OPENSSL_VERSION'] || '3.2.1'
35
- self.postgresql_version = ENV['POSTGRESQL_VERSION'] || '16.2'
34
+ self.openssl_version = ENV['OPENSSL_VERSION'] || '3.3.2'
35
+ self.postgresql_version = ENV['POSTGRESQL_VERSION'] || '16.4'
36
36
 
37
37
  # Check if symlinks work in the current working directory.
38
38
  # This fails, if rake-compiler-dock is running on a Windows box.
@@ -52,9 +52,8 @@ class CrossLibrary < OpenStruct
52
52
 
53
53
  # Static OpenSSL build vars
54
54
  self.static_openssl_builddir = static_builddir + "openssl-#{openssl_version}"
55
-
56
55
  self.openssl_source_uri =
57
- URI( "http://www.openssl.org/source/openssl-#{openssl_version}.tar.gz" )
56
+ URI( "https://github.com/openssl/openssl/releases/download/openssl-#{openssl_version}/openssl-#{openssl_version}.tar.gz" )
58
57
  self.openssl_tarball = static_sourcesdir + File.basename( openssl_source_uri.path )
59
58
  self.openssl_makefile = static_openssl_builddir + 'Makefile'
60
59
 
@@ -291,7 +290,7 @@ CrossLibraries.each do |xlib|
291
290
  RakeCompilerDock.sh <<-EOT, platform: platform
292
291
  (cp build/gem/gem-*.pem ~/.gem/ || true) &&
293
292
  bundle install --local &&
294
- rake native:#{platform} pkg/#{$gem_spec.full_name}-#{platform}.gem MAKE="make -j`nproc`" RUBY_CC_VERSION=3.3.0:3.2.0:3.1.0:3.0.0:2.7.0:2.6.0:2.5.0
293
+ rake native:#{platform} pkg/#{$gem_spec.full_name}-#{platform}.gem MAKEOPTS=-j`nproc` RUBY_CC_VERSION=3.3.0:3.2.0:3.1.0:3.0.0:2.7.0:2.6.0:2.5.0
295
294
  EOT
296
295
  end
297
296
  desc "Build the windows binary gems"
data/ext/extconf.rb CHANGED
@@ -9,6 +9,8 @@ if ENV['MAINTAINER_MODE']
9
9
  ' -ggdb' <<
10
10
  ' -DDEBUG' <<
11
11
  ' -pedantic'
12
+ $LDFLAGS <<
13
+ ' -ggdb'
12
14
  end
13
15
 
14
16
  if pgdir = with_config( 'pg' )
@@ -158,6 +160,7 @@ have_func 'timegm'
158
160
  have_func 'rb_gc_adjust_memory_usage' # since ruby-2.4
159
161
  have_func 'rb_gc_mark_movable' # since ruby-2.7
160
162
  have_func 'rb_io_wait' # since ruby-3.0
163
+ have_func 'rb_io_descriptor' # since ruby-3.1
161
164
 
162
165
  # unistd.h confilicts with ruby/win32.h when cross compiling for win32 and ruby 1.9.1
163
166
  have_header 'unistd.h'
data/ext/pg.c CHANGED
@@ -543,7 +543,7 @@ Init_pg_ext(void)
543
543
  /* Result#result_error_field argument constant
544
544
  *
545
545
  * The SQLSTATE code for the error.
546
- * The SQLSTATE code identies the type of error that has occurred; it can be used by front-end applications to perform specific operations (such as error handling) in response to a particular database error.
546
+ * The SQLSTATE code identifies the type of error that has occurred; it can be used by front-end applications to perform specific operations (such as error handling) in response to a particular database error.
547
547
  * For a list of the possible SQLSTATE codes, see Appendix A.
548
548
  * This field is not localizable, and is always present.
549
549
  */
@@ -233,6 +233,8 @@ j2date(int jd, int *year, int *month, int *day)
233
233
  *
234
234
  * This is a decoder class for conversion of PostgreSQL binary date
235
235
  * to Ruby Date objects.
236
+ *
237
+ * As soon as this class is used, it requires the ruby standard library 'date'.
236
238
  */
237
239
  static VALUE
238
240
  pg_bin_dec_date(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx)
data/ext/pg_connection.c CHANGED
@@ -33,8 +33,8 @@ static VALUE pgconn_async_flush(VALUE self);
33
33
  #ifdef __GNUC__
34
34
  __attribute__((format(printf, 3, 4)))
35
35
  #endif
36
- static void
37
- pg_raise_conn_error( VALUE klass, VALUE self, const char *format, ...)
36
+ NORETURN( static void
37
+ pg_raise_conn_error( VALUE klass, VALUE self, const char *format, ...))
38
38
  {
39
39
  VALUE msg, error;
40
40
  va_list ap;
@@ -264,6 +264,7 @@ pgconn_s_allocate( VALUE klass )
264
264
  RB_OBJ_WRITE(self, &this->decoder_for_get_copy_data, Qnil);
265
265
  RB_OBJ_WRITE(self, &this->trace_stream, Qnil);
266
266
  rb_ivar_set(self, rb_intern("@calls_to_put_copy_data"), INT2FIX(0));
267
+ rb_ivar_set(self, rb_intern("@iopts_for_reset"), Qnil);
267
268
 
268
269
  return self;
269
270
  }
@@ -515,9 +516,9 @@ static VALUE
515
516
  pgconn_connect_poll(VALUE self)
516
517
  {
517
518
  PostgresPollingStatusType status;
518
- status = gvl_PQconnectPoll(pg_get_pgconn(self));
519
519
 
520
520
  pgconn_close_socket_io(self);
521
+ status = gvl_PQconnectPoll(pg_get_pgconn(self));
521
522
 
522
523
  return INT2FIX((int)status);
523
524
  }
@@ -615,9 +616,9 @@ static VALUE
615
616
  pgconn_reset_poll(VALUE self)
616
617
  {
617
618
  PostgresPollingStatusType status;
618
- status = gvl_PQresetPoll(pg_get_pgconn(self));
619
619
 
620
620
  pgconn_close_socket_io(self);
621
+ status = gvl_PQresetPoll(pg_get_pgconn(self));
621
622
 
622
623
  return INT2FIX((int)status);
623
624
  }
@@ -2266,6 +2267,17 @@ pgconn_notifies(VALUE self)
2266
2267
  return hash;
2267
2268
  }
2268
2269
 
2270
+ #ifndef HAVE_RB_IO_DESCRIPTOR
2271
+ static int
2272
+ rb_io_descriptor(VALUE io)
2273
+ {
2274
+ Check_Type(io, T_FILE);
2275
+ rb_io_t *fptr = RFILE(io)->fptr;
2276
+ rb_io_check_closed(fptr);
2277
+ return fptr->fd;
2278
+ }
2279
+ #endif
2280
+
2269
2281
  #if defined(_WIN32)
2270
2282
 
2271
2283
  /* We use a specialized implementation of rb_io_wait() on Windows.
@@ -2286,7 +2298,6 @@ int rb_w32_wait_events( HANDLE *events, int num, DWORD timeout );
2286
2298
 
2287
2299
  static VALUE
2288
2300
  pg_rb_thread_io_wait(VALUE io, VALUE events, VALUE timeout) {
2289
- rb_io_t *fptr;
2290
2301
  struct timeval ptimeout;
2291
2302
 
2292
2303
  struct timeval aborttime={0,0}, currtime, waittime;
@@ -2297,7 +2308,6 @@ pg_rb_thread_io_wait(VALUE io, VALUE events, VALUE timeout) {
2297
2308
  long w32_events = 0;
2298
2309
  DWORD wait_ret;
2299
2310
 
2300
- GetOpenFile((io), fptr);
2301
2311
  if( !NIL_P(timeout) ){
2302
2312
  ptimeout.tv_sec = (time_t)(NUM2DBL(timeout));
2303
2313
  ptimeout.tv_usec = (time_t)((NUM2DBL(timeout) - (double)ptimeout.tv_sec) * 1e6);
@@ -2311,7 +2321,7 @@ pg_rb_thread_io_wait(VALUE io, VALUE events, VALUE timeout) {
2311
2321
  if(rb_events & PG_RUBY_IO_PRIORITY) w32_events |= FD_OOB;
2312
2322
 
2313
2323
  for(;;) {
2314
- if ( WSAEventSelect(_get_osfhandle(fptr->fd), hEvent, w32_events) == SOCKET_ERROR ) {
2324
+ if ( WSAEventSelect(_get_osfhandle(rb_io_descriptor(io)), hEvent, w32_events) == SOCKET_ERROR ) {
2315
2325
  WSACloseEvent( hEvent );
2316
2326
  rb_raise( rb_eConnectionBad, "WSAEventSelect socket error: %d", WSAGetLastError() );
2317
2327
  }
@@ -2354,7 +2364,7 @@ static VALUE
2354
2364
  pg_rb_io_wait(VALUE io, VALUE events, VALUE timeout) {
2355
2365
  #if defined(HAVE_RUBY_FIBER_SCHEDULER_H)
2356
2366
  /* We don't support Fiber.scheduler on Windows ruby-3.0 because there is no fast way to check whether a scheduler is active.
2357
- * Fortunatelly ruby-3.1 offers a C-API for it.
2367
+ * Fortunately ruby-3.1 offers a C-API for it.
2358
2368
  */
2359
2369
  VALUE scheduler = rb_fiber_scheduler_current();
2360
2370
 
@@ -2384,16 +2394,14 @@ typedef enum {
2384
2394
 
2385
2395
  static VALUE
2386
2396
  pg_rb_io_wait(VALUE io, VALUE events, VALUE timeout) {
2387
- rb_io_t *fptr;
2388
2397
  struct timeval waittime;
2389
2398
  int res;
2390
2399
 
2391
- GetOpenFile((io), fptr);
2392
2400
  if( !NIL_P(timeout) ){
2393
2401
  waittime.tv_sec = (time_t)(NUM2DBL(timeout));
2394
2402
  waittime.tv_usec = (time_t)((NUM2DBL(timeout) - (double)waittime.tv_sec) * 1e6);
2395
2403
  }
2396
- res = rb_wait_for_single_fd(fptr->fd, NUM2UINT(events), NIL_P(timeout) ? NULL : &waittime);
2404
+ res = rb_wait_for_single_fd(rb_io_descriptor(io), NUM2UINT(events), NIL_P(timeout) ? NULL : &waittime);
2397
2405
 
2398
2406
  return UINT2NUM(res);
2399
2407
  }
@@ -3128,7 +3136,9 @@ pgconn_async_get_last_result(VALUE self)
3128
3136
  for(;;) {
3129
3137
  int status;
3130
3138
 
3131
- /* wait for input (without blocking) before reading each result */
3139
+ /* Wait for input before reading each result.
3140
+ * That way we support the ruby-3.x IO scheduler and don't block other ruby threads.
3141
+ */
3132
3142
  wait_socket_readable(self, NULL, get_result_readable);
3133
3143
 
3134
3144
  cur = gvl_PQgetResult(conn);
@@ -3162,7 +3172,7 @@ pgconn_async_get_last_result(VALUE self)
3162
3172
  * Returns:
3163
3173
  * * +nil+ when the connection is already idle
3164
3174
  * * +true+ when some results have been discarded
3165
- * * +false+ when a failure occured and the connection was closed
3175
+ * * +false+ when a failure occurred and the connection was closed
3166
3176
  *
3167
3177
  */
3168
3178
  static VALUE
data/ext/pg_copy_coder.c CHANGED
@@ -212,6 +212,7 @@ pg_copycoder_type_map_get(VALUE self)
212
212
  *
213
213
  * See also PG::TextDecoder::CopyRow for the decoding direction with
214
214
  * PG::Connection#get_copy_data .
215
+ * And see PG::BinaryEncoder::CopyRow for an encoder of the COPY binary format.
215
216
  */
216
217
  static int
217
218
  pg_text_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx)
@@ -235,7 +236,7 @@ pg_text_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermedia
235
236
  char *ptr1;
236
237
  char *ptr2;
237
238
  int strlen;
238
- int backslashs;
239
+ int backslashes;
239
240
  VALUE subint;
240
241
  VALUE entry;
241
242
 
@@ -286,19 +287,19 @@ pg_text_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermedia
286
287
  ptr2 = current_out + strlen;
287
288
 
288
289
  /* count required backlashs */
289
- for(backslashs = 0; ptr1 != ptr2; ptr1++) {
290
+ for(backslashes = 0; ptr1 != ptr2; ptr1++) {
290
291
  /* Escape backslash itself, newline, carriage return, and the current delimiter character. */
291
292
  if(*ptr1 == '\\' || *ptr1 == '\n' || *ptr1 == '\r' || *ptr1 == this->delimiter){
292
- backslashs++;
293
+ backslashes++;
293
294
  }
294
295
  }
295
296
 
296
297
  ptr1 = current_out + strlen;
297
- ptr2 = current_out + strlen + backslashs;
298
+ ptr2 = current_out + strlen + backslashes;
298
299
  current_out = ptr2;
299
300
 
300
301
  /* Then store the escaped string on the final position, walking
301
- * right to left, until all backslashs are placed. */
302
+ * right to left, until all backslashes are placed. */
302
303
  while( ptr1 != ptr2 ) {
303
304
  *--ptr2 = *--ptr1;
304
305
  if(*ptr1 == '\\' || *ptr1 == '\n' || *ptr1 == '\r' || *ptr1 == this->delimiter){
@@ -358,6 +359,7 @@ pg_text_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermedia
358
359
  *
359
360
  * See also PG::BinaryDecoder::CopyRow for the decoding direction with
360
361
  * PG::Connection#get_copy_data .
362
+ * And see PG::TextEncoder::CopyRow for an encoder of the COPY text format.
361
363
  */
362
364
  static int
363
365
  pg_bin_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate, int enc_idx)
@@ -391,7 +393,7 @@ pg_bin_enc_copy_row(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediat
391
393
 
392
394
  switch(TYPE(entry)){
393
395
  case T_NIL:
394
- /* 4 bytes for -1 indicationg a NULL value */
396
+ /* 4 bytes for -1 indicating a NULL value */
395
397
  PG_RB_STR_ENSURE_CAPA( *intermediate, 4, current_out, end_capa_ptr );
396
398
  write_nbo32(-1, current_out);
397
399
  current_out += 4;
@@ -496,6 +498,7 @@ GetDecimalFromHex(char hex)
496
498
  *
497
499
  * See also PG::TextEncoder::CopyRow for the encoding direction with
498
500
  * PG::Connection#put_copy_data .
501
+ * And see PG::BinaryDecoder::CopyRow for a decoder of the COPY binary format.
499
502
  */
500
503
  /*
501
504
  * Parse the current line into separate attributes (fields),
@@ -763,6 +766,7 @@ static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
763
766
  *
764
767
  * See also PG::BinaryEncoder::CopyRow for the encoding direction with
765
768
  * PG::Connection#put_copy_data .
769
+ * And see PG::TextDecoder::CopyRow for a decoder of the COPY text format.
766
770
  */
767
771
  static VALUE
768
772
  pg_bin_dec_copy_row(t_pg_coder *conv, const char *input_line, int len, int _tuple, int _field, int enc_idx)
@@ -198,7 +198,7 @@ pg_text_enc_record(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate
198
198
  char *ptr1;
199
199
  char *ptr2;
200
200
  long strlen;
201
- int backslashs;
201
+ int backslashes;
202
202
  VALUE subint;
203
203
  VALUE entry;
204
204
 
@@ -249,19 +249,19 @@ pg_text_enc_record(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate
249
249
  ptr2 = current_out + strlen;
250
250
 
251
251
  /* count required backlashs */
252
- for(backslashs = 0; ptr1 != ptr2; ptr1++) {
252
+ for(backslashes = 0; ptr1 != ptr2; ptr1++) {
253
253
  /* Escape backslash itself, newline, carriage return, and the current delimiter character. */
254
254
  if(*ptr1 == '"' || *ptr1 == '\\'){
255
- backslashs++;
255
+ backslashes++;
256
256
  }
257
257
  }
258
258
 
259
259
  ptr1 = current_out + strlen;
260
- ptr2 = current_out + strlen + backslashs;
260
+ ptr2 = current_out + strlen + backslashes;
261
261
  current_out = ptr2;
262
262
 
263
263
  /* Then store the escaped string on the final position, walking
264
- * right to left, until all backslashs are placed. */
264
+ * right to left, until all backslashes are placed. */
265
265
  while( ptr1 != ptr2 ) {
266
266
  *--ptr2 = *--ptr1;
267
267
  if(*ptr1 == '"' || *ptr1 == '\\'){
@@ -340,7 +340,7 @@ record_isspace(char ch)
340
340
  * conn.exec("SELECT * FROM my_table").map_types!(PG::TypeMapByColumn.new([deco]*2)).to_a
341
341
  * # => [{"v1"=>[2.0, 3.0], "v2"=>[4.0, 5.0]}, {"v1"=>[6.0, 7.0], "v2"=>[8.0, 9.0]}]
342
342
  *
343
- * It's more very convenient to use the PG::BasicTypeRegistry, which is based on database OIDs.
343
+ * It's more convenient to use the PG::BasicTypeRegistry, which is based on database OIDs.
344
344
  * # Fetch a NULL record of our type to retrieve the OIDs of the two fields "r" and "i"
345
345
  * oids = conn.exec( "SELECT (NULL::complex).*" )
346
346
  * # Build a type map (PG::TypeMapByColumn) for decoding the "complex" type
data/ext/pg_result.c CHANGED
@@ -664,7 +664,7 @@ pgresult_verbose_error_message(VALUE self, VALUE verbosity, VALUE show_context)
664
664
  * An example:
665
665
  *
666
666
  * begin
667
- * conn.exec( "SELECT * FROM nonexistant_table" )
667
+ * conn.exec( "SELECT * FROM nonexistent_table" )
668
668
  * rescue PG::Error => err
669
669
  * p [
670
670
  * err.result.error_field( PG::Result::PG_DIAG_SEVERITY ),
@@ -684,7 +684,7 @@ pgresult_verbose_error_message(VALUE self, VALUE verbosity, VALUE show_context)
684
684
  *
685
685
  * Outputs:
686
686
  *
687
- * ["ERROR", "42P01", "relation \"nonexistant_table\" does not exist", nil, nil,
687
+ * ["ERROR", "42P01", "relation \"nonexistent_table\" does not exist", nil, nil,
688
688
  * "15", nil, nil, nil, "path/to/parse_relation.c", "857", "parserOpenTable"]
689
689
  */
690
690
  static VALUE
@@ -163,6 +163,8 @@ pg_text_dec_integer(t_pg_coder *conv, const char *val, int len, int tuple, int f
163
163
  * This is a decoder class for conversion of PostgreSQL numeric types
164
164
  * to Ruby BigDecimal objects.
165
165
  *
166
+ * As soon as this class is used, it requires the 'bigdecimal' gem.
167
+ *
166
168
  */
167
169
  static VALUE
168
170
  pg_text_dec_numeric(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx)
@@ -174,7 +176,7 @@ pg_text_dec_numeric(t_pg_coder *conv, const char *val, int len, int tuple, int f
174
176
  static VALUE
175
177
  init_pg_text_decoder_numeric(VALUE rb_mPG_TextDecoder)
176
178
  {
177
- rb_require("bigdecimal");
179
+ rb_funcall(rb_mPG, rb_intern("require_bigdecimal_without_warning"), 0);
178
180
  s_id_BigDecimal = rb_intern("BigDecimal");
179
181
 
180
182
  /* dummy = rb_define_class_under( rb_mPG_TextDecoder, "Numeric", rb_cPG_SimpleDecoder ); */
@@ -811,6 +813,7 @@ static VALUE pg_text_dec_timestamp(t_pg_coder *conv, const char *val, int len, i
811
813
  * This is a decoder class for conversion of PostgreSQL inet type
812
814
  * to Ruby IPAddr values.
813
815
  *
816
+ * As soon as this class is used, it requires the ruby standard library 'ipaddr'.
814
817
  */
815
818
  static VALUE
816
819
  pg_text_dec_inet(t_pg_coder *conv, const char *val, int len, int tuple, int field, int enc_idx)
@@ -119,6 +119,10 @@ pg_text_enc_boolean(t_pg_coder *this, VALUE value, char *out, VALUE *intermediat
119
119
  int
120
120
  pg_coder_enc_to_s(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate, int enc_idx)
121
121
  {
122
+ /* Attention:
123
+ * In contrast to all other encoders, the "this" pointer of this encoder can be NULL.
124
+ * This is because it is used as a fall-back if no encoder is defined.
125
+ */
122
126
  VALUE str = rb_obj_as_string(value);
123
127
  if( ENCODING_GET(str) == enc_idx ){
124
128
  *intermediate = str;
@@ -345,6 +349,8 @@ pg_text_enc_float(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate,
345
349
  *
346
350
  * It converts Integer, Float and BigDecimal objects.
347
351
  * All other objects are expected to respond to +to_s+.
352
+ *
353
+ * As soon as this class is used, it requires the 'bigdecimal' gem.
348
354
  */
349
355
  static int
350
356
  pg_text_enc_numeric(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate, int enc_idx)
@@ -377,7 +383,7 @@ init_pg_text_encoder_numeric(VALUE rb_mPG_TextDecoder)
377
383
  {
378
384
  s_str_F = rb_str_freeze(rb_str_new_cstr("F"));
379
385
  rb_global_variable(&s_str_F);
380
- rb_require("bigdecimal");
386
+ rb_funcall(rb_mPG, rb_intern("require_bigdecimal_without_warning"), 0);
381
387
  s_cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
382
388
 
383
389
  /* dummy = rb_define_class_under( rb_mPG_TextEncoder, "Numeric", rb_cPG_SimpleEncoder ); */
@@ -437,7 +443,7 @@ quote_array_buffer( void *_this, char *p_in, int strlen, char *p_out ){
437
443
  t_pg_composite_coder *this = _this;
438
444
  char *ptr1;
439
445
  char *ptr2;
440
- int backslashs = 0;
446
+ int backslashes = 0;
441
447
  int needquote;
442
448
 
443
449
  /* count data plus backslashes; detect chars needing quotes */
@@ -454,7 +460,7 @@ quote_array_buffer( void *_this, char *p_in, int strlen, char *p_out ){
454
460
 
455
461
  if (ch == '"' || ch == '\\'){
456
462
  needquote = 1;
457
- backslashs++;
463
+ backslashes++;
458
464
  } else if (ch == '{' || ch == '}' || ch == this->delimiter ||
459
465
  ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f'){
460
466
  needquote = 1;
@@ -463,12 +469,12 @@ quote_array_buffer( void *_this, char *p_in, int strlen, char *p_out ){
463
469
 
464
470
  if( needquote ){
465
471
  ptr1 = p_in + strlen;
466
- ptr2 = p_out + strlen + backslashs + 2;
472
+ ptr2 = p_out + strlen + backslashes + 2;
467
473
  /* Write end quote */
468
474
  *--ptr2 = '"';
469
475
 
470
476
  /* Then store the escaped string on the final position, walking
471
- * right to left, until all backslashs are placed. */
477
+ * right to left, until all backslashes are placed. */
472
478
  while( ptr1 != p_in ) {
473
479
  *--ptr2 = *--ptr1;
474
480
  if(*ptr2 == '"' || *ptr2 == '\\'){
@@ -477,7 +483,7 @@ quote_array_buffer( void *_this, char *p_in, int strlen, char *p_out ){
477
483
  }
478
484
  /* Write start quote */
479
485
  *p_out = '"';
480
- return strlen + backslashs + 2;
486
+ return strlen + backslashes + 2;
481
487
  } else {
482
488
  if( p_in != p_out )
483
489
  memcpy( p_out, p_in, strlen );
@@ -692,22 +698,22 @@ static int
692
698
  quote_literal_buffer( void *_this, char *p_in, int strlen, char *p_out ){
693
699
  char *ptr1;
694
700
  char *ptr2;
695
- int backslashs = 0;
701
+ int backslashes = 0;
696
702
 
697
703
  /* count required backlashs */
698
704
  for(ptr1 = p_in; ptr1 != p_in + strlen; ptr1++) {
699
705
  if (*ptr1 == '\''){
700
- backslashs++;
706
+ backslashes++;
701
707
  }
702
708
  }
703
709
 
704
710
  ptr1 = p_in + strlen;
705
- ptr2 = p_out + strlen + backslashs + 2;
711
+ ptr2 = p_out + strlen + backslashes + 2;
706
712
  /* Write end quote */
707
713
  *--ptr2 = '\'';
708
714
 
709
715
  /* Then store the escaped string on the final position, walking
710
- * right to left, until all backslashs are placed. */
716
+ * right to left, until all backslashes are placed. */
711
717
  while( ptr1 != p_in ) {
712
718
  *--ptr2 = *--ptr1;
713
719
  if(*ptr2 == '\''){
@@ -716,7 +722,7 @@ quote_literal_buffer( void *_this, char *p_in, int strlen, char *p_out ){
716
722
  }
717
723
  /* Write start quote */
718
724
  *p_out = '\'';
719
- return strlen + backslashs + 2;
725
+ return strlen + backslashes + 2;
720
726
  }
721
727
 
722
728
 
data/lib/2.5/pg_ext.so CHANGED
Binary file
data/lib/2.6/pg_ext.so CHANGED
Binary file
data/lib/2.7/pg_ext.so CHANGED
Binary file
data/lib/3.0/pg_ext.so CHANGED
Binary file
@@ -167,7 +167,7 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
167
167
  end
168
168
 
169
169
  begin
170
- require "bigdecimal"
170
+ PG.require_bigdecimal_without_warning
171
171
  has_bigdecimal = true
172
172
  rescue LoadError
173
173
  end
@@ -171,7 +171,14 @@ class PG::BasicTypeRegistry
171
171
  include Checker
172
172
 
173
173
  def initialize
174
- # The key of these hashs maps to the `typname` column from the table pg_type.
174
+ # @coders_by_name has a content of
175
+ # Array< Hash< Symbol: Hash< String: Coder > > >
176
+ #
177
+ # The layers are:
178
+ # * index of Array is 0 (text) and 1 (binary)
179
+ # * Symbol key in the middle Hash is :encoder and :decoder
180
+ # * String key in the inner Hash corresponds to the `typname` column in the table pg_type
181
+ # * Coder value in the inner Hash is the associated coder object
175
182
  @coders_by_name = []
176
183
  end
177
184
 
@@ -226,7 +233,7 @@ class PG::BasicTypeRegistry
226
233
  alias_type 0, 'oid', 'int2'
227
234
 
228
235
  begin
229
- require "bigdecimal"
236
+ PG.require_bigdecimal_without_warning
230
237
  register_type 0, 'numeric', PG::TextEncoder::Numeric, PG::TextDecoder::Numeric
231
238
  rescue LoadError
232
239
  end
@@ -271,6 +278,7 @@ class PG::BasicTypeRegistry
271
278
  register_type 0, 'inet', PG::TextEncoder::Inet, PG::TextDecoder::Inet
272
279
  alias_type 0, 'cidr', 'inet'
273
280
 
281
+ register_type 0, 'record', PG::TextEncoder::Record, PG::TextDecoder::Record
274
282
 
275
283
 
276
284
  register_type 1, 'int2', PG::BinaryEncoder::Int2, PG::BinaryDecoder::Integer
data/lib/pg/connection.rb CHANGED
@@ -166,7 +166,10 @@ class PG::Connection
166
166
  # conn.put_copy_data ['more', 'data', 'to', 'copy']
167
167
  # end
168
168
  #
169
- # Also PG::BinaryEncoder::CopyRow can be used to send data in binary format to the server.
169
+ # All 4 CopyRow classes can take a type map to specify how the columns are mapped to and from the database format.
170
+ # For details see the particular CopyRow class description.
171
+ #
172
+ # PG::BinaryEncoder::CopyRow can be used to send data in binary format to the server.
170
173
  # In this case copy_data generates the header and trailer data automatically:
171
174
  # enco = PG::BinaryEncoder::CopyRow.new
172
175
  # conn.copy_data "COPY my_table FROM STDIN (FORMAT binary)", enco do
@@ -306,6 +309,11 @@ class PG::Connection
306
309
  rollback = false
307
310
  exec "BEGIN"
308
311
  yield(self)
312
+ rescue PG::RollbackTransaction
313
+ rollback = true
314
+ cancel if transaction_status == PG::PQTRANS_ACTIVE
315
+ block
316
+ exec "ROLLBACK"
309
317
  rescue Exception
310
318
  rollback = true
311
319
  cancel if transaction_status == PG::PQTRANS_ACTIVE
@@ -493,7 +501,7 @@ class PG::Connection
493
501
  # See also #copy_data.
494
502
  #
495
503
  def put_copy_data(buffer, encoder=nil)
496
- # sync_put_copy_data does a non-blocking attept to flush data.
504
+ # sync_put_copy_data does a non-blocking attempt to flush data.
497
505
  until res=sync_put_copy_data(buffer, encoder)
498
506
  # It didn't flush immediately and allocation of more buffering memory failed.
499
507
  # Wait for all data sent by doing a blocking flush.
@@ -565,7 +573,9 @@ class PG::Connection
565
573
  # Resets the backend connection. This method closes the
566
574
  # backend connection and tries to re-connect.
567
575
  def reset
568
- iopts = conninfo_hash.compact
576
+ # Use connection options from PG::Connection.new to reconnect with the same options but with renewed DNS resolution.
577
+ # Use conninfo_hash as a fallback when connect_start was used to create the connection object.
578
+ iopts = @iopts_for_reset || conninfo_hash.compact
569
579
  if iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
570
580
  iopts = self.class.send(:resolve_hosts, iopts)
571
581
  end
@@ -817,6 +827,7 @@ class PG::Connection
817
827
  iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
818
828
  iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts)
819
829
 
830
+ iopts_for_reset = iopts
820
831
  if iopts[:hostaddr]
821
832
  # hostaddr is provided -> no need to resolve hostnames
822
833
 
@@ -830,6 +841,8 @@ class PG::Connection
830
841
 
831
842
  raise PG::ConnectionBad, conn.error_message if conn.status == PG::CONNECTION_BAD
832
843
 
844
+ # save the connection options for conn.reset
845
+ conn.instance_variable_set(:@iopts_for_reset, iopts_for_reset)
833
846
  conn.send(:async_connect_or_reset, :connect_poll)
834
847
  conn
835
848
  end
data/lib/pg/exceptions.rb CHANGED
@@ -21,5 +21,11 @@ module PG
21
21
  class NotInBlockingMode < PG::Error
22
22
  end
23
23
 
24
+ # PG::Connection#transaction uses this exception to distinguish a deliberate rollback from other exceptional situations.
25
+ # Normally, raising an exception will cause the .transaction method to rollback the database transaction and pass on the exception.
26
+ # But if you raise an PG::RollbackTransaction exception, then the database transaction will be rolled back, without passing on the exception.
27
+ class RollbackTransaction < StandardError
28
+ end
29
+
24
30
  end # module PG
25
31
 
@@ -5,6 +5,9 @@ require 'date'
5
5
 
6
6
  module PG
7
7
  module TextDecoder
8
+ # This is a decoder class for conversion of PostgreSQL date type to Ruby Date values.
9
+ #
10
+ # As soon as this class is used, it requires the ruby standard library 'date'.
8
11
  class Date < SimpleDecoder
9
12
  def decode(string, tuple=nil, field=nil)
10
13
  if string =~ /\A(\d{4})-(\d\d)-(\d\d)\z/
@@ -5,6 +5,9 @@ require 'json'
5
5
 
6
6
  module PG
7
7
  module TextDecoder
8
+ # This is a decoder class for conversion of PostgreSQL JSON/JSONB type to Ruby Hash, Array, String, Numeric, nil values.
9
+ #
10
+ # As soon as this class is used, it requires the ruby standard library 'json'.
8
11
  class JSON < SimpleDecoder
9
12
  def decode(string, tuple=nil, field=nil)
10
13
  ::JSON.parse(string, quirks_mode: true)
@@ -3,6 +3,7 @@
3
3
 
4
4
  module PG
5
5
  module TextEncoder
6
+ # This is a encoder class for conversion of Ruby Date values to PostgreSQL date type.
6
7
  class Date < SimpleEncoder
7
8
  def encode(value)
8
9
  value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d") : value
@@ -5,6 +5,9 @@ require 'ipaddr'
5
5
 
6
6
  module PG
7
7
  module TextEncoder
8
+ # This is a encoder class for conversion of Ruby IPAddr values to PostgreSQL inet type.
9
+ #
10
+ # As soon as this class is used, it requires the ruby standard library 'ipaddr'.
8
11
  class Inet < SimpleEncoder
9
12
  def encode(value)
10
13
  case value
@@ -5,6 +5,9 @@ require 'json'
5
5
 
6
6
  module PG
7
7
  module TextEncoder
8
+ # This is a encoder class for conversion of Ruby Hash, Array, String, Numeric, nil values to PostgreSQL JSON/JSONB type.
9
+ #
10
+ # As soon as this class is used, it requires the ruby standard library 'json'.
8
11
  class JSON < SimpleEncoder
9
12
  def encode(value)
10
13
  ::JSON.generate(value, quirks_mode: true)
data/lib/pg/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module PG
2
2
  # Library version
3
- VERSION = '1.5.6'
3
+ VERSION = '1.5.8'
4
4
  end
data/lib/pg.rb CHANGED
@@ -126,4 +126,14 @@ module PG
126
126
  Warning.extend(TruffleFixWarn)
127
127
  end
128
128
 
129
+ # Ruby-3.4+ prints a warning, if bigdecimal is required but not in the Gemfile.
130
+ # But it's a false positive, since we enable bigdecimal depending features only if it's available.
131
+ # And most people don't need these features.
132
+ def self.require_bigdecimal_without_warning
133
+ oldverb, $VERBOSE = $VERBOSE, nil
134
+ require "bigdecimal"
135
+ ensure
136
+ $VERBOSE = oldverb
137
+ end
138
+
129
139
  end # module PG
Binary file
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.6
4
+ version: 1.5.8
5
5
  platform: x64-mingw32
6
6
  authors:
7
7
  - Michael Granger
@@ -34,7 +34,7 @@ cert_chain:
34
34
  5wFER6XhvvLDFAMh/jMg+s7Wd5SbSHgHNSUaUGVtdWkVPOer6oF0aLdZUR3CETkn
35
35
  5nWXZma/BUd3YgYA/Xumc6QQqIS4p7mr
36
36
  -----END CERTIFICATE-----
37
- date: 2024-03-01 00:00:00.000000000 Z
37
+ date: 2024-09-06 00:00:00.000000000 Z
38
38
  dependencies: []
39
39
  description: Pg is the Ruby interface to the PostgreSQL RDBMS. It works with PostgreSQL
40
40
  9.3 and later.
metadata.gz.sig CHANGED
Binary file