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,188 @@
1
+ module DBI::DBD::Mysql
2
+ #
3
+ # Models the DBI::BaseStatement API to create DBI::StatementHandle objects.
4
+ #
5
+ class Statement < DBI::BaseStatement
6
+ include Util
7
+
8
+ def initialize(parent, handle, statement, mutex)
9
+ super(nil)
10
+
11
+ @parent, @handle, @mutex = parent, handle, mutex
12
+ @params = []
13
+
14
+ @prep_stmt = DBI::SQL::PreparedStatement.new(@parent, statement)
15
+ end
16
+
17
+ #
18
+ # See DBI::BaseStatement#bind_param. This method will also raise
19
+ # DBI::InterfaceError if +param+ is not a Fixnum, to prevent incorrect
20
+ # binding.
21
+ #
22
+ def bind_param(param, value, attribs)
23
+ raise InterfaceError, "only ? parameters supported" unless param.is_a? Fixnum
24
+ @params[param-1] = value
25
+ end
26
+
27
+ #
28
+ # See DBI::BaseStatement#execute. If DBI thinks this is a query via DBI::SQL.query?(),
29
+ # it will force the row processed count to 0. Otherwise, it will return
30
+ # what MySQL thinks is the row processed count.
31
+ #
32
+ def execute
33
+ sql = @prep_stmt.bind(@params)
34
+ @mutex.synchronize {
35
+ @handle.query_with_result = true
36
+ @res_handle = @handle.query(sql)
37
+ @column_info = self.column_info
38
+ @current_row = 0
39
+ @rows = DBI::SQL.query?(sql) ? 0 : @handle.affected_rows
40
+ }
41
+ rescue MyError => err
42
+ error(err)
43
+ end
44
+
45
+ def finish
46
+ @res_handle.free if @res_handle
47
+ rescue MyError => err
48
+ error(err)
49
+ end
50
+
51
+ #
52
+ # Helper method to aid #fetch. Do not call directly.
53
+ #
54
+ def fill_array(rowdata)
55
+ return nil if rowdata.nil?
56
+ return rowdata.dup
57
+ end
58
+
59
+ def fetch
60
+ @current_row += 1
61
+ fill_array(@res_handle.fetch_row)
62
+ rescue MyError => err
63
+ error(err)
64
+ end
65
+
66
+ #
67
+ # See DBI::BaseStatement#fetch_scroll. These additional constants are also supported:
68
+ #
69
+ # * DBI::SQL_FETCH_PRIOR: Fetch the row previous to the current one.
70
+ # * DBI::SQL_FETCH_FIRST: Fetch the first row.
71
+ # * DBI::SQL_FETCH_ABSOLUTE: Fetch the row at the offset provided.
72
+ # * DBI::SQL_FETCH_RELATIVE: Fetch the row at the current point + offset.
73
+ #
74
+ def fetch_scroll(direction, offset)
75
+ case direction
76
+ when DBI::SQL_FETCH_NEXT
77
+ @current_row += 1
78
+ fill_array(@res_handle.fetch_row)
79
+ when DBI::SQL_FETCH_PRIOR
80
+ @res_handle.data_seek(@current_row - 1)
81
+ fill_array(@res_handle.fetch_row)
82
+ when DBI::SQL_FETCH_FIRST
83
+ @current_row = 1
84
+ @res_handle.data_seek(@current_row - 1)
85
+ fill_array(@res_handle.fetch_row)
86
+ when DBI::SQL_FETCH_LAST
87
+ @current_row = @res_handle.num_rows
88
+ @res_handle.data_seek(@current_row - 1)
89
+ fill_array(@res_handle.fetch_row)
90
+ when DBI::SQL_FETCH_ABSOLUTE
91
+ @current_row = offset + 1
92
+ @res_handle.data_seek(@current_row - 1)
93
+ fill_array(@res_handle.fetch_row)
94
+ when DBI::SQL_FETCH_RELATIVE
95
+ @current_row += offset + 1
96
+ @res_handle.data_seek(@current_row - 1)
97
+ fill_array(@res_handle.fetch_row)
98
+ else
99
+ raise NotSupportedError
100
+ end
101
+ #end
102
+ end
103
+
104
+ #
105
+ # See DBI::BaseStatement#column_info, and DBI::DBD::Mysql::Database#columns.
106
+ #
107
+ # This method provides all the attributes the +columns+ method
108
+ # provides, and a few others:
109
+ #
110
+ # * mysql_type: These correspond to constants in the Mysql::Types
111
+ # package, in the lower-level 'mysql' package.
112
+ # * mysql_type_name: A text representation of +mysql_type+.
113
+ # * mysql_length: The length of the column.
114
+ # * mysql_max_length: The max length of the column. FIXME DESCRIBE
115
+ # DIFFERENCE
116
+ # * mysql_flags: Internal MySQL flags on this column.
117
+ #
118
+ def column_info
119
+ retval = []
120
+
121
+ return [] if @res_handle.nil?
122
+
123
+ unique_key_flag = MysqlField.const_get(:UNIQUE_KEY_FLAG)
124
+ multiple_key_flag = MysqlField.const_get(:MULTIPLE_KEY_FLAG)
125
+ indexed = (unique_key_flag | multiple_key_flag)
126
+
127
+ # Note: Cannot get 'default' column attribute because MysqlField.def
128
+ # is set only by mysql_list_fields()
129
+
130
+ @res_handle.fetch_fields.each {|col|
131
+ mysql_type_name, dbi_type = Database::TYPE_MAP[col.type] rescue [nil, nil]
132
+ xopen_info = Database::MYSQL_to_XOPEN[mysql_type_name] ||
133
+ Database::MYSQL_to_XOPEN[nil]
134
+ sql_type = xopen_info[0]
135
+ type_name = DBI::SQL_TYPE_NAMES[sql_type]
136
+
137
+ retval << {
138
+ # Standard Ruby DBI column attributes
139
+ 'name' => col.name,
140
+ 'sql_type' => sql_type,
141
+ 'type_name' => type_name,
142
+ # XXX it seems mysql counts the literal decimal point when weighing in the "length".
143
+ 'precision' => type_name == "NUMERIC" ? col.length - 2 : col.length,
144
+ 'scale' => col.decimals,
145
+ 'nullable' => !col.is_not_null?,
146
+ 'indexed' => ((col.flags & indexed) != 0) ||
147
+ col.is_pri_key?,
148
+ 'primary' => col.is_pri_key?,
149
+ 'unique' => ((col.flags & unique_key_flag) != 0) ||
150
+ col.is_pri_key?,
151
+ # MySQL-specific attributes (signified by leading "mysql_")
152
+ 'mysql_type' => col.type,
153
+ 'mysql_type_name' => mysql_type_name,
154
+ 'mysql_length' => col.length,
155
+ 'mysql_max_length' => col.max_length,
156
+ 'mysql_flags' => col.flags
157
+ }
158
+
159
+ if retval[-1]['sql_type'] == DBI::SQL_TINYINT and retval[-1]['precision'] == 1
160
+ retval[-1]['dbi_type'] = DBI::Type::Boolean
161
+ elsif dbi_type
162
+ retval[-1]['dbi_type'] = dbi_type
163
+ end
164
+ }
165
+ retval
166
+ rescue MyError => err
167
+ error(err)
168
+ end
169
+
170
+ def rows
171
+ @rows
172
+ end
173
+
174
+ # def []=(attr, value)
175
+ # case attr
176
+ # when 'mysql_use_result'
177
+ # @attr['mysql_store_result'] = ! value
178
+ # @attr['mysql_use_result'] = value
179
+ # when 'mysql_store_result'
180
+ # @attr['mysql_use_result'] = ! value
181
+ # @attr['mysql_store_result'] = value
182
+ # else
183
+ # raise NotSupportedError
184
+ # end
185
+ # end
186
+
187
+ end # class Statement
188
+ end
@@ -0,0 +1,128 @@
1
+ #
2
+ # See DBI::BaseDatabase.
3
+ #
4
+ class DBI::DBD::ODBC::Database < DBI::BaseDatabase
5
+ def disconnect
6
+ @handle.rollback
7
+ @handle.disconnect
8
+ rescue DBI::DBD::ODBC::ODBCErr => err
9
+ raise DBI::DatabaseError.new(err.message)
10
+ end
11
+
12
+ def database_name
13
+ @handle.get_info('SQL_DATABASE_NAME')
14
+ end
15
+
16
+ def ping
17
+ @handle.connected?
18
+ end
19
+
20
+ #
21
+ # See DBI::BaseDatabase#columns. Additional Attributes:
22
+ #
23
+ # * nullable: boolean, true if NULLs are allowed in this column.
24
+ #
25
+ def columns(table)
26
+ cols = []
27
+
28
+ stmt = @handle.columns(table)
29
+ stmt.ignorecase = true
30
+
31
+ stmt.each_hash do |row|
32
+ info = Hash.new
33
+ cols << info
34
+
35
+ info['name'] = row['COLUMN_NAME']
36
+ info['type_name'] = row['TYPE_NAME']
37
+ info['sql_type'] = row['DATA_TYPE']
38
+ info['nullable'] =
39
+ case row['NULLABLE']
40
+ when 1
41
+ true
42
+ when 0
43
+ false
44
+ else
45
+ nil
46
+ end
47
+ info['precision'] = row['PRECISION']
48
+ info['scale'] = row['SCALE']
49
+ end
50
+
51
+ stmt.drop
52
+ cols
53
+ rescue DBI::DBD::ODBC::ODBCErr => err
54
+ raise DBI::DatabaseError.new(err.message)
55
+ end
56
+
57
+ def tables
58
+ stmt = @handle.tables
59
+ stmt.ignorecase = true
60
+ tabs = []
61
+ stmt.each_hash {|row|
62
+ tabs << row["TABLE_NAME"]
63
+ }
64
+ stmt.drop
65
+ tabs
66
+ rescue DBI::DBD::ODBC::ODBCErr => err
67
+ raise DBI::DatabaseError.new(err.message)
68
+ end
69
+
70
+ def prepare(statement)
71
+ DBI::DBD::ODBC::Statement.new(@handle.prepare(statement), statement)
72
+ rescue DBI::DBD::ODBC::ODBCErr => err
73
+ raise DBI::DatabaseError.new(err.message)
74
+ end
75
+
76
+ def do(statement, *bindvars)
77
+ @handle.do(statement, *bindvars)
78
+ rescue DBI::DBD::ODBC::ODBCErr => err
79
+ raise DBI::DatabaseError.new(err.message)
80
+ end
81
+
82
+ def execute(statement, *bindvars)
83
+ stmt = @handle.run(statement, *bindvars)
84
+ DBI::DBD::ODBC::Statement.new(stmt, statement)
85
+ rescue DBI::DBD::ODBC::ODBCErr => err
86
+ raise DBI::DatabaseError.new(err.message)
87
+ end
88
+
89
+ #
90
+ # Additional Attributes on the DatabaseHandle:
91
+ #
92
+ # * AutoCommit: force a commit after each statement execution.
93
+ # * odbc_ignorecase: Be case-insensitive in operations.
94
+ # * odbc_timeout: Return after a certain time regardless of whether the operation returned anything.
95
+ #
96
+ def []=(attr, value)
97
+ case attr
98
+ when 'AutoCommit'
99
+ @handle.autocommit(value)
100
+ when 'odbc_ignorecase'
101
+ @handle.ignorecase(value)
102
+ when 'odbc_timeout'
103
+ @handle.timeout(value)
104
+ else
105
+ if attr =~ /^odbc_/ or attr != /_/
106
+ raise DBI::NotSupportedError, "Option '#{attr}' not supported"
107
+ else # option for some other driver - quitly ignore
108
+ return
109
+ end
110
+ end
111
+ @attr[attr] = value
112
+ rescue DBI::DBD::ODBC::ODBCErr => err
113
+ raise DBI::DatabaseError.new(err.message)
114
+ end
115
+
116
+ def commit
117
+ @handle.commit
118
+ rescue DBI::DBD::ODBC::ODBCErr => err
119
+ raise DBI::DatabaseError.new(err.message)
120
+ end
121
+
122
+ def rollback
123
+ @handle.rollback
124
+ rescue DBI::DBD::ODBC::ODBCErr => err
125
+ raise DBI::DatabaseError.new(err.message)
126
+ end
127
+
128
+ end # class Database
@@ -0,0 +1,38 @@
1
+ #
2
+ # See DBI::BaseDriver
3
+ #
4
+ class DBI::DBD::ODBC::Driver < DBI::BaseDriver
5
+ def initialize
6
+ super("0.4.0")
7
+ end
8
+
9
+ def data_sources
10
+ ::ODBC.datasources.collect {|dsn| "dbi:ODBC:" + dsn.name }
11
+ rescue DBI::DBD::ODBC::ODBCErr => err
12
+ raise DBI::DatabaseError.new(err.message)
13
+ end
14
+
15
+ def connect(dbname, user, auth, attr)
16
+ driver_attrs = dbname.split(';')
17
+
18
+ if driver_attrs.size > 1
19
+ # DNS-less connection
20
+ drv = ::ODBC::Driver.new
21
+ drv.name = 'Driver1'
22
+ driver_attrs.each do |param|
23
+ pv = param.split('=')
24
+ next if pv.size < 2
25
+ drv.attrs[pv[0]] = pv[1]
26
+ end
27
+ db = ::ODBC::Database.new
28
+ handle = db.drvconnect(drv)
29
+ else
30
+ # DNS given
31
+ handle = ::ODBC.connect(dbname, user, auth)
32
+ end
33
+
34
+ return DBI::DBD::ODBC::Database.new(handle, attr)
35
+ rescue DBI::DBD::ODBC::ODBCErr => err
36
+ raise DBI::DatabaseError.new(err.message)
37
+ end
38
+ end
@@ -0,0 +1,137 @@
1
+ #
2
+ # See DBI::BaseStatement.
3
+ #
4
+ class DBI::DBD::ODBC::Statement < DBI::BaseStatement
5
+ def initialize(handle, statement)
6
+ @statement = statement
7
+ @handle = handle
8
+ @params = []
9
+ @arr = []
10
+ end
11
+
12
+ #
13
+ # See DBI::BaseStatement#bind_param. This method will also raise
14
+ # DBI::InterfaceError if +param+ is not a Fixnum, to prevent incorrect
15
+ # binding.
16
+ #
17
+ def bind_param(param, value, attribs)
18
+ raise DBI::InterfaceError, "only ? parameters supported" unless param.is_a? Fixnum
19
+ @params[param-1] = value
20
+ end
21
+
22
+ def execute
23
+ @handle.execute(*@params)
24
+ rescue DBI::DBD::ODBC::ODBCErr => err
25
+ raise DBI::DatabaseError.new(err.message)
26
+ end
27
+
28
+ def finish
29
+ @handle.drop
30
+ rescue DBI::DBD::ODBC::ODBCErr => err
31
+ raise DBI::DatabaseError.new(err.message)
32
+ end
33
+
34
+ def cancel
35
+ @handle.cancel
36
+ rescue DBI::DBD::ODBC::ODBCErr => err
37
+ raise DBI::DatabaseError.new(err.message)
38
+ end
39
+
40
+ def fetch
41
+ convert_row(@handle.fetch)
42
+ rescue DBI::DBD::ODBC::ODBCErr => err
43
+ raise DBI::DatabaseError.new(err.message)
44
+ end
45
+
46
+ #
47
+ # See DBI::BaseStatement#fetch_scroll.
48
+ #
49
+ # ODBC has a native version of this method and the constnats in the ODBC
50
+ # driver themselves are supported. If you'd prefer to use DBI constants
51
+ # (recommended), you can use these which map to the ODBC functionality:
52
+ #
53
+ # * DBI::SQL_FETCH_FIRST
54
+ # * DBI::SQL_FETCH_LAST
55
+ # * DBI::SQL_FETCH_NEXT
56
+ # * DBI::SQL_FETCH_PRIOR
57
+ # * DBI::SQL_FETCH_ABSOLUTE
58
+ # * DBI::SQL_FETCH_RELATIVE
59
+ #
60
+ def fetch_scroll(direction, offset)
61
+ direction = case direction
62
+ when DBI::SQL_FETCH_FIRST then ::ODBC::SQL_FETCH_FIRST
63
+ when DBI::SQL_FETCH_LAST then ::ODBC::SQL_FETCH_LAST
64
+ when DBI::SQL_FETCH_NEXT then ::ODBC::SQL_FETCH_NEXT
65
+ when DBI::SQL_FETCH_PRIOR then ::ODBC::SQL_FETCH_PRIOR
66
+ when DBI::SQL_FETCH_ABSOLUTE then ::ODBC::SQL_FETCH_ABSOLUTE
67
+ when DBI::SQL_FETCH_RELATIVE then ::ODBC::SQL_FETCH_RELATIVE
68
+ else
69
+ direction
70
+ end
71
+
72
+ convert_row(@handle.fetch_scroll(direction, offset))
73
+ rescue DBI::DBD::ODBC::ODBCErr => err
74
+ raise DBI::DatabaseError.new(err.message)
75
+ end
76
+
77
+ #
78
+ # See DBI::BaseStatement#column_info. These additional attributes are also
79
+ # supported:
80
+ #
81
+ # * table: the table this column came from, if available.
82
+ # * nullable: boolean, true if NULL is accepted as a value in this column.
83
+ # * searchable: FIXME DOCUMENT
84
+ # * length: FIXME DOCUMENT
85
+ # * unsigned: For numeric columns, whether or not the result value is signed.
86
+ #
87
+ def column_info
88
+ info = []
89
+ @handle.columns(true).each do |col|
90
+ info << {
91
+ 'name' => col.name,
92
+ 'table' => col.table,
93
+ 'nullable' => col.nullable,
94
+ 'searchable' => col.searchable,
95
+ 'precision' => col.precision,
96
+ 'scale' => col.scale,
97
+ 'sql_type' => col.type,
98
+ 'type_name' => DBI::SQL_TYPE_NAMES[col.type],
99
+ 'length' => col.length,
100
+ 'unsigned' => col.unsigned
101
+ }
102
+ end
103
+ info
104
+ rescue DBI::DBD::ODBC::ODBCErr => err
105
+ raise DBI::DatabaseError.new(err.message)
106
+ end
107
+
108
+ #
109
+ # See DBI::BaseStatement#rows.
110
+ #
111
+ # For queries which DBI::SQL.query? returns true, will explicitly return 0.
112
+ # Otherwise, it will return the row processed count.
113
+ #
114
+ def rows
115
+ return 0 if DBI::SQL.query?(@statement)
116
+ return @handle.nrows
117
+ rescue DBI::DBD::ODBC::ODBCErr => err
118
+ raise DBI::DatabaseError.new(err.message)
119
+ end
120
+
121
+ private # -----------------------------------
122
+
123
+ # convert the ODBC datatypes to DBI datatypes
124
+ def convert_row(row)
125
+ return nil if row.nil?
126
+ row.collect do |col|
127
+ case col
128
+ when nil
129
+ nil
130
+ when ODBC::TimeStamp
131
+ DBI::Type::Timestamp.create col.year, col.month, col.day, col.hour, col.minute, col.second
132
+ else
133
+ col.to_s
134
+ end
135
+ end
136
+ end
137
+ end