ydbi 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/ChangeLog +3699 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +25 -0
  6. data/Rakefile +8 -0
  7. data/TODO +44 -0
  8. data/bench/bench.rb +79 -0
  9. data/bin/dbi +518 -0
  10. data/bin/test_broken_dbi +37 -0
  11. data/build/Rakefile.dbi.rb +60 -0
  12. data/build/rake_task_lib.rb +187 -0
  13. data/doc/DBD_SPEC.rdoc +88 -0
  14. data/doc/DBI_SPEC.rdoc +157 -0
  15. data/doc/homepage/contact.html +62 -0
  16. data/doc/homepage/development.html +124 -0
  17. data/doc/homepage/index.html +83 -0
  18. data/doc/homepage/ruby-dbi.css +91 -0
  19. data/examples/test1.pl +39 -0
  20. data/examples/test1.rb +20 -0
  21. data/examples/xmltest.rb +8 -0
  22. data/lib/dbd/Mysql.rb +137 -0
  23. data/lib/dbd/ODBC.rb +89 -0
  24. data/lib/dbd/Pg.rb +188 -0
  25. data/lib/dbd/SQLite.rb +97 -0
  26. data/lib/dbd/SQLite3.rb +124 -0
  27. data/lib/dbd/mysql/database.rb +405 -0
  28. data/lib/dbd/mysql/driver.rb +125 -0
  29. data/lib/dbd/mysql/statement.rb +188 -0
  30. data/lib/dbd/odbc/database.rb +128 -0
  31. data/lib/dbd/odbc/driver.rb +38 -0
  32. data/lib/dbd/odbc/statement.rb +137 -0
  33. data/lib/dbd/pg/database.rb +516 -0
  34. data/lib/dbd/pg/exec.rb +47 -0
  35. data/lib/dbd/pg/statement.rb +160 -0
  36. data/lib/dbd/pg/tuples.rb +121 -0
  37. data/lib/dbd/pg/type.rb +209 -0
  38. data/lib/dbd/sqlite/database.rb +151 -0
  39. data/lib/dbd/sqlite/statement.rb +125 -0
  40. data/lib/dbd/sqlite3/database.rb +201 -0
  41. data/lib/dbd/sqlite3/statement.rb +78 -0
  42. data/lib/dbi.rb +336 -0
  43. data/lib/dbi/base_classes.rb +12 -0
  44. data/lib/dbi/base_classes/database.rb +135 -0
  45. data/lib/dbi/base_classes/driver.rb +47 -0
  46. data/lib/dbi/base_classes/statement.rb +171 -0
  47. data/lib/dbi/binary.rb +25 -0
  48. data/lib/dbi/columninfo.rb +107 -0
  49. data/lib/dbi/exceptions.rb +65 -0
  50. data/lib/dbi/handles.rb +49 -0
  51. data/lib/dbi/handles/database.rb +241 -0
  52. data/lib/dbi/handles/driver.rb +60 -0
  53. data/lib/dbi/handles/statement.rb +408 -0
  54. data/lib/dbi/row.rb +269 -0
  55. data/lib/dbi/sql.rb +22 -0
  56. data/lib/dbi/sql/preparedstatement.rb +115 -0
  57. data/lib/dbi/sql_type_constants.rb +75 -0
  58. data/lib/dbi/trace.rb +91 -0
  59. data/lib/dbi/types.rb +218 -0
  60. data/lib/dbi/typeutil.rb +109 -0
  61. data/lib/dbi/utils.rb +60 -0
  62. data/lib/dbi/utils/date.rb +59 -0
  63. data/lib/dbi/utils/tableformatter.rb +112 -0
  64. data/lib/dbi/utils/time.rb +52 -0
  65. data/lib/dbi/utils/timestamp.rb +96 -0
  66. data/lib/dbi/utils/xmlformatter.rb +73 -0
  67. data/lib/dbi/version.rb +3 -0
  68. data/prototypes/types2.rb +237 -0
  69. data/readme.md +274 -0
  70. data/setup.rb +1585 -0
  71. data/test/DBD_TESTS +50 -0
  72. data/test/TESTING +16 -0
  73. data/test/dbd/general/test_database.rb +206 -0
  74. data/test/dbd/general/test_statement.rb +326 -0
  75. data/test/dbd/general/test_types.rb +296 -0
  76. data/test/dbd/mysql/base.rb +26 -0
  77. data/test/dbd/mysql/down.sql +19 -0
  78. data/test/dbd/mysql/test_blob.rb +18 -0
  79. data/test/dbd/mysql/test_new_methods.rb +7 -0
  80. data/test/dbd/mysql/test_patches.rb +111 -0
  81. data/test/dbd/mysql/up.sql +28 -0
  82. data/test/dbd/odbc/base.rb +30 -0
  83. data/test/dbd/odbc/down.sql +19 -0
  84. data/test/dbd/odbc/test_new_methods.rb +12 -0
  85. data/test/dbd/odbc/test_ping.rb +10 -0
  86. data/test/dbd/odbc/test_statement.rb +44 -0
  87. data/test/dbd/odbc/test_transactions.rb +58 -0
  88. data/test/dbd/odbc/up.sql +33 -0
  89. data/test/dbd/postgresql/base.rb +31 -0
  90. data/test/dbd/postgresql/down.sql +31 -0
  91. data/test/dbd/postgresql/test_arrays.rb +179 -0
  92. data/test/dbd/postgresql/test_async.rb +121 -0
  93. data/test/dbd/postgresql/test_blob.rb +36 -0
  94. data/test/dbd/postgresql/test_bytea.rb +87 -0
  95. data/test/dbd/postgresql/test_ping.rb +10 -0
  96. data/test/dbd/postgresql/test_timestamp.rb +77 -0
  97. data/test/dbd/postgresql/test_transactions.rb +58 -0
  98. data/test/dbd/postgresql/testdbipg.rb +307 -0
  99. data/test/dbd/postgresql/up.sql +60 -0
  100. data/test/dbd/sqlite/base.rb +32 -0
  101. data/test/dbd/sqlite/test_database.rb +30 -0
  102. data/test/dbd/sqlite/test_driver.rb +68 -0
  103. data/test/dbd/sqlite/test_statement.rb +112 -0
  104. data/test/dbd/sqlite/up.sql +25 -0
  105. data/test/dbd/sqlite3/base.rb +32 -0
  106. data/test/dbd/sqlite3/test_database.rb +77 -0
  107. data/test/dbd/sqlite3/test_driver.rb +67 -0
  108. data/test/dbd/sqlite3/test_statement.rb +88 -0
  109. data/test/dbd/sqlite3/up.sql +33 -0
  110. data/test/dbi/tc_columninfo.rb +94 -0
  111. data/test/dbi/tc_date.rb +88 -0
  112. data/test/dbi/tc_dbi.rb +184 -0
  113. data/test/dbi/tc_row.rb +256 -0
  114. data/test/dbi/tc_sqlbind.rb +168 -0
  115. data/test/dbi/tc_statementhandle.rb +29 -0
  116. data/test/dbi/tc_time.rb +77 -0
  117. data/test/dbi/tc_timestamp.rb +142 -0
  118. data/test/dbi/tc_types.rb +268 -0
  119. data/test/ts_dbd.rb +131 -0
  120. data/test/ts_dbi.rb +16 -0
  121. data/ydbi.gemspec +24 -0
  122. metadata +224 -0
