jdbc-helper 0.1.2

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.
@@ -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
+