rdo-postgres 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/.rspec +1 -0
- data/Gemfile +9 -0
- data/LICENSE +22 -0
- data/README.md +94 -0
- data/Rakefile +14 -0
- data/ext/rdo_postgres/casts.c +134 -0
- data/ext/rdo_postgres/casts.h +16 -0
- data/ext/rdo_postgres/driver.c +169 -0
- data/ext/rdo_postgres/driver.h +20 -0
- data/ext/rdo_postgres/extconf.rb +39 -0
- data/ext/rdo_postgres/macros.h +180 -0
- data/ext/rdo_postgres/params.c +106 -0
- data/ext/rdo_postgres/params.h +15 -0
- data/ext/rdo_postgres/rdo_postgres.c +18 -0
- data/ext/rdo_postgres/statements.c +237 -0
- data/ext/rdo_postgres/statements.h +15 -0
- data/ext/rdo_postgres/tuples.c +85 -0
- data/ext/rdo_postgres/tuples.h +20 -0
- data/lib/rdo-postgres.rb +1 -0
- data/lib/rdo/postgres.rb +16 -0
- data/lib/rdo/postgres/driver.rb +68 -0
- data/lib/rdo/postgres/version.rb +12 -0
- data/rdo-postgres.gemspec +23 -0
- data/spec/postgres/bind_params_spec.rb +902 -0
- data/spec/postgres/driver_spec.rb +285 -0
- data/spec/postgres/type_cast_spec.rb +272 -0
- data/spec/spec_helper.rb +10 -0
- metadata +126 -0
@@ -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
|
+
}
|