jeremyevans-postgres-pr 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,34 @@
1
+ $LOAD_PATH.unshift "../lib"
2
+ require 'postgres-pr/message'
3
+ require 'socket'
4
+ include PostgresPR
5
+
6
+ s = UNIXSocket.new(ARGV.shift || "/tmp/.s.PGSQL.5432")
7
+
8
+ msg = StartupMessage.new(196608, "user" => "mneumann", "database" => "mneumann")
9
+ s << msg.dump
10
+
11
+ Thread.start(s) { |s|
12
+ sleep 2
13
+ s << Query.new("drop table test").dump
14
+ s << Query.new("create table test (i int, v varchar(100))").dump
15
+ s << Parse.new("insert into test (i, v) values ($1, $2)", "blah").dump
16
+ s << Query.new("EXECUTE blah(1, 'hallo')").dump
17
+
18
+ while not (line = gets.chomp).empty?
19
+ s << Query.new(line).dump
20
+ end
21
+ exit
22
+ }
23
+
24
+ loop do
25
+ msg = Message.read(s)
26
+ p msg
27
+
28
+ case msg
29
+ when AuthentificationOk
30
+ p "OK"
31
+ when ErrorResponse
32
+ p "FAILED"
33
+ end
34
+ end
@@ -0,0 +1,50 @@
1
+ $LOAD_PATH.unshift '../../lib'
2
+ require 'rubygems'
3
+ require 'og'
4
+ require 'glue/logger'
5
+
6
+ $DBG = true
7
+
8
+ class User
9
+ end
10
+
11
+ class Comment
12
+ prop_accessor :body, String
13
+ belongs_to :user, User
14
+ end
15
+
16
+ class User
17
+ prop_accessor :name, String
18
+ has_many :comments, Comment
19
+ end
20
+
21
+ if __FILE__ == $0
22
+ config = {
23
+ :address => "localhost",
24
+ :database => "mneumann",
25
+ :backend => "psql",
26
+ :user => "mneumann",
27
+ :password => "",
28
+ :connection_count => 1
29
+ }
30
+ $log = Logger.new(STDERR)
31
+ $og = Og::Database.new(config)
32
+
33
+ $og.get_connection
34
+
35
+ u1 = User.new
36
+ u1.name = "Michael Neumann"
37
+ u1.save!
38
+
39
+ u2 = User.new
40
+ u2.name = "John User"
41
+ u2.save!
42
+
43
+ c1 = Comment.new
44
+ c1.body = "og test"
45
+ c1.user = u1
46
+ c1.save!
47
+
48
+ p User.all
49
+ p Comment.all
50
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift "../lib"
2
+ require 'postgres-pr/message'
3
+ require 'socket'
4
+ include PostgresPR
5
+
6
+ s = UNIXServer.open(ARGV.shift || raise).accept
7
+ startup = true
8
+ loop do
9
+ msg = Message.read(s, startup)
10
+ p msg
11
+ startup = false
12
+ end
@@ -0,0 +1,18 @@
1
+ $LOAD_PATH.unshift '../lib'
2
+ require 'postgres-pr/connection'
3
+
4
+ conn = PostgresPR::Connection.new('mneumann', 'mneumann', nil, 'unix:/var/run/postgresql/.s.PGSQL.5432')
5
+ p conn.query("DROP TABLE test") rescue nil
6
+ p conn.query("CREATE TABLE test (a VARCHAR(100))")
7
+ p conn.query("INSERT INTO test VALUES ('hallo')")
8
+ p conn.query("INSERT INTO test VALUES ('leute')")
9
+ conn.query("COMMIT")
10
+
11
+ conn.query("BEGIN")
12
+ 10000.times do |i|
13
+ p i
14
+ conn.query("INSERT INTO test VALUES ('#{i}')")
15
+ end
16
+ conn.query("COMMIT")
17
+
18
+ p conn.query("SELECT * FROM test")
@@ -0,0 +1,120 @@
1
+ require 'byteorder'
2
+
3
+ # This mixin solely depends on method read(n), which must be defined
4
+ # in the class/module where you mixin this module.
5
+ module BinaryReaderMixin
6
+
7
+ # == 8 bit
8
+
9
+ # no byteorder for 8 bit!
10
+
11
+ def read_word8
12
+ ru(1, 'C')
13
+ end
14
+
15
+ def read_int8
16
+ ru(1, 'c')
17
+ end
18
+
19
+ alias read_byte read_word8
20
+
21
+ # == 16 bit
22
+
23
+ # === Unsigned
24
+
25
+ def read_word16_native
26
+ ru(2, 'S')
27
+ end
28
+
29
+ def read_word16_little
30
+ ru(2, 'v')
31
+ end
32
+
33
+ def read_word16_big
34
+ ru(2, 'n')
35
+ end
36
+
37
+ # === Signed
38
+
39
+ def read_int16_native
40
+ ru(2, 's')
41
+ end
42
+
43
+ def read_int16_little
44
+ # swap bytes if native=big (but we want little)
45
+ ru_swap(2, 's', ByteOrder::Big)
46
+ end
47
+
48
+ def read_int16_big
49
+ # swap bytes if native=little (but we want big)
50
+ ru_swap(2, 's', ByteOrder::Little)
51
+ end
52
+
53
+ # == 32 bit
54
+
55
+ # === Unsigned
56
+
57
+ def read_word32_native
58
+ ru(4, 'L')
59
+ end
60
+
61
+ def read_word32_little
62
+ ru(4, 'V')
63
+ end
64
+
65
+ def read_word32_big
66
+ ru(4, 'N')
67
+ end
68
+
69
+ # === Signed
70
+
71
+ def read_int32_native
72
+ ru(4, 'l')
73
+ end
74
+
75
+ def read_int32_little
76
+ # swap bytes if native=big (but we want little)
77
+ ru_swap(4, 'l', ByteOrder::Big)
78
+ end
79
+
80
+ def read_int32_big
81
+ # swap bytes if native=little (but we want big)
82
+ ru_swap(4, 'l', ByteOrder::Little)
83
+ end
84
+
85
+ # == Aliases
86
+
87
+ alias read_uint8 read_word8
88
+
89
+ # add some short-cut functions
90
+ %w(word16 int16 word32 int32).each do |typ|
91
+ alias_method "read_#{typ}_network", "read_#{typ}_big"
92
+ end
93
+
94
+ {:word16 => :uint16, :word32 => :uint32}.each do |old, new|
95
+ ['_native', '_little', '_big', '_network'].each do |bo|
96
+ alias_method "read_#{new}#{bo}", "read_#{old}#{bo}"
97
+ end
98
+ end
99
+
100
+ # read exactly n characters, otherwise raise an exception.
101
+ def readn(n)
102
+ str = read(n)
103
+ raise "couldn't read #{n} characters" if str.nil? or str.size != n
104
+ str
105
+ end
106
+
107
+ private
108
+
109
+ # shortcut method for readn+unpack
110
+ def ru(size, template)
111
+ readn(size).unpack(template).first
112
+ end
113
+
114
+ # same as method +ru+, but swap bytes if native byteorder == _byteorder_
115
+ def ru_swap(size, template, byteorder)
116
+ str = readn(size)
117
+ str.reverse! if ByteOrder.byteorder == byteorder
118
+ str.unpack(template).first
119
+ end
120
+ end
@@ -0,0 +1,100 @@
1
+ require 'byteorder'
2
+
3
+ module BinaryWriterMixin
4
+
5
+ # == 8 bit
6
+
7
+ # no byteorder for 8 bit!
8
+
9
+ def write_word8(val)
10
+ pw(val, 'C')
11
+ end
12
+
13
+ def write_int8(val)
14
+ pw(val, 'c')
15
+ end
16
+
17
+ alias write_byte write_word8
18
+
19
+ # == 16 bit
20
+
21
+ # === Unsigned
22
+
23
+ def write_word16_native(val)
24
+ pw(val, 'S')
25
+ end
26
+
27
+ def write_word16_little(val)
28
+ str = [val].pack('S')
29
+ str.reverse! if ByteOrder.network? # swap bytes as native=network (and we want little)
30
+ write(str)
31
+ end
32
+
33
+ def write_word16_network(val)
34
+ str = [val].pack('S')
35
+ str.reverse! if ByteOrder.little? # swap bytes as native=little (and we want network)
36
+ write(str)
37
+ end
38
+
39
+ # === Signed
40
+
41
+ def write_int16_native(val)
42
+ pw(val, 's')
43
+ end
44
+
45
+ def write_int16_little(val)
46
+ pw(val, 'v')
47
+ end
48
+
49
+ def write_int16_network(val)
50
+ pw(val, 'n')
51
+ end
52
+
53
+ # == 32 bit
54
+
55
+ # === Unsigned
56
+
57
+ def write_word32_native(val)
58
+ pw(val, 'L')
59
+ end
60
+
61
+ def write_word32_little(val)
62
+ str = [val].pack('L')
63
+ str.reverse! if ByteOrder.network? # swap bytes as native=network (and we want little)
64
+ write(str)
65
+ end
66
+
67
+ def write_word32_network(val)
68
+ str = [val].pack('L')
69
+ str.reverse! if ByteOrder.little? # swap bytes as native=little (and we want network)
70
+ write(str)
71
+ end
72
+
73
+ # === Signed
74
+
75
+ def write_int32_native(val)
76
+ pw(val, 'l')
77
+ end
78
+
79
+ def write_int32_little(val)
80
+ pw(val, 'V')
81
+ end
82
+
83
+ def write_int32_network(val)
84
+ pw(val, 'N')
85
+ end
86
+
87
+ # add some short-cut functions
88
+ %w(word16 int16 word32 int32).each do |typ|
89
+ alias_method "write_#{typ}_big", "write_#{typ}_network"
90
+ end
91
+
92
+ # == Other methods
93
+
94
+ private
95
+
96
+ # shortcut for pack and write
97
+ def pw(val, template)
98
+ write([val].pack(template))
99
+ end
100
+ end
data/lib/buffer.rb ADDED
@@ -0,0 +1,97 @@
1
+ require 'binary_writer'
2
+ require 'binary_reader'
3
+
4
+ # Fixed size buffer.
5
+ class Buffer
6
+
7
+ class Error < RuntimeError; end
8
+ class EOF < Error; end
9
+
10
+ def self.from_string(str)
11
+ new(str)
12
+ end
13
+
14
+ def self.of_size(size)
15
+ raise ArgumentError if size < 0
16
+ new('#' * size)
17
+ end
18
+
19
+ def initialize(content)
20
+ @size = content.size
21
+ @content = content
22
+ @position = 0
23
+ end
24
+
25
+ def size
26
+ @size
27
+ end
28
+
29
+ def position
30
+ @position
31
+ end
32
+
33
+ def position=(new_pos)
34
+ raise ArgumentError if new_pos < 0 or new_pos > @size
35
+ @position = new_pos
36
+ end
37
+
38
+ def at_end?
39
+ @position == @size
40
+ end
41
+
42
+ def content
43
+ @content
44
+ end
45
+
46
+ def read(n)
47
+ raise EOF, 'cannot read beyond the end of buffer' if @position + n > @size
48
+ str = @content[@position, n]
49
+ @position += n
50
+ str
51
+ end
52
+
53
+ def write(str)
54
+ sz = str.size
55
+ raise EOF, 'cannot write beyond the end of buffer' if @position + sz > @size
56
+ @content[@position, sz] = str
57
+ @position += sz
58
+ self
59
+ end
60
+
61
+ def copy_from_stream(stream, n)
62
+ raise ArgumentError if n < 0
63
+ while n > 0
64
+ str = stream.read(n)
65
+ write(str)
66
+ n -= str.size
67
+ end
68
+ raise if n < 0
69
+ end
70
+
71
+ NUL = "\000"
72
+
73
+ def write_cstring(cstr)
74
+ raise ArgumentError, "Invalid Ruby/cstring" if cstr.include?(NUL)
75
+ write(cstr)
76
+ write(NUL)
77
+ end
78
+
79
+ # returns a Ruby string without the trailing NUL character
80
+ def read_cstring
81
+ nul_pos = @content.index(NUL, @position)
82
+ raise Error, "no cstring found!" unless nul_pos
83
+
84
+ sz = nul_pos - @position
85
+ str = @content[@position, sz]
86
+ @position += sz + 1
87
+ return str
88
+ end
89
+
90
+ # read till the end of the buffer
91
+ def read_rest
92
+ read(self.size-@position)
93
+ end
94
+
95
+ include BinaryWriterMixin
96
+ include BinaryReaderMixin
97
+ end
data/lib/byteorder.rb ADDED
@@ -0,0 +1,32 @@
1
+ module ByteOrder
2
+ Native = :Native
3
+ BigEndian = Big = Network = :BigEndian
4
+ LittleEndian = Little = :LittleEndian
5
+
6
+ # examines the byte order of the underlying machine
7
+ def byte_order
8
+ if [0x12345678].pack("L") == "\x12\x34\x56\x78"
9
+ BigEndian
10
+ else
11
+ LittleEndian
12
+ end
13
+ end
14
+
15
+ alias byteorder byte_order
16
+
17
+ def little_endian?
18
+ byte_order == LittleEndian
19
+ end
20
+
21
+ def big_endian?
22
+ byte_order == BigEndian
23
+ end
24
+
25
+ alias little? little_endian?
26
+ alias big? big_endian?
27
+ alias network? big_endian?
28
+
29
+ module_function :byte_order, :byteorder
30
+ module_function :little_endian?, :little?
31
+ module_function :big_endian?, :big?, :network?
32
+ end
@@ -0,0 +1,174 @@
1
+ #
2
+ # Author:: Michael Neumann
3
+ # Copyright:: (c) 2005 by Michael Neumann
4
+ # License:: Same as Ruby's or BSD
5
+ #
6
+
7
+ require 'postgres-pr/message'
8
+ require 'postgres-pr/version'
9
+ require 'uri'
10
+ require 'socket'
11
+
12
+ module PostgresPR
13
+
14
+ PROTO_VERSION = 3 << 16 #196608
15
+
16
+ class Connection
17
+
18
+ # A block which is called with the NoticeResponse object as parameter.
19
+ attr_accessor :notice_processor
20
+
21
+ #
22
+ # Returns one of the following statuses:
23
+ #
24
+ # PQTRANS_IDLE = 0 (connection idle)
25
+ # PQTRANS_INTRANS = 2 (idle, within transaction block)
26
+ # PQTRANS_INERROR = 3 (idle, within failed transaction)
27
+ # PQTRANS_UNKNOWN = 4 (cannot determine status)
28
+ #
29
+ # Not yet implemented is:
30
+ #
31
+ # PQTRANS_ACTIVE = 1 (command in progress)
32
+ #
33
+ def transaction_status
34
+ case @transaction_status
35
+ when ?I
36
+ 0
37
+ when ?T
38
+ 2
39
+ when ?E
40
+ 3
41
+ else
42
+ 4
43
+ end
44
+ end
45
+
46
+ def initialize(database, user, password=nil, uri = nil)
47
+ uri ||= DEFAULT_URI
48
+
49
+ @transaction_status = nil
50
+ @params = {}
51
+ establish_connection(uri)
52
+
53
+ @conn << StartupMessage.new(PROTO_VERSION, 'user' => user, 'database' => database).dump
54
+
55
+ loop do
56
+ msg = Message.read(@conn)
57
+
58
+ case msg
59
+ when AuthentificationClearTextPassword
60
+ raise PGError, "no password specified" if password.nil?
61
+ @conn << PasswordMessage.new(password).dump
62
+
63
+ when AuthentificationCryptPassword
64
+ raise PGError, "no password specified" if password.nil?
65
+ @conn << PasswordMessage.new(password.crypt(msg.salt)).dump
66
+
67
+ when AuthentificationMD5Password
68
+ raise PGError, "no password specified" if password.nil?
69
+ require 'digest/md5'
70
+
71
+ m = Digest::MD5.hexdigest(password + user)
72
+ m = Digest::MD5.hexdigest(m + msg.salt)
73
+ m = 'md5' + m
74
+ @conn << PasswordMessage.new(m).dump
75
+
76
+ when AuthentificationKerberosV4, AuthentificationKerberosV5, AuthentificationSCMCredential
77
+ raise PGError, "unsupported authentification"
78
+
79
+ when AuthentificationOk
80
+ when ErrorResponse
81
+ raise PGError, msg.field_values.join("\t")
82
+ when NoticeResponse
83
+ @notice_processor.call(msg) if @notice_processor
84
+ when ParameterStatus
85
+ @params[msg.key] = msg.value
86
+ when BackendKeyData
87
+ # TODO
88
+ #p msg
89
+ when ReadyForQuery
90
+ @transaction_status = msg.backend_transaction_status_indicator
91
+ break
92
+ else
93
+ raise PGError, "unhandled message type"
94
+ end
95
+ end
96
+ end
97
+
98
+ def close
99
+ raise(PGError, "connection already closed") if @conn.nil?
100
+ @conn.shutdown
101
+ @conn = nil
102
+ end
103
+
104
+ class Result
105
+ attr_accessor :rows, :fields, :cmd_tag
106
+ def initialize(rows=[], fields=[])
107
+ @rows, @fields = rows, fields
108
+ end
109
+ end
110
+
111
+ def query(sql)
112
+ @conn << Query.dump(sql)
113
+
114
+ result = Result.new
115
+ errors = []
116
+
117
+ loop do
118
+ msg = Message.read(@conn)
119
+ case msg
120
+ when DataRow
121
+ result.rows << msg.columns
122
+ when CommandComplete
123
+ result.cmd_tag = msg.cmd_tag
124
+ when ReadyForQuery
125
+ @transaction_status = msg.backend_transaction_status_indicator
126
+ break
127
+ when RowDescription
128
+ result.fields = msg.fields
129
+ when CopyInResponse
130
+ when CopyOutResponse
131
+ when EmptyQueryResponse
132
+ when ErrorResponse
133
+ # TODO
134
+ errors << msg
135
+ when NoticeResponse
136
+ @notice_processor.call(msg) if @notice_processor
137
+ else
138
+ # TODO
139
+ end
140
+ end
141
+
142
+ raise(PGError, errors.map{|e| e.field_values.join("\t") }.join("\n")) unless errors.empty?
143
+
144
+ result
145
+ end
146
+
147
+ DEFAULT_PORT = 5432
148
+ DEFAULT_HOST = 'localhost'
149
+ DEFAULT_PATH = '/tmp'
150
+ DEFAULT_URI =
151
+ if RUBY_PLATFORM.include?('win')
152
+ 'tcp://' + DEFAULT_HOST + ':' + DEFAULT_PORT.to_s
153
+ else
154
+ 'unix:' + File.join(DEFAULT_PATH, '.s.PGSQL.' + DEFAULT_PORT.to_s)
155
+ end
156
+
157
+ private
158
+
159
+ # tcp://localhost:5432
160
+ # unix:/tmp/.s.PGSQL.5432
161
+ def establish_connection(uri)
162
+ u = URI.parse(uri)
163
+ case u.scheme
164
+ when 'tcp'
165
+ @conn = TCPSocket.new(u.host || DEFAULT_HOST, u.port || DEFAULT_PORT)
166
+ when 'unix'
167
+ @conn = UNIXSocket.new(u.path)
168
+ else
169
+ raise PGError, 'unrecognized uri scheme format (must be tcp or unix)'
170
+ end
171
+ end
172
+ end
173
+
174
+ end # module PostgresPR