jeremyevans-postgres-pr 0.6.1

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,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