Class: Epoxy
- Inherits:
-
Object
- Object
- Epoxy
- Defined in:
- lib/epoxy.rb
Overview
Epoxy - bind data to queries for any query language.
Let me hit ya with some science!
# numbered binds
ep = Epoxy.new("select * from foo where bar=?")
binds = %W[foo]
bound_query = ep.quote { |x| "'" + binds[x] + "'" }
"select * from foo where bar='foo'"
# named binds
binds = { :name => 'Lee', :age => 132 }
ep = Epoxy.new("select * from people where name=?name and age=?age")
bound_query = ep.quote(binds) { |x| "'#{binds[x]}'" }
"select * from people where name='Lee' and age='132'"
# mix them!
binds = { 0 => "Age", :name => 'Lee' }
ep = Epoxy.new("select * from people where name=?name and age=?")
bound_query = ep.quote(binds) { |x| "'#{binds[x]}'" }
"select * from people where name='Lee' and age='Age'"
Epoxy handles:
-
?<name> for named binds
-
? for numbered binds
-
?? for a real question mark
-
‘?’ for a real question mark
-
comments, weird quoting styles (look at the “holy shit” test for examples)
-
not telling you how to quote your data. This solution works for any query language and any database.
Constant Summary collapse
- LEGAL_NAMED_BIND =
/[a-zA-Z]+/
Instance Attribute Summary collapse
-
#comment_chars ⇒ Object
readonly
leader comment characters - defaults to SQL “–”.
-
#query ⇒ Object
readonly
the original query, before quoting.
-
#tokens ⇒ Object
readonly
tokens generated by Epoxy.parse_tokens.
Class Method Summary collapse
-
.parse_tokens(query, comment_chars) ⇒ Object
Token parser, isolates components of the query into parts to where they can be managed indepdently.
Instance Method Summary collapse
-
#indexed_binds ⇒ Object
Returns a hash of position => name (as Symbol), if any, which correspond to binds located in the query.
-
#initialize(query, comment_chars = %r{--|//}) ⇒ Epoxy
constructor
Takes a query as a string and an optional regexp defining beginning-of-line comments.
-
#quote(binds = {}, &block) ⇒ Object
Processes your query for quoting.
Constructor Details
#initialize(query, comment_chars = %r{--|//}) ⇒ Epoxy
Takes a query as a string and an optional regexp defining beginning-of-line comments. The binding rules are as follows:
-
?<name> for named binds
-
? for numbered binds
-
?? for a real question mark
-
‘?’ for a real question mark
-
comments, weird quoting styles are unaffected.
79 80 81 82 83 |
# File 'lib/epoxy.rb', line 79 def initialize(query, comment_chars=%r{--|//}) @comment_chars = comment_chars @query = query @tokens = self.class.parse_tokens(query, @comment_chars) end |
Instance Attribute Details
#comment_chars ⇒ Object (readonly)
leader comment characters - defaults to SQL “–”
67 68 69 |
# File 'lib/epoxy.rb', line 67 def comment_chars @comment_chars end |
#query ⇒ Object (readonly)
the original query, before quoting.
65 66 67 |
# File 'lib/epoxy.rb', line 65 def query @query end |
#tokens ⇒ Object (readonly)
tokens generated by Epoxy.parse_tokens. Just use Epoxy#quote for now.
63 64 65 |
# File 'lib/epoxy.rb', line 63 def tokens @tokens end |
Class Method Details
.parse_tokens(query, comment_chars) ⇒ Object
Token parser, isolates components of the query into parts to where they can be managed indepdently.
Probably not the easiest thing to deal with by itself. Use the standard methods plox.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/epoxy.rb', line 42 def self.parse_tokens(query, comment_chars) query.scan(%r{ ( #{comment_chars}.* (?# matches "--" style comments to the end of line or string ) | ' ( [^'\\] | '' | \\. )* ' (?# match strings surrounded by apostophes ) | " ( [^"\\] | "" | \\. )* " (?# match strings surrounded by " ) | ['"] (?# match a loose quote ) | \?#{LEGAL_NAMED_BIND} (?# match a named bind ) | \?\?? (?# match one or two question marks ) | [^'"?]+ (?# match all characters except ' " ? - : and / ) ) }x).collect(&:first) end |
Instance Method Details
#indexed_binds ⇒ Object
Returns a hash of position => name (as Symbol), if any, which correspond to binds located in the query. nil is provided as a name if it is an indexed bind already. This is useful for sanitizing features Epoxy has before sending them to the SQL engine.
Ex:
ep = Epoxy.new("select * from foo where bar=?bar and quux=? and foomatic=?foo")
ep.indexed_binds
# yields...
[ :bar, nil, :foo]
NOTE: all syntax lookalikes are considered in this method; in the actual quote() routine, only named binds with a corresponding map are considered.
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/epoxy.rb', line 143 def indexed_binds ary = [] tokens.each do |toke| case toke when '?' ary.push(toke) when /\?(#{LEGAL_NAMED_BIND})/ ary.push($1.to_sym) end end ary.map! { |x| x == '?' ? nil : x } return ary end |
#quote(binds = {}, &block) ⇒ Object
Processes your query for quoting. Provide a block that emulates how your data should be quoted. This method accepts a Hash to process named bindings, which when provided will yield each successive Hash key which has a match in the named binds. Keys are coerced to symbols before being yielded.
Without a Hash it will yield on each successive bound element with the index of that element passed.
You are responsible for quoting your data properly. Epoxy just makes it easier to get the places you need to quote out of the query.
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/epoxy.rb', line 97 def quote(binds = {}, &block) result = "" bind_pos = 0 binds = binds.keys.inject({}) { |x,y| x.merge({ y.kind_of?(String) ? y.to_sym : y => binds[y] }) } tokens.each do |part| case part when '?' result << block.call(bind_pos) bind_pos += 1 when '??' result << "?" when /^\?(#{LEGAL_NAMED_BIND})$/ key = $1.to_sym if binds.has_key?(key) result << block.call(key) bind_pos += 1 else result << part end else result << part end end return result end |