rdo-postgres 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -8,9 +8,8 @@ This is the PostgreSQL driver for [RDO—Ruby Data Objects]
8
8
  Refer to the RDO project [README](https://github.com/d11wtq/rdo) for usage
9
9
  information.
10
10
 
11
- This driver cannot be used with Postgres versions older than 7.4, since the
12
- protocol has changed and this driver takes advantage of newer protocol (3.0)
13
- features.
11
+ This driver cannot be used with PostgreSQL versions older than 7.4. Those
12
+ versions are no longer supported by PostgreSQL in any case.
14
13
 
15
14
  ## Installation
16
15
 
@@ -51,6 +50,33 @@ conn = RDO.connect(
51
50
  )
52
51
  ```
53
52
 
53
+ ### Type Support
54
+
55
+ If not listed below, the String form of the value will be returned. The
56
+ currently mapped types are:
57
+
58
+ - NULL -> nil
59
+ - BOOLEAN -> TrueClass/FalseClass
60
+ - TEXT -> String
61
+ - VARCHAR -> String
62
+ - CHAR -> String
63
+ - BYTEA -> String
64
+ - INTEGER -> Fixnum
65
+ - INT2 -> Fixnum
66
+ - INT4 -> Fixnum
67
+ - INT8 -> Fixnum
68
+ - FLOAT/REAL -> Float
69
+ - FLOAT4 -> Float
70
+ - FLOAT8 -> Float
71
+ - NUMERIC/DECIMAL -> BigDecimal
72
+ - DATE -> Date
73
+ - TIMESTAMP -> DateTime (in the system time zone)
74
+ - TIMESTAMPTZ -> DateTime (in the specified time zone)
75
+
76
+ All 1-dimensional Arrays of the above listed are also available. Support for
77
+ multi-dimensional Arrays is planned immediately. Support for custom-typed
78
+ Arrays is coming.
79
+
54
80
  ### Bind parameters support
55
81
 
56
82
  PostgreSQL uses $1, $2 etc for bind parameters. RDO uses '?'. You can use
@@ -72,17 +98,13 @@ conn.execute("SELECT * FROM users WHERE banned = $1 AND created_at > ?", true, 1
72
98
 
73
99
  ## Contributing
74
100
 
75
- Contributions to support older versions of PostgreSQL (< 7.4) welcomed,
76
- though if the changes required break the native support for newer versions
77
- pull requests will not be accepted. It is possible to write a separate
78
- driver for older versions of PostgreSQL and register it under the driver
79
- name 'postgresql' instead of this one if that is preferable. Alternatively,
80
- use an explicit name that indicates legacy compatibility, such as
81
- 'postgres73'.
82
-
83
101
  If you find any bugs, please send a pull request if you think you can
84
102
  fix it, or file in an issue in the issue tracker.
85
103
 
104
+ I'm particulary interested in patches surrounding support for built-in type
105
+ arrays, multi-dimensional arrays and arrays of custom types, such as ENUMs
106
+ (in order of difficulty/preference).
107
+
86
108
  When sending pull requests, please use topic branches—don't send a pull
87
109
  request from the master branch of your fork, as that may change
88
110
  unintentionally.
@@ -0,0 +1,48 @@
1
+ /*
2
+ * RDO Postgres Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include "arrays.h"
9
+ #include "casts.h"
10
+ #include "macros.h"
11
+ #include <ruby.h>
12
+ #include <libpq-fe.h>
13
+
14
+ /** Parse a bytea string into a binary Ruby String */
15
+ static VALUE rdo_postgres_array_bytea_parse_value(VALUE self, VALUE s) {
16
+ s = rb_call_super(1, &s);
17
+ Check_Type(s, T_STRING);
18
+ return rdo_postgres_cast_bytea(RSTRING_PTR(s), RSTRING_LEN(s));
19
+ }
20
+
21
+ /** Format a value as a bytea */
22
+ static VALUE rdo_postgres_array_bytea_format_value(VALUE self, VALUE v) {
23
+ if (TYPE(v) != T_STRING) {
24
+ v = RDO_OBJ_TO_S(v);
25
+ }
26
+
27
+ size_t len = 0;
28
+ unsigned char * bytea = PQescapeBytea((unsigned char *) RSTRING_PTR(v),
29
+ RSTRING_LEN(v), &len);
30
+
31
+ VALUE escaped = rb_str_new((char *) bytea, len - 1);
32
+ VALUE formatted = rb_call_super(1, &escaped);
33
+
34
+ PQfreemem(bytea);
35
+
36
+ return formatted;
37
+ }
38
+
39
+ /** Initialize Array extensions */
40
+ void Init_rdo_postgres_arrays(void) {
41
+ VALUE cByteaArray = rb_path2class("RDO::Postgres::Array::Bytea");
42
+
43
+ rb_define_method(cByteaArray,
44
+ "parse_value", rdo_postgres_array_bytea_parse_value, 1);
45
+
46
+ rb_define_method(cByteaArray,
47
+ "format_value", rdo_postgres_array_bytea_format_value, 1);
48
+ }
@@ -0,0 +1,9 @@
1
+ /*
2
+ * RDO Postgres Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ /** Initialize Array C extensions */
9
+ void Init_rdo_postgres_arrays(void);
@@ -7,13 +7,18 @@
7
7
 
8
8
  #include "casts.h"
9
9
  #include <stdlib.h>
10
- #include <postgres.h>
11
- #include <catalog/pg_type.h>
12
10
  #include "macros.h"
11
+ #include "types.h"
13
12
 
14
13
  /** Predicate test if the given string is formatted as \x0afe... */
15
14
  #define RDO_PG_NEW_HEX_P(s, len) (len >= 2 && s[0] == '\\' && s[1] == 'x')
16
15
 
16
+ /** Parse a PostgreSQL array and return an Array for the given type */
17
+ #define RDO_PG_ARRAY(clsname, s, len) \
18
+ (rb_funcall((rb_funcall(rb_path2class("RDO::Postgres::Array::" clsname), \
19
+ rb_intern("parse"), 1, rb_str_new(s, len))), \
20
+ rb_intern("to_a"), 0))
21
+
17
22
  /** Lookup table for fast conversion of bytea hex strings to binary data */
18
23
  static char * RDOPostgres_HexLookup;
19
24
 
@@ -45,19 +50,27 @@ static VALUE rdo_postgres_cast_bytea_hex(char * hex, size_t len) {
45
50
 
46
51
  /** Cast from a bytea to a String according to a regular escape format */
47
52
  static VALUE rdo_postgres_cast_bytea_escape(char * escaped, size_t len) {
48
- unsigned char * buffer = PQunescapeBytea(escaped, &len);
53
+ unsigned char * buffer = PQunescapeBytea((unsigned char *) escaped, &len);
49
54
 
50
55
  if (buffer == NULL) {
51
56
  rb_raise(rb_eRuntimeError,
52
57
  "Failed to allocate memory for PQunescapeBytea() conversion");
53
58
  }
54
59
 
55
- VALUE str = rb_str_new(buffer, len);
60
+ VALUE str = rb_str_new((char *) buffer, len);
56
61
  PQfreemem(buffer);
57
62
 
58
63
  return str;
59
64
  }
60
65
 
66
+ /** Cast from a bytea to a String, automatically detecting the format */
67
+ VALUE rdo_postgres_cast_bytea(char * escaped, size_t len) {
68
+ if (RDO_PG_NEW_HEX_P(escaped, len))
69
+ return rdo_postgres_cast_bytea_hex(escaped, len);
70
+ else
71
+ return rdo_postgres_cast_bytea_escape(escaped, len);
72
+ }
73
+
61
74
  /** Get the value as a ruby type */
62
75
  VALUE rdo_postgres_cast_value(PGresult * res, int row, int col, int enc) {
63
76
  if (PQgetisnull(res, row, col)) {
@@ -68,42 +81,72 @@ VALUE rdo_postgres_cast_value(PGresult * res, int row, int col, int enc) {
68
81
  int length = PQgetlength(res, row, col);
69
82
 
70
83
  switch (PQftype(res, col)) {
71
- case INT2OID:
72
- case INT4OID:
73
- case INT8OID:
84
+ case RDO_PG_INT2OID:
85
+ case RDO_PG_INT4OID:
86
+ case RDO_PG_INT8OID:
74
87
  return RDO_FIXNUM(value);
75
88
 
76
- case FLOAT4OID:
77
- case FLOAT8OID:
89
+ case RDO_PG_FLOAT4OID:
90
+ case RDO_PG_FLOAT8OID:
78
91
  return RDO_FLOAT(value);
79
92
 
80
- case NUMERICOID:
93
+ case RDO_PG_NUMERICOID:
81
94
  return RDO_DECIMAL(value);
82
95
 
83
- case BOOLOID:
96
+ case RDO_PG_BOOLOID:
84
97
  return RDO_BOOL(value);
85
98
 
86
- case BYTEAOID:
87
- if (RDO_PG_NEW_HEX_P(value, length))
88
- return rdo_postgres_cast_bytea_hex(value, length);
89
- else
90
- return rdo_postgres_cast_bytea_escape(value, length);
99
+ case RDO_PG_BYTEAOID:
100
+ return rdo_postgres_cast_bytea(value, length);
91
101
 
92
- case DATEOID:
102
+ case RDO_PG_DATEOID:
93
103
  return RDO_DATE(value);
94
104
 
95
- case TIMESTAMPOID:
105
+ case RDO_PG_TIMESTAMPOID:
96
106
  return RDO_DATE_TIME_WITHOUT_ZONE(value);
97
107
 
98
- case TIMESTAMPTZOID:
108
+ case RDO_PG_TIMESTAMPTZOID:
99
109
  return RDO_DATE_TIME_WITH_ZONE(value);
100
110
 
101
- case TEXTOID:
102
- case CHAROID:
103
- case VARCHAROID:
104
- case BPCHAROID:
111
+ case RDO_PG_TEXTOID:
112
+ case RDO_PG_CHAROID:
113
+ case RDO_PG_VARCHAROID:
114
+ case RDO_PG_BPCHAROID:
105
115
  return RDO_STRING(value, length, enc);
106
116
 
117
+ case RDO_PG_TEXTARRAYOID:
118
+ case RDO_PG_CHARARRAYOID:
119
+ case RDO_PG_BPCHARARRAYOID:
120
+ case RDO_PG_VARCHARARRAYOID:
121
+ return RDO_PG_ARRAY("Text", value, length);
122
+
123
+ case RDO_PG_INT2ARRAYOID:
124
+ case RDO_PG_INT4ARRAYOID:
125
+ case RDO_PG_INT8ARRAYOID:
126
+ return RDO_PG_ARRAY("Integer", value, length);
127
+
128
+ case RDO_PG_FLOAT4ARRAYOID:
129
+ case RDO_PG_FLOAT8ARRAYOID:
130
+ return RDO_PG_ARRAY("Float", value, length);
131
+
132
+ case RDO_PG_NUMERICARRAYOID:
133
+ return RDO_PG_ARRAY("Numeric", value, length);
134
+
135
+ case RDO_PG_BOOLARRAYOID:
136
+ return RDO_PG_ARRAY("Boolean", value, length);
137
+
138
+ case RDO_PG_BYTEAARRAYOID:
139
+ return RDO_PG_ARRAY("Bytea", value, length);
140
+
141
+ case RDO_PG_DATEARRAYOID:
142
+ return RDO_PG_ARRAY("Date", value, length);
143
+
144
+ case RDO_PG_TIMESTAMPARRAYOID:
145
+ return RDO_PG_ARRAY("Timestamp", value, length);
146
+
147
+ case RDO_PG_TIMESTAMPTZARRAYOID:
148
+ return RDO_PG_ARRAY("TimestampTZ", value, length);
149
+
107
150
  default:
108
151
  return RDO_BINARY_STRING(value, length);
109
152
  }
@@ -111,11 +154,11 @@ VALUE rdo_postgres_cast_value(PGresult * res, int row, int col, int enc) {
111
154
 
112
155
  /* Initialize hex decoding lookup table */
113
156
  void Init_rdo_postgres_casts(void) {
114
- RDOPostgres_HexLookup = malloc(sizeof(char) * 128);
157
+ RDOPostgres_HexLookup = malloc(sizeof(char) * 256);
115
158
 
116
159
  if (RDOPostgres_HexLookup == NULL) {
117
160
  rb_raise(rb_eRuntimeError,
118
- "Failed to allocate 128 bytes for internal lookup table");
161
+ "Failed to allocate 256 bytes for internal lookup table");
119
162
  }
120
163
 
121
164
  char c;
@@ -12,5 +12,8 @@
12
12
  /** Cast the given value from the result to a ruby type */
13
13
  VALUE rdo_postgres_cast_value(PGresult * res, int row, int col, int enc);
14
14
 
15
+ /** Special case for casting a bytea value */
16
+ VALUE rdo_postgres_cast_bytea(char * escaped, size_t len);
17
+
15
18
  /** Initialize the casting framework */
16
19
  void Init_rdo_postgres_casts(void);
@@ -49,12 +49,10 @@ static VALUE rdo_postgres_driver_open(VALUE self) {
49
49
  RSTRING_PTR(rb_funcall(self, rb_intern("connect_db_string"), 0)));
50
50
 
51
51
  if (driver->conn_ptr == NULL || PQstatus(driver->conn_ptr) == CONNECTION_BAD) {
52
- rb_raise(rb_path2class("RDO::Exception"),
53
- "PostgreSQL connection failed: %s",
52
+ RDO_ERROR("PostgreSQL connection failed: %s",
54
53
  PQerrorMessage(driver->conn_ptr));
55
54
  } else if (PQprotocolVersion(driver->conn_ptr) < 3) {
56
- rb_raise(rb_path2class("RDO::Exception"),
57
- "rdo-postgres requires PostgreSQL protocol version >= 3 (using %u). "
55
+ RDO_ERROR("rdo-postgres requires PostgreSQL protocol version >= 3 (using %u). "
58
56
  "PostgreSQL >= 7.4 required.",
59
57
  PQprotocolVersion(driver->conn_ptr));
60
58
  } else {
@@ -98,8 +96,7 @@ static VALUE rdo_postgres_driver_prepare(VALUE self, VALUE cmd) {
98
96
  Data_Get_Struct(self, RDOPostgresDriver, driver);
99
97
 
100
98
  if (!(driver->is_open)) {
101
- rb_raise(rb_path2class("RDO::Exception"),
102
- "Unable to prepare statement: connection is not open");
99
+ RDO_ERROR("Unable to prepare statement: connection is not open");
103
100
  }
104
101
 
105
102
  char name[32];
@@ -120,8 +117,7 @@ static VALUE rdo_postgres_driver_quote(VALUE self, VALUE str) {
120
117
  Data_Get_Struct(self, RDOPostgresDriver, driver);
121
118
 
122
119
  if (!(driver->is_open)) {
123
- rb_raise(rb_path2class("RDO::Exception"),
124
- "Unable to quote string: connection is not open");
120
+ RDO_ERROR("Unable to quote string: connection is not open");
125
121
  }
126
122
 
127
123
  char * quoted = malloc(sizeof(char) * RSTRING_LEN(str) * 2 + 1);
@@ -14,8 +14,7 @@ def have_build_env
14
14
  [
15
15
  have_library("pq") || have_library("libpq"),
16
16
  have_header("libpq-fe.h"),
17
- have_header("postgres.h"),
18
- have_header("catalog/pg_type.h")
17
+ have_header("postgres.h")
19
18
  ].all?
20
19
  end
21
20
 
@@ -8,6 +8,7 @@
8
8
  #include <stdlib.h>
9
9
  #include <ruby.h>
10
10
  #include "driver.h"
11
+ #include "arrays.h"
11
12
 
12
13
  /**
13
14
  * Extension initializer.
@@ -15,4 +16,5 @@
15
16
  void Init_rdo_postgres(void) {
16
17
  rb_require("rdo");
17
18
  Init_rdo_postgres_driver();
19
+ Init_rdo_postgres_arrays();
18
20
  }
@@ -12,8 +12,7 @@
12
12
  #include "macros.h"
13
13
  #include <stdlib.h>
14
14
  #include <libpq-fe.h>
15
- #include <postgres.h>
16
- #include <catalog/pg_type.h>
15
+ #include "types.h"
17
16
 
18
17
  /** I don't like magic numbers */
19
18
  #define RDO_PG_NO_OIDS 0
@@ -21,6 +20,11 @@
21
20
  #define RDO_PG_TEXT_INPUT NULL
22
21
  #define RDO_PG_TEXT_OUTPUT 0
23
22
 
23
+ /** Wrap a Ruby Array with a RDO::Postgres::Array */
24
+ #define RDO_PG_WRAP_ARRAY(clsname, a) \
25
+ (rb_funcall(rb_path2class("RDO::Postgres::Array::" clsname), \
26
+ rb_intern("new"), 1, a))
27
+
24
28
  /** RDO::Postgres::StatementExecutor */
25
29
  static VALUE rdo_postgres_cStatementExecutor;
26
30
 
@@ -60,8 +64,7 @@ static void rdo_postgres_statement_executor_prepare(VALUE self) {
60
64
  Data_Get_Struct(self, RDOPostgresStatementExecutor, executor);
61
65
 
62
66
  if (!(executor->driver->is_open)) {
63
- rb_raise(rb_path2class("RDO::Exception"),
64
- "Unable to prepare statement: connection is not open");
67
+ RDO_ERROR("Unable to prepare statement: connection is not open");
65
68
  }
66
69
 
67
70
  char * cmd = rdo_postgres_params_inject_markers(executor->cmd);
@@ -85,8 +88,7 @@ static void rdo_postgres_statement_executor_prepare(VALUE self) {
85
88
  char msg[sizeof(char) * (strlen(PQresultErrorMessage(res)) + 1)];
86
89
  strcpy(msg, PQresultErrorMessage(res));
87
90
  PQclear(res);
88
- rb_raise(rb_path2class("RDO::Exception"),
89
- "Failed to prepare statement: %s", msg);
91
+ RDO_ERROR("Failed to prepare statement: %s", msg);
90
92
  }
91
93
 
92
94
  res = PQdescribePrepared(executor->driver->conn_ptr, executor->stmt_name);
@@ -95,8 +97,7 @@ static void rdo_postgres_statement_executor_prepare(VALUE self) {
95
97
  char msg[sizeof(char) * (strlen(PQresultErrorMessage(res)) + 1)];
96
98
  strcpy(msg, PQresultErrorMessage(res));
97
99
  PQclear(res);
98
- rb_raise(rb_path2class("RDO::Exception"),
99
- "Failed to prepare statement: %s", msg);
100
+ RDO_ERROR("Failed to prepare statement: %s", msg);
100
101
  }
101
102
 
102
103
  executor->nparams = PQnparams(res);
@@ -154,12 +155,11 @@ static VALUE rdo_postgres_statement_executor_execute(int argc, VALUE * args,
154
155
  Data_Get_Struct(self, RDOPostgresStatementExecutor, executor);
155
156
 
156
157
  if (!(executor->driver->is_open)) {
157
- rb_raise(rb_path2class("RDO::Exception"),
158
- "Unable to execute statement: connection is not open");
158
+ RDO_ERROR("Unable to execute statement: connection is not open");
159
159
  }
160
160
 
161
161
  if (argc != executor->nparams) {
162
- rb_raise(rb_path2class("RDO::Exception"),
162
+ rb_raise(rb_eArgError,
163
163
  "Bind parameter count mismatch: wanted %i, got %i",
164
164
  executor->nparams, argc);
165
165
  }
@@ -173,13 +173,21 @@ static VALUE rdo_postgres_statement_executor_execute(int argc, VALUE * args,
173
173
  values[i] = NULL;
174
174
  lengths[i] = 0;
175
175
  } else {
176
+ if (TYPE(args[i]) == T_ARRAY) {
177
+ if (executor->param_types[i] == RDO_PG_BYTEAARRAYOID) {
178
+ args[i] = RDO_PG_WRAP_ARRAY("Bytea", args[i]);
179
+ } else {
180
+ args[i] = RDO_PG_WRAP_ARRAY("Text", args[i]);
181
+ }
182
+ }
183
+
176
184
  if (TYPE(args[i]) != T_STRING) {
177
185
  args[i] = RDO_OBJ_TO_S(args[i]);
178
186
  }
179
187
 
180
- if (executor->param_types[i] == BYTEAOID) {
181
- values[i] = PQescapeByteaConn(executor->driver->conn_ptr,
182
- RSTRING_PTR(args[i]),
188
+ if (executor->param_types[i] == RDO_PG_BYTEAOID) {
189
+ values[i] = (char *) PQescapeByteaConn(executor->driver->conn_ptr,
190
+ (unsigned char *) RSTRING_PTR(args[i]),
183
191
  RSTRING_LEN(args[i]),
184
192
  &(lengths[i]));
185
193
  } else {
@@ -199,7 +207,7 @@ static VALUE rdo_postgres_statement_executor_execute(int argc, VALUE * args,
199
207
  RDO_PG_TEXT_OUTPUT);
200
208
 
201
209
  for (i = 0; i < argc; ++i) {
202
- if (executor->param_types[i] == BYTEAOID) {
210
+ if (executor->param_types[i] == RDO_PG_BYTEAOID) {
203
211
  PQfreemem(values[i]);
204
212
  }
205
213
  }
@@ -208,13 +216,10 @@ static VALUE rdo_postgres_statement_executor_execute(int argc, VALUE * args,
208
216
 
209
217
  if (status == PGRES_BAD_RESPONSE || status == PGRES_FATAL_ERROR) {
210
218
  PQclear(res);
211
- rb_raise(rb_path2class("RDO::Exception"),
212
- "Failed to execute statement: %s", PQresultErrorMessage(res));
219
+ RDO_ERROR("Failed to execute statement: %s", PQresultErrorMessage(res));
213
220
  }
214
221
 
215
- return rb_funcall(rb_path2class("RDO::Result"),
216
- rb_intern("new"), 2,
217
- rdo_postgres_tuple_list_new(res, executor->driver->encoding),
222
+ return RDO_RESULT(rdo_postgres_tuple_list_new(res, executor->driver->encoding),
218
223
  rdo_postgres_result_info_new(res));
219
224
  }
220
225