rdo 0.0.7 → 0.0.8

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