ydbi 0.5.6 → 0.5.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +35 -0
  3. data/.gitignore +8 -0
  4. data/.travis.yml +15 -0
  5. data/ChangeLog +321 -314
  6. data/Gemfile +5 -0
  7. data/Rakefile +10 -0
  8. data/TODO +44 -0
  9. data/bench/bench.rb +79 -0
  10. data/build/rake_task_lib.rb +186 -0
  11. data/doc/DBD_SPEC.rdoc +88 -0
  12. data/doc/DBI_SPEC.rdoc +157 -0
  13. data/doc/homepage/contact.html +62 -0
  14. data/doc/homepage/development.html +124 -0
  15. data/doc/homepage/index.html +83 -0
  16. data/doc/homepage/ruby-dbi.css +91 -0
  17. data/lib/dbd/Mysql.rb +137 -0
  18. data/lib/dbd/ODBC.rb +89 -0
  19. data/lib/dbd/Pg.rb +188 -0
  20. data/lib/dbd/SQLite.rb +97 -0
  21. data/lib/dbd/SQLite3.rb +124 -0
  22. data/lib/dbd/mysql/database.rb +405 -0
  23. data/lib/dbd/mysql/driver.rb +125 -0
  24. data/lib/dbd/mysql/statement.rb +188 -0
  25. data/lib/dbd/odbc/database.rb +128 -0
  26. data/lib/dbd/odbc/driver.rb +38 -0
  27. data/lib/dbd/odbc/statement.rb +137 -0
  28. data/lib/dbd/pg/database.rb +504 -0
  29. data/lib/dbd/pg/exec.rb +47 -0
  30. data/lib/dbd/pg/statement.rb +160 -0
  31. data/lib/dbd/pg/tuples.rb +121 -0
  32. data/lib/dbd/pg/type.rb +209 -0
  33. data/lib/dbd/sqlite/database.rb +151 -0
  34. data/lib/dbd/sqlite/statement.rb +125 -0
  35. data/lib/dbd/sqlite3/database.rb +201 -0
  36. data/lib/dbd/sqlite3/statement.rb +78 -0
  37. data/lib/dbi.rb +13 -17
  38. data/lib/dbi/utils/date.rb +7 -3
  39. data/lib/dbi/version.rb +1 -1
  40. data/prototypes/types2.rb +237 -0
  41. data/setup.rb +1585 -0
  42. data/test/DBD_TESTS +50 -0
  43. data/test/TESTING +16 -0
  44. data/test/dbd/general/test_database.rb +206 -0
  45. data/test/dbd/general/test_statement.rb +326 -0
  46. data/test/dbd/general/test_types.rb +296 -0
  47. data/test/dbd/mysql/base.rb +26 -0
  48. data/test/dbd/mysql/down.sql +19 -0
  49. data/test/dbd/mysql/test_blob.rb +18 -0
  50. data/test/dbd/mysql/test_new_methods.rb +7 -0
  51. data/test/dbd/mysql/test_patches.rb +111 -0
  52. data/test/dbd/mysql/up.sql +28 -0
  53. data/test/dbd/odbc/base.rb +30 -0
  54. data/test/dbd/odbc/down.sql +19 -0
  55. data/test/dbd/odbc/test_new_methods.rb +12 -0
  56. data/test/dbd/odbc/test_ping.rb +10 -0
  57. data/test/dbd/odbc/test_statement.rb +44 -0
  58. data/test/dbd/odbc/test_transactions.rb +58 -0
  59. data/test/dbd/odbc/up.sql +33 -0
  60. data/test/dbd/postgresql/base.rb +31 -0
  61. data/test/dbd/postgresql/down.sql +31 -0
  62. data/test/dbd/postgresql/test_arrays.rb +179 -0
  63. data/test/dbd/postgresql/test_async.rb +121 -0
  64. data/test/dbd/postgresql/test_blob.rb +36 -0
  65. data/test/dbd/postgresql/test_bytea.rb +87 -0
  66. data/test/dbd/postgresql/test_ping.rb +10 -0
  67. data/test/dbd/postgresql/test_timestamp.rb +77 -0
  68. data/test/dbd/postgresql/test_transactions.rb +58 -0
  69. data/test/dbd/postgresql/testdbipg.rb +307 -0
  70. data/test/dbd/postgresql/up.sql +60 -0
  71. data/test/dbd/sqlite/base.rb +32 -0
  72. data/test/dbd/sqlite/test_database.rb +30 -0
  73. data/test/dbd/sqlite/test_driver.rb +68 -0
  74. data/test/dbd/sqlite/test_statement.rb +112 -0
  75. data/test/dbd/sqlite/up.sql +25 -0
  76. data/test/dbd/sqlite3/base.rb +32 -0
  77. data/test/dbd/sqlite3/test_database.rb +77 -0
  78. data/test/dbd/sqlite3/test_driver.rb +67 -0
  79. data/test/dbd/sqlite3/test_statement.rb +88 -0
  80. data/test/dbd/sqlite3/up.sql +33 -0
  81. data/test/dbi/tc_columninfo.rb +4 -9
  82. data/test/dbi/tc_date.rb +2 -9
  83. data/test/dbi/tc_dbi.rb +3 -9
  84. data/test/dbi/tc_row.rb +17 -23
  85. data/test/dbi/tc_sqlbind.rb +6 -7
  86. data/test/dbi/tc_statementhandle.rb +3 -4
  87. data/test/dbi/tc_time.rb +2 -8
  88. data/test/dbi/tc_timestamp.rb +2 -16
  89. data/test/dbi/tc_types.rb +5 -11
  90. data/test/ts_dbd.rb +131 -0
  91. data/ydbi.gemspec +23 -0
  92. metadata +128 -10
@@ -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 PG::Error => 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(PG::Connection::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 PG::Error, 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
+ PG::Connection.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 = PG::Connection.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