Add the conversion procs to the database and extend the datasets to correctly literalize ruby Range values.
# File lib/sequel/extensions/pg_range.rb, line 136 def self.extended(db) db.instance_exec do @pg_range_schema_types ||= {} extend_datasets(DatasetMethods) register_range_type('int4range', :oid=>3904, :subtype_oid=>23) register_range_type('numrange', :oid=>3906, :subtype_oid=>1700) register_range_type('tsrange', :oid=>3908, :subtype_oid=>1114) register_range_type('tstzrange', :oid=>3910, :subtype_oid=>1184) register_range_type('daterange', :oid=>3912, :subtype_oid=>1082) register_range_type('int8range', :oid=>3926, :subtype_oid=>20) if respond_to?(:register_array_type) register_array_type('int4range', :oid=>3905, :scalar_oid=>3904, :scalar_typecast=>:int4range) register_array_type('numrange', :oid=>3907, :scalar_oid=>3906, :scalar_typecast=>:numrange) register_array_type('tsrange', :oid=>3909, :scalar_oid=>3908, :scalar_typecast=>:tsrange) register_array_type('tstzrange', :oid=>3911, :scalar_oid=>3910, :scalar_typecast=>:tstzrange) register_array_type('daterange', :oid=>3913, :scalar_oid=>3912, :scalar_typecast=>:daterange) register_array_type('int8range', :oid=>3927, :scalar_oid=>3926, :scalar_typecast=>:int8range) end [:int4range, :numrange, :tsrange, :tstzrange, :daterange, :int8range].each do |v| @schema_type_classes[v] = PGRange end procs = conversion_procs add_conversion_proc(3908, Parser.new("tsrange", procs[1114])) add_conversion_proc(3910, Parser.new("tstzrange", procs[1184])) if defined?(PGArray::Creator) add_conversion_proc(3909, PGArray::Creator.new("tsrange", procs[3908])) add_conversion_proc(3911, PGArray::Creator.new("tstzrange", procs[3910])) end end end
Handle Range and PGRange values in bound variables
# File lib/sequel/extensions/pg_range.rb, line 169 def bound_variable_arg(arg, conn) case arg when PGRange arg.unquoted_literal(schema_utility_dataset) when Range PGRange.from_range(arg).unquoted_literal(schema_utility_dataset) else super end end
Freeze the pg range schema types to prevent adding new ones.
# File lib/sequel/extensions/pg_range.rb, line 181 def freeze @pg_range_schema_types.freeze super end
Register a database specific range type. This can be used to support different range types per Database. Options:
A callable object (e.g. Proc), that is called with the start or end of the range (usually a string), and should return the appropriate typecasted object.
The PostgreSQL OID for the range type. This is used by the Sequel postgres adapter to set up automatic type conversion on retrieval from the database.
Should be the PostgreSQL OID for the range's subtype. If given, automatically sets the :converter option by looking for scalar conversion proc.
If a block is given, it is treated as the :converter option.
# File lib/sequel/extensions/pg_range.rb, line 198 def register_range_type(db_type, opts=OPTS, &block) oid = opts[:oid] soid = opts[:subtype_oid] if has_converter = opts.has_key?(:converter) raise Error, "can't provide both a block and :converter option to register_range_type" if block converter = opts[:converter] else has_converter = true if block converter = block end unless (soid || has_converter) && oid range_oid, subtype_oid = from(:pg_range).join(:pg_type, :oid=>:rngtypid).where(:typname=>db_type.to_s).get([:rngtypid, :rngsubtype]) soid ||= subtype_oid unless has_converter oid ||= range_oid end db_type = db_type.to_s.dup.freeze if converter = opts[:converter] raise Error, "can't provide both a block and :converter option to register" if block else converter = block end if soid raise Error, "can't provide both a converter and :subtype_oid option to register" if has_converter raise Error, "no conversion proc for :subtype_oid=>#{soid.inspect} in conversion_procs" unless converter = conversion_procs[soid] end parser = Parser.new(db_type, converter) add_conversion_proc(oid, parser) @pg_range_schema_types[db_type] = db_type.to_sym singleton_class.class_eval do meth = :"typecast_value_#{db_type}" define_method(meth){|v| typecast_value_pg_range(v, parser)} private meth end @schema_type_classes[:"#{opts[:type_symbol] || db_type}"] = PGRange nil end
Handle arrays of range types in bound variables.
# File lib/sequel/extensions/pg_range.rb, line 247 def bound_variable_array(a) case a when PGRange, Range "\"#{bound_variable_arg(a, nil)}\"" else super end end
Recognize the registered database range types.
# File lib/sequel/extensions/pg_range.rb, line 257 def schema_column_type(db_type) if type = @pg_range_schema_types[db_type] type else super end end
Set the :ruby_default value if the default value is recognized as a range.
# File lib/sequel/extensions/pg_range.rb, line 266 def schema_post_process(_) super.each do |a| h = a[1] db_type = h[:db_type] if @pg_range_schema_types[db_type] && h[:default] =~ /\A'([^']+)'::#{db_type}\z/ default = $1 if convertor = conversion_procs[h[:oid]] h[:ruby_default] = convertor.call(default) end end end end
Typecast value correctly to a PGRange. If already an PGRange instance with the same db_type, return as is. If a PGRange with a different subtype, return a new PGRange with the same values and the expected subtype. If a Range object, create a PGRange with the given db_type. If a string, assume it is in PostgreSQL output format and parse it using the parser.
# File lib/sequel/extensions/pg_range.rb, line 286 def typecast_value_pg_range(value, parser) case value when PGRange if value.db_type.to_s == parser.db_type value elsif value.empty? PGRange.empty(parser.db_type) else PGRange.new(value.begin, value.end, :exclude_begin=>value.exclude_begin?, :exclude_end=>value.exclude_end?, :db_type=>parser.db_type) end when Range PGRange.from_range(value, parser.db_type) when String parser.call(value) else raise Sequel::InvalidValue, "invalid value for range type: #{value.inspect}" end end