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