pg 1.6.0.rc1 → 1.6.0.rc2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 79b95ddd7cf2f41c4295570df7472993c734fa4f562631f4d8a2dd57f959a2e2
4
- data.tar.gz: 14c732693af768e0ee2b071e9bbd2a7def69ca3e3e2140b8c55406634ab7272c
3
+ metadata.gz: 956a534d3545f2d25ab922f903042bf6ae160f97c2b0e67a535d70559ce78cde
4
+ data.tar.gz: facd2045b9f51620fd66f407202d4b5d4f1ad31f991eef7b5eb4c2f4606416ef
5
5
  SHA512:
6
- metadata.gz: 89d7479a9c132c1ff8844b1346d083ce3b7d37a275fcc8e4d56523a3a49f79e44f4620fa1b4ad8b53d0e71ebdb36f278a48b9aa792169492395a2e412142b161
7
- data.tar.gz: ac4ad88528e83ab1fb7132ae9b8f6b0c685a747ea46c802c4f16fda4d9c26b01e01fe207a31525293eb3e68d74044850a754598a4ba61640b24762b6b39eb542
6
+ metadata.gz: bfbeb91b593698fbce35d3d7eb54b1c918ab741f96568faedcc045ddf0fe9917735d411df339f4fc2b7ced6d5fb4e9fb4845c56ee17d8b5880b30620741650c1
7
+ data.tar.gz: 71524df43d1c3891d9ecd052153f93e8e563b0ab5f4324292fe176bc8c93ace60be175fe275fb7546420d996b8cc7eecf27a254729029d566bcbe67c34ad67bc
checksums.yaml.gz.sig CHANGED
Binary file
@@ -1,3 +1,31 @@
1
+ ## v1.6.0.rc2 [2025-07-16] Lars Kanis <lars@greiz-reinsdorf.de>
2
+
3
+ Added:
4
+
5
+ - Add binary gems for Ruby 3.4.
6
+ - Add fat binary gem for platform `aarch64-mingw-ucrt` aka Windows on ARM [#626](https://github.com/ged/ruby-pg/pull/626), for platform Macos on Intel and ARM [#643](https://github.com/ged/ruby-pg/pull/643) and for platform `aarch64-linux` [#646](https://github.com/ged/ruby-pg/pull/646).
7
+ - Update fat binary gem to OpenSSL-3.5.1 and PostgreSQL-17.5.
8
+ - Add a patch to libpq to avoid starvation on bigger SSL records, which some database engines other than vanilla PostgreSQL use.
9
+ This patch applies to platform specific binary gems only.
10
+ [#616](https://github.com/ged/ruby-pg/pull/616)
11
+ - Fix missing array input verification in PG::TypeMapByColumn.
12
+ This could cause a segfault.
13
+ [#620](https://github.com/ged/ruby-pg/pull/620)
14
+ - Add possibility to define the number of array dimensions to be encoded.
15
+ Setting dimensions is especially useful, when a Record shall be encoded into an Array, since the Array encoder can not distinguish if the array shall be encoded as a higher dimension or as a record otherwise.
16
+ [#622](https://github.com/ged/ruby-pg/pull/622)
17
+ - Add MINGW package dependency which is resolved by RubyInstaller.
18
+ [#617](https://github.com/ged/ruby-pg/pull/617)
19
+ - Change `conn.server_version` and `conn.protocol_version` to raise instead of return 0 on error.
20
+ [#632](https://github.com/ged/ruby-pg/pull/632)
21
+ - Fix making PG::BasicTypeMapForQueries shareable for Ractor in ruby-3.5.
22
+ [#636](https://github.com/ged/ruby-pg/pull/636)
23
+ - Rename `History.md` to `CHANGELOG.md`, which is more common.
24
+ [#642](https://github.com/ged/ruby-pg/pull/642)
25
+ - Fix connecting to multiple hosts after `connnect_timeout`.
26
+ [#637](https://github.com/ged/ruby-pg/pull/637)
27
+
28
+
1
29
  ## v1.6.0.rc1 [2024-11-28] Lars Kanis <lars@greiz-reinsdorf.de>
2
30
 
3
31
  Added:
data/Gemfile CHANGED
@@ -13,7 +13,7 @@ end
13
13
  group :test do
14
14
  gem "bundler", ">= 1.16", "< 3.0"
15
15
  gem "rake-compiler", "~> 1.0"
16
- gem "rake-compiler-dock", "~> 1.5"
16
+ gem "rake-compiler-dock", "~> 1.9.1"
17
17
  gem "rspec", "~> 3.5"
18
18
  # "bigdecimal" is a gem on ruby-3.4+ and it's optional for ruby-pg.
19
19
  # Specs should succeed without it, but 4 examples are then excluded.
data/README.ja.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  * ホーム :: https://github.com/ged/ruby-pg
4
4
  * ドキュメント :: http://deveiate.org/code/pg (英語)、 https://deveiate.org/code/pg/README_ja_md.html (日本語)
5
- * 変更履歴 :: link:/History.md
5
+ * 変更履歴 :: link:/CHANGELOG.md
6
6
 
7
7
  [![https://gitter.im/ged/ruby-pg
8
8
  でチャットに参加](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ged/ruby-pg?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  * home :: https://github.com/ged/ruby-pg
4
4
  * docs :: http://deveiate.org/code/pg (English) ,
5
5
  https://deveiate.org/code/pg/README_ja_md.html (Japanese)
6
- * clog :: link:/History.md
6
+ * clog :: link:/CHANGELOG.md
7
7
 
8
8
  [![Join the chat at https://gitter.im/ged/ruby-pg](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ged/ruby-pg?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
9
9
 
@@ -175,6 +175,7 @@ The following type maps are prefilled with type mappings from the PG::BasicTypeR
175
175
  * PG::BasicTypeMapBasedOnResult - a PG::TypeMapByOid prefilled with encoders for common PostgreSQL column types
176
176
  * PG::BasicTypeMapForQueries - a PG::TypeMapByClass prefilled with encoders for common Ruby value classes
177
177
 
178
+ Several type maps can be chained by setting PG::TypeMap::DefaultTypeMappable#default_type_map .
178
179
 
179
180
  ## Thread support
180
181
 
data/Rakefile CHANGED
@@ -11,6 +11,7 @@ require 'rake/clean'
11
11
  require 'rspec/core/rake_task'
12
12
  require 'bundler'
13
13
  require 'bundler/gem_helper'
14
+ require_relative "rakelib/pg_gem_helper"
14
15
 
15
16
  # Build directory constants
16
17
  BASEDIR = Pathname( __FILE__ ).dirname
@@ -32,9 +33,9 @@ CLEAN.include "lib/*/libpq.dll"
32
33
  CLEAN.include "lib/pg_ext.*"
33
34
  CLEAN.include "lib/pg/postgresql_lib_path.rb"
34
35
  CLEAN.include "ports/*.installed"
35
- CLEAN.include "ports/*mingw*", "ports/*linux*"
36
+ CLEAN.include "ports/*mingw*", "ports/*linux*", "ports/*darwin*"
36
37
 
37
- Bundler::GemHelper.install_tasks
38
+ PgGemHelper.install_tasks
38
39
  $gem_spec = Bundler.load_gemspec(GEMSPEC)
39
40
 
40
41
  desc "Turn on warnings and debugging in the build."
@@ -44,14 +45,21 @@ end
44
45
 
45
46
  CrossLibrary = Struct.new :platform, :openssl_config, :toolchain
46
47
  CrossLibraries = [
48
+ ['aarch64-mingw-ucrt', 'mingwarm64', 'aarch64-w64-mingw32'],
47
49
  ['x64-mingw-ucrt', 'mingw64', 'x86_64-w64-mingw32'],
48
50
  ['x86-mingw32', 'mingw', 'i686-w64-mingw32'],
49
51
  ['x64-mingw32', 'mingw64', 'x86_64-w64-mingw32'],
50
- ['x86_64-linux', 'linux-x86_64', 'x86_64-redhat-linux-gnu'],
52
+ ['x86_64-linux', 'linux-x86_64', 'x86_64-linux-gnu'],
53
+ ['aarch64-linux', 'linux-aarch64', 'aarch64-linux-gnu'],
54
+ ['x86_64-darwin', 'darwin64-x86_64', 'x86_64-apple-darwin'],
55
+ ['arm64-darwin', 'darwin64-arm64', 'arm64-apple-darwin'],
51
56
  ].map do |platform, openssl_config, toolchain|
52
57
  CrossLibrary.new platform, openssl_config, toolchain
53
58
  end
54
59
 
60
+ # Register binary gems to be pushed to rubygems.org
61
+ Bundler::GemHelper.instance.cross_platforms = CrossLibraries.map(&:platform)
62
+
55
63
  # Rake-compiler task
56
64
  Rake::ExtensionTask.new do |ext|
57
65
  ext.name = 'pg_ext'
@@ -75,6 +83,7 @@ Rake::ExtensionTask.new do |ext|
75
83
  # Add libpq.dll/.so to fat binary gemspecs
76
84
  ext.cross_compiling do |spec|
77
85
  spec.files << "ports/#{spec.platform.to_s}/lib/libpq-ruby-pg.so.1" if spec.platform.to_s =~ /linux/
86
+ spec.files << "ports/#{spec.platform.to_s}/lib/libpq-ruby-pg.1.dylib" if spec.platform.to_s =~ /darwin/
78
87
  spec.files << "ports/#{spec.platform.to_s}/lib/libpq.dll" if spec.platform.to_s =~ /mingw|mswin/
79
88
  end
80
89
  end
@@ -95,18 +104,33 @@ task 'gem:native:prepare' do
95
104
  end
96
105
  end
97
106
 
107
+ task 'install_darwin_mig', [:arch] do |t, args|
108
+ sh <<~EOT
109
+ rm -rf bootstrap_cmds &&
110
+ git clone --branch=cross_platform https://github.com/markmentovai/bootstrap_cmds &&
111
+ cd bootstrap_cmds &&
112
+ autoreconf --install &&
113
+ sh configure &&
114
+ make &&
115
+ sed -E -i 's/^cppflags=(.*)/cppflags=(\\1 "-D#{args[:arch]}" "-I\\/opt\\/osxcross\\/target\\/SDK\\/MacOSX11.1.sdk\\/usr\\/include")/' migcom.tproj/mig.sh &&
116
+ sudo make install
117
+ EOT
118
+ end
119
+
98
120
  CrossLibraries.each do |xlib|
99
121
  platform = xlib.platform
100
122
  desc "Build fat binary gem for platform #{platform}"
101
123
  task "gem:native:#{platform}" => ['gem:native:prepare'] do
102
124
  RakeCompilerDock.sh <<-EOT, platform: platform
103
- #{ "sudo yum install -y perl-IPC-Cmd bison flex &&" if platform =~ /linux/ }
125
+ #{ "sudo apt-get update && sudo apt-get install -y bison flex &&" if platform =~ /darwin/ }
104
126
  #{ # remove nm on Linux to suppress PostgreSQL's check for exit which raises thread_exit as a false positive:
105
- "sudo mv `which nm` `which nm`.bak && sudo mv `which nm` `which nm`.bak &&" if platform =~ /linux/ }
106
- #{ "sudo apt-get update && sudo apt-get install -y bison flex &&" if platform =~ /mingw/ }
127
+ "sudo mv `which nm` `which nm`.bak &&" if platform =~ /linux/ }
128
+ sudo apt-get update && sudo apt-get install -y bison flex &&
107
129
  (cp build/gem/gem-*.pem ~/.gem/ || true) &&
108
130
  bundle install --local &&
109
- 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
131
+ #{ "rake install_darwin_mig[__arm64__]" if platform =~ /arm64-darwin/ }
132
+ #{ "rake install_darwin_mig[__x86_64__]" if platform =~ /x86_64-darwin/ }
133
+ rake native:#{platform} pkg/#{$gem_spec.full_name}-#{platform}.gem MAKEOPTS=-j`nproc` RUBY_CC_VERSION=#{RakeCompilerDock.ruby_cc_version("~>2.7", "~>3.0")}
110
134
  EOT
111
135
  end
112
136
  desc "Build the native binary gems"
data/ext/extconf.rb CHANGED
@@ -27,13 +27,13 @@ if gem_platform=with_config("cross-build")
27
27
  gem 'mini_portile2', '~>2.1'
28
28
  require 'mini_portile2'
29
29
 
30
- OPENSSL_VERSION = ENV['OPENSSL_VERSION'] || '3.4.0'
30
+ OPENSSL_VERSION = ENV['OPENSSL_VERSION'] || '3.5.1'
31
31
  OPENSSL_SOURCE_URI = "http://www.openssl.org/source/openssl-#{OPENSSL_VERSION}.tar.gz"
32
32
 
33
33
  KRB5_VERSION = ENV['KRB5_VERSION'] || '1.21.3'
34
34
  KRB5_SOURCE_URI = "http://kerberos.org/dist/krb5/#{KRB5_VERSION[/^(\d+\.\d+)/]}/krb5-#{KRB5_VERSION}.tar.gz"
35
35
 
36
- POSTGRESQL_VERSION = ENV['POSTGRESQL_VERSION'] || '17.2'
36
+ POSTGRESQL_VERSION = ENV['POSTGRESQL_VERSION'] || '17.5'
37
37
  POSTGRESQL_SOURCE_URI = "http://ftp.postgresql.org/pub/source/v#{POSTGRESQL_VERSION}/postgresql-#{POSTGRESQL_VERSION}.tar.bz2"
38
38
 
39
39
  class BuildRecipe < MiniPortile
@@ -69,7 +69,7 @@ if gem_platform=with_config("cross-build")
69
69
  def configure
70
70
  envs = []
71
71
  envs << "CFLAGS=-DDSO_WIN32 -DOPENSSL_THREADS" if RUBY_PLATFORM =~ /mingw|mswin/
72
- envs << "CFLAGS=-fPIC -DOPENSSL_THREADS" if RUBY_PLATFORM =~ /linux/
72
+ envs << "CFLAGS=-fPIC -DOPENSSL_THREADS" if RUBY_PLATFORM =~ /linux|darwin/
73
73
  execute('configure', ['env', *envs, "./Configure", openssl_platform, "threads", "-static", "CROSS_COMPILE=#{host}-", configure_prefix], altlog: "config.log")
74
74
  end
75
75
  def compile
@@ -85,22 +85,56 @@ if gem_platform=with_config("cross-build")
85
85
  recipe.cook_and_activate
86
86
  end
87
87
 
88
- if RUBY_PLATFORM =~ /linux/
88
+ if RUBY_PLATFORM =~ /linux|darwin/
89
89
  krb5_recipe = BuildRecipe.new("krb5", KRB5_VERSION, [KRB5_SOURCE_URI]).tap do |recipe|
90
90
  class << recipe
91
91
  def work_path
92
92
  File.join(super, "src")
93
93
  end
94
+ def configure
95
+ if RUBY_PLATFORM=~/darwin/
96
+ ENV["CC"] = host[/^.*[^\.\d]/] + "-clang"
97
+ ENV["CXX"] = host[/^.*[^\.\d]/] + "-c++"
98
+
99
+ # Manually set the correct values for configure checks that libkrb5 won't be
100
+ # able to perform because we're cross-compiling.
101
+ ENV["krb5_cv_attr_constructor_destructor"] = "yes"
102
+ ENV["ac_cv_func_regcomp"] = "yes"
103
+ ENV["ac_cv_printf_positional"] = "yes"
104
+ end
105
+ super
106
+ end
94
107
  end
95
108
  # We specify -fcommon to get around duplicate definition errors in recent gcc.
96
109
  # See https://github.com/cockroachdb/cockroach/issues/49734
97
110
  recipe.configure_options << "CFLAGS=-fcommon#{" -fPIC" if RUBY_PLATFORM =~ /linux/}"
111
+ recipe.configure_options << "LDFLAGS=-framework Kerberos" if RUBY_PLATFORM =~ /darwin/
98
112
  recipe.configure_options << "--without-keyutils"
113
+ recipe.configure_options << "--disable-nls"
114
+ recipe.configure_options << "--disable-silent-rules"
115
+ recipe.configure_options << "--without-system-verto"
116
+ recipe.configure_options << "krb5_cv_attr_constructor_destructor=yes"
117
+ recipe.configure_options << "ac_cv_func_regcomp=yes"
118
+ recipe.configure_options << "ac_cv_printf_positional=yes"
99
119
  recipe.host = toolchain
100
120
  recipe.cook_and_activate
101
121
  end
102
122
  end
103
123
 
124
+ # We build a libpq library file which static links OpenSSL and krb5.
125
+ # Our builtin libpq is referenced in different ways depending on the OS:
126
+ # - Window: Add the ports directory at runtime per RubyInstaller::Runtime.add_dll_directory
127
+ # The file is called "libpq.dll"
128
+ # - Linux: Add a rpath to pg_ext.so which references the ports directory.
129
+ # The file is called "libpq-ruby-pg.so.1" to avoid loading of system libpq by accident.
130
+ # - Macos: Add a reference with relative path in pg_ext.so to the ports directory.
131
+ # The file is called "libpq-ruby-pg.1.dylib" to avoid loading of other libpq by accident.
132
+ libpq_orig, libpq_rubypg = case RUBY_PLATFORM
133
+ when /linux/ then ["libpq.so.5", "libpq-ruby-pg.so.1"]
134
+ when /darwin/ then ["libpq.5.dylib", "libpq-ruby-pg.1.dylib"]
135
+ # when /mingw/ then ["libpq.dll", "libpq.dll"] # renaming not needed
136
+ end
137
+
104
138
  postgresql_recipe = BuildRecipe.new("postgresql", POSTGRESQL_VERSION, [POSTGRESQL_SOURCE_URI]).tap do |recipe|
105
139
  class << recipe
106
140
  def configure_defaults
@@ -108,9 +142,11 @@ if gem_platform=with_config("cross-build")
108
142
  "--target=#{host}",
109
143
  "--host=#{host}",
110
144
  '--with-openssl',
111
- *(RUBY_PLATFORM=~/linux/ ? ['--with-gssapi'] : []),
145
+ *(RUBY_PLATFORM=~/linux|darwin/ ? ['--with-gssapi'] : []),
112
146
  '--without-zlib',
113
147
  '--without-icu',
148
+ '--without-readline',
149
+ 'ac_cv_search_gss_store_cred_into=',
114
150
  ]
115
151
  end
116
152
  def compile
@@ -121,23 +157,25 @@ if gem_platform=with_config("cross-build")
121
157
  end
122
158
  end
123
159
 
124
- recipe.configure_options << "CFLAGS=#{" -fPIC" if RUBY_PLATFORM =~ /linux/}"
125
- recipe.configure_options << "LDFLAGS=-L#{openssl_recipe.path}/lib -L#{openssl_recipe.path}/lib64 #{"-Wl,-soname,libpq-ruby-pg.so.1 -lgssapi_krb5 -lkrb5 -lk5crypto -lkrb5support" if RUBY_PLATFORM =~ /linux/}"
160
+ recipe.host = toolchain
161
+ recipe.configure_options << "CFLAGS=#{" -fPIC" if RUBY_PLATFORM =~ /linux|darwin/}"
162
+ recipe.configure_options << "LDFLAGS=-L#{openssl_recipe.path}/lib -L#{openssl_recipe.path}/lib64 -L#{openssl_recipe.path}/lib-arm64 #{"-Wl,-soname,#{libpq_rubypg} -lgssapi_krb5 -lkrb5 -lk5crypto -lkrb5support -ldl" if RUBY_PLATFORM =~ /linux/} #{"-Wl,-install_name,@loader_path/../../ports/#{gem_platform}/lib/#{libpq_rubypg} -lgssapi_krb5 -lkrb5 -lk5crypto -lkrb5support -lresolv -framework Kerberos" if RUBY_PLATFORM =~ /darwin/}"
126
163
  recipe.configure_options << "LIBS=-lkrb5 -lcom_err -lk5crypto -lkrb5support -lresolv" if RUBY_PLATFORM =~ /linux/
127
164
  recipe.configure_options << "LIBS=-lssl -lwsock32 -lgdi32 -lws2_32 -lcrypt32" if RUBY_PLATFORM =~ /mingw|mswin/
128
165
  recipe.configure_options << "CPPFLAGS=-I#{openssl_recipe.path}/include"
129
- recipe.host = toolchain
130
166
  recipe.cook_and_activate
131
167
  end
132
168
 
133
169
  # Use our own library name for libpq to avoid loading of system libpq by accident.
134
- FileUtils.ln_sf File.join(postgresql_recipe.port_path, "lib/libpq.so.5"),
135
- File.join(postgresql_recipe.port_path, "lib/libpq-ruby-pg.so.1")
170
+ FileUtils.ln_sf File.join(postgresql_recipe.port_path, "lib/#{libpq_orig}"),
171
+ File.join(postgresql_recipe.port_path, "lib/#{libpq_rubypg}")
136
172
  # Avoid dependency to external libgcc.dll on x86-mingw32
137
- $LDFLAGS << " -static-libgcc"
138
- # Find libpq in the ports directory coming from lib/3.3
173
+ $LDFLAGS << " -static-libgcc" if RUBY_PLATFORM =~ /mingw|mswin/
174
+ # Avoid: "libpq.so: undefined reference to `dlopen'" in cross-ruby-2.7.8
175
+ $LDFLAGS << " -Wl,--no-as-needed" if RUBY_PLATFORM !~ /aarch64|arm64|darwin/
176
+ # Find libpq in the ports directory coming from lib/3.x
139
177
  # It is shared between all compiled ruby versions.
140
- $LDFLAGS << " '-Wl,-rpath=$$ORIGIN/../../ports/#{gem_platform}/lib'"
178
+ $LDFLAGS << " '-Wl,-rpath=$$ORIGIN/../../ports/#{gem_platform}/lib'" if RUBY_PLATFORM =~ /linux/
141
179
  # Don't use pg_config for cross build, but --with-pg-* path options
142
180
  dir_config('pg', "#{postgresql_recipe.path}/include", "#{postgresql_recipe.path}/lib")
143
181
 
data/ext/pg.h CHANGED
@@ -206,6 +206,7 @@ typedef struct {
206
206
  t_pg_coder comp;
207
207
  t_pg_coder *elem;
208
208
  int needs_quotation;
209
+ int dimensions;
209
210
  char delimiter;
210
211
  } t_pg_composite_coder;
211
212
 
@@ -320,6 +320,9 @@ pg_bin_enc_date(t_pg_coder *this, VALUE value, char *out, VALUE *intermediate, i
320
320
  * This encoder expects an Array of values or sub-arrays as input.
321
321
  * Other values are passed through as byte string without interpretation.
322
322
  *
323
+ * It is possible to enforce a number of dimensions to be encoded by #dimensions= .
324
+ * Deeper nested arrays are then passed to the elements encoder and less nested arrays raise an ArgumentError.
325
+ *
323
326
  * The accessors needs_quotation and delimiter are ignored for binary encoding.
324
327
  *
325
328
  */
@@ -346,7 +349,8 @@ pg_bin_enc_array(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate,
346
349
  dim_sizes[ndim-1] = RARRAY_LENINT(el1);
347
350
  nitems *= dim_sizes[ndim-1];
348
351
  el2 = rb_ary_entry(el1, 0);
349
- if (TYPE(el2) == T_ARRAY) {
352
+ if ( (this->dimensions < 0 || ndim < this->dimensions) &&
353
+ TYPE(el2) == T_ARRAY) {
350
354
  ndim++;
351
355
  if (ndim > MAXDIM)
352
356
  rb_raise( rb_eArgError, "unsupported number of array dimensions: >%d", ndim );
@@ -356,6 +360,9 @@ pg_bin_enc_array(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate,
356
360
  el1 = el2;
357
361
  }
358
362
  }
363
+ if( this->dimensions >= 0 && (ndim==0 ? 1 : ndim) != this->dimensions ){
364
+ rb_raise(rb_eArgError, "less array dimensions to encode (%d) than expected (%d)", ndim, this->dimensions);
365
+ }
359
366
 
360
367
  if(out){
361
368
  /* Second encoder pass -> write data to `out` */
data/ext/pg_coder.c CHANGED
@@ -135,6 +135,7 @@ pg_composite_encoder_allocate( VALUE klass )
135
135
  this->elem = NULL;
136
136
  this->needs_quotation = 1;
137
137
  this->delimiter = ',';
138
+ this->dimensions = -1;
138
139
  rb_iv_set( self, "@elements_type", Qnil );
139
140
  return self;
140
141
  }
@@ -157,6 +158,7 @@ pg_composite_decoder_allocate( VALUE klass )
157
158
  this->elem = NULL;
158
159
  this->needs_quotation = 1;
159
160
  this->delimiter = ',';
161
+ this->dimensions = -1;
160
162
  rb_iv_set( self, "@elements_type", Qnil );
161
163
  return self;
162
164
  }
@@ -421,6 +423,49 @@ pg_coder_delimiter_get(VALUE self)
421
423
  return rb_str_new(&this->delimiter, 1);
422
424
  }
423
425
 
426
+ /*
427
+ * call-seq:
428
+ * coder.dimensions = Integer
429
+ * coder.dimensions = nil
430
+ *
431
+ * Set number of array dimensions to be encoded.
432
+ *
433
+ * This property ensures, that this number of dimensions is always encoded.
434
+ * If less dimensions than this number are in the given value, an ArgumentError is raised.
435
+ * If more dimensions than this number are in the value, the Array value is passed to the next encoder.
436
+ *
437
+ * Setting dimensions is especially useful, when a Record shall be encoded into an Array, since the Array encoder can not distinguish if the array shall be encoded as a higher dimension or as a record otherwise.
438
+ *
439
+ * The default is +nil+.
440
+ *
441
+ * See #dimensions
442
+ */
443
+ static VALUE
444
+ pg_coder_dimensions_set(VALUE self, VALUE dimensions)
445
+ {
446
+ t_pg_composite_coder *this = RTYPEDDATA_DATA(self);
447
+ rb_check_frozen(self);
448
+ if(!NIL_P(dimensions) && NUM2INT(dimensions) < 0)
449
+ rb_raise( rb_eArgError, "dimensions must be nil or >= 0");
450
+ this->dimensions = NIL_P(dimensions) ? -1 : NUM2INT(dimensions);
451
+ return dimensions;
452
+ }
453
+
454
+ /*
455
+ * call-seq:
456
+ * coder.dimensions -> Integer | nil
457
+ *
458
+ * Get number of enforced array dimensions or +nil+ if not set.
459
+ *
460
+ * See #dimensions=
461
+ */
462
+ static VALUE
463
+ pg_coder_dimensions_get(VALUE self)
464
+ {
465
+ t_pg_composite_coder *this = RTYPEDDATA_DATA(self);
466
+ return this->dimensions < 0 ? Qnil : INT2NUM(this->dimensions);
467
+ }
468
+
424
469
  /*
425
470
  * call-seq:
426
471
  * coder.elements_type = coder
@@ -602,6 +647,8 @@ init_pg_coder(void)
602
647
  *
603
648
  * This is the base class for all type cast classes of PostgreSQL types,
604
649
  * that are made up of some sub type.
650
+ *
651
+ * See PG::TextEncoder::Array, PG::TextDecoder::Array, PG::BinaryEncoder::Array, PG::BinaryDecoder::Array, etc.
605
652
  */
606
653
  rb_cPG_CompositeCoder = rb_define_class_under( rb_mPG, "CompositeCoder", rb_cPG_Coder );
607
654
  rb_define_method( rb_cPG_CompositeCoder, "elements_type=", pg_coder_elements_type_set, 1 );
@@ -610,6 +657,8 @@ init_pg_coder(void)
610
657
  rb_define_method( rb_cPG_CompositeCoder, "needs_quotation?", pg_coder_needs_quotation_get, 0 );
611
658
  rb_define_method( rb_cPG_CompositeCoder, "delimiter=", pg_coder_delimiter_set, 1 );
612
659
  rb_define_method( rb_cPG_CompositeCoder, "delimiter", pg_coder_delimiter_get, 0 );
660
+ rb_define_method( rb_cPG_CompositeCoder, "dimensions=", pg_coder_dimensions_set, 1 );
661
+ rb_define_method( rb_cPG_CompositeCoder, "dimensions", pg_coder_dimensions_get, 0 );
613
662
 
614
663
  /* Document-class: PG::CompositeEncoder < PG::CompositeCoder */
615
664
  rb_cPG_CompositeEncoder = rb_define_class_under( rb_mPG, "CompositeEncoder", rb_cPG_CompositeCoder );
data/ext/pg_connection.c CHANGED
@@ -845,31 +845,52 @@ pgconn_parameter_status(VALUE self, VALUE param_name)
845
845
  * call-seq:
846
846
  * conn.protocol_version -> Integer
847
847
  *
848
- * The 3.0 protocol will normally be used when communicating with PostgreSQL 7.4
849
- * or later servers; pre-7.4 servers support only protocol 2.0. (Protocol 1.0 is
850
- * obsolete and not supported by libpq.)
848
+ * Interrogates the frontend/backend protocol being used.
849
+ *
850
+ * Applications might wish to use this function to determine whether certain features are supported.
851
+ * Currently, the only value is 3 (3.0 protocol).
852
+ * The protocol version will not change after connection startup is complete, but it could theoretically change during a connection reset.
853
+ * The 3.0 protocol is supported by PostgreSQL server versions 7.4 and above.
854
+ *
855
+ * PG::ConnectionBad is raised if the connection is bad.
851
856
  */
852
857
  static VALUE
853
858
  pgconn_protocol_version(VALUE self)
854
859
  {
855
- return INT2NUM(PQprotocolVersion(pg_get_pgconn(self)));
860
+ int protocol_version = PQprotocolVersion(pg_get_pgconn(self));
861
+ if (protocol_version == 0) {
862
+ pg_raise_conn_error( rb_eConnectionBad, self, "PQprotocolVersion() can't get protocol version");
863
+ }
864
+ return INT2NUM(protocol_version);
856
865
  }
857
866
 
858
867
  /*
859
868
  * call-seq:
860
869
  * conn.server_version -> Integer
861
870
  *
862
- * The number is formed by converting the major, minor, and revision
863
- * numbers into two-decimal-digit numbers and appending them together.
864
- * For example, version 7.4.2 will be returned as 70402, and version
865
- * 8.1 will be returned as 80100 (leading zeroes are not shown). Zero
866
- * is returned if the connection is bad.
871
+ * Returns an integer representing the server version.
872
+ *
873
+ * Applications might use this function to determine the version of the database server they are connected to.
874
+ * The result is formed by multiplying the server's major version number by 10000 and adding the minor version number.
875
+ * For example, version 10.1 will be returned as 100001, and version 11.0 will be returned as 110000.
876
+ *
877
+ * PG::ConnectionBad is raised if the connection is bad.
878
+ *
879
+ * Prior to major version 10, PostgreSQL used three-part version numbers in which the first two parts together represented the major version.
880
+ * For those versions, PQserverVersion uses two digits for each part; for example version 9.1.5 will be returned as 90105, and version 9.2.0 will be returned as 90200.
881
+ *
882
+ * Therefore, for purposes of determining feature compatibility, applications should divide the result of PQserverVersion by 100 not 10000 to determine a logical major version number.
883
+ * In all release series, only the last two digits differ between minor releases (bug-fix releases).
867
884
  *
868
885
  */
869
886
  static VALUE
870
887
  pgconn_server_version(VALUE self)
871
888
  {
872
- return INT2NUM(PQserverVersion(pg_get_pgconn(self)));
889
+ int server_version = PQserverVersion(pg_get_pgconn(self));
890
+ if (server_version == 0) {
891
+ pg_raise_conn_error( rb_eConnectionBad, self, "PQserverVersion() can't get server version");
892
+ }
893
+ return INT2NUM(server_version);
873
894
  }
874
895
 
875
896
  /*
@@ -537,7 +537,7 @@ quote_string(t_pg_coder *this, VALUE value, VALUE string, char *current_out, int
537
537
  }
538
538
 
539
539
  static char *
540
- write_array(t_pg_composite_coder *this, VALUE value, char *current_out, VALUE string, int quote, int enc_idx)
540
+ write_array(t_pg_composite_coder *this, VALUE value, char *current_out, VALUE string, int quote, int enc_idx, int dimension)
541
541
  {
542
542
  int i;
543
543
 
@@ -545,6 +545,10 @@ write_array(t_pg_composite_coder *this, VALUE value, char *current_out, VALUE st
545
545
  current_out = pg_rb_str_ensure_capa( string, 2, current_out, NULL );
546
546
  *current_out++ = '{';
547
547
 
548
+ if( RARRAY_LEN(value) == 0 && this->dimensions >= 0 && dimension != this->dimensions ){
549
+ rb_raise(rb_eArgError, "less array dimensions to encode (%d) than expected (%d)", dimension, this->dimensions);
550
+ }
551
+
548
552
  for( i=0; i<RARRAY_LEN(value); i++){
549
553
  VALUE entry = rb_ary_entry(value, i);
550
554
 
@@ -554,17 +558,26 @@ write_array(t_pg_composite_coder *this, VALUE value, char *current_out, VALUE st
554
558
  }
555
559
 
556
560
  switch(TYPE(entry)){
557
- case T_ARRAY:
558
- current_out = write_array(this, entry, current_out, string, quote, enc_idx);
559
- break;
560
561
  case T_NIL:
562
+ if( this->dimensions >= 0 && dimension != this->dimensions ){
563
+ rb_raise(rb_eArgError, "less array dimensions to encode (%d) than expected (%d)", dimension, this->dimensions);
564
+ }
561
565
  current_out = pg_rb_str_ensure_capa( string, 4, current_out, NULL );
562
566
  *current_out++ = 'N';
563
567
  *current_out++ = 'U';
564
568
  *current_out++ = 'L';
565
569
  *current_out++ = 'L';
566
570
  break;
571
+ case T_ARRAY:
572
+ if( this->dimensions < 0 || dimension < this->dimensions ){
573
+ current_out = write_array(this, entry, current_out, string, quote, enc_idx, dimension+1);
574
+ break;
575
+ }
576
+ /* Number of dimensions reached -> handle array as normal value */
567
577
  default:
578
+ if( this->dimensions >= 0 && dimension != this->dimensions ){
579
+ rb_raise(rb_eArgError, "less array dimensions to encode (%d) than expected (%d)", dimension, this->dimensions);
580
+ }
568
581
  current_out = quote_string( this->elem, entry, string, current_out, quote, quote_array_buffer, this, enc_idx );
569
582
  }
570
583
  }
@@ -596,7 +609,7 @@ pg_text_enc_array(t_pg_coder *conv, VALUE value, char *out, VALUE *intermediate,
596
609
  VALUE out_str = rb_str_new(NULL, 0);
597
610
  PG_ENCODING_SET_NOCHECK(out_str, enc_idx);
598
611
 
599
- end_ptr = write_array(this, value, RSTRING_PTR(out_str), out_str, this->needs_quotation, enc_idx);
612
+ end_ptr = write_array(this, value, RSTRING_PTR(out_str), out_str, this->needs_quotation, enc_idx, 1);
600
613
 
601
614
  rb_str_set_len( out_str, end_ptr - RSTRING_PTR(out_str) );
602
615
  *intermediate = out_str;
@@ -54,6 +54,7 @@ pg_tmbc_fit_to_query( VALUE self, VALUE params )
54
54
  t_tmbc *this = RTYPEDDATA_DATA( self );
55
55
  t_typemap *default_tm;
56
56
 
57
+ Check_Type(params, T_ARRAY);
57
58
  nfields = (int)RARRAY_LEN( params );
58
59
  if ( this->nfields != nfields ) {
59
60
  rb_raise( rb_eArgError, "number of result fields (%d) does not match number of mapped columns (%d)",
@@ -315,6 +315,8 @@ pg_tmbo_coders( VALUE self )
315
315
  * The type map will do Hash lookups for each result value, if the number of rows
316
316
  * is below or equal +number+.
317
317
  *
318
+ * Default is 10.
319
+ *
318
320
  */
319
321
  static VALUE
320
322
  pg_tmbo_max_rows_for_online_lookup_set( VALUE self, VALUE value )
@@ -53,14 +53,18 @@ class PG::BasicTypeMapForQueries < PG::TypeMapByClass
53
53
  @coder_maps = build_coder_maps(connection_or_coder_maps, registry: registry)
54
54
  @array_encoders_by_klass = array_encoders_by_klass
55
55
  @encode_array_as = :array
56
- @if_undefined = if_undefined || method(:raise_undefined_type).to_proc
56
+ @if_undefined = if_undefined || UndefinedDefault
57
57
  init_encoders
58
58
  end
59
59
 
60
- private def raise_undefined_type(oid_name, format)
61
- raise UndefinedEncoder, "no encoder defined for type #{oid_name.inspect} format #{format}"
60
+ class UndefinedDefault
61
+ def self.call(oid_name, format)
62
+ raise UndefinedEncoder, "no encoder defined for type #{oid_name.inspect} format #{format}"
63
+ end
62
64
  end
63
65
 
66
+ private_constant :UndefinedDefault
67
+
64
68
  # Change the mechanism that is used to encode ruby array values
65
69
  #
66
70
  # Possible values:
@@ -7,8 +7,24 @@ if defined?(PG::CancelConnection)
7
7
  class PG::CancelConnection
8
8
  include PG::Connection::Pollable
9
9
 
10
- # The timeout used by #cancel and async_cancel to establish the cancel connection.
11
- attr_accessor :async_connect_timeout
10
+ alias c_initialize initialize
11
+
12
+ def initialize(conn)
13
+ c_initialize(conn)
14
+
15
+ # A cancel connection is always to one destination server only.
16
+ # Prepare conninfo_hash with just enough information to allow a shared polling_loop.
17
+ @host = conn.host
18
+ @hostaddr = conn.hostaddr
19
+ @port = conn.port
20
+
21
+ @conninfo_hash = {
22
+ host: @host,
23
+ hostaddr: @hostaddr,
24
+ port: @port.to_s,
25
+ connect_timeout: conn.conninfo_hash[:connect_timeout],
26
+ }
27
+ end
12
28
 
13
29
  # call-seq:
14
30
  # conn.cancel
@@ -23,8 +39,15 @@ if defined?(PG::CancelConnection)
23
39
  #
24
40
  def cancel
25
41
  start
26
- polling_loop(:poll, async_connect_timeout)
42
+ polling_loop(:poll)
27
43
  end
28
44
  alias async_cancel cancel
45
+
46
+ # These private methods are there to allow a shared polling_loop.
47
+ private
48
+ attr_reader :host
49
+ attr_reader :hostaddr
50
+ attr_reader :port
51
+ attr_reader :conninfo_hash
29
52
  end
30
53
  end
data/lib/pg/coder.rb CHANGED
@@ -76,12 +76,13 @@ module PG
76
76
  elements_type: elements_type,
77
77
  needs_quotation: needs_quotation?,
78
78
  delimiter: delimiter,
79
+ dimensions: dimensions,
79
80
  }
80
81
  end
81
82
 
82
83
  def inspect
83
84
  str = super
84
- str[-1,0] = " elements_type=#{elements_type.inspect} #{needs_quotation? ? 'needs' : 'no'} quotation"
85
+ str[-1,0] = " elements_type=#{elements_type.inspect} #{needs_quotation? ? 'needs' : 'no'} quotation#{dimensions && " #{dimensions} dimensions"}"
85
86
  str
86
87
  end
87
88
  end