ydbd-pg 0.5.1

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.
@@ -0,0 +1,47 @@
1
+ module DBI::DBD::Pg
2
+ ################################################################
3
+ # Convenience adaptor to hide details command execution API calls.
4
+ # See PgExecutorAsync subclass
5
+ class PgExecutor
6
+ def initialize(pg_conn)
7
+ @pg_conn = pg_conn
8
+ end
9
+
10
+ def exec(sql, parameters = nil)
11
+ @pg_conn.exec(sql, parameters)
12
+ end
13
+
14
+ def exec_prepared(stmt_name, parameters = nil)
15
+ @pg_conn.exec_prepared(stmt_name, parameters)
16
+ end
17
+
18
+ def prepare(stmt_name, sql)
19
+ @pg_conn.prepare(stmt_name, sql)
20
+ end
21
+ end
22
+
23
+ # Asynchronous implementation of PgExecutor, useful for 'green
24
+ # thread' implementations (e.g., MRI <= 1.8.x) which would otherwise
25
+ # suspend other threads while awaiting query results.
26
+ #--
27
+ # FIXME: PQsetnonblocking + select/poll would make the exec*
28
+ # methods truly 'async', though this is rarely needed in
29
+ # practice.
30
+ class PgExecutorAsync < PgExecutor
31
+ def exec(sql, parameters = nil)
32
+ @pg_conn.async_exec(sql, parameters)
33
+ end
34
+
35
+ def exec_prepared(stmt_name, parameters = nil)
36
+ @pg_conn.send_query_prepared(stmt_name, parameters)
37
+ @pg_conn.block()
38
+ @pg_conn.get_last_result()
39
+ end
40
+
41
+ def prepare(stmt_name, sql)
42
+ @pg_conn.send_prepare(stmt_name, sql)
43
+ @pg_conn.block()
44
+ @pg_conn.get_last_result()
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,160 @@
1
+ #
2
+ # See DBI::BaseStatement, and DBI::DBD::Pg::Tuples.
3
+ #
4
+ #--
5
+ # Peculiar Statement responsibilities:
6
+ # - Translate dbi params (?, ?, ...) to Pg params ($1, $2, ...)
7
+ # - Translate DBI::Binary objects to Pg large objects (lo_*)
8
+
9
+ class DBI::DBD::Pg::Statement < DBI::BaseStatement
10
+
11
+ PG_STMT_NAME_PREFIX = 'ruby-dbi:Pg:'
12
+
13
+ def initialize(db, sql)
14
+ super(db)
15
+ @db = db
16
+ @sql = sql
17
+ @stmt_name = PG_STMT_NAME_PREFIX + self.object_id.to_s + Time.now.to_f.to_s
18
+ @result = nil
19
+ @bindvars = []
20
+ @prepared = false
21
+ rescue PGError => err
22
+ raise DBI::ProgrammingError.new(err.message)
23
+ end
24
+
25
+ def bind_param(index, value, options)
26
+ @bindvars[index-1] = value
27
+ end
28
+
29
+ #
30
+ # See DBI::BaseDatabase#execute.
31
+ #
32
+ # This method will make use of PostgreSQL's native BLOB support if
33
+ # DBI::Binary objects are passed in.
34
+ #
35
+ def execute
36
+ # replace DBI::Binary object by oid returned by lo_import
37
+ @bindvars.collect! do |var|
38
+ if var.is_a? DBI::Binary then
39
+ oid = @db.__blob_create(PGconn::INV_WRITE)
40
+ @db.__blob_write(oid, var.to_s)
41
+ oid
42
+ else
43
+ var
44
+ end
45
+ end
46
+
47
+ internal_prepare
48
+
49
+ if not @db['AutoCommit'] then
50
+ # if not SQL.query?(boundsql) and not @db['AutoCommit'] then
51
+ @db.start_transaction unless @db.in_transaction?
52
+ end
53
+
54
+ if @db["pg_native_binding"]
55
+ pg_result = @db._exec_prepared(@stmt_name, *@bindvars)
56
+ else
57
+ pg_result = @db._exec_prepared(@stmt_name)
58
+ end
59
+
60
+ @result = DBI::DBD::Pg::Tuples.new(@db, pg_result)
61
+ rescue PGError, RuntimeError => err
62
+ raise DBI::ProgrammingError.new(err.message)
63
+ end
64
+
65
+ def fetch
66
+ @result.fetchrow
67
+ end
68
+
69
+ def fetch_scroll(direction, offset)
70
+ @result.fetch_scroll(direction, offset)
71
+ end
72
+
73
+ def finish
74
+ internal_finish
75
+ @result = nil
76
+ @db = nil
77
+ end
78
+
79
+ #
80
+ # See DBI::DBD::Pg::Tuples#column_info.
81
+ #
82
+ def column_info
83
+ @result.column_info
84
+ end
85
+
86
+ def rows
87
+ if @result
88
+ @result.rows_affected
89
+ else
90
+ nil
91
+ end
92
+ end
93
+
94
+ #
95
+ # Attributes:
96
+ #
97
+ # If +pg_row_count+ is requested and the statement has already executed,
98
+ # postgres will return what it believes is the row count.
99
+ #
100
+ def [](attr)
101
+ case attr
102
+ when 'pg_row_count'
103
+ if @result
104
+ @result.row_count
105
+ else
106
+ nil
107
+ end
108
+ else
109
+ @attr[attr]
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ #
116
+ # A native binding helper.
117
+ #
118
+ class DummyQuoter
119
+ # dummy to substitute ?-style parameter markers by :1 :2 etc.
120
+ def quote(str)
121
+ str
122
+ end
123
+ end
124
+
125
+ # finish the statement at a lower level
126
+ def internal_finish
127
+ @result.finish if @result
128
+ @db._exec("DEALLOCATE \"#{@stmt_name}\"") if @prepared rescue nil
129
+ end
130
+
131
+ # prepare the statement at a lower level.
132
+ def internal_prepare
133
+ if @db["pg_native_binding"]
134
+ unless @prepared
135
+ @stmt = @db._prepare(@stmt_name, translate_param_markers(@sql))
136
+ end
137
+ else
138
+ internal_finish
139
+ @stmt = @db._prepare(@stmt_name, DBI::SQL::PreparedStatement.new(DBI::DBD::Pg, @sql).bind(@bindvars))
140
+ end
141
+ @prepared = true
142
+ end
143
+
144
+ # Prepare the given SQL statement, returning its PostgreSQL string
145
+ # handle. ?-style parameters are translated to $1, $2, etc.
146
+ #--
147
+ # TESTME do ?::TYPE qualifers work?
148
+ # FIXME: DBI ought to supply a generic param converter, e.g.:
149
+ # sql = DBI::Utils::convert_placeholders(sql) do |i|
150
+ # '$' + i.to_s
151
+ # end
152
+ def translate_param_markers(sql)
153
+ translator = DBI::SQL::PreparedStatement.new(DummyQuoter.new, sql)
154
+ if translator.unbound.size > 0
155
+ arr = (1..(translator.unbound.size)).collect{|i| "$#{i}"}
156
+ sql = translator.bind( arr )
157
+ end
158
+ sql
159
+ end
160
+ end # Statement
@@ -0,0 +1,121 @@
1
+ #
2
+ # Tuples is a class to represent result sets.
3
+ #
4
+ # Many of these methods are extremely similar to the methods that deal with
5
+ # result sets in DBI::BaseStatement and are wrapped by the StatementHandle.
6
+ # Unless you plan on working on this driver, these methods should never be
7
+ # called directly.
8
+ #
9
+ class DBI::DBD::Pg::Tuples
10
+
11
+ def initialize(db, pg_result)
12
+ @db = db
13
+ @pg_result = pg_result
14
+ @index = -1
15
+ @row = []
16
+ end
17
+
18
+ # See DBI::BaseStatement#column_info. Additional attributes:
19
+ #
20
+ # * array_of_type: True if this is actually an array of this type. In this
21
+ # case, +dbi_type+ will be the type authority for conversion.
22
+ #
23
+ def column_info
24
+ a = []
25
+ 0.upto(@pg_result.num_fields-1) do |i|
26
+ str = @pg_result.fname(i)
27
+
28
+ typeinfo = nil
29
+
30
+ begin
31
+ typmod = @pg_result.fmod(i)
32
+ rescue
33
+ end
34
+
35
+ if typmod and typ = @pg_result.ftype(i)
36
+ res = @db._exec("select format_type(#{typ}, #{typmod})")
37
+ typeinfo = DBI::DBD::Pg.parse_type(res[0].values[0])
38
+ end
39
+
40
+ map = @db.type_map[@pg_result.ftype(i)] || { }
41
+ h = { "name" => str }.merge(map)
42
+
43
+ if typeinfo
44
+ h["precision"] = typeinfo[:size]
45
+ h["scale"] = typeinfo[:decimal]
46
+ h["type"] = typeinfo[:type]
47
+ h["array_of_type"] = typeinfo[:array]
48
+
49
+ if typeinfo[:array]
50
+ h['dbi_type'] =
51
+ DBI::DBD::Pg::Type::Array.new(
52
+ DBI::TypeUtil.type_name_to_module(typeinfo[:type])
53
+ )
54
+ end
55
+ end
56
+
57
+ a.push h
58
+ end
59
+
60
+ return a
61
+ end
62
+
63
+ def fetchrow
64
+ @index += 1
65
+ if @index < @pg_result.num_tuples && @index >= 0
66
+ @row = Array.new
67
+ 0.upto(@pg_result.num_fields-1) do |x|
68
+ @row.push(@pg_result.getvalue(@index, x))
69
+ end
70
+ @row
71
+ else
72
+ nil
73
+ end
74
+ end
75
+
76
+ #
77
+ # Just don't use this method. It'll be fixed soon.
78
+ #
79
+ def fetch_scroll(direction, offset)
80
+ # Exact semantics aren't too closely defined. I attempted to follow the DBI:Mysql example.
81
+ case direction
82
+ when SQL_FETCH_NEXT
83
+ # Nothing special to do, besides the fetchrow
84
+ when SQL_FETCH_PRIOR
85
+ @index -= 2
86
+ when SQL_FETCH_FIRST
87
+ @index = -1
88
+ when SQL_FETCH_LAST
89
+ @index = @pg_result.num_tuples - 2
90
+ when SQL_FETCH_ABSOLUTE
91
+ # Note: if you go "out of range", all fetches will give nil until you get back
92
+ # into range, this doesn't raise an error.
93
+ @index = offset-1
94
+ when SQL_FETCH_RELATIVE
95
+ # Note: if you go "out of range", all fetches will give nil until you get back
96
+ # into range, this doesn't raise an error.
97
+ @index += offset - 1
98
+ else
99
+ raise NotSupportedError
100
+ end
101
+ self.fetchrow
102
+ end
103
+
104
+ #
105
+ # The number of rows returned.
106
+ #
107
+ def row_count
108
+ @pg_result.num_tuples
109
+ end
110
+
111
+ #
112
+ # The row processed count. This is analogue to DBI::StatementHandle#rows.
113
+ #
114
+ def rows_affected
115
+ @pg_result.cmdtuples
116
+ end
117
+
118
+ def finish
119
+ @pg_result.clear
120
+ end
121
+ end # Tuples
@@ -0,0 +1,209 @@
1
+ #
2
+ # Type Management for PostgreSQL-specific types.
3
+ #
4
+ # See DBI::Type and DBI::TypeUtil for more information.
5
+ #
6
+ module DBI::DBD::Pg::Type
7
+ #
8
+ # ByteA is a special escaped form of binary data, suitable for inclusion in queries.
9
+ #
10
+ # This class is an attempt to abstract that type so you do not have to
11
+ # concern yourself with the conversion issues.
12
+ #
13
+ class ByteA
14
+
15
+ attr_reader :original
16
+ attr_reader :escaped
17
+
18
+ #
19
+ # Build a new ByteA object.
20
+ #
21
+ # The data supplied is the unescaped binary data you wish to put in the
22
+ # database.
23
+ #
24
+ def initialize(obj)
25
+ @original = obj
26
+ @escaped = escape_bytea(obj)
27
+ @original.freeze
28
+ @escaped.freeze
29
+ end
30
+
31
+ #
32
+ # Escapes the supplied data. Has no effect on the object.
33
+ #
34
+ def escape_bytea(str)
35
+ PGconn.escape_bytea(str)
36
+ end
37
+
38
+ #
39
+ # Returns the original data.
40
+ #
41
+ def to_s
42
+ return @original.dup
43
+ end
44
+
45
+ #
46
+ # Class method to escape the data into ByteA format.
47
+ #
48
+ def self.escape_bytea(str)
49
+ self.new(str).escaped
50
+ end
51
+
52
+ #
53
+ # Class method to unescape the ByteA data and present it as a string.
54
+ #
55
+ def self.parse(obj)
56
+
57
+ return nil if obj.nil?
58
+
59
+ # FIXME there's a bug in the upstream 'pg' driver that does not
60
+ # properly decode bytea, leaving in an extra slash for each decoded
61
+ # character.
62
+ #
63
+ # Fix this for now, but beware that we'll have to unfix this as
64
+ # soon as they fix their end.
65
+ ret = PGconn.unescape_bytea(obj)
66
+
67
+ # XXX
68
+ # String#split does not properly create a full array if the the
69
+ # string ENDS in the split regex, unless this oddball -1 argument is supplied.
70
+ #
71
+ # Another way of saying this:
72
+ # if foo = "foo\\\\\" and foo.split(/\\\\/), the result will be
73
+ # ["foo"]. You can add as many delimiters to the end of the string
74
+ # as you'd like - the result is no different.
75
+ #
76
+
77
+ ret = ret.split(/\\\\/, -1).collect { |x| x.length > 0 ? x.gsub(/\\[0-7]{3}/) { |y| y[1..3].oct.chr } : "" }.join("\\")
78
+ ret.gsub!(/''/, "'")
79
+ return ret
80
+ end
81
+ end
82
+
83
+ #
84
+ # PostgreSQL arrays are simply a specification that sits on top of normal
85
+ # types. They have a specialized string grammar and this class facilitates
86
+ # converting that syntax and the types within those arrays.
87
+ #
88
+ class Array
89
+
90
+ attr_reader :base_type
91
+
92
+ #
93
+ # +base_type+ is a DBI::Type that is used to parse the inner types when
94
+ # a non-array one is found.
95
+ #
96
+ # For instance, if you had an array of integer, one would pass
97
+ # DBI::Type::Integer here.
98
+ #
99
+ def initialize(base_type)
100
+ @base_type = base_type
101
+ end
102
+
103
+ #
104
+ # Object method. Please note that this is different than most DBI::Type
105
+ # classes! One must initialize an Array object with an appropriate
106
+ # DBI::Type used to convert the indices of the array before this method
107
+ # can be called.
108
+ #
109
+ # Returns an appropriately converted array.
110
+ #
111
+ def parse(obj)
112
+ if obj.nil?
113
+ nil
114
+ elsif obj.index('{') == 0 and obj.rindex('}') == (obj.length - 1)
115
+ convert_array(obj)
116
+ else
117
+ raise "Not an array"
118
+ end
119
+ end
120
+
121
+ #
122
+ # Parse a PostgreSQL-Array output and convert into ruby array. This
123
+ # does the real parsing work.
124
+ #
125
+ def convert_array(str)
126
+
127
+ array_nesting = 0 # nesting level of the array
128
+ in_string = false # currently inside a quoted string ?
129
+ escaped = false # if the character is escaped
130
+ sbuffer = '' # buffer for the current element
131
+ result_array = ::Array.new # the resulting Array
132
+
133
+ str.each_byte { |char| # parse character by character
134
+ char = char.chr # we need the Character, not it's Integer
135
+
136
+ if escaped then # if this character is escaped, just add it to the buffer
137
+ sbuffer += char
138
+ escaped = false
139
+ next
140
+ end
141
+
142
+ case char # let's see what kind of character we have
143
+ #------------- {: beginning of an array ----#
144
+ when '{'
145
+ if in_string then # ignore inside a string
146
+ sbuffer += char
147
+ next
148
+ end
149
+
150
+ if array_nesting >= 1 then # if it's an nested array, defer for recursion
151
+ sbuffer += char
152
+ end
153
+ array_nesting += 1 # inside another array
154
+
155
+ #------------- ": string deliminator --------#
156
+ when '"'
157
+ in_string = !in_string
158
+
159
+ #------------- \: escape character, next is regular character #
160
+ when "\\" # single \, must be extra escaped in Ruby
161
+ if array_nesting > 1
162
+ sbuffer += char
163
+ else
164
+ escaped = true
165
+ end
166
+
167
+ #------------- ,: element separator ---------#
168
+ when ','
169
+ if in_string or array_nesting > 1 then # don't care if inside string or
170
+ sbuffer += char # nested array
171
+ else
172
+ if !sbuffer.is_a? ::Array then
173
+ sbuffer = @base_type.parse(sbuffer)
174
+ end
175
+ result_array << sbuffer # otherwise, here ends an element
176
+ sbuffer = ''
177
+ end
178
+
179
+ #------------- }: End of Array --------------#
180
+ when '}'
181
+ if in_string then # ignore if inside quoted string
182
+ sbuffer += char
183
+ next
184
+ end
185
+
186
+ array_nesting -=1 # decrease nesting level
187
+
188
+ if array_nesting == 1 # must be the end of a nested array
189
+ sbuffer += char
190
+ sbuffer = convert_array( sbuffer ) # recurse, using the whole nested array
191
+ elsif array_nesting > 1 # inside nested array, keep it for later
192
+ sbuffer += char
193
+ else # array_nesting = 0, must be the last }
194
+ if !sbuffer.is_a? ::Array then
195
+ sbuffer = @base_type.parse( sbuffer )
196
+ end
197
+
198
+ result_array << sbuffer unless sbuffer.nil? # upto here was the last element
199
+ end
200
+
201
+ #------------- all other characters ---------#
202
+ else
203
+ sbuffer += char # simply append
204
+ end
205
+ }
206
+ return result_array
207
+ end # convert_array()
208
+ end
209
+ end