@@ -0,0 +1,151 @@
1
+ #
2
+ # See DBI::BaseDatabase.
3
+ #
4
+ class DBI::DBD::SQLite::Database < DBI::BaseDatabase
5
+ attr_reader :db
6
+ attr_reader :attr_hash
7
+ attr_accessor :open_handles
8
+
9
+ #
10
+ # Constructor. Valid attributes:
11
+ #
12
+ # * AutoCommit: Commit after every statement execution.
13
+ #
14
+ def initialize(dbname, user, auth, attr_hash)
15
+ # FIXME why isn't this crap being done in DBI?
16
+ unless dbname.kind_of? String
17
+ raise DBI::InterfaceError, "Database Name must be a string"
18
+ end
19
+
20
+ unless dbname.length > 0
21
+ raise DBI::InterfaceError, "Database Name needs to be length > 0"
22
+ end
23
+
24
+ unless attr_hash.kind_of? Hash
25
+ raise DBI::InterfaceError, "Attributes should be a hash"
26
+ end
27
+
28
+ # FIXME handle busy_timeout in SQLite driver
29
+ # FIXME handle SQLite pragmas in SQLite driver
30
+ @attr_hash = attr_hash
31
+ @open_handles = 0
32
+
33
+ self["AutoCommit"] = true if self["AutoCommit"].nil?
34
+
35
+ # open the database
36
+ begin
37
+ @db = ::SQLite::Database.new(dbname)
38
+ rescue Exception => e
39
+ raise DBI::OperationalError, "Couldn't open database #{dbname}: #{e.message}"
40
+ end
41
+ end
42
+
43
+ def disconnect
44
+ rollback rescue nil
45
+ @db.close if @db and !@db.closed?
46
+ @db = nil
47
+ end
48
+
49
+ def database_name
50
+ st = DBI::DBD::SQLite::Statement.new('PRAGMA database_list', self)
51
+ st.execute
52
+ row = st.fetch
53
+ st.finish
54
+
55
+ return row[2]
56
+ end
57
+
58
+ def prepare(stmt)
59
+ return DBI::DBD::SQLite::Statement.new(stmt, self)
60
+ end
61
+
62
+ def ping
63
+ return !@db.closed?
64
+ end
65
+
66
+ def tables
67
+ sth = prepare("select name from sqlite_master where type in ('table', 'view')")
68
+ sth.execute
69
+ tables = sth.fetch_all.flatten
70
+ sth.finish
71
+ return tables
72
+ # FIXME does sqlite use views too? not sure, but they need to be included according to spec
73
+ end
74
+
75
+ def commit
76
+ @db.commit if @db.transaction_active?
77
+ end
78
+
79
+ #
80
+ # Rollback the transaction. SQLite has some issues with open statement
81
+ # handles when this happens. If there are still open handles, a
82
+ # DBI::Warning exception will be raised.
83
+ #
84
+ def rollback
85
+ if @open_handles > 0
86
+ raise DBI::Warning, "Leaving unfinished select statement handles while rolling back a transaction can corrupt your database or crash your program"
87
+ end
88
+
89
+ @db.rollback if @db.transaction_active?
90
+ end
91
+
92
+ def [](key)
93
+ return @attr_hash[key]
94
+ end
95
+
96
+ #
97
+ # See DBI::BaseDatabase#[]=.
98
+ #
99
+ # If AutoCommit is set to +true+ using this method, was previously +false+,
100
+ # and we are currently in a transaction, The act of setting this will cause
101
+ # an immediate commit.
102
+ #
103
+ def []=(key, value)
104
+
105
+ old_value = @attr_hash[key]
106
+
107
+ @attr_hash[key] = value
108
+
109
+ # special handling of settings
110
+ case key
111
+ when "AutoCommit"
112
+ # if the value being set is true and the previous value is false,
113
+ # commit the current transaction (if any)
114
+ # FIXME I still think this is a horrible way of handling this.
115
+ if value and !old_value
116
+ begin
117
+ @dbh.commit
118
+ rescue Exception => e
119
+ end
120
+ end
121
+ end
122
+
123
+ return @attr_hash[key]
124
+ end
125
+
126
+ def columns(tablename)
127
+ return nil unless tablename and tablename.kind_of? String
128
+
129
+ sth = prepare("PRAGMA table_info(?)")
130
+ sth.bind_param(1, tablename)
131
+ sth.execute
132
+ columns = [ ]
133
+ while row = sth.fetch
134
+ column = { }
135
+ column["name"] = row[1]
136
+
137
+ m = DBI::DBD::SQLite.parse_type(row[2])
138
+ column["type_name"] = m[1]
139
+ column["precision"] = m[3].to_i if m[3]
140
+ column["scale"] = m[5].to_i if m[5]
141
+
142
+ column["nullable"] = row[3].to_i == 0
143
+ column["default"] = row[4]
144
+ columns.push column
145
+ end
146
+
147
+ sth.finish
148
+ return columns
149
+ # XXX it'd be nice if the spec was changed to do this k/v with the name as the key.
150
+ end
151
+ end
@@ -0,0 +1,125 @@
1
+ #
2
+ # See DBI::BaseStatement.
3
+ #
4
+ class DBI::DBD::SQLite::Statement < DBI::BaseStatement
5
+ DBI_TYPE_MAP = [
6
+ [ /^INT(EGER)?$/i, DBI::SQL_INTEGER ],
7
+ [ /^(OID|ROWID|_ROWID_)$/i, DBI::SQL_OTHER ],
8
+ [ /^FLOAT$/i, DBI::SQL_FLOAT ],
9
+ [ /^REAL$/i, DBI::SQL_REAL ],
10
+ [ /^DOUBLE$/i, DBI::SQL_DOUBLE ],
11
+ [ /^DECIMAL/i, DBI::SQL_DECIMAL ],
12
+ [ /^(BOOL|BOOLEAN)$/i, DBI::SQL_BOOLEAN ],
13
+ [ /^TIME$/i, DBI::SQL_TIME ],
14
+ [ /^DATE$/i, DBI::SQL_DATE ],
15
+ [ /^TIMESTAMP$/i, DBI::SQL_TIMESTAMP ],
16
+ [ /^(VARCHAR|TEXT)/i, DBI::SQL_VARCHAR ],
17
+ [ /^CHAR$/i, DBI::SQL_CHAR ],
18
+ ]
19
+
20
+ def initialize(stmt, dbh)
21
+ @dbh = dbh
22
+ @statement = DBI::SQL::PreparedStatement.new(@dbh, stmt)
23
+ @attr = { }
24
+ @params = [ ]
25
+ @rows = [ ]
26
+ @result_set = nil
27
+ @dbh.open_handles += 1
28
+ end
29
+
30
+ #
31
+ # See DBI::BaseStatement#bind_param. This method will also raise
32
+ # DBI::InterfaceError if +param+ is not a Fixnum, to prevent incorrect
33
+ # binding.
34
+ #
35
+ def bind_param(param, value, attributes=nil)
36
+ unless param.kind_of? Fixnum
37
+ raise DBI::InterfaceError, "Only numeric parameters are supported"
38
+ end
39
+
40
+ @params[param-1] = value
41
+
42
+ # FIXME what to do with attributes? are they important in SQLite?
43
+ end
44
+
45
+ #
46
+ # See DBI::BaseStatement#execute.
47
+ #
48
+ # In the event AutoCommit is off and no transaction is currently executing,
49
+ # one will be opened at this point. It is your responsibility to #finish,
50
+ # #cancel, #rollback, or #commit.
51
+ #
52
+ def execute
53
+ sql = @statement.bind(@params)
54
+ # XXX sqlite re-escapes this for us automatically, it's causing trouble with everything else.
55
+ # this will probably break in a horrible manner and I will be forced to "fix" it again.
56
+ sql.gsub!(/\\\\/) { '\\' }
57
+ DBI::DBD::SQLite.check_sql(sql)
58
+
59
+ begin
60
+ unless @dbh.db.transaction_active?
61
+ @dbh.db.transaction
62
+ end
63
+ @result_set = @dbh.db.query(sql)
64
+ @dbh.commit if @dbh["AutoCommit"]
65
+ rescue Exception => e
66
+ raise DBI::DatabaseError, e.message
67
+ end
68
+ end
69
+
70
+ alias :finish :cancel
71
+
72
+ def finish
73
+ # nil out the result set
74
+ @result_set.close if @result_set
75
+ @result_set = nil
76
+ @rows = nil
77
+ @dbh.open_handles -= 1
78
+ end
79
+
80
+ def fetch
81
+ return nil if @result_set.eof?
82
+
83
+ row = @result_set.next
84
+ return nil unless row
85
+
86
+ return row
87
+ end
88
+
89
+ def column_info
90
+ columns = [ ]
91
+
92
+ # FIXME this shit should *really* be abstracted into DBI
93
+ # FIXME this still doesn't handle nullable/unique/default stuff.
94
+ @result_set.columns.each_with_index do |name, i|
95
+ columns[i] = { } unless columns[i]
96
+ columns[i]["name"] = name
97
+ type_name = @result_set.types[i]
98
+
99
+ if type_name
100
+ m = DBI::DBD::SQLite.parse_type(type_name)
101
+
102
+ columns[i]["type_name"] = m[1]
103
+ columns[i]["precision"] = m[3].to_i if m[3]
104
+ columns[i]["scale"] = m[5].to_i if m[5]
105
+ DBI_TYPE_MAP.each do |map|
106
+ if columns[i]["type_name"] =~ map[0]
107
+ columns[i]["sql_type"] = map[1]
108
+ break
109
+ end
110
+ end
111
+
112
+ case columns[i]["type_name"]
113
+ when 'double'
114
+ columns[i]["dbi_type"] = DBI::Type::Float
115
+ end
116
+ end
117
+ end
118
+
119
+ return columns
120
+ end
121
+
122
+ def rows
123
+ return @dbh.db.changes
124
+ end
125
+ end
@@ -0,0 +1,201 @@
1
+ #
2
+ # See DBI::BaseDatabase.
3
+ #
4
+ class DBI::DBD::SQLite3::Database < DBI::BaseDatabase
5
+ #
6
+ # Constructor. Valid attributes:
7
+ #
8
+ # * AutoCommit: Commit after every statement execution.
9
+ #
10
+ # The following attributes go directly to the low-level SQLite3 driver.
11
+ # Please consult it's documentation for more information.
12
+ #
13
+ # * auto_vacuum
14
+ # * cache_size
15
+ # * default_cache_size
16
+ # * default_synchronous
17
+ # * default_temp_store
18
+ # * full_column_names
19
+ # * synchronous
20
+ # * temp_store
21
+ # * type_translation
22
+ #
23
+ def initialize(dbname, attr)
24
+ @db = ::SQLite3::Database.new(dbname)
25
+
26
+ @db.type_translation = false
27
+
28
+ @attr = {'AutoCommit' => true}
29
+ if attr then
30
+ attr.each_pair do |key, value|
31
+ begin
32
+ self[key] = value
33
+ rescue DBI::NotSupportedError
34
+ end
35
+ end
36
+ end
37
+ __generate_attr__
38
+ end
39
+
40
+ def disconnect()
41
+ @db.rollback if @db.transaction_active?
42
+ @db.close
43
+ end
44
+
45
+ def prepare(statement)
46
+ DBI::DBD::SQLite3::Statement.new(statement, @db)
47
+ end
48
+
49
+ def database_name
50
+ st = DBI::DBD::SQLite3::Statement.new('PRAGMA database_list', @db)
51
+ st.execute
52
+ row = st.fetch
53
+ st.finish
54
+
55
+ return row[2]
56
+ end
57
+
58
+ def ping()
59
+ not @db.closed?
60
+ end
61
+
62
+ def commit()
63
+ if @db.transaction_active?
64
+ @db.commit
65
+ @db.transaction
66
+ else
67
+ raise DBI::ProgrammingError.new("No active transaction.")
68
+ end
69
+ end
70
+
71
+ #
72
+ # See DBI::BaseDatabase#rollback.
73
+ #
74
+ # If all statements were not closed before the rollback occurs, a
75
+ # DBI::Warning may be raised if the database encounters an error because of
76
+ # it.
77
+ #
78
+ # This method will also raise DBI::ProgrammingError if not in a
79
+ # transaction.
80
+ #
81
+ def rollback()
82
+ if @db.transaction_active?
83
+ begin
84
+ @db.rollback
85
+ @db.transaction
86
+ rescue Exception => e
87
+ raise DBI::Warning, "Statements were not closed prior to rollback"
88
+ end
89
+ else
90
+ raise DBI::ProgrammingError.new("No active transaction.")
91
+ end
92
+ end
93
+
94
+ def tables()
95
+ ret = []
96
+ result = @db.execute(%q(
97
+ SELECT name FROM sqlite_master WHERE type IN ('table', 'view')
98
+ UNION ALL
99
+ SELECT name FROM sqlite_temp_master WHERE type in ('table', 'view') ORDER BY 1
100
+ ))
101
+ result.each{|row| ret.push(row[0])}
102
+ ret
103
+ end
104
+
105
+ #
106
+ # See DBI::BaseDatabase#columns.
107
+ #
108
+ # Additional Attributes:
109
+ #
110
+ # * sql_type: XOPEN integer SQL Type.
111
+ # * nullable: true if NULL is allowed in this column.
112
+ # * default: the value that will be used in new rows if this column
113
+ # receives no data.
114
+ #
115
+ def columns(table)
116
+ @db.type_translation = false
117
+ ret =
118
+ @db.table_info(table).map do |hash|
119
+ m = DBI::DBD::SQLite3.parse_type(hash['type'])
120
+ h = {
121
+ 'name' => hash['name'],
122
+ 'type_name' => m[1],
123
+ 'sql_type' =>
124
+ begin
125
+ DBI.const_get('SQL_'+hash['type'].upcase)
126
+ rescue NameError
127
+ DBI::SQL_OTHER
128
+ end,
129
+ 'nullable' => (hash['notnull'] == '0'),
130
+ 'default' => (@attr['type_translation'] && (not hash['dflt_value'])) ?
131
+ @db.translator.translate(hash['type'], hash['dflt_value']) :
132
+ hash['dflt_value']
133
+ }
134
+
135
+ h['precision'] = m[3].to_i if m[3]
136
+ h['scale'] = m[5].to_i if m[5]
137
+
138
+ h
139
+ end
140
+ @db.type_translation = @attr['type_translation']
141
+ ret
142
+ end
143
+
144
+ def quote(value)
145
+ ::SQLite3::Database.quote(value.to_s)
146
+ end
147
+
148
+ #
149
+ # This method is used to aid the constructor and probably should not be
150
+ # used independently.
151
+ #
152
+ def __generate_attr__()
153
+ tt = @db.type_translation
154
+ @db.type_translation = false
155
+ [ 'auto_vacuum', 'cache_size', 'default_cache_size',
156
+ 'default_synchronous', 'default_temp_store', 'full_column_names',
157
+ 'synchronous', 'temp_store', 'type_translation' ].each do |key|
158
+ unless @attr.has_key?(key) then
159
+ @attr[key] = @db.__send__(key)
160
+ end
161
+ end
162
+ @db.type_translation = tt
163
+ end
164
+
165
+ #
166
+ # See #new for valid attributes.
167
+ #
168
+ # If Autocommit is set to true, commit happens immediately if a transaction
169
+ # is open.
170
+ #
171
+ def []=(attr, value)
172
+ case attr
173
+ when 'AutoCommit'
174
+ if value
175
+ @db.commit if @db.transaction_active?
176
+ else
177
+ @db.transaction unless @db.transaction_active?
178
+ end
179
+ @attr[attr] = value
180
+ when 'auto_vacuum', 'cache_size', 'count_changes',
181
+ 'default_cache_size', 'encoding', 'full_column_names',
182
+ 'page_size', 'short_column_names', 'synchronous',
183
+ 'temp_store', 'temp_store_directory'
184
+ @db.__send__((attr+'='), value)
185
+ @attr[attr] = @db.__send__(attr)
186
+ when 'busy_timeout'
187
+ @db.busy_timeout(value)
188
+ @attr[attr] = value
189
+ when 'busy_handler'
190
+ @db.busy_timeout(&value)
191
+ @attr[attr] = value
192
+ when 'type_translation'
193
+ @db.type_translation = value
194
+ @attr[attr] = value
195
+ else
196
+ raise DBI::NotSupportedError
197
+ end
198
+
199
+ return value
200
+ end
201
+ end