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 +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
@@ -0,0 +1,59 @@
|
|
1
|
+
// vim:ts=4:sts=4:sw=4:expandtab
|
2
|
+
|
3
|
+
// (c) Bharanee Rathna 2012
|
4
|
+
|
5
|
+
#include "common.h"
|
6
|
+
#include <uuid/uuid.h>
|
7
|
+
|
8
|
+
VALUE rb_uuid_string() {
|
9
|
+
size_t n;
|
10
|
+
uuid_t uuid;
|
11
|
+
char uuid_hex[sizeof(uuid_t) * 2 + 1];
|
12
|
+
|
13
|
+
uuid_generate(uuid);
|
14
|
+
for (n = 0; n < sizeof(uuid_t); n++)
|
15
|
+
sprintf(uuid_hex + n * 2 + 1, "%02x", uuid[n]);
|
16
|
+
|
17
|
+
uuid_hex[0] = 'u';
|
18
|
+
return rb_str_new(uuid_hex, sizeof(uuid_t) * 2 + 1);
|
19
|
+
}
|
20
|
+
|
21
|
+
/* NOTE: very naive, no regex etc. */
|
22
|
+
/* TODO: a better ragel based replace thingamajigy */
|
23
|
+
VALUE db_postgres_normalized_sql(VALUE sql) {
|
24
|
+
int i = 0, j = 0, n = 1;
|
25
|
+
char normalized[RSTRING_LEN(sql) * 2], *ptr = RSTRING_PTR(sql);
|
26
|
+
|
27
|
+
while (i < RSTRING_LEN(sql)) {
|
28
|
+
if (*ptr == '?')
|
29
|
+
j += snprintf(normalized + j, 4, "$%d", n++);
|
30
|
+
else
|
31
|
+
normalized[j++] = *ptr;
|
32
|
+
ptr++;
|
33
|
+
i++;
|
34
|
+
}
|
35
|
+
|
36
|
+
return rb_str_new(normalized, j);
|
37
|
+
}
|
38
|
+
|
39
|
+
void db_postgres_check_result(PGresult *result) {
|
40
|
+
VALUE error;
|
41
|
+
switch (PQresultStatus(result)) {
|
42
|
+
case PGRES_TUPLES_OK:
|
43
|
+
case PGRES_COPY_OUT:
|
44
|
+
case PGRES_COPY_IN:
|
45
|
+
case PGRES_EMPTY_QUERY:
|
46
|
+
case PGRES_COMMAND_OK:
|
47
|
+
break;
|
48
|
+
case PGRES_BAD_RESPONSE:
|
49
|
+
case PGRES_FATAL_ERROR:
|
50
|
+
case PGRES_NONFATAL_ERROR:
|
51
|
+
error = rb_str_new2(PQresultErrorMessage(result));
|
52
|
+
PQclear(result);
|
53
|
+
rb_raise(strstr(CSTRING(error), "bind message") ? eSwiftArgumentError : eSwiftRuntimeError, "%s", CSTRING(error));
|
54
|
+
break;
|
55
|
+
default:
|
56
|
+
PQclear(result);
|
57
|
+
rb_raise(eSwiftRuntimeError, "unknown error, check logs");
|
58
|
+
}
|
59
|
+
}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
#define DLL_PRIVATE __attribute__ ((visibility ("hidden")))
|
4
|
+
#define CONST_GET(scope, constant) rb_funcall(scope, rb_intern("const_get"), 1, rb_str_new2(constant))
|
5
|
+
#define TO_S(v) rb_funcall(v, rb_intern("to_s"), 0)
|
6
|
+
#define CSTRING(v) RSTRING_PTR(TO_S(v))
|
7
|
+
|
8
|
+
#include <ruby/ruby.h>
|
9
|
+
#include <ruby/encoding.h>
|
10
|
+
|
11
|
+
#include <libpq-fe.h>
|
12
|
+
#include <libpq/libpq-fs.h>
|
13
|
+
|
14
|
+
#include <fcntl.h>
|
15
|
+
#include <sys/stat.h>
|
16
|
+
#include <sys/types.h>
|
17
|
+
#include <time.h>
|
18
|
+
#include <unistd.h>
|
19
|
+
|
20
|
+
extern VALUE mSwift, mDB;
|
21
|
+
extern VALUE cDPA, cDPS, cDPR;
|
22
|
+
extern VALUE eSwiftError, eSwiftArgumentError, eSwiftRuntimeError, eSwiftConnectionError;
|
23
|
+
extern VALUE cStringIO;
|
24
|
+
|
25
|
+
DLL_PRIVATE VALUE rb_uuid_string();
|
26
|
+
DLL_PRIVATE VALUE db_postgres_normalized_sql(VALUE);
|
27
|
+
DLL_PRIVATE void db_postgres_check_result(PGresult *);
|
28
|
+
|
29
|
+
typedef struct Query {
|
30
|
+
PGconn *connection;
|
31
|
+
char *command;
|
32
|
+
int n_args;
|
33
|
+
char **data;
|
34
|
+
int *size, *format;
|
35
|
+
} Query;
|
@@ -0,0 +1,99 @@
|
|
1
|
+
#include "datetime.h"
|
2
|
+
#include <ctype.h>
|
3
|
+
|
4
|
+
extern VALUE dtformat;
|
5
|
+
|
6
|
+
VALUE cSwiftDateTime, day_seconds;
|
7
|
+
ID fcivil, fparse, fstrptime;
|
8
|
+
|
9
|
+
// NOTE: only parses '%F %T.%N %z' format and falls back to the built-in DateTime#parse
|
10
|
+
// and is almost 2x faster than doing:
|
11
|
+
//
|
12
|
+
// rb_funcall(klass, fstrptime, 2, rb_str_new(data, size), dtformat);
|
13
|
+
//
|
14
|
+
VALUE datetime_parse(VALUE klass, const char *data, size_t size) {
|
15
|
+
struct tm tm;
|
16
|
+
double seconds;
|
17
|
+
const char *ptr;
|
18
|
+
char tzsign = 0, fraction[32];
|
19
|
+
int tzhour = 0, tzmin = 0, lastmatch = -1, offset = 0, idx;
|
20
|
+
|
21
|
+
memset(&tm, 0, sizeof(struct tm));
|
22
|
+
sscanf(data, "%04d-%02d-%02d %02d:%02d:%02d%n",
|
23
|
+
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &lastmatch);
|
24
|
+
|
25
|
+
// fallback to default datetime parser, this is more expensive.
|
26
|
+
if (tm.tm_mday == 0)
|
27
|
+
return Qnil;
|
28
|
+
|
29
|
+
seconds = tm.tm_sec;
|
30
|
+
|
31
|
+
// parse millisecs if any -- tad faster than using %lf in sscanf above.
|
32
|
+
if (lastmatch > 0 && lastmatch < (int)size && *(data + lastmatch) == '.') {
|
33
|
+
idx = 0;
|
34
|
+
ptr = data + ++lastmatch;
|
35
|
+
while (*ptr && isdigit(*ptr) && idx < 31) {
|
36
|
+
lastmatch++;
|
37
|
+
fraction[idx++] = *ptr++;
|
38
|
+
}
|
39
|
+
|
40
|
+
fraction[idx] = 0;
|
41
|
+
seconds += (double)atoll(fraction) / pow(10, idx);
|
42
|
+
}
|
43
|
+
|
44
|
+
// parse timezone offsets if any - matches +HH:MM +HH MM +HHMM
|
45
|
+
if (lastmatch > 0 && lastmatch < (int)size) {
|
46
|
+
const char *ptr = data + lastmatch;
|
47
|
+
while(*ptr && *ptr != '+' && *ptr != '-') ptr++;
|
48
|
+
tzsign = *ptr++;
|
49
|
+
if (*ptr && isdigit(*ptr)) {
|
50
|
+
tzhour = *ptr++ - '0';
|
51
|
+
if (*ptr && isdigit(*ptr)) tzhour = tzhour * 10 + *ptr++ - '0';
|
52
|
+
while(*ptr && !isdigit(*ptr)) ptr++;
|
53
|
+
if (*ptr) {
|
54
|
+
tzmin = *ptr++ - '0';
|
55
|
+
if (*ptr && isdigit(*ptr)) tzmin = tzmin * 10 + *ptr++ - '0';
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
if (tzsign) {
|
61
|
+
offset = tzsign == '+'
|
62
|
+
? (time_t)tzhour * 3600 + (time_t)tzmin * 60
|
63
|
+
: (time_t)tzhour * -3600 + (time_t)tzmin * -60;
|
64
|
+
}
|
65
|
+
|
66
|
+
return rb_funcall(klass, fcivil, 7,
|
67
|
+
INT2FIX(tm.tm_year), INT2FIX(tm.tm_mon), INT2FIX(tm.tm_mday),
|
68
|
+
INT2FIX(tm.tm_hour), INT2FIX(tm.tm_min), DBL2NUM(seconds),
|
69
|
+
offset == 0 ? INT2FIX(0) : rb_Rational(INT2FIX(offset), day_seconds)
|
70
|
+
);
|
71
|
+
}
|
72
|
+
|
73
|
+
VALUE rb_datetime_parse(VALUE self, VALUE string) {
|
74
|
+
VALUE datetime;
|
75
|
+
const char *data = CSTRING(string);
|
76
|
+
size_t size = TYPE(string) == T_STRING ? (size_t)RSTRING_LEN(string) : strlen(data);
|
77
|
+
|
78
|
+
if (NIL_P(string))
|
79
|
+
return Qnil;
|
80
|
+
|
81
|
+
datetime = datetime_parse(self, data, size);
|
82
|
+
return NIL_P(datetime) ? rb_call_super(1, &string) : datetime;
|
83
|
+
}
|
84
|
+
|
85
|
+
void init_swift_datetime() {
|
86
|
+
VALUE mSwift, cDateTime;
|
87
|
+
|
88
|
+
rb_require("date");
|
89
|
+
mSwift = rb_define_module("Swift");
|
90
|
+
cDateTime = CONST_GET(rb_mKernel, "DateTime");
|
91
|
+
cSwiftDateTime = rb_define_class_under(mSwift, "DateTime", cDateTime);
|
92
|
+
fcivil = rb_intern("civil");
|
93
|
+
fparse = rb_intern("parse");
|
94
|
+
fstrptime = rb_intern("strptime");
|
95
|
+
day_seconds = INT2FIX(86400);
|
96
|
+
|
97
|
+
rb_global_variable(&day_seconds);
|
98
|
+
rb_define_singleton_method(cSwiftDateTime, "parse", RUBY_METHOD_FUNC(rb_datetime_parse), 1);
|
99
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'mkmf'
|
4
|
+
|
5
|
+
$CFLAGS = '-std=c99'
|
6
|
+
|
7
|
+
inc_paths = %w(
|
8
|
+
/usr/include/postgresql
|
9
|
+
/usr/local/include/postgresql
|
10
|
+
/opt/local/include
|
11
|
+
/opt/local/include/postgresql90
|
12
|
+
/opt/local/include/postgresql85
|
13
|
+
/opt/local/include/postgresql84
|
14
|
+
/sw/include
|
15
|
+
)
|
16
|
+
|
17
|
+
lib_paths = %w(
|
18
|
+
/usr/lib
|
19
|
+
/usr/local/lib
|
20
|
+
/opt/lib
|
21
|
+
/opt/local/lib
|
22
|
+
/sw/lib
|
23
|
+
)
|
24
|
+
|
25
|
+
(inc_paths << ENV['SPG_INCLUDE_DIRS']).compact!
|
26
|
+
(lib_paths << ENV['SPG_LIBRARY_DIRS']).compact!
|
27
|
+
|
28
|
+
find_header('libpq-fe.h', *inc_paths) or raise 'unable to locate postgresql headers set SPG_INCLUDE_DIRS'
|
29
|
+
find_header('uuid/uuid.h', *inc_paths) or raise 'unable to locate uuid headers set SPG_INCLUDE_DIRS'
|
30
|
+
|
31
|
+
find_library('pq', 'main', *lib_paths) or raise 'unable to locate postgresql lib set SPG_LIBRARY_DIRS'
|
32
|
+
find_library('uuid','main', *lib_paths) or raise 'unable to locate uuid lib set SPG_LIBRARY_DIRS'
|
33
|
+
create_makefile('swift_db_postgres_ext')
|
@@ -0,0 +1,28 @@
|
|
1
|
+
// vim:ts=4:sts=4:sw=4:expandtab
|
2
|
+
|
3
|
+
// (c) Bharanee Rathna 2012
|
4
|
+
|
5
|
+
#include "common.h"
|
6
|
+
#include "adapter.h"
|
7
|
+
#include "statement.h"
|
8
|
+
#include "result.h"
|
9
|
+
#include "datetime.h"
|
10
|
+
|
11
|
+
VALUE mSwift, mDB;
|
12
|
+
VALUE eSwiftError, eSwiftArgumentError, eSwiftRuntimeError, eSwiftConnectionError;
|
13
|
+
|
14
|
+
void Init_swift_db_postgres_ext() {
|
15
|
+
mSwift = rb_define_module("Swift");
|
16
|
+
mDB = rb_define_module_under(mSwift, "DB");
|
17
|
+
|
18
|
+
eSwiftError = rb_define_class_under(mSwift, "Error", rb_eStandardError);
|
19
|
+
eSwiftArgumentError = rb_define_class_under(mSwift, "ArgumentError", eSwiftError);
|
20
|
+
eSwiftRuntimeError = rb_define_class_under(mSwift, "RuntimeError", eSwiftError);
|
21
|
+
eSwiftConnectionError = rb_define_class_under(mSwift, "ConnectionError", eSwiftError);
|
22
|
+
|
23
|
+
init_swift_db_postgres_adapter();
|
24
|
+
init_swift_db_postgres_statement();
|
25
|
+
init_swift_db_postgres_result();
|
26
|
+
init_swift_datetime();
|
27
|
+
init_swift_db_postgres_typecast();
|
28
|
+
}
|
@@ -0,0 +1,172 @@
|
|
1
|
+
// vim:ts=4:sts=4:sw=4:expandtab
|
2
|
+
|
3
|
+
// (c) Bharanee Rathna 2012
|
4
|
+
|
5
|
+
#include "result.h"
|
6
|
+
#include <stdlib.h>
|
7
|
+
|
8
|
+
/* declaration */
|
9
|
+
|
10
|
+
typedef struct Result {
|
11
|
+
PGresult *result;
|
12
|
+
VALUE fields;
|
13
|
+
VALUE types;
|
14
|
+
VALUE rows;
|
15
|
+
size_t selected;
|
16
|
+
size_t affected;
|
17
|
+
size_t insert_id;
|
18
|
+
} Result;
|
19
|
+
|
20
|
+
VALUE cDPR;
|
21
|
+
|
22
|
+
/* definition */
|
23
|
+
|
24
|
+
Result* db_postgres_result_handle(VALUE self) {
|
25
|
+
Result *r;
|
26
|
+
Data_Get_Struct(self, Result, r);
|
27
|
+
if (!r)
|
28
|
+
rb_raise(eSwiftRuntimeError, "Invalid postgres result");
|
29
|
+
return r;
|
30
|
+
}
|
31
|
+
|
32
|
+
void db_postgres_result_mark(Result *r) {
|
33
|
+
if (r) {
|
34
|
+
if (r->fields)
|
35
|
+
rb_gc_mark_maybe(r->fields);
|
36
|
+
if (r->types)
|
37
|
+
rb_gc_mark_maybe(r->types);
|
38
|
+
if (r->rows)
|
39
|
+
rb_gc_mark_maybe(r->rows);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
VALUE db_postgres_result_deallocate(Result *r) {
|
44
|
+
if (r) {
|
45
|
+
if (r->result)
|
46
|
+
PQclear(r->result);
|
47
|
+
free(r);
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
VALUE db_postgres_result_allocate(VALUE klass) {
|
52
|
+
Result *r = (Result*)malloc(sizeof(Result));
|
53
|
+
memset(r, 0, sizeof(Result));
|
54
|
+
return Data_Wrap_Struct(klass, db_postgres_result_mark, db_postgres_result_deallocate, r);
|
55
|
+
}
|
56
|
+
|
57
|
+
VALUE db_postgres_result_load(VALUE self, PGresult *result) {
|
58
|
+
size_t n, rows, cols;
|
59
|
+
const char *type, *data;
|
60
|
+
|
61
|
+
Result *r = db_postgres_result_handle(self);
|
62
|
+
r->fields = rb_ary_new();
|
63
|
+
r->types = rb_ary_new();
|
64
|
+
r->rows = rb_ary_new();
|
65
|
+
r->result = result;
|
66
|
+
r->affected = atol(PQcmdTuples(result));
|
67
|
+
r->selected = PQntuples(result);
|
68
|
+
r->insert_id = 0;
|
69
|
+
|
70
|
+
rows = PQntuples(result);
|
71
|
+
cols = PQnfields(result);
|
72
|
+
if (rows > 0)
|
73
|
+
r->insert_id = PQgetisnull(result, 0, 0) ? 0 : atol(PQgetvalue(result, 0, 0));
|
74
|
+
|
75
|
+
for (n = 0; n < cols; n++) {
|
76
|
+
/* this must be a command execution result without field information */
|
77
|
+
if (!(data = PQfname(result, n)))
|
78
|
+
break;
|
79
|
+
rb_ary_push(r->fields, ID2SYM(rb_intern(data)));
|
80
|
+
|
81
|
+
switch (PQftype(result, n)) {
|
82
|
+
case 16: rb_ary_push(r->types, INT2NUM(SWIFT_TYPE_BOOLEAN)); break;
|
83
|
+
case 17: rb_ary_push(r->types, INT2NUM(SWIFT_TYPE_BLOB)); break;
|
84
|
+
case 20:
|
85
|
+
case 21:
|
86
|
+
case 23: rb_ary_push(r->types, INT2NUM(SWIFT_TYPE_INT)); break;
|
87
|
+
case 25: rb_ary_push(r->types, INT2NUM(SWIFT_TYPE_TEXT)); break;
|
88
|
+
case 700:
|
89
|
+
case 701: rb_ary_push(r->types, INT2NUM(SWIFT_TYPE_FLOAT)); break;
|
90
|
+
case 1082: rb_ary_push(r->types, INT2NUM(SWIFT_TYPE_DATE)); break;
|
91
|
+
case 1114:
|
92
|
+
case 1184: rb_ary_push(r->types, INT2NUM(SWIFT_TYPE_TIMESTAMP)); break;
|
93
|
+
case 1700: rb_ary_push(r->types, INT2NUM(SWIFT_TYPE_NUMERIC)); break;
|
94
|
+
case 1083:
|
95
|
+
case 1266: rb_ary_push(r->types, INT2NUM(SWIFT_TYPE_TIME)); break;
|
96
|
+
default: rb_ary_push(r->types, INT2NUM(SWIFT_TYPE_TEXT));
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
return self;
|
101
|
+
}
|
102
|
+
|
103
|
+
VALUE db_postgres_result_each(VALUE self) {
|
104
|
+
int row, col;
|
105
|
+
Result *r = db_postgres_result_handle(self);
|
106
|
+
|
107
|
+
if (!r->result)
|
108
|
+
return Qnil;
|
109
|
+
|
110
|
+
for (row = 0; row < PQntuples(r->result); row++) {
|
111
|
+
VALUE tuple = rb_hash_new();
|
112
|
+
for (col = 0; col < PQnfields(r->result); col++) {
|
113
|
+
if (PQgetisnull(r->result, row, col))
|
114
|
+
rb_hash_aset(tuple, rb_ary_entry(r->fields, col), Qnil);
|
115
|
+
else {
|
116
|
+
rb_hash_aset(
|
117
|
+
tuple,
|
118
|
+
rb_ary_entry(r->fields, col),
|
119
|
+
typecast_detect(
|
120
|
+
PQgetvalue(r->result, row, col),
|
121
|
+
PQgetlength(r->result, row, col),
|
122
|
+
NUM2INT(rb_ary_entry(r->types, col))
|
123
|
+
)
|
124
|
+
);
|
125
|
+
}
|
126
|
+
}
|
127
|
+
rb_yield(tuple);
|
128
|
+
}
|
129
|
+
return Qtrue;
|
130
|
+
}
|
131
|
+
|
132
|
+
VALUE db_postgres_result_selected_rows(VALUE self) {
|
133
|
+
Result *r = db_postgres_result_handle(self);
|
134
|
+
return SIZET2NUM(r->selected);
|
135
|
+
}
|
136
|
+
|
137
|
+
VALUE db_postgres_result_affected_rows(VALUE self) {
|
138
|
+
Result *r = db_postgres_result_handle(self);
|
139
|
+
return SIZET2NUM(r->selected > 0 ? 0 : r->affected);
|
140
|
+
}
|
141
|
+
|
142
|
+
VALUE db_postgres_result_fields(VALUE self) {
|
143
|
+
Result *r = db_postgres_result_handle(self);
|
144
|
+
return r->fields ? r->fields : rb_ary_new();
|
145
|
+
}
|
146
|
+
|
147
|
+
VALUE db_postgres_result_types(VALUE self) {
|
148
|
+
Result *r = db_postgres_result_handle(self);
|
149
|
+
return r->types ? typecast_description(r->types) : rb_ary_new();
|
150
|
+
}
|
151
|
+
|
152
|
+
VALUE db_postgres_result_insert_id(VALUE self) {
|
153
|
+
Result *r = db_postgres_result_handle(self);
|
154
|
+
return SIZET2NUM(r->insert_id);
|
155
|
+
}
|
156
|
+
|
157
|
+
void init_swift_db_postgres_result() {
|
158
|
+
rb_require("bigdecimal");
|
159
|
+
rb_require("stringio");
|
160
|
+
rb_require("date");
|
161
|
+
|
162
|
+
cDPR = rb_define_class_under(cDPA, "Result", rb_cObject);
|
163
|
+
|
164
|
+
rb_include_module(cDPR, rb_mEnumerable);
|
165
|
+
rb_define_alloc_func(cDPR, db_postgres_result_allocate);
|
166
|
+
rb_define_method(cDPR, "each", db_postgres_result_each, 0);
|
167
|
+
rb_define_method(cDPR, "selected_rows", db_postgres_result_selected_rows, 0);
|
168
|
+
rb_define_method(cDPR, "affected_rows", db_postgres_result_affected_rows, 0);
|
169
|
+
rb_define_method(cDPR, "fields", db_postgres_result_fields, 0);
|
170
|
+
rb_define_method(cDPR, "types", db_postgres_result_types, 0);
|
171
|
+
rb_define_method(cDPR, "insert_id", db_postgres_result_insert_id, 0);
|
172
|
+
}
|