rdo-postgres 0.0.1
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/.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
|
+
}
|