rdo 0.0.7 → 0.0.8

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 CHANGED
@@ -15,3 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ *.so
19
+ *.bundle
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ script: "bundle exec rake spec"
3
+ rvm:
4
+ - 1.9.2
5
+ - 1.9.3
6
+ notifications:
7
+ email: chris@w3style.co.uk
data/README.md CHANGED
@@ -6,7 +6,9 @@ interface. Where a feature is not natively supported by the DBMS—for example,
6
6
  prepared statements—it is seamlessly emulated, so you don't need to code
7
7
  around it.
8
8
 
9
- It targets **Ruby version 1.9** and newer.
9
+ It targets **Ruby 1.9** and newer.
10
+
11
+ [![Build Status](https://secure.travis-ci.org/d11wtq/rdo.png?branch=master)](http://travis-ci.org/d11wtq/rdo)
10
12
 
11
13
  ``` ruby
12
14
  require "rdo"
@@ -103,21 +105,27 @@ And then execute:
103
105
  <td>sqlite, sqlite3</td>
104
106
  <td><a href="https://github.com/d11wtq/rdo-sqlite">rdo-sqlite</a></td>
105
107
  <td><a href="https://github.com/d11wtq">d11wtq</a></td>
106
- <td>Published</td>
108
+ <td>
109
+ <img src="https://secure.travis-ci.org/d11wtq/rdo-sqlite.png?branch=master"
110
+ alt="Build Status" title="Build Status" />
111
+ </td>
107
112
  </tr>
108
113
  <tr>
109
114
  <th>PostgreSQL</th>
110
115
  <td>postgresql, postgres</td>
111
116
  <td><a href="https://github.com/d11wtq/rdo-postgres">rdo-postgres</a></td>
112
117
  <td><a href="https://github.com/d11wtq">d11wtq</a></td>
113
- <td>Published</td>
118
+ <td>
119
+ <img src="https://secure.travis-ci.org/d11wtq/rdo-postgres.png?branch=master"
120
+ alt="Build Status" title="Build Status" />
121
+ </td>
114
122
  </tr>
115
123
  <tr>
116
124
  <th>MySQL</th>
117
125
  <td>mysql</td>
118
126
  <td><a href="https://github.com/d11wtq/rdo-mysql">rdo-mysql</a></td>
119
127
  <td><a href="https://github.com/d11wtq">d11wtq</a></td>
120
- <td>Pending development</td>
128
+ <td>In development</td>
121
129
  </tr>
122
130
  </tbody>
123
131
  </table>
@@ -165,7 +173,7 @@ p conn.open? #=> true
165
173
  ### One-time use connections
166
174
 
167
175
  If you pass a block to RDO.connect, RDO yields the connection into the block,
168
- returns the result of the block, the closes the connection.
176
+ returns the result of the block, then closes the connection.
169
177
 
170
178
  ``` ruby
171
179
  puts RDO.open("sqlite:some.db") do |c|
@@ -254,8 +262,8 @@ include any error messaage provided by the DBMS.
254
262
  ### Tread carefully, there be danger ahead
255
263
 
256
264
  While driver developers are expected to provide a suitable implementation,
257
- it is generally riskier to use #quote and interpolate inputs directly into
258
- the SQL, than it is to use bind parameters. There are times where you might
265
+ it is generally riskier to escape and interpolate inputs directly into the
266
+ SQL than it is to use bind parameters. There are times where you might
259
267
  need to escape some input yourself, however. For that, you can call #quote.
260
268
 
261
269
  ``` ruby
@@ -300,6 +308,10 @@ When sending pull requests, please use topic branches—don't send a pull
300
308
  request from the master branch of your fork, as that may change
301
309
  unintentionally.
302
310
 
311
+ I haven't looked at what I need to change to have the drivers compile on
312
+ Windows yet, but I will do. If anybody beats me to it, pull requests will
313
+ be gladly accepted! I'll probably add some thin JDBC wrappers for jRuby.
314
+
303
315
  ### Writing a driver for RDO
304
316
 
305
317
  The more drivers that RDO has support for, the better. Writing drivers for
data/Rakefile CHANGED
@@ -1,2 +1,14 @@
1
- #!/usr/bin/env rake
2
1
  require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "rake/extensiontask"
4
+
5
+ Rake::ExtensionTask.new('rdo') 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,7 @@
1
+ require "mkmf"
2
+
3
+ if ENV["CC"]
4
+ RbConfig::MAKEFILE_CONFIG["CC"] = ENV["CC"]
5
+ end
6
+
7
+ create_makefile("rdo/rdo")
@@ -0,0 +1,175 @@
1
+ /*
2
+ * RDO—Ruby Data Objects.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include <ruby.h>
9
+ #include <stdio.h>
10
+ #include <string.h>
11
+
12
+ /** Quote parameters in params array */
13
+ static char ** rdo_driver_quote_params(VALUE self, VALUE * args, int argc, long * len) {
14
+ char ** quoted = malloc(sizeof(char *) * argc);
15
+ int idx = 0;
16
+ VALUE tmp;
17
+
18
+ *len = 0;
19
+
20
+ for (; idx < argc; ++idx) {
21
+ switch (TYPE(args[idx])) {
22
+ case T_NIL:
23
+ quoted[idx] = strdup("NULL");
24
+ *len += 4;
25
+ break;
26
+
27
+ case T_FIXNUM:
28
+ case T_FLOAT:
29
+ tmp = rb_funcall(args[idx], rb_intern("to_s"), 0);
30
+ Check_Type(tmp, T_STRING);
31
+
32
+ quoted[idx] = strdup(RSTRING_PTR(tmp));
33
+ *len += RSTRING_LEN(tmp);
34
+ break;
35
+
36
+ default:
37
+ tmp = rb_funcall(self, rb_intern("quote"), 1, args[idx]);
38
+ Check_Type(tmp, T_STRING);
39
+
40
+ quoted[idx] = malloc(sizeof(char) * (RSTRING_LEN(tmp) + 3));
41
+ sprintf(quoted[idx], "'%s'", RSTRING_PTR(tmp));
42
+ *len += 2 + RSTRING_LEN(tmp);
43
+ break;
44
+ }
45
+ }
46
+
47
+ return quoted;
48
+ }
49
+
50
+ /** Release heap memory allocated for quoted params */
51
+ static void rdo_driver_free_params(char ** quoted, int len) {
52
+ int i;
53
+ for (i = 0; i < len; ++i) free(quoted[i]);
54
+ free(quoted);
55
+ }
56
+
57
+ /**
58
+ * Takes String stmt, which contains ? markers and interpolates the values in Array params.
59
+ *
60
+ * Each value in Array params that is not NilClass, Fixnum or Float is passed
61
+ * to #quote on the Driver.
62
+ *
63
+ * Non-numeric values are surrounded by String quote.
64
+ *
65
+ * @param VALUE (String) stmt
66
+ * SQL, possibly containining '?' markers.
67
+ *
68
+ * @param VALUE (Array) params
69
+ * arguments to interpolate in place of the ? markers
70
+ *
71
+ * @return VALUE (String)
72
+ * the same SQL with the parameters interpolated.
73
+ */
74
+ static VALUE rdo_driver_interpolate(VALUE self, VALUE stmt, VALUE params) {
75
+ Check_Type(stmt, T_STRING);
76
+ Check_Type(params, T_ARRAY);
77
+
78
+ int argc = RARRAY_LEN(params);
79
+ long buflen = 0;
80
+ char ** quoted_params = rdo_driver_quote_params(
81
+ self,
82
+ RARRAY_PTR(params), argc,
83
+ &buflen);
84
+ char buffer[buflen + RSTRING_LEN(stmt) + 1];
85
+
86
+ char * b = buffer;
87
+ char * s = RSTRING_PTR(stmt);
88
+ int n = 0;
89
+
90
+ int insquote = 0;
91
+ int indquote = 0;
92
+ int inmlcmt = 0;
93
+ int inslcmt = 0;
94
+
95
+ // this loop is intentionally kept procedural (for performance)
96
+ for (; *s; ++s, ++b) {
97
+ switch (*s) {
98
+ case '?':
99
+ if (insquote || indquote || inmlcmt || inslcmt) {
100
+ *b = *s;
101
+ } else {
102
+ if (n < argc) {
103
+ strcpy(b, quoted_params[n]);
104
+ b += strlen(quoted_params[n]) - 1;
105
+ } else {
106
+ *b = *s;
107
+ }
108
+ ++n;
109
+ }
110
+ break;
111
+
112
+ case '-':
113
+ if (!insquote && !indquote && !inmlcmt && *(s + 1) == '-') {
114
+ inslcmt = 1;
115
+ *(b++) = *(s++);
116
+ }
117
+ *b = *s;
118
+ break;
119
+
120
+ case '\r':
121
+ case '\n':
122
+ inslcmt = 0;
123
+ *b = *s;
124
+ break;
125
+
126
+ case '/':
127
+ if (!insquote && !indquote && !inslcmt && *(s + 1) == '*') {
128
+ ++inmlcmt;
129
+ *(b++) = *(s++);
130
+ }
131
+ *b = *s;
132
+ break;
133
+
134
+ case '*':
135
+ if (inmlcmt && *(s + 1) == '/') {
136
+ --inmlcmt;
137
+ *(b++) = *(s++);
138
+ }
139
+ *b = *s;
140
+ break;
141
+
142
+ case '\'':
143
+ if (!indquote && !inmlcmt && !inslcmt) insquote = !insquote;
144
+ *b = *s;
145
+ break;
146
+
147
+ case '"':
148
+ if (!insquote && !inmlcmt && !inslcmt) indquote = !indquote;
149
+ *b = *s;
150
+ break;
151
+
152
+ default:
153
+ *b = *s;
154
+ }
155
+ }
156
+
157
+ *b = '\0';
158
+
159
+ rdo_driver_free_params(quoted_params, argc);
160
+
161
+ if (n != argc) {
162
+ rb_raise(rb_eArgError,
163
+ "Bind parameter mismatch (%i for %i) in query %s",
164
+ argc, n, RSTRING_PTR(stmt));
165
+ }
166
+
167
+ return rb_str_new2(buffer);
168
+ }
169
+
170
+ /** Extension initializer */
171
+ void Init_rdo(void) {
172
+ rb_require("rdo/driver");
173
+ VALUE cDriver = rb_path2class("RDO::Driver");
174
+ rb_define_method(cDriver, "interpolate", rdo_driver_interpolate, 2);
175
+ }
data/lib/rdo.rb CHANGED
@@ -14,6 +14,9 @@ require "rdo/emulated_statement_executor"
14
14
  require "rdo/result"
15
15
  require "rdo/util"
16
16
 
17
+ # c extension
18
+ require "rdo/rdo"
19
+
17
20
  module RDO
18
21
  class << self
19
22
  # Establish a connection to the RDBMS.
@@ -99,6 +99,9 @@ module RDO
99
99
 
100
100
  # Escape a given value for safe interpolation into a statement.
101
101
  #
102
+ # The value may be any type of Object and will be formatted to a String
103
+ # as needed.
104
+ #
102
105
  # This should be avoided where the driver natively supports bind parameters.
103
106
  #
104
107
  # Drivers MUST override this with a RDBMS-specific solution.
@@ -111,6 +114,33 @@ module RDO
111
114
  def quote(value)
112
115
  end
113
116
 
117
+ protected
118
+
119
+ # Replace the values in params with the '?' markers in sql.
120
+ #
121
+ # This method exists for drivers that don't natively support bind
122
+ # parameters, or don't fully support them (e.g. MySQL). The
123
+ # implementation is done in C, since the scanning routine is
124
+ # considerably faster.
125
+ #
126
+ # Each value in params is processed according to its type:
127
+ #
128
+ # - NilClass, Fixnum, Float, inserted as literals (NULL or a number)
129
+ # - String passed through #quote, then wrapped in single quotes
130
+ # - All other objects converted to String then processed as a String
131
+ #
132
+ # @param [String] sql
133
+ # a String of SQL including '?' markers
134
+ #
135
+ # @param [Array] params
136
+ # an Array of objects to interpolate
137
+ #
138
+ # @return [String]
139
+ # the SQL to be executed
140
+ def interpolate(sql, params)
141
+ # implemented in ext/rdo/rdo.c
142
+ end
143
+
114
144
  private
115
145
 
116
146
  def emulated_statement_executor(stmt)
@@ -6,5 +6,5 @@
6
6
  ##
7
7
 
8
8
  module RDO
9
- VERSION = "0.0.7"
9
+ VERSION = "0.0.8"
10
10
  end
@@ -21,7 +21,6 @@ Gem::Specification.new do |gem|
21
21
  * Type casting to Ruby types
22
22
  * Time zone handling (via the DBMS, not via some crazy time logic in Ruby)
23
23
  * Native bind values parameterization of queries, where supported by the DBMS
24
- * Buffered result sets (i.e. cursors, to avoid exhausting memory)
25
24
  * Retrieve query info from executed commands (e.g. affected rows)
26
25
  * Access RETURNING values just like any read query
27
26
  * Native prepared statements where supported, emulated where not
@@ -48,6 +47,8 @@ Gem::Specification.new do |gem|
48
47
  gem.name = "rdo"
49
48
  gem.require_paths = ["lib"]
50
49
  gem.version = RDO::VERSION
50
+ gem.extensions = ["ext/rdo/extconf.rb"]
51
51
 
52
52
  gem.add_development_dependency "rspec"
53
+ gem.add_development_dependency "rake-compiler"
53
54
  end
@@ -26,4 +26,100 @@ describe RDO::Driver do
26
26
  stmt.execute(true).should be_a_kind_of(RDO::Result)
27
27
  end
28
28
  end
29
+
30
+ describe "#interpolate" do
31
+ let(:driver) { RDO::DriverWithBackwardsQuote.new }
32
+
33
+ it "interpolates nil as literal NULL" do
34
+ driver.send(:interpolate, "SELECT ?", [nil]).should == "SELECT NULL"
35
+ end
36
+
37
+ it "interpolates a Fixnum as a literal integer" do
38
+ driver.send(:interpolate, "SELECT ? * 4", [123456789]).should == "SELECT 123456789 * 4"
39
+ end
40
+
41
+ it "interpolates a Float as a literal float" do
42
+ driver.send(:interpolate, "SELECT ? * 4", [12.34]).should == "SELECT 12.34 * 4"
43
+ end
44
+
45
+ it "interpolates a String as a quoted String" do
46
+ driver.send(:interpolate, "SELECT ?", ["string"]).should == "SELECT 'gnirts'"
47
+ end
48
+
49
+ it "interpolates an Object as a quoted String" do
50
+ driver.send(:interpolate, "SELECT ?", [Date.new(2012, 9, 22)]).should == "SELECT '22-90-2102'"
51
+ end
52
+
53
+ it "interpolates multiple params" do
54
+ driver.send(
55
+ :interpolate,
56
+ "SELECT ?, ?, ?",
57
+ ["test", 42, nil]
58
+ ).should == "SELECT 'tset', 42, NULL"
59
+ end
60
+
61
+ context "with not enough params" do
62
+ it "raises an ArgumentError" do
63
+ expect {
64
+ driver.send(
65
+ :interpolate,
66
+ "SELECT ?, ?, ?",
67
+ ["test", 42]
68
+ )
69
+ }.to raise_error(ArgumentError)
70
+ end
71
+ end
72
+
73
+ context "with too many params" do
74
+ it "raises an ArgumentError" do
75
+ expect {
76
+ driver.send(
77
+ :interpolate,
78
+ "SELECT ?, ?",
79
+ ["test", 42, nil]
80
+ )
81
+ }.to raise_error(ArgumentError)
82
+ end
83
+ end
84
+
85
+ context "with marks placed inside single quotes" do
86
+ it "ignores the quoted marks" do
87
+ driver.send(
88
+ :interpolate,
89
+ "SELECT 'quoted?', ?, ?",
90
+ ["test", 42]
91
+ ).should == "SELECT 'quoted?', 'tset', 42"
92
+ end
93
+ end
94
+
95
+ context "with marks placed inside double quotes" do
96
+ it "ignores the quoted marks" do
97
+ driver.send(
98
+ :interpolate,
99
+ "SELECT \"quoted?\", ?, ?",
100
+ ["test", 42]
101
+ ).should == "SELECT \"quoted?\", 'tset', 42"
102
+ end
103
+ end
104
+
105
+ context "with marks placed inside multiline comments" do
106
+ it "ignores the comments marks" do
107
+ driver.send(
108
+ :interpolate,
109
+ "SELECT /* commented? */ ?, ?",
110
+ ["test", 42]
111
+ ).should == "SELECT /* commented? */ 'tset', 42"
112
+ end
113
+ end
114
+
115
+ context "with marks placed inside line comments" do
116
+ it "ignores the comments marks" do
117
+ driver.send(
118
+ :interpolate,
119
+ "SELECT\n -- commented?\n ?, ?",
120
+ ["test", 42]
121
+ ).should == "SELECT\n -- commented?\n 'tset', 42"
122
+ end
123
+ end
124
+ end
29
125
  end
@@ -0,0 +1,29 @@
1
+ require "rdo"
2
+
3
+ module RDO
4
+ class DriverWithBackwardsQuote < Driver
5
+ def open
6
+ @open = true
7
+ end
8
+
9
+ def open?
10
+ !!@open
11
+ end
12
+
13
+ def close
14
+ @open = false
15
+ true
16
+ end
17
+
18
+ def execute(stmt, *args)
19
+ Result.new([])
20
+ end
21
+
22
+ def quote(obj)
23
+ obj.to_s.reverse
24
+ end
25
+ end
26
+
27
+ Connection.register_driver(:rdo_with_backwards_quote, DriverWithBackwardsQuote)
28
+ end
29
+
@@ -17,7 +17,8 @@ module RDO
17
17
  end
18
18
 
19
19
  def close
20
- !(@open = false)
20
+ @open = false
21
+ true
21
22
  end
22
23
 
23
24
  def execute(stmt, *args)
@@ -11,7 +11,8 @@ module RDO
11
11
  end
12
12
 
13
13
  def close
14
- !(@open = false)
14
+ @open = false
15
+ true
15
16
  end
16
17
 
17
18
  def execute(stmt, *args)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-27 00:00:00.000000000 Z
12
+ date: 2012-09-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -27,6 +27,22 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake-compiler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
30
46
  description: ! "== Ruby Data Objects\n\nIf you're building something in Ruby that
31
47
  needs access to a database, you may\nopt to use an ORM like ActiveRecord, DataMapper
32
48
  or Sequel. But if your needs\ndon't fit well with an ORM—maybe you're even writing
@@ -36,27 +52,30 @@ description: ! "== Ruby Data Objects\n\nIf you're building something in Ruby tha
36
52
  library:\n\n * Consistent API to connect to various DBMS's\n * Type casting to
37
53
  Ruby types\n * Time zone handling (via the DBMS, not via some crazy time logic
38
54
  in Ruby)\n * Native bind values parameterization of queries, where supported by
39
- the DBMS\n * Buffered result sets (i.e. cursors, to avoid exhausting memory)\n
40
- \ * Retrieve query info from executed commands (e.g. affected rows)\n * Access
41
- RETURNING values just like any read query\n * Native prepared statements where
42
- supported, emulated where not\n * Results given using simple core Ruby data types\n\n==
43
- RDBMS Support\n\nSupport for each RDBMS is provided in separate gems, so as to minimize
44
- the\ninstallation requirements and to facilitate the maintenace of each driver.
45
- Many\ngems are maintained by separate users who work more closely with those RDBMS's.\n\nDue
46
- to the nature of this gem, most of the nitty-gritty code is actually\nwritten in
47
- C.\n\nSee the official README for full details."
55
+ the DBMS\n * Retrieve query info from executed commands (e.g. affected rows)\n
56
+ \ * Access RETURNING values just like any read query\n * Native prepared statements
57
+ where supported, emulated where not\n * Results given using simple core Ruby data
58
+ types\n\n== RDBMS Support\n\nSupport for each RDBMS is provided in separate gems,
59
+ so as to minimize the\ninstallation requirements and to facilitate the maintenace
60
+ of each driver. Many\ngems are maintained by separate users who work more closely
61
+ with those RDBMS's.\n\nDue to the nature of this gem, most of the nitty-gritty code
62
+ is actually\nwritten in C.\n\nSee the official README for full details."
48
63
  email:
49
64
  - chris@w3style.co.uk
50
65
  executables: []
51
- extensions: []
66
+ extensions:
67
+ - ext/rdo/extconf.rb
52
68
  extra_rdoc_files: []
53
69
  files:
54
70
  - .gitignore
55
71
  - .rspec
72
+ - .travis.yml
56
73
  - Gemfile
57
74
  - LICENSE
58
75
  - README.md
59
76
  - Rakefile
77
+ - ext/rdo/extconf.rb
78
+ - ext/rdo/rdo.c
60
79
  - lib/rdo.rb
61
80
  - lib/rdo/connection.rb
62
81
  - lib/rdo/driver.rb
@@ -75,6 +94,7 @@ files:
75
94
  - spec/rdo/statement_spec.rb
76
95
  - spec/rdo/util_spec.rb
77
96
  - spec/spec_helper.rb
97
+ - spec/support/driver_with_backwards_quote.rb
78
98
  - spec/support/driver_with_everything.rb
79
99
  - spec/support/driver_without_statements.rb
80
100
  - util/macros.h
@@ -111,5 +131,6 @@ test_files:
111
131
  - spec/rdo/statement_spec.rb
112
132
  - spec/rdo/util_spec.rb
113
133
  - spec/spec_helper.rb
134
+ - spec/support/driver_with_backwards_quote.rb
114
135
  - spec/support/driver_with_everything.rb
115
136
  - spec/support/driver_without_statements.rb