ydbi 0.5.2 → 0.5.7
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.
- checksums.yaml +5 -5
- data/.github/workflows/ruby.yml +35 -0
- data/.gitignore +8 -0
- data/.travis.yml +15 -0
- data/ChangeLog +339 -314
- data/Gemfile +5 -0
- data/Rakefile +10 -0
- data/TODO +44 -0
- data/bench/bench.rb +79 -0
- data/build/rake_task_lib.rb +186 -0
- data/doc/DBD_SPEC.rdoc +88 -0
- data/doc/DBI_SPEC.rdoc +157 -0
- data/doc/homepage/contact.html +62 -0
- data/doc/homepage/development.html +124 -0
- data/doc/homepage/index.html +83 -0
- data/doc/homepage/ruby-dbi.css +91 -0
- data/lib/dbd/Mysql.rb +137 -0
- data/lib/dbd/ODBC.rb +89 -0
- data/lib/dbd/Pg.rb +188 -0
- data/lib/dbd/SQLite.rb +97 -0
- data/lib/dbd/SQLite3.rb +124 -0
- data/lib/dbd/mysql/database.rb +405 -0
- data/lib/dbd/mysql/driver.rb +125 -0
- data/lib/dbd/mysql/statement.rb +188 -0
- data/lib/dbd/odbc/database.rb +128 -0
- data/lib/dbd/odbc/driver.rb +38 -0
- data/lib/dbd/odbc/statement.rb +137 -0
- data/lib/dbd/pg/database.rb +504 -0
- data/lib/dbd/pg/exec.rb +47 -0
- data/lib/dbd/pg/statement.rb +160 -0
- data/lib/dbd/pg/tuples.rb +121 -0
- data/lib/dbd/pg/type.rb +209 -0
- data/lib/dbd/sqlite/database.rb +151 -0
- data/lib/dbd/sqlite/statement.rb +125 -0
- data/lib/dbd/sqlite3/database.rb +201 -0
- data/lib/dbd/sqlite3/statement.rb +78 -0
- data/lib/dbi.rb +14 -17
- data/lib/dbi/utils/date.rb +7 -3
- data/lib/dbi/version.rb +1 -1
- data/prototypes/types2.rb +237 -0
- data/readme.md +15 -0
- data/setup.rb +1585 -0
- data/test/DBD_TESTS +50 -0
- data/test/TESTING +16 -0
- data/test/dbd/general/test_database.rb +206 -0
- data/test/dbd/general/test_statement.rb +326 -0
- data/test/dbd/general/test_types.rb +296 -0
- data/test/dbd/mysql/base.rb +26 -0
- data/test/dbd/mysql/down.sql +19 -0
- data/test/dbd/mysql/test_blob.rb +18 -0
- data/test/dbd/mysql/test_new_methods.rb +7 -0
- data/test/dbd/mysql/test_patches.rb +111 -0
- data/test/dbd/mysql/up.sql +28 -0
- data/test/dbd/odbc/base.rb +30 -0
- data/test/dbd/odbc/down.sql +19 -0
- data/test/dbd/odbc/test_new_methods.rb +12 -0
- data/test/dbd/odbc/test_ping.rb +10 -0
- data/test/dbd/odbc/test_statement.rb +44 -0
- data/test/dbd/odbc/test_transactions.rb +58 -0
- data/test/dbd/odbc/up.sql +33 -0
- data/test/dbd/postgresql/base.rb +31 -0
- data/test/dbd/postgresql/down.sql +31 -0
- data/test/dbd/postgresql/test_arrays.rb +179 -0
- data/test/dbd/postgresql/test_async.rb +121 -0
- data/test/dbd/postgresql/test_blob.rb +36 -0
- data/test/dbd/postgresql/test_bytea.rb +87 -0
- data/test/dbd/postgresql/test_ping.rb +10 -0
- data/test/dbd/postgresql/test_timestamp.rb +77 -0
- data/test/dbd/postgresql/test_transactions.rb +58 -0
- data/test/dbd/postgresql/testdbipg.rb +307 -0
- data/test/dbd/postgresql/up.sql +60 -0
- data/test/dbd/sqlite/base.rb +32 -0
- data/test/dbd/sqlite/test_database.rb +30 -0
- data/test/dbd/sqlite/test_driver.rb +68 -0
- data/test/dbd/sqlite/test_statement.rb +112 -0
- data/test/dbd/sqlite/up.sql +25 -0
- data/test/dbd/sqlite3/base.rb +32 -0
- data/test/dbd/sqlite3/test_database.rb +77 -0
- data/test/dbd/sqlite3/test_driver.rb +67 -0
- data/test/dbd/sqlite3/test_statement.rb +88 -0
- data/test/dbd/sqlite3/up.sql +33 -0
- data/test/dbi/tc_columninfo.rb +4 -9
- data/test/dbi/tc_date.rb +2 -9
- data/test/dbi/tc_dbi.rb +3 -9
- data/test/dbi/tc_row.rb +17 -23
- data/test/dbi/tc_sqlbind.rb +6 -7
- data/test/dbi/tc_statementhandle.rb +3 -4
- data/test/dbi/tc_time.rb +2 -8
- data/test/dbi/tc_timestamp.rb +2 -16
- data/test/dbi/tc_types.rb +5 -11
- data/test/ts_dbd.rb +131 -0
- data/ydbi.gemspec +23 -0
- metadata +128 -10
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
$:.unshift 'build'
|
2
|
+
if File.exists? 'lib/dbi'
|
3
|
+
require 'Rakefile.dbi'
|
4
|
+
elsif File.exists? 'lib/dbd'
|
5
|
+
require 'rake_task_lib'
|
6
|
+
build_dbd_tasks(File.basename(Dir['lib/dbd/*.rb'][0], '.rb').downcase)
|
7
|
+
else
|
8
|
+
abort "Well, this is odd; No DBI or DBD found."
|
9
|
+
end
|
10
|
+
require 'bundler/gem_tasks'
|
data/TODO
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
0.4.0:
|
2
|
+
x Work
|
3
|
+
x Postgres quoting toggles
|
4
|
+
x Postgres binding toggles
|
5
|
+
x Test for #20489
|
6
|
+
* Release checklist:
|
7
|
+
x Update ChangeLog
|
8
|
+
x rake
|
9
|
+
x remove all related gems, install all DBI/DBD gems (check prereqs)
|
10
|
+
x write a few test scripts to make sure drivers are loading
|
11
|
+
x Update homepage (irc channel, git, other changes)
|
12
|
+
* tag as 0.4.0
|
13
|
+
* upload rdoc
|
14
|
+
* upload packages
|
15
|
+
* release announcement (rf, ruby-talk, dbi lists, other places?)
|
16
|
+
* rebase master from development
|
17
|
+
* BEER
|
18
|
+
0.6.0:
|
19
|
+
* Cleanup:
|
20
|
+
* Arrays:
|
21
|
+
* Find some way to get the extents of an array type
|
22
|
+
* checked pg_type, pg_attribute and pg_class, can't find anything.
|
23
|
+
* I don't think this is possible, but let's slate it for 0.6.0 anyways.
|
24
|
+
* Tracing
|
25
|
+
* WTH do we want to do with this
|
26
|
+
* Wow, this module has *serious issues*. Crashes DBI without warning. Redo this completely.
|
27
|
+
* 0.6.0
|
28
|
+
* Re-institute drivers
|
29
|
+
* Proxy
|
30
|
+
* Slated for 0.6.0
|
31
|
+
* Finish type management system
|
32
|
+
* Unify ColumnInfo
|
33
|
+
* Should we enforce ColumnInfo requirements at the DBI level?
|
34
|
+
* At least test the result of statement colinfo uniformly
|
35
|
+
* 0.6.0
|
36
|
+
* Cleanup core
|
37
|
+
* Require code is a mess
|
38
|
+
* Just remove the case-sensitivity from the DBDs in general
|
39
|
+
* 0.6.0
|
40
|
+
* Find a good clean way to enumerate drivers in separate gems
|
41
|
+
* Some registration-on-require would be cleaner and safer
|
42
|
+
* 0.6.0
|
43
|
+
* Scripts
|
44
|
+
* bin/dbd_proxy seems to have never worked; slate it for 0.6.0
|
data/bench/bench.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
3
|
+
require 'dbi'
|
4
|
+
|
5
|
+
class DbiBenchmark
|
6
|
+
attr_reader :db
|
7
|
+
|
8
|
+
def initialize db, quiet=false
|
9
|
+
@db = db
|
10
|
+
@quiet = quiet
|
11
|
+
end
|
12
|
+
|
13
|
+
def puts(*args)
|
14
|
+
super unless @quiet
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
times = {}
|
19
|
+
%w[create_test_table selecting_floats selecting_datetimes].each do |name|
|
20
|
+
t = Time.now
|
21
|
+
puts "* #{name.tr('_', ' ').capitalize}"
|
22
|
+
send name
|
23
|
+
took = Time.now - t
|
24
|
+
puts " (took #{took} seconds)"
|
25
|
+
puts
|
26
|
+
times[name] = took
|
27
|
+
end
|
28
|
+
times
|
29
|
+
ensure
|
30
|
+
db.do 'drop table data'
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_test_table
|
34
|
+
db.do <<-end
|
35
|
+
create table data (
|
36
|
+
date timestamp,
|
37
|
+
value float
|
38
|
+
)
|
39
|
+
end
|
40
|
+
db.do 'begin'
|
41
|
+
today = Date.today
|
42
|
+
5_000.times do
|
43
|
+
db.do "insert into data values ('#{today + rand(100) - 50}', #{10 + rand * 30})"
|
44
|
+
end
|
45
|
+
db.do 'commit'
|
46
|
+
end
|
47
|
+
|
48
|
+
def selecting_floats
|
49
|
+
strs = db.select_all('select value from data').map { |v| v.to_s }
|
50
|
+
puts *strs[0, 5]
|
51
|
+
puts '...'
|
52
|
+
end
|
53
|
+
|
54
|
+
def selecting_datetimes
|
55
|
+
strs = db.select_all('select date from data').map { |v| v.to_s }
|
56
|
+
puts *strs[0, 5]
|
57
|
+
puts '...'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def bench
|
62
|
+
dbiurls = [
|
63
|
+
'DBI:Mysql:dbitest:localhost',
|
64
|
+
'DBI:ODBC:MYDBITEST',
|
65
|
+
'DBI:Pg:dbitest:localhost',
|
66
|
+
'DBI:ODBC:PGDBITEST',
|
67
|
+
]
|
68
|
+
order = %w[create_test_table selecting_floats selecting_datetimes]
|
69
|
+
dbiurls.map do |url|
|
70
|
+
# assume all dbs have the same credentials
|
71
|
+
DBI.connect(url, *ARGV) do |db|
|
72
|
+
[url.first.sub('DBI:', ''), *DbiBenchmark.new(db, true).run.values_at(*order)]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
puts 'Running benchmark:'
|
78
|
+
DBI::Utils::TableFormatter.ascii(%w[db insert float datetime], bench, nil, nil, nil, nil, 30)
|
79
|
+
|
@@ -0,0 +1,186 @@
|
|
1
|
+
$:.unshift 'lib'
|
2
|
+
require 'rake'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rubygems/package_task'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rdoc/task'
|
7
|
+
|
8
|
+
|
9
|
+
DEFAULT_TASKS = [:clobber_package, :package, :gem]
|
10
|
+
|
11
|
+
DBD_GEM_DEP_MAP = {
|
12
|
+
'pg' => 'pg',
|
13
|
+
'mysql' => 'mysql',
|
14
|
+
'sqlite' => 'sqlite-ruby',
|
15
|
+
'sqlite3' => 'sqlite3-ruby'
|
16
|
+
}
|
17
|
+
|
18
|
+
#
|
19
|
+
# Packaging
|
20
|
+
#
|
21
|
+
|
22
|
+
PACKAGE_FILES = %w(Rakefile build/rake_task_lib.rb setup.rb)
|
23
|
+
DOC_FILES = %w(readme.md LICENSE ChangeLog)
|
24
|
+
EXCLUSIONS = %w(test/sql.log)
|
25
|
+
DBD_FILES = %w(test/DBD_TESTS)
|
26
|
+
|
27
|
+
#
|
28
|
+
# some inlines
|
29
|
+
#
|
30
|
+
|
31
|
+
def gem_files(code_files)
|
32
|
+
(code_files + DOC_FILES).collect { |x| Dir[x] }.reject { |x| EXCLUSIONS.include? x }.flatten
|
33
|
+
end
|
34
|
+
|
35
|
+
def package_files(code_files)
|
36
|
+
code_files + DOC_FILES + PACKAGE_FILES
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_package_tasks(spec, code_files)
|
40
|
+
Gem::PackageTask.new(spec) do |s|
|
41
|
+
end
|
42
|
+
|
43
|
+
Rake::PackageTask.new('y'+spec.name, spec.version) do |p|
|
44
|
+
p.need_tar_gz = true
|
45
|
+
p.need_zip = true
|
46
|
+
|
47
|
+
package_files(code_files).each do |x|
|
48
|
+
p.package_files.include(x)
|
49
|
+
end
|
50
|
+
|
51
|
+
EXCLUSIONS.each do |x|
|
52
|
+
p.package_files.exclude(x)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def boilerplate_spec
|
58
|
+
gem = Gem::Specification.new
|
59
|
+
gem.authors = ['Erik Hollensbe', 'Christopher Maujean']
|
60
|
+
gem.email = 'zdavatz@ywesee.com'
|
61
|
+
gem.homepage = 'https://github.com/zdavatz/ydbi'
|
62
|
+
gem.platform = Gem::Platform::RUBY
|
63
|
+
gem.extra_rdoc_files = DOC_FILES
|
64
|
+
gem.required_ruby_version = '>= 1.8.0'
|
65
|
+
gem.rubyforge_project = 'ydbi'
|
66
|
+
return gem
|
67
|
+
end
|
68
|
+
|
69
|
+
# builds a dbd namespace from the DBD_PACKAGES hash
|
70
|
+
def dbd_namespace(dbd)
|
71
|
+
"ydbd-" + dbd.to_s.downcase
|
72
|
+
end
|
73
|
+
|
74
|
+
def dbd_code_files(dbd)
|
75
|
+
code_files = [
|
76
|
+
"test/dbd/general/**",
|
77
|
+
File.join("test", "dbd", dbd.downcase == "pg" ? "postgresql" : dbd.downcase, "*"),
|
78
|
+
File.join("lib", "dbd", dbd + ".rb"),
|
79
|
+
"lib/dbd/#{dbd.downcase}/*.rb",
|
80
|
+
] + DBD_FILES
|
81
|
+
end
|
82
|
+
|
83
|
+
def dbd_gem_files(code_files)
|
84
|
+
DBD_FILES + gem_files(code_files)
|
85
|
+
end
|
86
|
+
|
87
|
+
def dbd_package_files(code_files)
|
88
|
+
DBD_FILES + package_files(code_files)
|
89
|
+
end
|
90
|
+
|
91
|
+
def dbd_gem_spec(dbd, dbd_const, code_files)
|
92
|
+
spec = boilerplate_spec
|
93
|
+
spec.name = dbd_namespace(dbd)
|
94
|
+
spec.version = dbd_version(dbd_const)
|
95
|
+
spec.test_file = 'test/ts_dbd.rb'
|
96
|
+
spec.files = gem_files(code_files)
|
97
|
+
spec.summary = dbd_description(dbd_const)
|
98
|
+
spec.description = dbd_description(dbd_const)
|
99
|
+
spec.add_dependency 'ydbi', DBI::VERSION
|
100
|
+
|
101
|
+
dcdbd = dbd.downcase
|
102
|
+
|
103
|
+
if DBD_GEM_DEP_MAP[dcdbd]
|
104
|
+
spec.add_dependency DBD_GEM_DEP_MAP[dcdbd]
|
105
|
+
end
|
106
|
+
|
107
|
+
return spec
|
108
|
+
end
|
109
|
+
|
110
|
+
def dbd_version(const)
|
111
|
+
DBI::DBD.const_get(const).const_get("VERSION")
|
112
|
+
end
|
113
|
+
|
114
|
+
def dbd_description(const)
|
115
|
+
DBI::DBD.const_get(const).const_get("DESCRIPTION")
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def build_dbd_tasks(dbd)
|
120
|
+
task :default => DEFAULT_TASKS
|
121
|
+
|
122
|
+
begin
|
123
|
+
done = false
|
124
|
+
dbd_const = nil
|
125
|
+
Dir["lib/dbd/*.rb"].each do |dbd_file|
|
126
|
+
if File.basename(dbd_file.downcase, '.rb') == dbd.to_s.downcase
|
127
|
+
dbd_const = File.basename(dbd_file, '.rb')
|
128
|
+
require "dbd/#{dbd_const}"
|
129
|
+
done = true
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
abort "No DBD found even though we asked to make tasks for it" unless done
|
134
|
+
|
135
|
+
code_files = dbd_code_files(dbd_const)
|
136
|
+
|
137
|
+
spec = dbd_gem_spec(dbd, dbd_const, code_files)
|
138
|
+
|
139
|
+
build_package_tasks(spec, code_files)
|
140
|
+
|
141
|
+
# FIXME: convert to a rake_test_loader sooner or later
|
142
|
+
task :test do
|
143
|
+
ENV["DBTYPES"] = dbd
|
144
|
+
ruby "test/ts_dbd.rb"
|
145
|
+
end
|
146
|
+
rescue LoadError => e
|
147
|
+
DEFAULT_TASKS.each do |x|
|
148
|
+
task x do
|
149
|
+
end
|
150
|
+
end
|
151
|
+
warn "Skipping #{dbd_namespace(dbd)} because we can't require DBD"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def build_dbi_tasks
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# basic tasks
|
160
|
+
#
|
161
|
+
|
162
|
+
task :dist => [:distclean, :package, :rdoc]
|
163
|
+
task :distclean => [:clobber_package, :clobber_rdoc]
|
164
|
+
task :clean => [:distclean]
|
165
|
+
task :default => [:test, :dist]
|
166
|
+
|
167
|
+
task :to_blog => [:clobber_rdoc, :rdoc] do
|
168
|
+
sh "rm -r $git/blog/content/docs/ruby-dbi && mv rdoc $git/blog/content/docs/ruby-dbi"
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
# Documentation
|
173
|
+
#
|
174
|
+
|
175
|
+
RDoc::Task.new do |rd|
|
176
|
+
rd.rdoc_dir = "rdoc"
|
177
|
+
rd.main = "readme.md"
|
178
|
+
rd.rdoc_files.include("./readme.md")
|
179
|
+
rd.rdoc_files.include("./ChangeLog")
|
180
|
+
rd.rdoc_files.include("./LICENSE")
|
181
|
+
rd.rdoc_files.include("./doc/**/*.rdoc")
|
182
|
+
rd.rdoc_files.include("./lib/**/*.rb")
|
183
|
+
rd.rdoc_files.include("./ext/**/*.c")
|
184
|
+
rd.options = %w(-a)
|
185
|
+
end
|
186
|
+
|
data/doc/DBD_SPEC.rdoc
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
= DBD Specification Version 0.4.0
|
2
|
+
By Erik Hollensbe <erik@hollensbe.org>
|
3
|
+
|
4
|
+
== FOREWORD
|
5
|
+
|
6
|
+
DBI is still in a large state of flux. Previous versions of this
|
7
|
+
specification rarely reflected reality, and the 0.4.0 release is an
|
8
|
+
attempt to get the code and documentation in touch with each other to
|
9
|
+
better reflect said reality.
|
10
|
+
|
11
|
+
While this is a goal, nobody's perfect and there is still a lot of
|
12
|
+
code to check, sanitize, and otherwise clean up. If you find something
|
13
|
+
missing in these specifications while working on a new DBD or a patch
|
14
|
+
for DBI, please, do not do what everything else is doing; alert the
|
15
|
+
appropriate person to get the spec revised. Doing this will save
|
16
|
+
yourself (and the DBI authors) infinite amounts of time.
|
17
|
+
|
18
|
+
== WHAT A DBD IS
|
19
|
+
|
20
|
+
DBD stands for "DataBase Driver" and is the layer that DBI uses to interface
|
21
|
+
with the database. DBDs often employ a low level driver to do the real work
|
22
|
+
with the database, leaving the DBD itself to act as a medium between DBI and
|
23
|
+
that lower level API.
|
24
|
+
|
25
|
+
This allows a great deal of flexibility without having to sacrifice features
|
26
|
+
for compatibility. For example, instead of having one PostgreSQL DBD that
|
27
|
+
handles all version of PostgreSQL and attempts to limit it's functionality
|
28
|
+
based on what version it detects (a error-prone and time/design prohibitive
|
29
|
+
process), one can write two PostgreSQL DBD that handle the differences between
|
30
|
+
"new" and "old" postgres, all while talking to the same low-level driver (yet
|
31
|
+
leveraging different functionality). This method leads to cleaner internals and
|
32
|
+
puts the choice of which to use on the end-user, who is probably a lot more
|
33
|
+
informed about what features they want than your code.
|
34
|
+
|
35
|
+
One traditionally loads a DBD using the DBI.connect method (see DBD LOAD
|
36
|
+
PATHS below) which will attempt to load the DBD, connect to the database with
|
37
|
+
the arguments supplied and return a DatabaseHandle if successful. However, if
|
38
|
+
the DBD is written properly, requiring it directly without DBI's involvement
|
39
|
+
(or existence) should not be an issue.
|
40
|
+
|
41
|
+
== WHERE DBDs LIVE
|
42
|
+
|
43
|
+
DBDs have an expected require path to be loaded by DBI. DBI will attempt to
|
44
|
+
load the middle portion of the DBI.connect DSN provided.
|
45
|
+
|
46
|
+
Example: DBI.connect("dbi:Mysql:mydb") # requires 'dbd/Mysql'
|
47
|
+
|
48
|
+
Since rubygems potentially renders this path virtual, it is not OK to expect
|
49
|
+
this path physically exists in one spot on the filesystem. Many assuptions are
|
50
|
+
currently made about this and will be pruned in 0.6.0.
|
51
|
+
|
52
|
+
If you wish to create submodules for your DBD, create a directory in the 'dbd'
|
53
|
+
directory named the same as the DBD. (E.g., "dbd/Mysql" would have a directory
|
54
|
+
with files in it relating to the Mysql.rb file that DBI loads).
|
55
|
+
|
56
|
+
== HOW DBI INTERFACES WITH YOUR DBD
|
57
|
+
|
58
|
+
Your DBD will create classes representing a DBI::BaseDriver, DBI::BaseDatabase,
|
59
|
+
and DBI::BaseStatement. DBI will link these to DBI::DriverHandle,
|
60
|
+
DBI::DatabaseHandle, and DBI::StatementHandle respectively. Your classes will
|
61
|
+
be called by the Handle classes to retreive information to manipulate and send
|
62
|
+
to the user. This manipulation can be influenced in a number of ways.
|
63
|
+
|
64
|
+
It is strongly recommended you make the effort to read the RDoc for all six
|
65
|
+
of these classes, as they are the meat of this specification, not this
|
66
|
+
document.
|
67
|
+
|
68
|
+
== BUILDING A DBD FROM SCRATCH
|
69
|
+
|
70
|
+
For the purposes of this discussion, we'll call your driver 'Foo'.
|
71
|
+
|
72
|
+
Create your module, DBI::DBD::Foo. Store it somewhere in your load path under
|
73
|
+
dbd/Foo.rb.
|
74
|
+
|
75
|
+
Create classes Driver, Database, and Statement in this new namespace, which
|
76
|
+
inherit from DBI::BaseDriver, DBI::BaseDatabase, and DBI::BaseStatement.
|
77
|
+
Override (at mininum) the methods that return NotImplementedError in your new
|
78
|
+
classes.
|
79
|
+
|
80
|
+
Create a method in the root namespace named +driver_name+. This should return a
|
81
|
+
string with a short name for your driver, this key will be used in type
|
82
|
+
conversion.
|
83
|
+
|
84
|
+
Everything else is up to you, up to and including worrying about interacting
|
85
|
+
with the database.
|
86
|
+
|
87
|
+
At this point, you should be ready to test your driver. See test/DBD_TESTS for
|
88
|
+
information on how to configure that.
|
data/doc/DBI_SPEC.rdoc
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
= DBI Interface Spec, for version 0.4.0
|
2
|
+
|
3
|
+
by Erik Hollensbe <erik@hollensbe.org>
|
4
|
+
|
5
|
+
== Foreword
|
6
|
+
|
7
|
+
DBI is still in a large state of flux. Previous versions of this
|
8
|
+
specification rarely reflected reality, and the 0.4.0 release is an
|
9
|
+
attempt to get the code and documentation more in sync.
|
10
|
+
|
11
|
+
While this is the goal, there is still a lot of
|
12
|
+
code to check, sanitize, and otherwise clean up. If you find something
|
13
|
+
missing in these specifications while working on a new DBD or a patch
|
14
|
+
for DBI, please bring it to our attention (in IRC or on the mailing list)
|
15
|
+
to get the spec revised. Doing this will save
|
16
|
+
yourself (and the DBI authors) a lot of time.
|
17
|
+
|
18
|
+
== Design
|
19
|
+
|
20
|
+
With DBI, there are the concepts of driver, database, and statement. The core
|
21
|
+
functionality for these concepts is provided by a database driver, or
|
22
|
+
DBD. DBI controls one or more drivers at once, a driver has databases, a
|
23
|
+
database may have statements.
|
24
|
+
|
25
|
+
DBI uses a delegation model to communicate with its DBDs through a
|
26
|
+
series of handles. When a connection to a database is requested, DBI contacts
|
27
|
+
the appropriate DBD and builds a handle in
|
28
|
+
its name that it aligns with a DBI base class for that concept. The
|
29
|
+
handle provided by the DBD is the first-class method of communication,
|
30
|
+
otherwise it resorts to calling the base class methods. This allows
|
31
|
+
DBI to provide a level of consistency unless the DBD author finds it
|
32
|
+
otherwise unnecessary.
|
33
|
+
|
34
|
+
For example: DBI will provide handy methods like fetch_all and
|
35
|
+
fetch_scroll which all leverage the fetch method in the base class,
|
36
|
+
and the fetch method must be implemented by the DBD. However, the DBD
|
37
|
+
may have an internal representation of fetch_scroll (as is the case
|
38
|
+
with the ODBC driver) that may be more suited to direct use, and
|
39
|
+
therefore DBI will never see the base class method. This is similar to
|
40
|
+
inheritance, but there is a distinct disconnect between the handles
|
41
|
+
and the base classes, intentionally so. This way the DBDs have no
|
42
|
+
access to the base class and DBI does all the delegation work. Also,
|
43
|
+
DBI has no idea what the DBD is doing underneath, nor does it need to
|
44
|
+
care as long as valid data is returned.
|
45
|
+
|
46
|
+
== Classes
|
47
|
+
|
48
|
+
These are the classes that make up the core of DBI and provide
|
49
|
+
various functionality:
|
50
|
+
|
51
|
+
=== DBI
|
52
|
+
Core module, responsible for everything underneath it, kickstarting
|
53
|
+
connections and loading drivers.
|
54
|
+
|
55
|
+
=== DBI::Row
|
56
|
+
|
57
|
+
Responsible for representing the result set and managing the type
|
58
|
+
conversion of the result set.
|
59
|
+
|
60
|
+
=== DBI::Utils
|
61
|
+
|
62
|
+
Utility methods which propogate through the rest of DBI.
|
63
|
+
|
64
|
+
=== DBI::SQL
|
65
|
+
|
66
|
+
Utility methods for working with SQL queries. Includes a
|
67
|
+
driver-independent SQL bind manager.
|
68
|
+
|
69
|
+
=== DBI::ColumnInfo
|
70
|
+
|
71
|
+
Responsible for representing the information per column for both
|
72
|
+
queries and table descriptions.
|
73
|
+
|
74
|
+
=== DBI::Type
|
75
|
+
|
76
|
+
Namespace for typecasting classes. These classes are provided with a
|
77
|
+
parse method which converts them to a native Ruby type from a string.
|
78
|
+
|
79
|
+
=== DBI::TypeUtil
|
80
|
+
|
81
|
+
The inverse of DBI::Type, this provides functionality to turn native
|
82
|
+
Ruby types into a representation suitable for the DBD's queries.
|
83
|
+
|
84
|
+
=== DBI::Binary
|
85
|
+
|
86
|
+
The representation of a BLOB/CLOB in a Ruby object. This will
|
87
|
+
eventually be rolled into DBI::Type::, but remains here currently for
|
88
|
+
compatibility purposes.
|
89
|
+
|
90
|
+
=== DBI::Base* and DBI::*Handle
|
91
|
+
|
92
|
+
Please see the Design section above for the description of these modules.
|
93
|
+
|
94
|
+
== Exceptions
|
95
|
+
|
96
|
+
DBI has a slew of custom exceptions it uses to control program flow,
|
97
|
+
and alert the user to specific classes of problems.
|
98
|
+
|
99
|
+
They currently all live in the DBI namespace, although it's expected
|
100
|
+
that there will eventually be an exception namespace.
|
101
|
+
|
102
|
+
=== DBI::Warning < RuntimeError
|
103
|
+
For important warnings such as data truncation, etc.
|
104
|
+
|
105
|
+
=== DBI::Error < RuntimeError
|
106
|
+
Base class of all other error exceptions.
|
107
|
+
Rescue this to rescue all DBI errors.
|
108
|
+
|
109
|
+
=== DBI::InterfaceError < DBI::Error
|
110
|
+
Exception for errors related to the DBI interface rather
|
111
|
+
than the database itself.
|
112
|
+
|
113
|
+
=== DBI::NotImplementedError < DBI::InterfaceError
|
114
|
+
Exception raised if the DBD driver has not specified
|
115
|
+
a mandatory method.
|
116
|
+
|
117
|
+
=== DBI::DatabaseError < DBI::Error
|
118
|
+
Exception for errors related to the database.
|
119
|
+
|
120
|
+
Has three attributes: ((|err|)), ((|errstr|)) and ((|state|)).
|
121
|
+
|
122
|
+
=== DBI::DataError < DBI::DatabaseError
|
123
|
+
Exception for errors due to problems with the processed
|
124
|
+
data, such as division by zero, numeric value out of range, etc.
|
125
|
+
|
126
|
+
=== DBI::OperationalError < DBI::DatabaseError
|
127
|
+
Exception for errors related to the database's operation which
|
128
|
+
are not necessarily under the control of the programmer. This would include
|
129
|
+
such things as unexpected disconnection, failure to find a datasource name,
|
130
|
+
failure to process a transaction, memory allocation errors, etc.
|
131
|
+
|
132
|
+
=== DBI::IntegrityError < DBI::DatabaseError
|
133
|
+
Exception raised when the relational integrity of the database
|
134
|
+
is affected, such as when a foreign key constraint is violated.
|
135
|
+
|
136
|
+
=== DBI::InternalError < DBI::DatabaseError
|
137
|
+
Exception raised when the database encounters an internal error,
|
138
|
+
such as a cursor not being valid anymore, or a transaction going out of
|
139
|
+
sync.
|
140
|
+
|
141
|
+
=== DBI::ProgrammingError < DBI::DatabaseError
|
142
|
+
Exception raised for programming errors, e.g., table not found
|
143
|
+
or already exists, syntax error in SQL statement, wrong number
|
144
|
+
of parameters specified, etc.
|
145
|
+
|
146
|
+
=== DBI::NotSupportedError < DBI::DatabaseError
|
147
|
+
Raised if, e.g., ((<commit>)) is called for a database that does not
|
148
|
+
support transactions.
|
149
|
+
|
150
|
+
== API
|
151
|
+
|
152
|
+
To save my sanity, I have joined the specification and the rdoc for
|
153
|
+
DBI. Please review the specification there.
|
154
|
+
|
155
|
+
If you wish to author your own DBD, please see DBD_SPEC.rdoc,
|
156
|
+
which is a more in-depth view of the communication between DBI and
|
157
|
+
DBDs.
|