jdbc-helper 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,141 @@
1
+ # encoding: UTF-8
2
+ # Junegunn Choi (junegunn.c@gmail.com)
3
+
4
+ module JDBCHelper
5
+ # An encapsulation of Java PreparedStatment object.
6
+ # Used to execute parameterized queries efficiently.
7
+ # Has a very similar set of interface as JDBCHelper::Connection.
8
+ #
9
+ # e.g.
10
+ #
11
+ # pstmt = conn.prepare("SELECT * FROM T WHERE a = ? and b = ?")
12
+ # rows = pstmt.query(10, 20)
13
+ # enum = pstmt.enumerate(10, 20)
14
+ #
15
+ class Connection
16
+ class PreparedStatement
17
+ attr_reader :sql
18
+
19
+ # Returns the encapsulated JDBC PreparedStatement object.
20
+ def java_obj
21
+ @pstmt
22
+ end
23
+
24
+ # Returns the number of parameters required
25
+ def parameter_count
26
+ @pmd ||= @pstmt.get_parameter_meta_data
27
+ @pmd.get_parameter_count
28
+ end
29
+
30
+ def update(*params)
31
+ check_closed
32
+
33
+ set_params(params)
34
+ measure(:p_update) { @pstmt.execute_update }
35
+ end
36
+
37
+ def query(*params, &blk)
38
+ check_closed
39
+
40
+ set_params(params)
41
+ # sorry, ignoring privacy
42
+ @conn.send(:process_and_close_rset,
43
+ measure(:p_query) { @pstmt.execute_query }, &blk)
44
+ end
45
+
46
+ def enumerate(*params, &blk)
47
+ check_closed
48
+
49
+ return query(*params, &blk) if block_given?
50
+
51
+ set_params(params)
52
+ ResultSetEnumerator.new(measure(:p_query) { @pstmt.execute_query })
53
+ end
54
+
55
+ # batch interface
56
+ def add_batch(*params)
57
+ check_closed
58
+
59
+ set_params(params)
60
+ @pstmt.add_batch
61
+ end
62
+ def execute_batch
63
+ check_closed
64
+
65
+ measure(:p_execute_batch) {
66
+ @pstmt.executeBatch
67
+ }
68
+ end
69
+ def clear_batch
70
+ check_closed
71
+
72
+ @pstmt.clear_batch
73
+ end
74
+
75
+ # Gives the JDBC driver a hint of the number of rows to fetch from the database by a single interaction.
76
+ # This is only a hint. It may no effect at all.
77
+ def set_fetch_size(fsz)
78
+ check_closed
79
+
80
+ @pstmt.set_fetch_size fsz
81
+ end
82
+
83
+ def close
84
+ return if closed?
85
+ @pstmt.close
86
+ @pstmts.delete @sql
87
+ @pstmt = @pstmts = nil
88
+ end
89
+
90
+ def closed?
91
+ @pstmt.nil?
92
+ end
93
+ private
94
+ def initialize(conn, pstmts, sql, pstmt) # :nodoc:
95
+ @conn = conn
96
+ @pstmts = pstmts
97
+ @sql = sql
98
+ @pstmt = pstmt
99
+ end
100
+
101
+ def set_params(params) # :nodoc:
102
+ idx = 0
103
+ params.each do | param |
104
+ if param.nil?
105
+ @pstmt.set_null(idx += 1, java.sql.Types::NULL)
106
+ elsif setter = SETTER_MAP[param.class.to_s]
107
+ if setter == :setBinaryStream
108
+ @pstmt.send(setter, idx += 1,
109
+ param.getBinaryStream, param.length)
110
+ else
111
+ @pstmt.send(setter, idx += 1, param)
112
+ end
113
+ else
114
+ @pstmt.set_string(idx += 1, param.to_s)
115
+ end
116
+ end
117
+ end
118
+
119
+ def measure(type, &blk) # :nodoc:
120
+ @conn.send(:measure, type, &blk)
121
+ end
122
+
123
+ def check_closed
124
+ raise RuntimeError.new("Prepared statement already closed") if closed?
125
+ end
126
+
127
+ SETTER_MAP =
128
+ {
129
+ 'java.sql.Date' => :setDate,
130
+ 'java.sql.Time' => :setTime,
131
+ 'java.sql.Timestamp' => :setTimestamp,
132
+ 'java.sql.Blob' => :setBinaryStream,
133
+
134
+ 'com.mysql.jdbc.Blob' => :setBinaryStream
135
+ # FIXME-MORE
136
+ } # :nodoc:
137
+
138
+ end#PreparedStatment
139
+ end#Connection
140
+ end#JDBCHelper
141
+
@@ -0,0 +1,100 @@
1
+ # encoding: UTF-8
2
+ # Junegunn Choi (junegunn.c@gmail.com)
3
+
4
+ module JDBCHelper
5
+ # Class for enumerating query results
6
+ # Automatically closed after used. When not used, you must close it explicitly by calling "close".
7
+ class Connection
8
+ class ResultSetEnumerator
9
+ include Enumerable
10
+
11
+ def each
12
+ return if closed?
13
+
14
+ count = -1
15
+ begin
16
+ while @rset.next
17
+ yield Connection::Row.new(
18
+ @col_labels,
19
+ (1..@num_col).map { | i |
20
+ v = @rset.send(GETTER_MAP.fetch(@cols_meta[i-1], :get_string), i)
21
+ @rset.was_null ? nil : v
22
+ },
23
+ count += 1)
24
+ end
25
+ ensure
26
+ close
27
+ end
28
+ end
29
+
30
+ def close
31
+ return if closed?
32
+
33
+ @rset.close
34
+ @close_callback.call if @close_callback
35
+ @closed = true
36
+ end
37
+
38
+ def closed?
39
+ @closed
40
+ end
41
+
42
+ private
43
+ def initialize(rset, &close_callback) # :nodoc:
44
+ unless rset.respond_to? :get_meta_data
45
+ rset.close if rset
46
+ @closed = true
47
+ return
48
+ end
49
+
50
+ @close_callback = close_callback
51
+ @rset = rset
52
+ @rsmd = @rset.get_meta_data
53
+ @num_col = @rsmd.get_column_count
54
+ @cols_meta = []
55
+ @col_labels = []
56
+ (1..@num_col).each do | i |
57
+ @cols_meta << @rsmd.get_column_type(i)
58
+ @col_labels << @rsmd.get_column_label(i)
59
+ end
60
+
61
+ @closed = false
62
+ end
63
+
64
+ GETTER_MAP =
65
+ {
66
+ java.sql.Types::TINYINT => :get_int,
67
+ java.sql.Types::SMALLINT => :get_int,
68
+ java.sql.Types::INTEGER => :get_int,
69
+ java.sql.Types::BIGINT => :get_long,
70
+
71
+ java.sql.Types::CHAR => :get_string,
72
+ java.sql.Types::VARCHAR => :get_string,
73
+ java.sql.Types::LONGVARCHAR => :get_string,
74
+ (java.sql.Types::NCHAR rescue nil) => :get_string,
75
+ (java.sql.Types::NVARCHAR rescue nil) => :get_string,
76
+ (java.sql.Types::LONGNVARCHAR rescue nil) => :get_blob, # FIXME: MySQL
77
+ java.sql.Types::BINARY => :get_string,
78
+ java.sql.Types::VARBINARY => :get_string,
79
+ java.sql.Types::LONGVARBINARY => :get_blob, # FIXME: MySQL
80
+
81
+ java.sql.Types::REAL => :get_double,
82
+ java.sql.Types::FLOAT => :get_float,
83
+ java.sql.Types::DOUBLE => :get_double,
84
+ java.sql.Types::NUMERIC => :get_string, # FIXME
85
+ java.sql.Types::DECIMAL => :get_string, # FIXME
86
+
87
+ java.sql.Types::DATE => :get_date,
88
+ java.sql.Types::TIME => :get_time,
89
+ java.sql.Types::TIMESTAMP => :get_timestamp,
90
+
91
+ java.sql.Types::BLOB => :get_blob,
92
+ java.sql.Types::CLOB => :get_string,
93
+ (java.sql.Types::NCLOB rescue nil) => :get_string,
94
+
95
+ java.sql.Types::BOOLEAN => :get_boolean
96
+ } # :nodoc:
97
+ end#ResultSetEnumerator
98
+ end#Connection
99
+ end#JDBCHelper
100
+
@@ -0,0 +1,110 @@
1
+ # encoding: UTF-8
2
+ # Junegunn Choi (junegunn.c@gmail.com)
3
+
4
+ module JDBCHelper
5
+ # The result of database queries.
6
+ # Designed to be very flexible on its interface.
7
+ # You can access it like an array, a hash or an ORM object.
8
+ #
9
+ # e.g.
10
+ #
11
+ # conn.query('SELECT a, b, c FROM t') do | row |
12
+ # puts row.a
13
+ # puts row[1]
14
+ # puts row['c']
15
+ #
16
+ # row.each do | value |
17
+ # # ...
18
+ # end
19
+ #
20
+ # labels = row.labels # equal to `row.keys`
21
+ # values = row.to_a # equal to `row.values`
22
+ #
23
+ # puts row.rownum
24
+ # end
25
+ #
26
+ class Connection
27
+ class Row
28
+ attr_reader :labels, :values, :rownum
29
+ alias_method :keys, :labels
30
+
31
+ include Enumerable
32
+
33
+ def [](idx)
34
+ if idx.is_a? Fixnum
35
+ raise RangeError.new("Index out of bound") if idx >= @values.length
36
+ @values[idx]
37
+ else
38
+ # case-insensitive, assuming no duplicate labels
39
+ vidx = @labels_d.index(idx.downcase) or
40
+ raise NameError.new("Unknown column label: #{idx}")
41
+ @values[vidx]
42
+ end
43
+ end
44
+
45
+ def each(&blk)
46
+ @values.each { | v | yield v }
47
+
48
+ # @labels.each_with_index do | label, idx |
49
+ # yield label, @values[idx]
50
+ # end
51
+ end
52
+
53
+ def inspect
54
+ strs = []
55
+ @labels.each do | col |
56
+ strs << "#{col}: #{self[col] || '(null)'}"
57
+ end
58
+ '[' + strs.join(', ') + ']'
59
+ end
60
+
61
+ def to_s
62
+ @values.to_s
63
+ end
64
+
65
+ def to_a
66
+ @values
67
+ end
68
+
69
+ def join(sep = $,)
70
+ to_a.join(sep)
71
+ end
72
+
73
+ def eql?(other)
74
+ self.hash == other.hash
75
+ end
76
+
77
+ def hash # :nodoc:
78
+ @labels.zip(@values).sort.hash
79
+ end
80
+
81
+ alias :== :eql?
82
+
83
+ private
84
+ def initialize(col_labels, values, rownum) # :nodoc:
85
+ @labels = col_labels
86
+ @labels_d = col_labels.map { | l | l.downcase }
87
+ @values = values
88
+ @rownum = rownum
89
+ end
90
+
91
+ def method_missing(symb, *args)
92
+ if vidx = @labels_d.index(symb.to_s.downcase)
93
+ @values[vidx]
94
+ elsif @values.respond_to?(symb)
95
+ @values.send(symb, *args)
96
+ else
97
+ raise NameError.new("Unknown method: #{symb}")
98
+ end
99
+ end
100
+
101
+ # Remove dangerous default methods
102
+ # excluding :object_id, :hash
103
+ [:id, :tap, :gem, :display, :class, :method, :methods, :trust].
104
+ select { | s | method_defined? s }.each do | m |
105
+ undef_method m
106
+ end
107
+ end#Row
108
+ end#Connection
109
+ end#JDBCHelper
110
+
@@ -0,0 +1,61 @@
1
+ # encoding: UTF-8
2
+ # Junegunn Choi (junegunn.c@gmail.com)
3
+
4
+ module JDBCHelper
5
+ # Internal implementation for supporting query nesting.
6
+ # Not thread-safe. Well, sharing a JDBC connection between threads is not the best idea anyway.
7
+ class Connection
8
+ class StatementPool # :nodoc:
9
+ def initialize(conn, max_size = 20)
10
+ @conn = conn
11
+ @max_size = max_size # TODO
12
+ @free = []
13
+ @occupied = []
14
+ end
15
+
16
+ def with
17
+ begin
18
+ yield stmt = take
19
+ ensure
20
+ give stmt
21
+ end
22
+ end
23
+
24
+ def take
25
+ if @free.empty?
26
+ raise Exception.new("Nesting too deep") if
27
+ @occupied.length >= @max_size
28
+ @occupied << nstmt = @conn.send(:create_statement)
29
+ nstmt
30
+ else
31
+ stmt = @free.pop
32
+ @occupied << stmt
33
+ stmt
34
+ end
35
+ end
36
+
37
+ def give(stmt)
38
+ raise Exception.new("Not my statement") unless
39
+ @occupied.include? stmt
40
+
41
+ @occupied.delete stmt
42
+ @free << stmt
43
+ end
44
+
45
+ def close
46
+ (@free + @occupied).each do | stmt |
47
+ stmt.close
48
+ end
49
+ @conn = nil
50
+ @free = []
51
+ @occupied = []
52
+ end
53
+
54
+ def each
55
+ (@free + @occupied).each do | stmt |
56
+ yield stmt
57
+ end
58
+ end
59
+ end#StatementPool
60
+ end#Connection
61
+ end#JDBCHelper
@@ -0,0 +1,6 @@
1
+ # encoding: UTF-8
2
+ # Junegunn Choi (junegunn.c@gmail.com)
3
+
4
+ require 'jdbc-helper/connector/oracle_connector'
5
+ require 'jdbc-helper/connector/mysql_connector'
6
+
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+ # Junegunn Choi (junegunn.c@gmail.com)
3
+
4
+ module JDBCHelper
5
+ # Shortcut connector for MySQL
6
+ class MySQLConnector
7
+ include Constants
8
+ include Constants::Connector
9
+
10
+ def self.connect(host, user, password, db,
11
+ timeout = DEFAULT_LOGIN_TIMEOUT,
12
+ extra_params = DEFAULT_PARAMETERS[:mysql])
13
+
14
+ if extra_params && extra_params.is_a?(Hash) == false
15
+ raise ArgumentError.new('extra_params must be a hash')
16
+ end
17
+
18
+ Connection.new(
19
+ (extra_params || {}).merge(
20
+ :driver => JDBC_DRIVER[:mysql],
21
+ :url => "jdbc:mysql://#{host}/#{db}",
22
+ :user => user,
23
+ :password => password,
24
+ :timeout => timeout))
25
+ end
26
+ end#MySQLConnector
27
+ end#JDBCHelper
28
+