lib/sequel/extensions/pg_row.rb in sequel-4.47.0 vs lib/sequel/extensions/pg_row.rb in sequel-4.48.0

- old
+ new

@@ -16,15 +16,18 @@ # # In addition to the parsers, this extension comes with literalizers # for HashRow and ArrayRow using the standard Sequel literalization callbacks, so # they work with on all adapters. # -# The first thing you are going to want to do is to load the extension into -# your Database object. Make sure you load the :pg_array extension first -# if you plan to use composite types in bound variables: +# To use this extension, first load it into the Database instance: +# +# DB.extension :pg_row +# +# If you plan to use arrays of composite types, make sure you load the +# pg_array extension first: # -# DB.extension(:pg_array, :pg_row) +# DB.extension :pg_array, :pg_row # # You can create an anonymous row type by calling the Sequel.pg_row with # an array: # # Sequel.pg_row(array) @@ -83,17 +86,19 @@ # # Related module: Sequel::Postgres::PGRow require 'delegate' require 'strscan' -Sequel.require 'adapters/utils/pg_types' +Sequel.require 'adapters/shared/postgres' module Sequel module Postgres module PGRow ROW = 'ROW'.freeze + Sequel::Deprecation.deprecate_constant(self, :ROW) CAST = '::'.freeze + Sequel::Deprecation.deprecate_constant(self, :CAST) # Class for row-valued/composite types that are treated as arrays. By default, # this is only used for generic PostgreSQL record types, as registered # types use HashRow by default. class ArrayRow < DelegateClass(Array) @@ -128,14 +133,14 @@ @db_type || self.class.db_type end # Append SQL fragment related to this object to the sql. def sql_literal_append(ds, sql) - sql << ROW + sql << 'ROW' ds.literal_append(sql, to_a) if db_type - sql << CAST + sql << '::' ds.quote_schema_table_append(sql, db_type) end end end @@ -200,55 +205,64 @@ end # Append SQL fragment related to this object to the sql. def sql_literal_append(ds, sql) check_columns! - sql << ROW + sql << 'ROW' ds.literal_append(sql, values_at(*columns)) if db_type - sql << CAST + sql << '::' ds.quote_schema_table_append(sql, db_type) end end end - ROW_TYPE_CLASSES = [HashRow, ArrayRow] + ROW_TYPE_CLASSES = [HashRow, ArrayRow]#.freeze # SEQUEL5 # This parser-like class splits the PostgreSQL # row-valued/composite type output string format # into an array of strings. Note this class makes # no attempt to handle all input formats that PostgreSQL # will accept, it only handles the output format that # PostgreSQL uses. class Splitter < StringScanner OPEN_PAREN = /\(/.freeze + Sequel::Deprecation.deprecate_constant(self, :OPEN_PAREN) CLOSE_PAREN = /\)/.freeze + Sequel::Deprecation.deprecate_constant(self, :CLOSE_PAREN) UNQUOTED_RE = /[^,)]*/.freeze + Sequel::Deprecation.deprecate_constant(self, :UNQUOTED_RE) SEP_RE = /[,)]/.freeze + Sequel::Deprecation.deprecate_constant(self, :SEP_RE) QUOTE_RE = /"/.freeze + Sequel::Deprecation.deprecate_constant(self, :QUOTE_RE) QUOTE_SEP_RE = /"[,)]/.freeze + Sequel::Deprecation.deprecate_constant(self, :QUOTE_SEP_RE) QUOTED_RE = /(\\.|""|[^"])*/.freeze + Sequel::Deprecation.deprecate_constant(self, :QUOTED_RE) REPLACE_RE = /\\(.)|"(")/.freeze + Sequel::Deprecation.deprecate_constant(self, :REPLACE_RE) REPLACE_WITH = '\1\2'.freeze + Sequel::Deprecation.deprecate_constant(self, :REPLACE_WITH) # Split the stored string into an array of strings, handling # the different types of quoting. def parse return @result if @result values = [] - skip(OPEN_PAREN) - if skip(CLOSE_PAREN) + skip(/\(/) + if skip(/\)/) values << nil else until eos? - if skip(QUOTE_RE) - values << scan(QUOTED_RE).gsub(REPLACE_RE, REPLACE_WITH) - skip(QUOTE_SEP_RE) + if skip(/"/) + values << scan(/(\\.|""|[^"])*/).gsub(/\\(.)|"(")/, '\1\2') + skip(/"[,)]/) else - v = scan(UNQUOTED_RE) + v = scan(/[^,)]*/) values << (v unless v.empty?) - skip(SEP_RE) + skip(/[,)]/) end end end values end @@ -372,42 +386,42 @@ end end module DatabaseMethods ESCAPE_RE = /("|\\)/.freeze + Sequel::Deprecation.deprecate_constant(self, :ESCAPE_RE) ESCAPE_REPLACEMENT = '\\\\\1'.freeze + Sequel::Deprecation.deprecate_constant(self, :ESCAPE_REPLACEMENT) COMMA = ','.freeze + Sequel::Deprecation.deprecate_constant(self, :COMMA) # A hash mapping row type keys (usually symbols), to option # hashes. At the least, the values will contain the :parser # option for the Parser instance that the type will use. attr_reader :row_types # Do some setup for the data structures the module uses. def self.extended(db) - # Return right away if row_types has already been set. This - # makes things not break if a user extends the database with - # this module more than once (since extended is called every - # time). - return if db.row_types - db.instance_eval do @row_types = {} @row_schema_types = {} extend(@row_type_method_module = Module.new) - copy_conversion_procs([2249, 2287]) + add_conversion_proc(2249, PGRow::Parser.new(:converter=>PGRow::ArrayRow)) + if respond_to?(:register_array_type) + register_array_type('record', :oid=>2287, :scalar_oid=>2249) + end end end # Handle ArrayRow and HashRow values in bound variables. def bound_variable_arg(arg, conn) case arg when ArrayRow - "(#{arg.map{|v| bound_variable_array(v) if v}.join(COMMA)})" + "(#{arg.map{|v| bound_variable_array(v) if v}.join(',')})" when HashRow arg.check_columns! - "(#{arg.values_at(*arg.columns).map{|v| bound_variable_array(v) if v}.join(COMMA)})" + "(#{arg.values_at(*arg.columns).map{|v| bound_variable_array(v) if v}.join(',')})" else super end end @@ -417,10 +431,12 @@ @row_schema_types.freeze @row_type_method_module.freeze super end + STRING_TYPES = [18, 19, 25, 1042, 1043].freeze + # Register a new row type for the Database instance. db_type should be the type # symbol. This parses the PostgreSQL system tables to get information the # composite type, and by default has the type return instances of a subclass # of HashRow. # @@ -469,33 +485,43 @@ parser_opts[:columns] = res.map{|r| r[0].to_sym} parser_opts[:column_oids] = res.map{|r| r[1].to_i} # Using the conversion_procs, lookup converters for each member of the composite type parser_opts[:column_converters] = parser_opts[:column_oids].map do |oid| + # procs[oid] # SEQUEL5 + + # SEQUEL5: Remove if pr = procs[oid] pr - elsif !Sequel::Postgres::STRING_TYPES.include?(oid) + elsif !STRING_TYPES.include?(oid) # It's not a string type, and it's possible a conversion proc for this # oid will be added later, so do a runtime check for it. - lambda{|s| (pr = procs[oid]) ? pr.call(s) : s} + lambda do |s| + if (pr = procs[oid]) + Sequel::Deprecation.deprecate("Calling conversion proc for subtype (oid: #{oid}) of composite type (oid: #{parser_opts[:oid]}) not added until after composite type registration", "Register subtype conversion procs before registering composite type") + pr.call(s) + else + s + end + end end end # Setup the converter and typecaster parser_opts[:converter] = opts.fetch(:converter){HashRow.subclass(db_type, parser_opts[:columns])} parser_opts[:typecaster] = opts.fetch(:typecaster, parser_opts[:converter]) parser = Parser.new(parser_opts) - @conversion_procs[parser.oid] = parser + add_conversion_proc(parser.oid, parser) - if defined?(PGArray) && PGArray.respond_to?(:register) && array_oid && array_oid > 0 + if respond_to?(:register_array_type) && array_oid && array_oid > 0 array_type_name = if type_schema "#{type_schema}.#{type_name}" else type_name end - PGArray.register(array_type_name, :oid=>array_oid, :converter=>parser, :type_procs=>@conversion_procs, :scalar_typecast=>schema_type_symbol) + register_array_type(array_type_name, :oid=>array_oid, :converter=>parser, :scalar_typecast=>schema_type_symbol) end @row_types[literal(db_type)] = opts.merge(:parser=>parser, :type=>db_type) @row_schema_types[schema_type_string] = schema_type_symbol @schema_type_classes[schema_type_symbol] = ROW_TYPE_CLASSES @@ -505,16 +531,15 @@ row_type(db_type, v) end private meth end - conversion_procs_updated + conversion_procs_updated # SEQUEL5: Remove nil end - # When reseting conversion procs, reregister all the row types so that - # the system tables are introspected again, picking up database changes. + # SEQUEL5: Remove def reset_conversion_procs procs = super row_types.values.each do |opts| register_row_type(opts[:type], opts) @@ -556,14 +581,14 @@ # Format composite types used in bound variable arrays. def bound_variable_array(arg) case arg when ArrayRow - "\"(#{arg.map{|v| bound_variable_array(v) if v}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\"" + "\"(#{arg.map{|v| bound_variable_array(v) if v}.join(',').gsub(/("|\\)/, '\\\\\1')})\"" when HashRow arg.check_columns! - "\"(#{arg.values_at(*arg.columns).map{|v| bound_variable_array(v) if v}.join(COMMA).gsub(ESCAPE_RE, ESCAPE_REPLACEMENT)})\"" + "\"(#{arg.values_at(*arg.columns).map{|v| bound_variable_array(v) if v}.join(',').gsub(/("|\\)/, '\\\\\1')})\"" else super end end @@ -576,13 +601,17 @@ end end end end - # Register the default anonymous record type - PG_TYPES[2249] = PGRow::Parser.new(:converter=>PGRow::ArrayRow) + # SEQUEL5: Remove + parser = PGRow::Parser.new(:converter=>PGRow::ArrayRow) + PG__TYPES[2249] = lambda do |s| + Sequel::Deprecation.deprecate("Conversion proc for record added globally by pg_row extension", "Load the pg_row extension into the Database instance") + parser.call(s) + end if defined?(PGArray) && PGArray.respond_to?(:register) - PGArray.register('record', :oid=>2287, :scalar_oid=>2249) + PGArray.register('record', :oid=>2287, :scalar_oid=>2249, :skip_deprecation_warning=>true) end end module SQL::Builders # Wraps the expr array in an anonymous Postgres::PGRow::ArrayRow instance.