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 ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.so
19
+ *.bundle
20
+ *.log
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rdo-postgres.gemspec
4
+ gemspec
5
+
6
+ # allow a local version of RDO to be used during dev
7
+ if ENV["RDO_PATH"]
8
+ gem 'rdo', path: ENV["RDO_PATH"]
9
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright © 2012 Chris Corbyn.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # RDO PostgreSQL Driver
2
+
3
+ This is the PostgreSQL driver for [RDO—Ruby Data Objects]
4
+ (https://github.com/d11wtq/rdo).
5
+
6
+ Refer to the RDO project [README](https://github.com/d11wtq/rdo) for usage
7
+ information.
8
+
9
+ This driver cannot be used with Postgres versions older than 7.4, since the
10
+ protocol has changed and this driver takes advantage of newer protocol (3.0)
11
+ features.
12
+
13
+ ## Installation
14
+
15
+ Via rubygems:
16
+
17
+ $ gem install rdo-postgres
18
+
19
+ Or add the following line to your application's Gemfile:
20
+
21
+ gem "rdo-postgres"
22
+
23
+ And install with Bundler:
24
+
25
+ $ bundle install
26
+
27
+ ## Usage
28
+
29
+ The registered URI schemes are postgres:// and postgresql://
30
+
31
+ ``` ruby
32
+ require "rdo"
33
+ require "rdo-postgres"
34
+
35
+ conn = RDO.connect("postgres://user:pass@localhost/dbname?encoding=utf-8")
36
+ ```
37
+
38
+ Alternatively, give the driver name as "postgres" or "postgresql" in an
39
+ options Hash.
40
+
41
+ ``` ruby
42
+ conn = RDO.connect(
43
+ driver: "postgresql",
44
+ host: "localhost",
45
+ user: "user",
46
+ passsword: "pass",
47
+ database: "dbname",
48
+ encoding: "utf-8"
49
+ )
50
+ ```
51
+
52
+ ### Bind parameters support
53
+
54
+ PostgreSQL uses $1, $2 etc for bind parameters. RDO uses '?'. You can use
55
+ either, but you **cannot** mix both styles in the same query, or you will
56
+ get errors.
57
+
58
+ These are ok:
59
+
60
+ ``` ruby
61
+ conn.execute("SELECT * FROM users WHERE banned = ? AND created_at > ?", true, 1.week.ago)
62
+ conn.execute("SELECT * FROM users WHERE banned = $1 AND created_at > $2", true, 1.week.ago)
63
+ ```
64
+
65
+ This is **not ok**:
66
+
67
+ ``` ruby
68
+ conn.execute("SELECT * FROM users WHERE banned = $1 AND created_at > ?", true, 1.week.ago)
69
+ ```
70
+
71
+ ## Contributing
72
+
73
+ Contributions to support older versions of PostgreSQL (< 7.4) welcomed,
74
+ though if the changes required break the native support for newer versions
75
+ pull requests will not be accepted. It is possible to write a separate
76
+ driver for older versions of PostgreSQL and register it under the driver
77
+ name 'postgresql' instead of this one if that is preferable. Alternatively,
78
+ use an explicit name that indicates legacy compatibility, such as
79
+ 'postgres73'.
80
+
81
+ If you find any bugs, please send a pull request if you think you can
82
+ fix it, or file in an issue in the issue tracker.
83
+
84
+ When sending pull requests, please use topic branches—don't send a pull
85
+ request from the master branch of your fork, as that may change
86
+ unintentionally.
87
+
88
+ Contributors will be credited in this README.
89
+
90
+ ## Copyright & Licensing
91
+
92
+ Written by Chris Corbyn.
93
+
94
+ Licensed under the MIT license. See the LICENSE file for full details.
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "rake/extensiontask"
4
+
5
+ Rake::ExtensionTask.new('rdo_postgres') do |ext|
6
+ ext.lib_dir = File.join('lib', 'rdo')
7
+ end
8
+
9
+ desc "Run the full RSpec suite"
10
+ RSpec::Core::RakeTask.new('spec') do |t|
11
+ t.pattern = 'spec/'
12
+ end
13
+
14
+ Rake::Task['spec'].prerequisites << :compile
@@ -0,0 +1,134 @@
1
+ /*
2
+ * RDO Postgres Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include "casts.h"
9
+ #include <stdlib.h>
10
+ #include <postgres.h>
11
+ #include <catalog/pg_type.h>
12
+ #include "macros.h"
13
+
14
+ /** Predicate test if the given string is formatted as \x0afe... */
15
+ #define RDO_PG_NEW_HEX_P(s, len) (len >= 2 && s[0] == '\\' && s[1] == 'x')
16
+
17
+ /** Lookup table for fast conversion of bytea hex strings to binary data */
18
+ static char * RDOPostgres_HexLookup;
19
+
20
+ /** Cast from a bytea to a String according to the new (PG 9.0) hex format */
21
+ static VALUE rdo_postgres_cast_bytea_hex(char * hex, size_t len) {
22
+ if ((len % 2) != 0) {
23
+ rb_raise(rb_eRuntimeError,
24
+ "Bad hex value provided for bytea (length not divisible by 2)");
25
+ }
26
+
27
+ size_t buflen = (len - 2) / 2;
28
+ char * buffer = malloc(sizeof(char) * buflen);
29
+ char * s = hex + 2;
30
+ char * b = buffer;
31
+
32
+ if (buffer == NULL) {
33
+ rb_raise(rb_eRuntimeError,
34
+ "Failed to allocate %ld bytes for bytea conversion", buflen);
35
+ }
36
+
37
+ for (; *s; s += 2, ++b)
38
+ *b = (RDOPostgres_HexLookup[*s] << 4) + (RDOPostgres_HexLookup[*(s + 1)]);
39
+
40
+ VALUE str = rb_str_new(buffer, buflen);
41
+ free(buffer);
42
+
43
+ return str;
44
+ }
45
+
46
+ /** Cast from a bytea to a String according to a regular escape format */
47
+ static VALUE rdo_postgres_cast_bytea_escape(char * escaped, size_t len) {
48
+ unsigned char * buffer = PQunescapeBytea(escaped, &len);
49
+
50
+ if (buffer == NULL) {
51
+ rb_raise(rb_eRuntimeError,
52
+ "Failed to allocate memory for PQunescapeBytea() conversion");
53
+ }
54
+
55
+ VALUE str = rb_str_new(buffer, len);
56
+ PQfreemem(buffer);
57
+
58
+ return str;
59
+ }
60
+
61
+ /** Get the value as a ruby type */
62
+ VALUE rdo_postgres_cast_value(PGresult * res, int row, int col, int enc) {
63
+ if (PQgetisnull(res, row, col)) {
64
+ return Qnil;
65
+ }
66
+
67
+ char * value = PQgetvalue(res, row, col);
68
+ int length = PQgetlength(res, row, col);
69
+
70
+ switch (PQftype(res, col)) {
71
+ case INT2OID:
72
+ case INT4OID:
73
+ case INT8OID:
74
+ return RDO_FIXNUM(value);
75
+
76
+ case FLOAT4OID:
77
+ case FLOAT8OID:
78
+ return RDO_FLOAT(value);
79
+
80
+ case NUMERICOID:
81
+ return RDO_DECIMAL(value);
82
+
83
+ case BOOLOID:
84
+ return RDO_BOOL(value);
85
+
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);
91
+
92
+ case DATEOID:
93
+ return RDO_DATE(value);
94
+
95
+ case TIMESTAMPOID:
96
+ return RDO_DATE_TIME_WITHOUT_ZONE(value);
97
+
98
+ case TIMESTAMPTZOID:
99
+ return RDO_DATE_TIME_WITH_ZONE(value);
100
+
101
+ case TEXTOID:
102
+ case CHAROID:
103
+ case VARCHAROID:
104
+ case BPCHAROID:
105
+ return RDO_STRING(value, length, enc);
106
+
107
+ default:
108
+ return RDO_BINARY_STRING(value, length);
109
+ }
110
+ }
111
+
112
+ /* Initialize hex decoding lookup table */
113
+ void Init_rdo_postgres_casts(void) {
114
+ RDOPostgres_HexLookup = malloc(sizeof(char) * 128);
115
+
116
+ if (RDOPostgres_HexLookup == NULL) {
117
+ rb_raise(rb_eRuntimeError,
118
+ "Failed to allocate 128 bytes for internal lookup table");
119
+ }
120
+
121
+ char c;
122
+
123
+ for (c = '\0'; c < '\x7f'; ++c)
124
+ RDOPostgres_HexLookup[c] = 0;
125
+
126
+ for (c = '0'; c <= '9'; ++c)
127
+ RDOPostgres_HexLookup[c] = c - '0';
128
+
129
+ for (c = 'a'; c <= 'f'; ++c)
130
+ RDOPostgres_HexLookup[c] = 10 + c - 'a';
131
+
132
+ for (c = 'A'; c <= 'F'; ++c)
133
+ RDOPostgres_HexLookup[c] = 10 + c - 'A';
134
+ }
@@ -0,0 +1,16 @@
1
+ /*
2
+ * RDO Postgres Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include <stdio.h>
9
+ #include <ruby.h>
10
+ #include <libpq-fe.h>
11
+
12
+ /** Cast the given value from the result to a ruby type */
13
+ VALUE rdo_postgres_cast_value(PGresult * res, int row, int col, int enc);
14
+
15
+ /** Initialize the casting framework */
16
+ void Init_rdo_postgres_casts(void);
@@ -0,0 +1,169 @@
1
+ /*
2
+ * RDO Postgres Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include "driver.h"
9
+ #include "statements.h"
10
+ #include "macros.h"
11
+ #include <ruby.h>
12
+ #include <stdlib.h>
13
+ #include <postgres.h>
14
+
15
+ /** During GC, free any stranded connection */
16
+ static void rdo_postgres_driver_free(RDOPostgresDriver * driver) {
17
+ PQfinish(driver->conn_ptr);
18
+ free(driver);
19
+ }
20
+
21
+ /** Postgres outputs notices (e.g. auto-generating sequence...) unless overridden */
22
+ static void rdo_postgres_driver_notice_processor(void * arg, const char * msg) {}
23
+
24
+ /** Allocate memory for the connection struct */
25
+ static VALUE rdo_postgres_driver_allocate(VALUE klass) {
26
+ RDOPostgresDriver * driver = malloc(sizeof(RDOPostgresDriver));
27
+
28
+ driver->conn_ptr = NULL;
29
+ driver->is_open = 0;
30
+ driver->stmt_count = 0;
31
+ driver->encoding = -1;
32
+
33
+ VALUE self = Data_Wrap_Struct(klass, 0,
34
+ rdo_postgres_driver_free, driver);
35
+
36
+ return self;
37
+ }
38
+
39
+ /** Connect to the postgres server */
40
+ static VALUE rdo_postgres_driver_open(VALUE self) {
41
+ RDOPostgresDriver * driver;
42
+ Data_Get_Struct(self, RDOPostgresDriver, driver);
43
+
44
+ if (driver->is_open) {
45
+ return Qtrue;
46
+ }
47
+
48
+ driver->conn_ptr = PQconnectdb(
49
+ RSTRING_PTR(rb_funcall(self, rb_intern("connect_db_string"), 0)));
50
+
51
+ if (driver->conn_ptr == NULL || PQstatus(driver->conn_ptr) == CONNECTION_BAD) {
52
+ rb_raise(rb_path2class("RDO::Exception"),
53
+ "PostgreSQL connection failed: %s",
54
+ PQerrorMessage(driver->conn_ptr));
55
+ } else if (PQprotocolVersion(driver->conn_ptr) < 3) {
56
+ rb_raise(rb_path2class("RDO::Exception"),
57
+ "rdo-postgres requires PostgreSQL protocol version >= 3 (using %u). "
58
+ "PostgreSQL >= 7.4 required.",
59
+ PQprotocolVersion(driver->conn_ptr));
60
+ } else {
61
+ PQsetNoticeProcessor(driver->conn_ptr, &rdo_postgres_driver_notice_processor, NULL);
62
+ driver->is_open = 1;
63
+ driver->stmt_count = 0;
64
+ driver->encoding = rb_enc_find_index(
65
+ RSTRING_PTR(rb_funcall(self, rb_intern("encoding"), 0)));
66
+ rb_funcall(self, rb_intern("after_open"), 0);
67
+ }
68
+
69
+ return Qtrue;
70
+ }
71
+
72
+ /** Disconnect from the postgres server, and release memory */
73
+ static VALUE rdo_postgres_driver_close(VALUE self) {
74
+ RDOPostgresDriver * driver;
75
+ Data_Get_Struct(self, RDOPostgresDriver, driver);
76
+
77
+ PQfinish(driver->conn_ptr);
78
+ driver->conn_ptr = NULL;
79
+ driver->is_open = 0;
80
+ driver->stmt_count = 0;
81
+ driver->encoding = -1;
82
+
83
+ return Qtrue;
84
+ }
85
+
86
+ /** Preciate test if connection is open */
87
+ static VALUE rdo_postgres_driver_open_p(VALUE self) {
88
+ RDOPostgresDriver * driver;
89
+ Data_Get_Struct(self, RDOPostgresDriver, driver);
90
+ return driver->is_open ? Qtrue : Qfalse;
91
+ }
92
+
93
+ /** Prepare a statement for execution */
94
+ static VALUE rdo_postgres_driver_prepare(VALUE self, VALUE cmd) {
95
+ Check_Type(cmd, T_STRING);
96
+
97
+ RDOPostgresDriver * driver;
98
+ Data_Get_Struct(self, RDOPostgresDriver, driver);
99
+
100
+ if (!(driver->is_open)) {
101
+ rb_raise(rb_path2class("RDO::Exception"),
102
+ "Unable to prepare statement: connection is not open");
103
+ }
104
+
105
+ char name[32];
106
+ sprintf(name, "rdo_stmt_%i", ++driver->stmt_count);
107
+
108
+ return RDO_STATEMENT(rdo_postgres_statement_executor_new(
109
+ self, cmd, rb_str_new2(name)));
110
+ }
111
+
112
+ /** Quote a string literal for safe insertion in a statement */
113
+ static VALUE rdo_postgres_driver_quote(VALUE self, VALUE str) {
114
+ if (TYPE(str) == T_NIL) {
115
+ return rb_str_new2("NULL");
116
+ } else if (TYPE(str) != T_STRING) {
117
+ str = RDO_OBJ_TO_S(str);
118
+ }
119
+
120
+ RDOPostgresDriver * driver;
121
+ Data_Get_Struct(self, RDOPostgresDriver, driver);
122
+
123
+ if (!(driver->is_open)) {
124
+ rb_raise(rb_path2class("RDO::Exception"),
125
+ "Unable to quote string: connection is not open");
126
+ }
127
+
128
+ char * quoted = malloc(sizeof(char) * RSTRING_LEN(str) * 4);
129
+ PQescapeStringConn(driver->conn_ptr, quoted,
130
+ RSTRING_PTR(str), RSTRING_LEN(str), NULL);
131
+
132
+ VALUE newstr = rb_str_new2(quoted);
133
+
134
+ free(quoted);
135
+
136
+ return newstr;
137
+ }
138
+
139
+ void Init_rdo_postgres_driver(void) {
140
+ rb_require("rdo/postgres/driver");
141
+
142
+ VALUE cPostgresConnection = rb_path2class("RDO::Postgres::Driver");
143
+
144
+ rb_define_alloc_func(
145
+ cPostgresConnection,
146
+ rdo_postgres_driver_allocate);
147
+
148
+ rb_define_method(
149
+ cPostgresConnection,
150
+ "open", rdo_postgres_driver_open, 0);
151
+
152
+ rb_define_method(
153
+ cPostgresConnection,
154
+ "close", rdo_postgres_driver_close, 0);
155
+
156
+ rb_define_method(
157
+ cPostgresConnection,
158
+ "open?", rdo_postgres_driver_open_p, 0);
159
+
160
+ rb_define_method(
161
+ cPostgresConnection,
162
+ "prepare", rdo_postgres_driver_prepare, 1);
163
+
164
+ rb_define_method(
165
+ cPostgresConnection,
166
+ "quote", rdo_postgres_driver_quote, 1);
167
+
168
+ Init_rdo_postgres_statements();
169
+ }