rdo-postgres 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ }