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 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
+ }