rdo-postgres 0.0.6 → 0.0.7

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/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