swift-db-postgres 0.1.0
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.
- data/CHANGELOG +3 -0
- data/README.md +131 -0
- data/ext/swift/db/postgres/adapter.c +580 -0
- data/ext/swift/db/postgres/adapter.h +14 -0
- data/ext/swift/db/postgres/common.c +59 -0
- data/ext/swift/db/postgres/common.h +35 -0
- data/ext/swift/db/postgres/datetime.c +99 -0
- data/ext/swift/db/postgres/datetime.h +8 -0
- data/ext/swift/db/postgres/extconf.rb +33 -0
- data/ext/swift/db/postgres/main.c +28 -0
- data/ext/swift/db/postgres/result.c +172 -0
- data/ext/swift/db/postgres/result.h +10 -0
- data/ext/swift/db/postgres/statement.c +169 -0
- data/ext/swift/db/postgres/statement.h +9 -0
- data/ext/swift/db/postgres/typecast.c +112 -0
- data/ext/swift/db/postgres/typecast.h +24 -0
- data/lib/swift-db-postgres.rb +1 -0
- data/lib/swift/db/postgres.rb +1 -0
- data/test/helper.rb +8 -0
- data/test/test_adapter.rb +106 -0
- data/test/test_async.rb +34 -0
- data/test/test_encoding.rb +30 -0
- data/test/test_ssl.rb +15 -0
- metadata +87 -0
data/CHANGELOG
ADDED
data/README.md
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
# Swift PostgreSQL adapter
|
2
|
+
|
3
|
+
MRI adapter for PostgreSQL
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
* Lightweight & fast
|
8
|
+
* Result typecasting
|
9
|
+
* Prepared statements
|
10
|
+
* Asynchronous support using PQsendQuery family of functions
|
11
|
+
* Nested transactions
|
12
|
+
|
13
|
+
## API
|
14
|
+
|
15
|
+
```
|
16
|
+
Swift::DB::Postgres
|
17
|
+
.new(options)
|
18
|
+
#execute(sql, *bind)
|
19
|
+
#prepare(sql)
|
20
|
+
#begin(savepoint = nil)
|
21
|
+
#commit(savepoint = nil)
|
22
|
+
#rollback(savepoint = nil)
|
23
|
+
#transaction(savepoint = nil, &block)
|
24
|
+
#close
|
25
|
+
#closed?
|
26
|
+
#escape(text)
|
27
|
+
#query(sql, *bind)
|
28
|
+
#fileno
|
29
|
+
#result
|
30
|
+
#write(table = nil, fields = nil, io_or_string)
|
31
|
+
#read(table = nil, fields = nil, io = nil, &block)
|
32
|
+
|
33
|
+
Swift::DB::Postgres::Statement
|
34
|
+
.new(Swift::DB::Postgres, sql)
|
35
|
+
#execute(*bind)
|
36
|
+
#release
|
37
|
+
|
38
|
+
Swift::DB::Postgres::Result
|
39
|
+
#selected_rows
|
40
|
+
#affected_rows
|
41
|
+
#fields
|
42
|
+
#types
|
43
|
+
#each
|
44
|
+
#insert_id
|
45
|
+
```
|
46
|
+
|
47
|
+
## Example
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
require 'swift/db/postgres'
|
51
|
+
|
52
|
+
db = Swift::DB::Postgres.new(db: 'swift_test')
|
53
|
+
|
54
|
+
db.execute('drop table if exists users')
|
55
|
+
db.execute('create table users (id serial, name text, age integer, created_at timestamp)')
|
56
|
+
db.execute('insert into users(name, age, created_at) values(?, ?, ?)', 'test', 30, Time.now.utc)
|
57
|
+
|
58
|
+
row = db.execute('select * from users').first
|
59
|
+
p row #=> {:id => 1, :name => 'test', :age => 30, :created_at=> #<Swift::DateTime>}
|
60
|
+
```
|
61
|
+
|
62
|
+
### Asynchronous
|
63
|
+
|
64
|
+
Hint: You can use `Adapter#fileno` and `EventMachine.watch` if you need to use this with EventMachine.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
require 'swift/db/postgres'
|
68
|
+
|
69
|
+
rows = []
|
70
|
+
pool = 3.times.map {Swift::DB::Postgres.new(db: 'swift_test')}
|
71
|
+
|
72
|
+
3.times do |n|
|
73
|
+
Thread.new do
|
74
|
+
pool[n].query("select pg_sleep(#{(3 - n) / 10.0}), #{n + 1} as query_id") do |row|
|
75
|
+
rows << row[:query_id]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
Thread.list.reject {|thread| Thread.current == thread}.each(&:join)
|
81
|
+
rows #=> [3, 2, 1]
|
82
|
+
```
|
83
|
+
|
84
|
+
### Data I/O
|
85
|
+
|
86
|
+
The adapter supports data read and write via COPY command.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
require 'swift/db/postgres'
|
90
|
+
|
91
|
+
db = Swift::DB::Postgres.new(db: 'swift_test')
|
92
|
+
db.execute('drop table if exists users')
|
93
|
+
db.execute('create table users (id serial, name text)')
|
94
|
+
|
95
|
+
db.write('users', %w{name}, "foo\nbar\nbaz\n")
|
96
|
+
db.write('users', %w{name}, StringIO.new("foo\nbar\nbaz\n"))
|
97
|
+
db.write('users', %w{name}, File.open("users.dat"))
|
98
|
+
|
99
|
+
|
100
|
+
db.read('users', %w{name}) do |data|
|
101
|
+
p data
|
102
|
+
end
|
103
|
+
|
104
|
+
csv = File.open('users.csv', 'w')
|
105
|
+
db.execute('copy users to stdout with csv')
|
106
|
+
db.read(csv)
|
107
|
+
```
|
108
|
+
|
109
|
+
## Performance
|
110
|
+
|
111
|
+
Don't read too much into it. Each library has its advantages and disadvantages.
|
112
|
+
|
113
|
+
* insert 1000 rows and read them back 100 times with typecast enabled
|
114
|
+
* pg uses the pg_typecast extension
|
115
|
+
|
116
|
+
```
|
117
|
+
$ ruby check.rb
|
118
|
+
user system total real
|
119
|
+
do_postgres insert 0.190000 0.080000 0.270000 ( 0.587877)
|
120
|
+
do_postgres select 1.440000 0.020000 1.460000 ( 2.081172)
|
121
|
+
|
122
|
+
pg insert 0.100000 0.030000 0.130000 ( 0.395280)
|
123
|
+
pg select 0.630000 0.220000 0.850000 ( 1.284905)
|
124
|
+
|
125
|
+
swift insert 0.070000 0.040000 0.110000 ( 0.348211)
|
126
|
+
swift select 0.640000 0.030000 0.670000 ( 1.111561)
|
127
|
+
```
|
128
|
+
|
129
|
+
## License
|
130
|
+
|
131
|
+
MIT
|
@@ -0,0 +1,580 @@
|
|
1
|
+
// vim:ts=4:sts=4:sw=4:expandtab
|
2
|
+
|
3
|
+
// (c) Bharanee Rathna 2012
|
4
|
+
|
5
|
+
#include <stdio.h>
|
6
|
+
#include "adapter.h"
|
7
|
+
#include "typecast.h"
|
8
|
+
|
9
|
+
/* declaration */
|
10
|
+
VALUE cDPA, sUser;
|
11
|
+
VALUE db_postgres_result_each(VALUE);
|
12
|
+
VALUE db_postgres_result_load(VALUE, PGresult *);
|
13
|
+
VALUE db_postgres_result_allocate(VALUE);
|
14
|
+
VALUE db_postgres_statement_allocate(VALUE);
|
15
|
+
VALUE db_postgres_statement_initialize(VALUE, VALUE, VALUE);
|
16
|
+
|
17
|
+
/* definition */
|
18
|
+
Adapter* db_postgres_adapter_handle(VALUE self) {
|
19
|
+
Adapter *a;
|
20
|
+
Data_Get_Struct(self, Adapter, a);
|
21
|
+
if (!a)
|
22
|
+
rb_raise(eSwiftRuntimeError, "Invalid postgres adapter");
|
23
|
+
return a;
|
24
|
+
}
|
25
|
+
|
26
|
+
Adapter* db_postgres_adapter_handle_safe(VALUE self) {
|
27
|
+
Adapter *a = db_postgres_adapter_handle(self);
|
28
|
+
if (!a->connection)
|
29
|
+
rb_raise(eSwiftConnectionError, "postgres database is not open");
|
30
|
+
return a;
|
31
|
+
}
|
32
|
+
|
33
|
+
VALUE db_postgres_adapter_deallocate(Adapter *a) {
|
34
|
+
if (a && a->connection)
|
35
|
+
PQfinish(a->connection);
|
36
|
+
if (a)
|
37
|
+
free(a);
|
38
|
+
}
|
39
|
+
|
40
|
+
VALUE db_postgres_adapter_allocate(VALUE klass) {
|
41
|
+
Adapter *a = (Adapter*)malloc(sizeof(Adapter));
|
42
|
+
|
43
|
+
a->connection = 0;
|
44
|
+
a->t_nesting = 0;
|
45
|
+
return Data_Wrap_Struct(klass, 0, db_postgres_adapter_deallocate, a);
|
46
|
+
}
|
47
|
+
|
48
|
+
/* TODO: log messages */
|
49
|
+
VALUE db_postgres_adapter_notice(VALUE self, char *message) {
|
50
|
+
return Qtrue;
|
51
|
+
}
|
52
|
+
|
53
|
+
void append_ssl_option(char *buffer, int size, VALUE ssl, char *key, char *fallback) {
|
54
|
+
int offset = strlen(buffer);
|
55
|
+
VALUE option = rb_hash_aref(ssl, ID2SYM(rb_intern(key)));
|
56
|
+
|
57
|
+
if (NIL_P(option) && fallback)
|
58
|
+
snprintf(buffer + offset, size - offset, " %s='%s'", key, fallback);
|
59
|
+
|
60
|
+
if (!NIL_P(option))
|
61
|
+
snprintf(buffer + offset, size - offset, " %s='%s'", key, CSTRING(option));
|
62
|
+
}
|
63
|
+
|
64
|
+
VALUE db_postgres_adapter_initialize(VALUE self, VALUE options) {
|
65
|
+
char *connection_info;
|
66
|
+
VALUE db, user, pass, host, port, ssl;
|
67
|
+
Adapter *a = db_postgres_adapter_handle(self);
|
68
|
+
|
69
|
+
if (TYPE(options) != T_HASH)
|
70
|
+
rb_raise(eSwiftArgumentError, "options needs to be a hash");
|
71
|
+
|
72
|
+
db = rb_hash_aref(options, ID2SYM(rb_intern("db")));
|
73
|
+
user = rb_hash_aref(options, ID2SYM(rb_intern("user")));
|
74
|
+
pass = rb_hash_aref(options, ID2SYM(rb_intern("pass")));
|
75
|
+
host = rb_hash_aref(options, ID2SYM(rb_intern("host")));
|
76
|
+
port = rb_hash_aref(options, ID2SYM(rb_intern("port")));
|
77
|
+
ssl = rb_hash_aref(options, ID2SYM(rb_intern("ssl")));
|
78
|
+
|
79
|
+
if (NIL_P(db))
|
80
|
+
rb_raise(eSwiftConnectionError, "Invalid db name");
|
81
|
+
if (NIL_P(host))
|
82
|
+
host = rb_str_new2("127.0.0.1");
|
83
|
+
if (NIL_P(port))
|
84
|
+
port = rb_str_new2("5432");
|
85
|
+
if (NIL_P(user))
|
86
|
+
user = sUser;
|
87
|
+
|
88
|
+
if (!NIL_P(ssl) && TYPE(ssl) != T_HASH)
|
89
|
+
rb_raise(eSwiftArgumentError, "ssl options needs to be a hash");
|
90
|
+
|
91
|
+
connection_info = (char *)malloc(4096);
|
92
|
+
snprintf(connection_info, 4096, "dbname='%s' user='%s' password='%s' host='%s' port='%s'",
|
93
|
+
CSTRING(db), CSTRING(user), CSTRING(pass), CSTRING(host), CSTRING(port));
|
94
|
+
|
95
|
+
if (!NIL_P(ssl)) {
|
96
|
+
append_ssl_option(connection_info, 4096, ssl, "sslmode", "allow");
|
97
|
+
append_ssl_option(connection_info, 4096, ssl, "sslcert", 0);
|
98
|
+
append_ssl_option(connection_info, 4096, ssl, "sslkey", 0);
|
99
|
+
append_ssl_option(connection_info, 4096, ssl, "sslrootcert", 0);
|
100
|
+
append_ssl_option(connection_info, 4096, ssl, "sslcrl", 0);
|
101
|
+
}
|
102
|
+
|
103
|
+
a->connection = PQconnectdb(connection_info);
|
104
|
+
free(connection_info);
|
105
|
+
|
106
|
+
if (!a->connection)
|
107
|
+
rb_raise(eSwiftRuntimeError, "unable to allocate database handle");
|
108
|
+
if (PQstatus(a->connection) == CONNECTION_BAD)
|
109
|
+
rb_raise(eSwiftConnectionError, PQerrorMessage(a->connection));
|
110
|
+
|
111
|
+
PQsetNoticeProcessor(a->connection, (PQnoticeProcessor)db_postgres_adapter_notice, (void*)self);
|
112
|
+
PQsetClientEncoding(a->connection, "utf8");
|
113
|
+
return self;
|
114
|
+
}
|
115
|
+
|
116
|
+
VALUE nogvl_pq_exec(void *ptr) {
|
117
|
+
Query *q = (Query *)ptr;
|
118
|
+
return (VALUE)PQexec(q->connection, q->command);
|
119
|
+
}
|
120
|
+
|
121
|
+
VALUE nogvl_pq_exec_params(void *ptr) {
|
122
|
+
Query *q = (Query *)ptr;
|
123
|
+
return (VALUE)PQexecParams(q->connection, q->command, q->n_args, 0, (const char * const *)q->data, q->size, q->format, 0);
|
124
|
+
}
|
125
|
+
|
126
|
+
VALUE db_postgres_adapter_execute(int argc, VALUE *argv, VALUE self) {
|
127
|
+
char **bind_args_data = 0;
|
128
|
+
int n, *bind_args_size = 0, *bind_args_fmt = 0;
|
129
|
+
PGresult *pg_result;
|
130
|
+
VALUE sql, bind, data;
|
131
|
+
Adapter *a = db_postgres_adapter_handle_safe(self);
|
132
|
+
|
133
|
+
rb_scan_args(argc, argv, "10*", &sql, &bind);
|
134
|
+
sql = db_postgres_normalized_sql(sql);
|
135
|
+
|
136
|
+
if (RARRAY_LEN(bind) > 0) {
|
137
|
+
bind_args_size = (int *) malloc(sizeof(int) * RARRAY_LEN(bind));
|
138
|
+
bind_args_fmt = (int *) malloc(sizeof(int) * RARRAY_LEN(bind));
|
139
|
+
bind_args_data = (char **) malloc(sizeof(char *) * RARRAY_LEN(bind));
|
140
|
+
|
141
|
+
for (n = 0; n < RARRAY_LEN(bind); n++) {
|
142
|
+
data = rb_ary_entry(bind, n);
|
143
|
+
if (NIL_P(data)) {
|
144
|
+
bind_args_size[n] = 0;
|
145
|
+
bind_args_data[n] = 0;
|
146
|
+
bind_args_fmt[n] = 0;
|
147
|
+
}
|
148
|
+
else {
|
149
|
+
if (rb_obj_is_kind_of(data, rb_cIO) || rb_obj_is_kind_of(data, cStringIO))
|
150
|
+
bind_args_fmt[n] = 1;
|
151
|
+
else
|
152
|
+
bind_args_fmt[n] = 0;
|
153
|
+
|
154
|
+
data = typecast_to_string(data);
|
155
|
+
bind_args_size[n] = RSTRING_LEN(data);
|
156
|
+
bind_args_data[n] = RSTRING_PTR(data);
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
Query q = {
|
161
|
+
.connection = a->connection,
|
162
|
+
.command = CSTRING(sql),
|
163
|
+
.n_args = RARRAY_LEN(bind),
|
164
|
+
.data = bind_args_data,
|
165
|
+
.size = bind_args_size,
|
166
|
+
.format = bind_args_fmt
|
167
|
+
};
|
168
|
+
|
169
|
+
pg_result = (PGresult *)rb_thread_blocking_region(nogvl_pq_exec_params, &q, RUBY_UBF_IO, 0);
|
170
|
+
free(bind_args_size);
|
171
|
+
free(bind_args_data);
|
172
|
+
free(bind_args_fmt);
|
173
|
+
}
|
174
|
+
else {
|
175
|
+
Query q = {.connection = a->connection, .command = CSTRING(sql)};
|
176
|
+
pg_result = (PGresult *)rb_thread_blocking_region(nogvl_pq_exec, &q, RUBY_UBF_IO, 0);
|
177
|
+
}
|
178
|
+
|
179
|
+
db_postgres_check_result(pg_result);
|
180
|
+
return db_postgres_result_load(db_postgres_result_allocate(cDPR), pg_result);
|
181
|
+
}
|
182
|
+
|
183
|
+
VALUE db_postgres_adapter_begin(int argc, VALUE *argv, VALUE self) {
|
184
|
+
char command[256];
|
185
|
+
VALUE savepoint;
|
186
|
+
PGresult *result;
|
187
|
+
|
188
|
+
Adapter *a = db_postgres_adapter_handle_safe(self);
|
189
|
+
rb_scan_args(argc, argv, "01", &savepoint);
|
190
|
+
|
191
|
+
if (a->t_nesting == 0) {
|
192
|
+
result = PQexec(a->connection, "begin");
|
193
|
+
db_postgres_check_result(result);
|
194
|
+
PQclear(result);
|
195
|
+
a->t_nesting++;
|
196
|
+
if (NIL_P(savepoint))
|
197
|
+
return Qtrue;
|
198
|
+
}
|
199
|
+
|
200
|
+
if (NIL_P(savepoint))
|
201
|
+
savepoint = rb_uuid_string();
|
202
|
+
|
203
|
+
snprintf(command, 256, "savepoint %s", CSTRING(savepoint));
|
204
|
+
result = PQexec(a->connection, command);
|
205
|
+
db_postgres_check_result(result);
|
206
|
+
PQclear(result);
|
207
|
+
|
208
|
+
a->t_nesting++;
|
209
|
+
return savepoint;
|
210
|
+
}
|
211
|
+
|
212
|
+
VALUE db_postgres_adapter_commit(int argc, VALUE *argv, VALUE self) {
|
213
|
+
VALUE savepoint;
|
214
|
+
char command[256];
|
215
|
+
PGresult *result;
|
216
|
+
|
217
|
+
Adapter *a = db_postgres_adapter_handle_safe(self);
|
218
|
+
rb_scan_args(argc, argv, "01", &savepoint);
|
219
|
+
|
220
|
+
if (a->t_nesting == 0)
|
221
|
+
return Qfalse;
|
222
|
+
|
223
|
+
if (NIL_P(savepoint)) {
|
224
|
+
result = PQexec(a->connection, "commit");
|
225
|
+
db_postgres_check_result(result);
|
226
|
+
PQclear(result);
|
227
|
+
a->t_nesting--;
|
228
|
+
}
|
229
|
+
else {
|
230
|
+
snprintf(command, 256, "release savepoint %s", CSTRING(savepoint));
|
231
|
+
result = PQexec(a->connection, command);
|
232
|
+
db_postgres_check_result(result);
|
233
|
+
PQclear(result);
|
234
|
+
a->t_nesting--;
|
235
|
+
}
|
236
|
+
return Qtrue;
|
237
|
+
}
|
238
|
+
|
239
|
+
VALUE db_postgres_adapter_rollback(int argc, VALUE *argv, VALUE self) {
|
240
|
+
VALUE savepoint;
|
241
|
+
char command[256];
|
242
|
+
PGresult *result;
|
243
|
+
|
244
|
+
Adapter *a = db_postgres_adapter_handle_safe(self);
|
245
|
+
rb_scan_args(argc, argv, "01", &savepoint);
|
246
|
+
|
247
|
+
if (a->t_nesting == 0)
|
248
|
+
return Qfalse;
|
249
|
+
|
250
|
+
if (NIL_P(savepoint)) {
|
251
|
+
result = PQexec(a->connection, "rollback");
|
252
|
+
db_postgres_check_result(result);
|
253
|
+
PQclear(result);
|
254
|
+
a->t_nesting--;
|
255
|
+
}
|
256
|
+
else {
|
257
|
+
snprintf(command, 256, "rollback to savepoint %s", CSTRING(savepoint));
|
258
|
+
result = PQexec(a->connection, command);
|
259
|
+
db_postgres_check_result(result);
|
260
|
+
PQclear(result);
|
261
|
+
a->t_nesting--;
|
262
|
+
}
|
263
|
+
return Qtrue;
|
264
|
+
}
|
265
|
+
|
266
|
+
VALUE db_postgres_adapter_transaction(int argc, VALUE *argv, VALUE self) {
|
267
|
+
int status;
|
268
|
+
VALUE savepoint, block, block_result;
|
269
|
+
|
270
|
+
Adapter *a = db_postgres_adapter_handle_safe(self);
|
271
|
+
rb_scan_args(argc, argv, "01&", &savepoint, &block);
|
272
|
+
|
273
|
+
if (NIL_P(block))
|
274
|
+
rb_raise(eSwiftRuntimeError, "postgres transaction requires a block");
|
275
|
+
|
276
|
+
if (a->t_nesting == 0) {
|
277
|
+
db_postgres_adapter_begin(1, &savepoint, self);
|
278
|
+
block_result = rb_protect(rb_yield, self, &status);
|
279
|
+
if (!status) {
|
280
|
+
db_postgres_adapter_commit(1, &savepoint, self);
|
281
|
+
if (!NIL_P(savepoint))
|
282
|
+
db_postgres_adapter_commit(0, 0, self);
|
283
|
+
}
|
284
|
+
else {
|
285
|
+
db_postgres_adapter_rollback(1, &savepoint, self);
|
286
|
+
if (!NIL_P(savepoint))
|
287
|
+
db_postgres_adapter_rollback(0, 0, self);
|
288
|
+
rb_jump_tag(status);
|
289
|
+
}
|
290
|
+
}
|
291
|
+
else {
|
292
|
+
if (NIL_P(savepoint))
|
293
|
+
savepoint = rb_uuid_string();
|
294
|
+
db_postgres_adapter_begin(1, &savepoint, self);
|
295
|
+
block_result = rb_protect(rb_yield, self, &status);
|
296
|
+
if (!status)
|
297
|
+
db_postgres_adapter_commit(1, &savepoint, self);
|
298
|
+
else {
|
299
|
+
db_postgres_adapter_rollback(1, &savepoint, self);
|
300
|
+
rb_jump_tag(status);
|
301
|
+
}
|
302
|
+
}
|
303
|
+
|
304
|
+
return block_result;
|
305
|
+
}
|
306
|
+
|
307
|
+
VALUE db_postgres_adapter_close(VALUE self) {
|
308
|
+
Adapter *a = db_postgres_adapter_handle(self);
|
309
|
+
if (a->connection) {
|
310
|
+
PQfinish(a->connection);
|
311
|
+
a->connection = 0;
|
312
|
+
return Qtrue;
|
313
|
+
}
|
314
|
+
return Qfalse;
|
315
|
+
}
|
316
|
+
|
317
|
+
VALUE db_postgres_adapter_closed_q(VALUE self) {
|
318
|
+
Adapter *a = db_postgres_adapter_handle(self);
|
319
|
+
return a->connection ? Qfalse : Qtrue;
|
320
|
+
}
|
321
|
+
|
322
|
+
VALUE db_postgres_adapter_prepare(VALUE self, VALUE sql) {
|
323
|
+
return db_postgres_statement_initialize(db_postgres_statement_allocate(cDPS), self, sql);
|
324
|
+
}
|
325
|
+
|
326
|
+
VALUE db_postgres_adapter_escape(VALUE self, VALUE fragment) {
|
327
|
+
int error;
|
328
|
+
VALUE text = TO_S(fragment);
|
329
|
+
char pg_escaped[RSTRING_LEN(text) * 2 + 1];
|
330
|
+
Adapter *a = db_postgres_adapter_handle_safe(self);
|
331
|
+
PQescapeStringConn(a->connection, pg_escaped, RSTRING_PTR(text), RSTRING_LEN(text), &error);
|
332
|
+
|
333
|
+
if (error)
|
334
|
+
rb_raise(eSwiftArgumentError, "invalid escape string: %s\n", PQerrorMessage(a->connection));
|
335
|
+
|
336
|
+
return rb_str_new2(pg_escaped);
|
337
|
+
}
|
338
|
+
|
339
|
+
VALUE db_postgres_adapter_fileno(VALUE self) {
|
340
|
+
Adapter *a = db_postgres_adapter_handle_safe(self);
|
341
|
+
return INT2NUM(PQsocket(a->connection));
|
342
|
+
}
|
343
|
+
|
344
|
+
VALUE db_postgres_adapter_result(VALUE self) {
|
345
|
+
PGresult *result, *rest;
|
346
|
+
Adapter *a = db_postgres_adapter_handle_safe(self);
|
347
|
+
while (1) {
|
348
|
+
PQconsumeInput(a->connection);
|
349
|
+
if (!PQisBusy(a->connection))
|
350
|
+
break;
|
351
|
+
}
|
352
|
+
result = PQgetResult(a->connection);
|
353
|
+
while ((rest = PQgetResult(a->connection))) PQclear(rest);
|
354
|
+
db_postgres_check_result(result);
|
355
|
+
return db_postgres_result_load(db_postgres_result_allocate(cDPR), result);
|
356
|
+
}
|
357
|
+
|
358
|
+
VALUE db_postgres_adapter_query(int argc, VALUE *argv, VALUE self) {
|
359
|
+
VALUE sql, bind, data;
|
360
|
+
char **bind_args_data = 0;
|
361
|
+
int n, ok = 1, *bind_args_size = 0, *bind_args_fmt = 0;
|
362
|
+
Adapter *a = db_postgres_adapter_handle_safe(self);
|
363
|
+
|
364
|
+
rb_scan_args(argc, argv, "10*", &sql, &bind);
|
365
|
+
sql = db_postgres_normalized_sql(sql);
|
366
|
+
|
367
|
+
if (RARRAY_LEN(bind) > 0) {
|
368
|
+
bind_args_size = (int *) malloc(sizeof(int) * RARRAY_LEN(bind));
|
369
|
+
bind_args_fmt = (int *) malloc(sizeof(int) * RARRAY_LEN(bind));
|
370
|
+
bind_args_data = (char **) malloc(sizeof(char *) * RARRAY_LEN(bind));
|
371
|
+
|
372
|
+
for (n = 0; n < RARRAY_LEN(bind); n++) {
|
373
|
+
data = rb_ary_entry(bind, n);
|
374
|
+
if (NIL_P(data)) {
|
375
|
+
bind_args_size[n] = 0;
|
376
|
+
bind_args_data[n] = 0;
|
377
|
+
bind_args_fmt[n] = 0;
|
378
|
+
}
|
379
|
+
else {
|
380
|
+
if (rb_obj_is_kind_of(data, rb_cIO) || rb_obj_is_kind_of(data, cStringIO))
|
381
|
+
bind_args_fmt[n] = 1;
|
382
|
+
else
|
383
|
+
bind_args_fmt[n] = 0;
|
384
|
+
|
385
|
+
data = typecast_to_string(data);
|
386
|
+
bind_args_size[n] = RSTRING_LEN(data);
|
387
|
+
bind_args_data[n] = RSTRING_PTR(data);
|
388
|
+
}
|
389
|
+
}
|
390
|
+
|
391
|
+
ok = PQsendQueryParams(a->connection, RSTRING_PTR(sql), RARRAY_LEN(bind), 0,
|
392
|
+
(const char* const *)bind_args_data, bind_args_size, bind_args_fmt, 0);
|
393
|
+
|
394
|
+
free(bind_args_size);
|
395
|
+
free(bind_args_data);
|
396
|
+
free(bind_args_fmt);
|
397
|
+
}
|
398
|
+
else
|
399
|
+
ok = PQsendQuery(a->connection, RSTRING_PTR(sql));
|
400
|
+
|
401
|
+
if (!ok)
|
402
|
+
rb_raise(eSwiftRuntimeError, "%s", PQerrorMessage(a->connection));
|
403
|
+
|
404
|
+
if (rb_block_given_p()) {
|
405
|
+
rb_thread_wait_fd(PQsocket(a->connection));
|
406
|
+
return db_postgres_result_each(db_postgres_adapter_result(self));
|
407
|
+
}
|
408
|
+
else
|
409
|
+
return Qtrue;
|
410
|
+
}
|
411
|
+
|
412
|
+
VALUE db_postgres_adapter_write(int argc, VALUE *argv, VALUE self) {
|
413
|
+
char *sql;
|
414
|
+
VALUE table, fields, io, data;
|
415
|
+
PGresult *result;
|
416
|
+
Adapter *a = db_postgres_adapter_handle_safe(self);
|
417
|
+
|
418
|
+
if (argc < 1 || argc > 3)
|
419
|
+
rb_raise(rb_eArgError, "wrong number of arguments (%d for 1..3)", argc);
|
420
|
+
|
421
|
+
table = io = fields = Qnil;
|
422
|
+
switch (argc) {
|
423
|
+
case 1:
|
424
|
+
io = argv[0];
|
425
|
+
break;
|
426
|
+
case 2:
|
427
|
+
table = argv[0];
|
428
|
+
io = argv[1];
|
429
|
+
break;
|
430
|
+
case 3:
|
431
|
+
table = argv[0];
|
432
|
+
fields = argv[1];
|
433
|
+
io = argv[2];
|
434
|
+
|
435
|
+
if (TYPE(fields) != T_ARRAY)
|
436
|
+
rb_raise(eSwiftArgumentError, "fields needs to be an array");
|
437
|
+
if (RARRAY_LEN(fields) < 1)
|
438
|
+
fields = Qnil;
|
439
|
+
}
|
440
|
+
|
441
|
+
if (argc > 1) {
|
442
|
+
sql = (char *)malloc(4096);
|
443
|
+
if (NIL_P(fields))
|
444
|
+
snprintf(sql, 4096, "copy %s from stdin", CSTRING(table));
|
445
|
+
else
|
446
|
+
snprintf(sql, 4096, "copy %s(%s) from stdin", CSTRING(table), CSTRING(rb_ary_join(fields, rb_str_new2(", "))));
|
447
|
+
|
448
|
+
result = PQexec(a->connection, sql);
|
449
|
+
free(sql);
|
450
|
+
|
451
|
+
db_postgres_check_result(result);
|
452
|
+
PQclear(result);
|
453
|
+
}
|
454
|
+
|
455
|
+
if (rb_respond_to(io, rb_intern("read"))) {
|
456
|
+
while (!NIL_P((data = rb_funcall(io, rb_intern("read"), 1, INT2NUM(4096))))) {
|
457
|
+
data = TO_S(data);
|
458
|
+
if (PQputCopyData(a->connection, RSTRING_PTR(data), RSTRING_LEN(data)) != 1)
|
459
|
+
rb_raise(eSwiftRuntimeError, "%s", PQerrorMessage(a->connection));
|
460
|
+
}
|
461
|
+
if (PQputCopyEnd(a->connection, 0) != 1)
|
462
|
+
rb_raise(eSwiftRuntimeError, "%s", PQerrorMessage(a->connection));
|
463
|
+
}
|
464
|
+
else {
|
465
|
+
io = TO_S(io);
|
466
|
+
if (PQputCopyData(a->connection, RSTRING_PTR(io), RSTRING_LEN(io)) != 1)
|
467
|
+
rb_raise(eSwiftRuntimeError, "%s", PQerrorMessage(a->connection));
|
468
|
+
if (PQputCopyEnd(a->connection, 0) != 1)
|
469
|
+
rb_raise(eSwiftRuntimeError, "%s", PQerrorMessage(a->connection));
|
470
|
+
}
|
471
|
+
|
472
|
+
result = PQgetResult(a->connection);
|
473
|
+
db_postgres_check_result(result);
|
474
|
+
if (!result)
|
475
|
+
rb_raise(eSwiftRuntimeError, "invalid result at the end of COPY command");
|
476
|
+
return db_postgres_result_load(db_postgres_result_allocate(cDPR), result);
|
477
|
+
}
|
478
|
+
|
479
|
+
VALUE db_postgres_adapter_read(int argc, VALUE *argv, VALUE self) {
|
480
|
+
int n, done = 0;
|
481
|
+
char *sql, *data;
|
482
|
+
PGresult *result;
|
483
|
+
VALUE table, fields, io;
|
484
|
+
Adapter *a = db_postgres_adapter_handle_safe(self);
|
485
|
+
|
486
|
+
if (argc > 3)
|
487
|
+
rb_raise(rb_eArgError, "wrong number of arguments (%d for 0..3)", argc);
|
488
|
+
|
489
|
+
table = fields = io = Qnil;
|
490
|
+
switch (argc) {
|
491
|
+
case 0:
|
492
|
+
if (!rb_block_given_p())
|
493
|
+
rb_raise(eSwiftArgumentError, "#read needs an IO object to write to or a block to call");
|
494
|
+
break;
|
495
|
+
case 1:
|
496
|
+
if (rb_respond_to(argv[0], rb_intern("write")))
|
497
|
+
io = argv[0];
|
498
|
+
else
|
499
|
+
table = argv[0];
|
500
|
+
break;
|
501
|
+
case 2:
|
502
|
+
table = argv[0];
|
503
|
+
io = argv[1];
|
504
|
+
if (!rb_respond_to(io, rb_intern("write")))
|
505
|
+
rb_raise(eSwiftArgumentError, "#read needs an IO object that responds to #write");
|
506
|
+
break;
|
507
|
+
case 3:
|
508
|
+
table = argv[0];
|
509
|
+
fields = argv[1];
|
510
|
+
io = argv[2];
|
511
|
+
if (!rb_respond_to(io, rb_intern("write")))
|
512
|
+
rb_raise(eSwiftArgumentError, "#read needs an IO object that responds to #write");
|
513
|
+
if (TYPE(fields) != T_ARRAY)
|
514
|
+
rb_raise(eSwiftArgumentError, "fields needs to be an array");
|
515
|
+
if (RARRAY_LEN(fields) < 1)
|
516
|
+
fields = Qnil;
|
517
|
+
}
|
518
|
+
|
519
|
+
|
520
|
+
if (!NIL_P(table)) {
|
521
|
+
sql = (char *)malloc(4096);
|
522
|
+
if (NIL_P(fields))
|
523
|
+
snprintf(sql, 4096, "copy %s to stdout", CSTRING(table));
|
524
|
+
else
|
525
|
+
snprintf(sql, 4096, "copy %s(%s) to stdout", CSTRING(table), CSTRING(rb_ary_join(fields, rb_str_new2(", "))));
|
526
|
+
|
527
|
+
result = PQexec(a->connection, sql);
|
528
|
+
free(sql);
|
529
|
+
|
530
|
+
db_postgres_check_result(result);
|
531
|
+
PQclear(result);
|
532
|
+
}
|
533
|
+
|
534
|
+
while (!done) {
|
535
|
+
switch ((n = PQgetCopyData(a->connection, &data, 0))) {
|
536
|
+
case -1: done = 1; break;
|
537
|
+
case -2: rb_raise(eSwiftRuntimeError, "%s", PQerrorMessage(a->connection));
|
538
|
+
default:
|
539
|
+
if (n > 0) {
|
540
|
+
if (NIL_P(io))
|
541
|
+
rb_yield(rb_str_new(data, n));
|
542
|
+
else
|
543
|
+
rb_funcall(io, rb_intern("write"), 1, rb_str_new(data, n));
|
544
|
+
PQfreemem(data);
|
545
|
+
}
|
546
|
+
}
|
547
|
+
}
|
548
|
+
|
549
|
+
result = PQgetResult(a->connection);
|
550
|
+
db_postgres_check_result(result);
|
551
|
+
if (!result)
|
552
|
+
rb_raise(eSwiftRuntimeError, "invalid result at the end of COPY command");
|
553
|
+
return db_postgres_result_load(db_postgres_result_allocate(cDPR), result);
|
554
|
+
}
|
555
|
+
|
556
|
+
void init_swift_db_postgres_adapter() {
|
557
|
+
rb_require("etc");
|
558
|
+
sUser = rb_funcall(CONST_GET(rb_mKernel, "Etc"), rb_intern("getlogin"), 0);
|
559
|
+
cDPA = rb_define_class_under(mDB, "Postgres", rb_cObject);
|
560
|
+
|
561
|
+
rb_define_alloc_func(cDPA, db_postgres_adapter_allocate);
|
562
|
+
|
563
|
+
rb_define_method(cDPA, "initialize", db_postgres_adapter_initialize, 1);
|
564
|
+
rb_define_method(cDPA, "execute", db_postgres_adapter_execute, -1);
|
565
|
+
rb_define_method(cDPA, "prepare", db_postgres_adapter_prepare, 1);
|
566
|
+
rb_define_method(cDPA, "begin", db_postgres_adapter_begin, -1);
|
567
|
+
rb_define_method(cDPA, "commit", db_postgres_adapter_commit, -1);
|
568
|
+
rb_define_method(cDPA, "rollback", db_postgres_adapter_rollback, -1);
|
569
|
+
rb_define_method(cDPA, "transaction", db_postgres_adapter_transaction, -1);
|
570
|
+
rb_define_method(cDPA, "close", db_postgres_adapter_close, 0);
|
571
|
+
rb_define_method(cDPA, "closed?", db_postgres_adapter_closed_q, 0);
|
572
|
+
rb_define_method(cDPA, "escape", db_postgres_adapter_escape, 1);
|
573
|
+
rb_define_method(cDPA, "fileno", db_postgres_adapter_fileno, 0);
|
574
|
+
rb_define_method(cDPA, "query", db_postgres_adapter_query, -1);
|
575
|
+
rb_define_method(cDPA, "result", db_postgres_adapter_result, 0);
|
576
|
+
rb_define_method(cDPA, "write", db_postgres_adapter_write, -1);
|
577
|
+
rb_define_method(cDPA, "read", db_postgres_adapter_read, -1);
|
578
|
+
|
579
|
+
rb_global_variable(&sUser);
|
580
|
+
}
|