1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
|
#
# Type Management for PostgreSQL-specific types.
#
# See DBI::Type and DBI::TypeUtil for more information.
#
module DBI::DBD::Pg::Type
#
# ByteA is a special escaped form of binary data, suitable for inclusion in queries.
#
# This class is an attempt to abstract that type so you do not have to
# concern yourself with the conversion issues.
#
class ByteA
attr_reader :original
attr_reader :escaped
#
# Build a new ByteA object.
#
# The data supplied is the unescaped binary data you wish to put in the
# database.
#
def initialize(obj)
@original = obj
@escaped = escape_bytea(obj)
@original.freeze
@escaped.freeze
end
#
# Escapes the supplied data. Has no effect on the object.
#
def escape_bytea(str)
PGconn.escape_bytea(str)
end
#
# Returns the original data.
#
def to_s
return @original.dup
end
#
# Class method to escape the data into ByteA format.
#
def self.escape_bytea(str)
self.new(str).escaped
end
#
# Class method to unescape the ByteA data and present it as a string.
#
def self.parse(obj)
return nil if obj.nil?
# FIXME there's a bug in the upstream 'pg' driver that does not
# properly decode bytea, leaving in an extra slash for each decoded
# character.
#
# Fix this for now, but beware that we'll have to unfix this as
# soon as they fix their end.
ret = PGconn.unescape_bytea(obj)
# XXX
# String#split does not properly create a full array if the the
# string ENDS in the split regex, unless this oddball -1 argument is supplied.
#
# Another way of saying this:
# if foo = "foo\\\\\" and foo.split(/\\\\/), the result will be
# ["foo"]. You can add as many delimiters to the end of the string
# as you'd like - the result is no different.
#
ret = ret.split(/\\\\/, -1).collect { |x| x.length > 0 ? x.gsub(/\\[0-7]{3}/) { |y| y[1..3].oct.chr } : "" }.join("\\")
ret.gsub!(/''/, "'")
return ret
end
end
#
# PostgreSQL arrays are simply a specification that sits on top of normal
# types. They have a specialized string grammar and this class facilitates
# converting that syntax and the types within those arrays.
#
class Array
attr_reader :base_type
#
# +base_type+ is a DBI::Type that is used to parse the inner types when
# a non-array one is found.
#
# For instance, if you had an array of integer, one would pass
# DBI::Type::Integer here.
#
def initialize(base_type)
@base_type = base_type
end
#
# Object method. Please note that this is different than most DBI::Type
# classes! One must initialize an Array object with an appropriate
# DBI::Type used to convert the indices of the array before this method
# can be called.
#
# Returns an appropriately converted array.
#
def parse(obj)
if obj.nil?
nil
elsif obj.index('{') == 0 and obj.rindex('}') == (obj.length - 1)
convert_array(obj)
else
raise "Not an array"
end
end
#
# Parse a PostgreSQL-Array output and convert into ruby array. This
# does the real parsing work.
#
def convert_array(str)
array_nesting = 0 # nesting level of the array
in_string = false # currently inside a quoted string ?
escaped = false # if the character is escaped
sbuffer = '' # buffer for the current element
result_array = ::Array.new # the resulting Array
str.each_byte { |char| # parse character by character
char = char.chr # we need the Character, not it's Integer
if escaped then # if this character is escaped, just add it to the buffer
sbuffer += char
escaped = false
next
end
case char # let's see what kind of character we have
#------------- {: beginning of an array ----#
when '{'
if in_string then # ignore inside a string
sbuffer += char
next
end
if array_nesting >= 1 then # if it's an nested array, defer for recursion
sbuffer += char
end
array_nesting += 1 # inside another array
#------------- ": string deliminator --------#
when '"'
in_string = !in_string
#------------- \: escape character, next is regular character #
when "\\" # single \, must be extra escaped in Ruby
if array_nesting > 1
sbuffer += char
else
escaped = true
end
#------------- ,: element separator ---------#
when ','
if in_string or array_nesting > 1 then # don't care if inside string or
sbuffer += char # nested array
else
if !sbuffer.is_a? ::Array then
sbuffer = @base_type.parse(sbuffer)
end
result_array << sbuffer # otherwise, here ends an element
sbuffer = ''
end
#------------- }: End of Array --------------#
when '}'
if in_string then # ignore if inside quoted string
sbuffer += char
next
end
array_nesting -=1 # decrease nesting level
if array_nesting == 1 # must be the end of a nested array
sbuffer += char
sbuffer = convert_array( sbuffer ) # recurse, using the whole nested array
elsif array_nesting > 1 # inside nested array, keep it for later
sbuffer += char
else # array_nesting = 0, must be the last }
if !sbuffer.is_a? ::Array then
sbuffer = @base_type.parse( sbuffer )
end
result_array << sbuffer unless sbuffer.nil? # upto here was the last element
end
#------------- all other characters ---------#
else
sbuffer += char # simply append
end
}
return result_array
end # convert_array()
end
end
|