swift-db-postgres 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,3 @@
1
+ == 0.1.0 (2012-07-20)
2
+
3
+ * Initial version.
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
+ }