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.
Files changed (93) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +35 -0
  3. data/.gitignore +8 -0
  4. data/.travis.yml +15 -0
  5. data/ChangeLog +339 -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 +14 -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/readme.md +15 -0
  42. data/setup.rb +1585 -0
  43. data/test/DBD_TESTS +50 -0
  44. data/test/TESTING +16 -0
  45. data/test/dbd/general/test_database.rb +206 -0
  46. data/test/dbd/general/test_statement.rb +326 -0
  47. data/test/dbd/general/test_types.rb +296 -0
  48. data/test/dbd/mysql/base.rb +26 -0
  49. data/test/dbd/mysql/down.sql +19 -0
  50. data/test/dbd/mysql/test_blob.rb +18 -0
  51. data/test/dbd/mysql/test_new_methods.rb +7 -0
  52. data/test/dbd/mysql/test_patches.rb +111 -0
  53. data/test/dbd/mysql/up.sql +28 -0
  54. data/test/dbd/odbc/base.rb +30 -0
  55. data/test/dbd/odbc/down.sql +19 -0
  56. data/test/dbd/odbc/test_new_methods.rb +12 -0
  57. data/test/dbd/odbc/test_ping.rb +10 -0
  58. data/test/dbd/odbc/test_statement.rb +44 -0
  59. data/test/dbd/odbc/test_transactions.rb +58 -0
  60. data/test/dbd/odbc/up.sql +33 -0
  61. data/test/dbd/postgresql/base.rb +31 -0
  62. data/test/dbd/postgresql/down.sql +31 -0
  63. data/test/dbd/postgresql/test_arrays.rb +179 -0
  64. data/test/dbd/postgresql/test_async.rb +121 -0
  65. data/test/dbd/postgresql/test_blob.rb +36 -0
  66. data/test/dbd/postgresql/test_bytea.rb +87 -0
  67. data/test/dbd/postgresql/test_ping.rb +10 -0
  68. data/test/dbd/postgresql/test_timestamp.rb +77 -0
  69. data/test/dbd/postgresql/test_transactions.rb +58 -0
  70. data/test/dbd/postgresql/testdbipg.rb +307 -0
  71. data/test/dbd/postgresql/up.sql +60 -0
  72. data/test/dbd/sqlite/base.rb +32 -0
  73. data/test/dbd/sqlite/test_database.rb +30 -0
  74. data/test/dbd/sqlite/test_driver.rb +68 -0
  75. data/test/dbd/sqlite/test_statement.rb +112 -0
  76. data/test/dbd/sqlite/up.sql +25 -0
  77. data/test/dbd/sqlite3/base.rb +32 -0
  78. data/test/dbd/sqlite3/test_database.rb +77 -0
  79. data/test/dbd/sqlite3/test_driver.rb +67 -0
  80. data/test/dbd/sqlite3/test_statement.rb +88 -0
  81. data/test/dbd/sqlite3/up.sql +33 -0
  82. data/test/dbi/tc_columninfo.rb +4 -9
  83. data/test/dbi/tc_date.rb +2 -9
  84. data/test/dbi/tc_dbi.rb +3 -9
  85. data/test/dbi/tc_row.rb +17 -23
  86. data/test/dbi/tc_sqlbind.rb +6 -7
  87. data/test/dbi/tc_statementhandle.rb +3 -4
  88. data/test/dbi/tc_time.rb +2 -8
  89. data/test/dbi/tc_timestamp.rb +2 -16
  90. data/test/dbi/tc_types.rb +5 -11
  91. data/test/ts_dbd.rb +131 -0
  92. data/ydbi.gemspec +23 -0
  93. metadata +128 -10
@@ -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