rdo-postgres 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ /*
2
+ * RDO Postgres Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include <ruby.h>
9
+ #include <libpq-fe.h>
10
+
11
+ /** Struct that RDO::Postgres::Driver wraps */
12
+ typedef struct {
13
+ PGconn * conn_ptr;
14
+ int is_open;
15
+ int stmt_count;
16
+ int encoding;
17
+ } RDOPostgresDriver;
18
+
19
+ /** Initializer called during extension init */
20
+ void Init_rdo_postgres_driver(void);
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ require "mkmf"
4
+
5
+ if ENV["CC"]
6
+ RbConfig::MAKEFILE_CONFIG["CC"] = ENV["CC"]
7
+ end
8
+
9
+ def config_value(type)
10
+ IO.popen("pg_config --#{type}").readline.chomp rescue nil
11
+ end
12
+
13
+ def have_build_env
14
+ [
15
+ have_library("pq") || have_library("libpq"),
16
+ have_header("libpq-fe.h"),
17
+ have_header("postgres.h"),
18
+ have_header("catalog/pg_type.h")
19
+ ].all?
20
+ end
21
+
22
+ dir_config(
23
+ "pgsql-server",
24
+ config_value("includedir-server"),
25
+ config_value("libdir")
26
+ )
27
+
28
+ dir_config(
29
+ "pgsql-client",
30
+ config_value("includedir"),
31
+ config_value("libdir")
32
+ )
33
+
34
+ unless have_build_env
35
+ puts "Unable to find postgresql libraries and headers. Not building."
36
+ exit(1)
37
+ end
38
+
39
+ create_makefile("rdo_postgres/rdo_postgres")
@@ -0,0 +1,180 @@
1
+ /*
2
+ * RDO Postgres Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ /** -------------------------------------------------------------------------
9
+ * These macros are for use by RDO driver developers.
10
+ *
11
+ * They simplify the logic needed when converting types provided by the RDBMS
12
+ * into their equivalent Ruby types.
13
+ *
14
+ * All of these macros take a C string and return a Ruby VALUE.
15
+ *
16
+ * The actual logic for many of the conversions is handled in RDO::Util, which
17
+ * is written in Ruby.
18
+ * --------------------------------------------------------------------------
19
+ */
20
+
21
+ #include <ruby.h>
22
+ #include <ruby/encoding.h>
23
+
24
+ /**
25
+ * Convert a C string to a ruby String.
26
+ *
27
+ * @param (char *) s
28
+ * a C string that is valid in the default encoding
29
+ *
30
+ * @param (size_t) len
31
+ * the length of the string
32
+ *
33
+ * @return VALUE (String)
34
+ * a Ruby String
35
+ */
36
+ #define RDO_STRING(s, len, enc) ( \
37
+ rb_enc_associate_index(rb_str_new(s, len), enc > 0 ? enc : 0) \
38
+ )
39
+
40
+ /**
41
+ * Convert a C string to a ruby String, assuming possible NULL bytes.
42
+ *
43
+ * @param (char *) s
44
+ * a C string, possibly containing nulls
45
+ *
46
+ * @param (size_t) len
47
+ * the length of the string
48
+ *
49
+ * @return VALUE (String)
50
+ * a Ruby String
51
+ */
52
+ #define RDO_BINARY_STRING(s, len) (rb_str_new(s, len))
53
+
54
+ /**
55
+ * Convert a C string to a Fixnum.
56
+ */
57
+ #define RDO_FIXNUM(s) (rb_cstr2inum(s, 10))
58
+
59
+ /**
60
+ * Convert a C string to a Float.
61
+ *
62
+ * This supports Infinity and NaN.
63
+ *
64
+ * @param (char *) s
65
+ * a C string representing a float (e.g. "1.234")
66
+ *
67
+ * @return VALUE (Float)
68
+ * a ruby Float
69
+ */
70
+ #define RDO_FLOAT(s) ( \
71
+ rb_funcall(rb_path2class("RDO::Util"), \
72
+ rb_intern("float"), 1, \
73
+ rb_str_new2(s)) \
74
+ )
75
+
76
+ /**
77
+ * Convert a C string representing a precision decimal into a BigDecimal.
78
+ *
79
+ * @param (char *) s
80
+ * a C string representing a decimal ("1.245")
81
+ *
82
+ * @return VALUE (BigDecimal)
83
+ * a BigDecimal representation of this string
84
+ *
85
+ * @example
86
+ * RDO_DECIMAL("1.234")
87
+ * => #<BigDecimal:7feb42b2b6e8,'0.1234E1',18(18)>
88
+ */
89
+ #define RDO_DECIMAL(s) ( \
90
+ rb_funcall(rb_path2class("RDO::Util"), \
91
+ rb_intern("decimal"), 1, \
92
+ rb_str_new2(s)) \
93
+ )
94
+
95
+ /**
96
+ * Convert a C string representing a date into a Date.
97
+ *
98
+ * @param (char *) s
99
+ * the C string with a parseable date
100
+ *
101
+ * @return VALUE (Date)
102
+ * a Date, exactly as was specified in the input
103
+ *
104
+ * @example
105
+ * RDO_DATE("431-09-22 BC")
106
+ * #<Date: -0430-09-22 ((1564265j,0s,0n),+0s,2299161j)>
107
+ */
108
+ #define RDO_DATE(s) ( \
109
+ rb_funcall(rb_path2class("RDO::Util"), \
110
+ rb_intern("date"), 1, \
111
+ rb_str_new2(s)) \
112
+ )
113
+
114
+ /**
115
+ * Convert a C string representing a date & time with no time zone into a DateTime.
116
+ *
117
+ * @param (char *) s
118
+ * the C string with the date & time provided
119
+ *
120
+ * @return VALUE (DateTime)
121
+ * a DateTime, assuming the system time zone
122
+ *
123
+ * @example
124
+ * RDO_DATE_TIME_WITHOUT_ZONE("2012-09-22 04:36:12")
125
+ * #<DateTime: 2012-09-22T04:36:12+10:00 ((2456192j,66972s,0n),+36000s,2299161j)>
126
+ */
127
+ #define RDO_DATE_TIME_WITHOUT_ZONE(s) ( \
128
+ rb_funcall(rb_path2class("RDO::Util"), \
129
+ rb_intern("date_time_without_zone"), 1, \
130
+ rb_str_new2(s)) \
131
+ )
132
+
133
+ /**
134
+ * Convert a C string representing a date & time that includes a time zone into a DateTime.
135
+ *
136
+ * @param (char *) s
137
+ * the C string with the date & time provided, including the time zone
138
+ *
139
+ * @return VALUE (DateTime)
140
+ * a DateTime, exactly as was specified in the input
141
+ *
142
+ * @example
143
+ * RDO_DATE_TIME_WITHOUT_ZONE("2012-09-22 04:36:12+10:00")
144
+ * #<DateTime: 2012-09-22T04:36:12+10:00 ((2456192j,66972s,0n),+36000s,2299161j)>
145
+ */
146
+ #define RDO_DATE_TIME_WITH_ZONE(s) ( \
147
+ rb_funcall(rb_path2class("RDO::Util"), \
148
+ rb_intern("date_time_with_zone"), 1, \
149
+ rb_str_new2(s)) \
150
+ )
151
+
152
+ /**
153
+ * Convert a boolean string to TrueClass/FalseClass.
154
+ *
155
+ * @param (char *) s
156
+ * a C string that is either 't', 'true', 'f' or 'false'
157
+ *
158
+ * @return VALUE (TrueClass, FalseClass)
159
+ * the boolean representation
160
+ */
161
+ #define RDO_BOOL(s) ((s[0] == 't') ? Qtrue : Qfalse)
162
+
163
+ /**
164
+ * Wrap the given StatementExecutor in a RDO::Statement.
165
+ *
166
+ * @param VALUE
167
+ * any object that responds to #command and #execute
168
+ *
169
+ * @return VALUE
170
+ * an RDO::Statement
171
+ */
172
+ #define RDO_STATEMENT(executor) ( \
173
+ rb_funcall(rb_path2class("RDO::Statement"), \
174
+ rb_intern("new"), 1, executor) \
175
+ )
176
+
177
+ /**
178
+ * Convenience to call #to_s on any Ruby object.
179
+ */
180
+ #define RDO_OBJ_TO_S(obj) (rb_funcall(obj, rb_intern("to_s"), 0))
@@ -0,0 +1,106 @@
1
+ /*
2
+ * RDO Postgres Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include "params.h"
9
+ #include <stdlib.h>
10
+ #include <string.h>
11
+
12
+ /** Get the strlen of the marker (e.g. $2) based on its index */
13
+ #define RDO_PG_MARKER_LEN(n) (n >= 100 ? 4 : (n >= 10 ? 3 : 2))
14
+
15
+ /**
16
+ * Replace e.g. "SELECT ? WHERE ?" with "SELECT $1 WHERE $2".
17
+ *
18
+ * Handles string literals and comments.
19
+ *
20
+ * This function is deliberately not broken apart, since it needs to be extremely fast.
21
+ */
22
+ char * rdo_postgres_params_inject_markers(char * stmt) {
23
+ char * buf = malloc(sizeof(char) * strlen(stmt) * 4);
24
+ char * s = stmt;
25
+ char * b = buf;
26
+ int n = 0;
27
+ int instr = 0;
28
+ int inident = 0;
29
+ int inslcmt = 0;
30
+ int inmlcmt = 0;
31
+
32
+ for (; *s; ++s, ++b) {
33
+ switch (*s) {
34
+ case '/':
35
+ if (!instr && !inident && !inslcmt && *(s + 1) == '*') {
36
+ ++inmlcmt;
37
+ *b = *s;
38
+ *(++b) = *(++s);
39
+ } else {
40
+ *b = *s;
41
+ }
42
+ break;
43
+
44
+ case '*':
45
+ if (inmlcmt && *(s + 1) == '/') {
46
+ --inmlcmt;
47
+ *b = *s;
48
+ *(++b) = *(++s);
49
+ } else {
50
+ *b = *s;
51
+ }
52
+ break;
53
+
54
+ case '-':
55
+ if (!instr && !inident && !inmlcmt && *(s + 1) == '-') {
56
+ inslcmt = 1;
57
+ *b = *s;
58
+ *(++b) = *(++s);
59
+ } else {
60
+ *b = *s;
61
+ }
62
+ break;
63
+
64
+ case '\n':
65
+ case '\r':
66
+ inslcmt = 0;
67
+ *b = *s;
68
+ break;
69
+
70
+ case '\'':
71
+ if (!inident && !inmlcmt && !inslcmt) {
72
+ instr = !instr;
73
+ *b = *s;
74
+ } else {
75
+ *b = *s;
76
+ }
77
+ break;
78
+
79
+ case '"':
80
+ if (!instr && !inmlcmt && !inslcmt) {
81
+ inident = !inident;
82
+ *b = *s;
83
+ } else {
84
+ *b = *s;
85
+ }
86
+ break;
87
+
88
+ case '?':
89
+ if (!instr && !inident && !inmlcmt && !inslcmt) {
90
+ sprintf(b, "$%i", ++n);
91
+ b += RDO_PG_MARKER_LEN(n -1) - 1;
92
+ } else {
93
+ *b= *s;
94
+ }
95
+ break;
96
+
97
+ default:
98
+ *b = *s;
99
+ break;
100
+ }
101
+ }
102
+
103
+ *b = '\0';
104
+
105
+ return buf;
106
+ }
@@ -0,0 +1,15 @@
1
+ /*
2
+ * RDO Postgres Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include <stdio.h>
9
+
10
+ /**
11
+ * Make a copy of the string sql, replacing '?' markers with numbered $1, $2 etc.
12
+ *
13
+ * Memory must be released with free() once the new string is no longer in use.
14
+ */
15
+ char * rdo_postgres_params_inject_markers(char * sql);
@@ -0,0 +1,18 @@
1
+ /*
2
+ * RDO Postgres Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include <stdlib.h>
9
+ #include <ruby.h>
10
+ #include "driver.h"
11
+
12
+ /**
13
+ * Extension initializer.
14
+ */
15
+ void Init_rdo_postgres(void) {
16
+ rb_require("rdo");
17
+ Init_rdo_postgres_driver();
18
+ }
@@ -0,0 +1,237 @@
1
+ /*
2
+ * RDO Postgres Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include "statements.h"
9
+ #include "driver.h"
10
+ #include "params.h"
11
+ #include "tuples.h"
12
+ #include "macros.h"
13
+ #include <stdlib.h>
14
+ #include <libpq-fe.h>
15
+ #include <postgres.h>
16
+ #include <catalog/pg_type.h>
17
+
18
+ /** I don't like magic numbers */
19
+ #define RDO_PG_NO_OIDS 0
20
+ #define RDO_PG_INFER_TYPES NULL
21
+ #define RDO_PG_TEXT_INPUT NULL
22
+ #define RDO_PG_TEXT_OUTPUT 0
23
+
24
+ /** RDO::Postgres::StatementExecutor */
25
+ static VALUE rdo_postgres_cStatementExecutor;
26
+
27
+ /** Struct that the StatementExecutor is wrapped around */
28
+ typedef struct {
29
+ char * stmt_name;
30
+ char * cmd;
31
+ int nparams;
32
+ Oid * param_types;
33
+ RDOPostgresDriver * driver;
34
+ } RDOPostgresStatementExecutor;
35
+
36
+ /** Free memory associated with the StatementExecutor during GC */
37
+ static void rdo_postgres_statement_executor_free(
38
+ RDOPostgresStatementExecutor * executor) {
39
+ free(executor->stmt_name);
40
+ free(executor->cmd);
41
+ free(executor->param_types);
42
+ free(executor);
43
+ }
44
+
45
+ /** Extract information about the result into a ruby Hash */
46
+ static VALUE rdo_postgres_result_info_new(PGresult * res) {
47
+ VALUE info = rb_hash_new();
48
+ rb_hash_aset(info, ID2SYM(rb_intern("count")), INT2NUM(PQntuples(res)));
49
+ if (strlen(PQcmdTuples(res)) > 0) {
50
+ rb_hash_aset(info,
51
+ ID2SYM(rb_intern("affected_rows")),
52
+ rb_cstr2inum(PQcmdTuples(res), 10));
53
+ }
54
+ return info;
55
+ }
56
+
57
+ /** Actually issue the PQprepare() command */
58
+ static void rdo_postgres_statement_executor_prepare(VALUE self) {
59
+ RDOPostgresStatementExecutor * executor;
60
+ Data_Get_Struct(self, RDOPostgresStatementExecutor, executor);
61
+
62
+ if (!(executor->driver->is_open)) {
63
+ rb_raise(rb_path2class("RDO::Exception"),
64
+ "Unable to prepare statement: connection is not open");
65
+ }
66
+
67
+ char * cmd = rdo_postgres_params_inject_markers(executor->cmd);
68
+ PGresult * res;
69
+ ExecStatusType status;
70
+
71
+ res = PQprepare(
72
+ executor->driver->conn_ptr,
73
+ executor->stmt_name,
74
+ cmd,
75
+ RDO_PG_NO_OIDS,
76
+ RDO_PG_INFER_TYPES);
77
+
78
+ free(cmd);
79
+
80
+ status = PQresultStatus(res);
81
+
82
+ if (status != PGRES_BAD_RESPONSE && status != PGRES_FATAL_ERROR) {
83
+ PQclear(res);
84
+ } else {
85
+ char msg[sizeof(char) * (strlen(PQresultErrorMessage(res)) + 1)];
86
+ strcpy(msg, PQresultErrorMessage(res));
87
+ PQclear(res);
88
+ rb_raise(rb_path2class("RDO::Exception"),
89
+ "Failed to prepare statement: %s", msg);
90
+ }
91
+
92
+ res = PQdescribePrepared(executor->driver->conn_ptr, executor->stmt_name);
93
+
94
+ if (status != PGRES_COMMAND_OK) {
95
+ char msg[sizeof(char) * (strlen(PQresultErrorMessage(res)) + 1)];
96
+ strcpy(msg, PQresultErrorMessage(res));
97
+ PQclear(res);
98
+ rb_raise(rb_path2class("RDO::Exception"),
99
+ "Failed to prepare statement: %s", msg);
100
+ }
101
+
102
+ executor->nparams = PQnparams(res);
103
+ executor->param_types = malloc(sizeof(Oid) * executor->nparams);
104
+
105
+ int i = 0;
106
+ for (; i < executor->nparams; ++i) {
107
+ executor->param_types[i] = PQparamtype(res, i);
108
+ }
109
+
110
+ PQclear(res);
111
+ }
112
+
113
+ /** Factory method to return a new StatementExecutor */
114
+ VALUE rdo_postgres_statement_executor_new(VALUE driver, VALUE cmd, VALUE name) {
115
+ Check_Type(cmd, T_STRING);
116
+ Check_Type(name, T_STRING);
117
+
118
+ RDOPostgresStatementExecutor * executor =
119
+ malloc(sizeof(RDOPostgresStatementExecutor));
120
+
121
+ Data_Get_Struct(driver, RDOPostgresDriver, executor->driver);
122
+ executor->stmt_name = strdup(RSTRING_PTR(name));
123
+ executor->cmd = strdup(RSTRING_PTR(cmd));
124
+ executor->nparams = 0;
125
+ executor->param_types = NULL;
126
+
127
+ VALUE self = Data_Wrap_Struct(rdo_postgres_cStatementExecutor, 0,
128
+ rdo_postgres_statement_executor_free, executor);
129
+
130
+ rb_obj_call_init(self, 1, &driver);
131
+
132
+ return self;
133
+ }
134
+
135
+ /** Initialize the StatementExecutor with the given driver and command */
136
+ static VALUE rdo_postgres_statement_executor_initialize(VALUE self, VALUE driver) {
137
+ rb_iv_set(self, "driver", driver); // GC safety
138
+ rdo_postgres_statement_executor_prepare(self);
139
+ return self;
140
+ }
141
+
142
+ /** Accessor for the @command ivar */
143
+ static VALUE rdo_postgres_statement_executor_command(VALUE self) {
144
+ RDOPostgresStatementExecutor * executor;
145
+ Data_Get_Struct(self, RDOPostgresStatementExecutor, executor);
146
+ return rb_str_new2(executor->cmd);
147
+ }
148
+
149
+ /** Execute with PQexecPrepared() and return a Result */
150
+ static VALUE rdo_postgres_statement_executor_execute(int argc, VALUE * args,
151
+ VALUE self) {
152
+
153
+ RDOPostgresStatementExecutor * executor;
154
+ Data_Get_Struct(self, RDOPostgresStatementExecutor, executor);
155
+
156
+ if (!(executor->driver->is_open)) {
157
+ rb_raise(rb_path2class("RDO::Exception"),
158
+ "Unable to execute statement: connection is not open");
159
+ }
160
+
161
+ if (argc != executor->nparams) {
162
+ rb_raise(rb_path2class("RDO::Exception"),
163
+ "Bind parameter count mismatch: wanted %i, got %i",
164
+ executor->nparams, argc);
165
+ }
166
+
167
+ char * values[argc];
168
+ size_t lengths[argc];
169
+ int i;
170
+
171
+ for (i = 0; i < argc; ++i) {
172
+ if (TYPE(args[i]) == T_NIL) {
173
+ values[i] = NULL;
174
+ lengths[i] = 0;
175
+ } else {
176
+ if (TYPE(args[i]) != T_STRING) {
177
+ args[i] = RDO_OBJ_TO_S(args[i]);
178
+ }
179
+
180
+ if (executor->param_types[i] == BYTEAOID) {
181
+ values[i] = PQescapeByteaConn(executor->driver->conn_ptr,
182
+ RSTRING_PTR(args[i]),
183
+ RSTRING_LEN(args[i]),
184
+ &(lengths[i]));
185
+ } else {
186
+ values[i] = RSTRING_PTR(args[i]);
187
+ lengths[i] = RSTRING_LEN(args[i]);
188
+ }
189
+ }
190
+ }
191
+
192
+ PGresult * res = PQexecPrepared(
193
+ executor->driver->conn_ptr,
194
+ executor->stmt_name,
195
+ argc,
196
+ (const char **) values,
197
+ (const int *) lengths,
198
+ RDO_PG_TEXT_INPUT,
199
+ RDO_PG_TEXT_OUTPUT);
200
+
201
+ for (i = 0; i < argc; ++i) {
202
+ if (executor->param_types[i] == BYTEAOID) {
203
+ PQfreemem(values[i]);
204
+ }
205
+ }
206
+
207
+ ExecStatusType status = PQresultStatus(res);
208
+
209
+ if (status == PGRES_BAD_RESPONSE || status == PGRES_FATAL_ERROR) {
210
+ rb_raise(rb_path2class("RDO::Exception"),
211
+ "Failed to execute statement: %s", PQresultErrorMessage(res));
212
+ }
213
+
214
+ return rb_funcall(rb_path2class("RDO::Result"),
215
+ rb_intern("new"), 2,
216
+ rdo_postgres_tuple_list_new(res, executor->driver->encoding),
217
+ rdo_postgres_result_info_new(res));
218
+ }
219
+
220
+ /** Statements framework initializer, called during extension init */
221
+ void Init_rdo_postgres_statements(void) {
222
+ VALUE mPostgres = rb_path2class("RDO::Postgres");
223
+
224
+ rdo_postgres_cStatementExecutor = rb_define_class_under(
225
+ mPostgres, "StatementExecutor", rb_cObject);
226
+
227
+ rb_define_method(rdo_postgres_cStatementExecutor,
228
+ "initialize", rdo_postgres_statement_executor_initialize, 1);
229
+
230
+ rb_define_method(rdo_postgres_cStatementExecutor,
231
+ "command", rdo_postgres_statement_executor_command, 0);
232
+
233
+ rb_define_method(rdo_postgres_cStatementExecutor,
234
+ "execute", rdo_postgres_statement_executor_execute, -1);
235
+
236
+ Init_rdo_postgres_tuples();
237
+ }