File: core_sql.rb

package info (click to toggle)
libsequel-core-ruby 1.5.1-1
  • links: PTS
  • area: main
  • in suites: lenny
  • size: 648 kB
  • ctags: 840
  • sloc: ruby: 10,949; makefile: 36
file content (196 lines) | stat: -rw-r--r-- 4,862 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# Array extensions
class Array
  # Concatenates an array of strings into an SQL string. ANSI SQL and C-style
  # comments are removed, as well as excessive white-space.
  def to_sql
    map {|l| (l =~ /^(.*)--/ ? $1 : l).chomp}.join(' '). \
      gsub(/\/\*.*\*\//, '').gsub(/\s+/, ' ').strip
  end
end

module Sequel
  # LiteralString is used to represent literal SQL expressions. An 
  # LiteralString is copied verbatim into an SQL statement. Instances of
  # LiteralString can be created by calling String#lit.
  class LiteralString < ::String
  end
end

# String extensions
class String
  # Converts a string into an SQL string by removing comments.
  # See also Array#to_sql.
  def to_sql
    split("\n").to_sql
  end
  
  # Splits a string into separate SQL statements, removing comments
  # and excessive white-space.
  def split_sql
    to_sql.split(';').map {|s| s.strip}
  end

  # Converts a string into an LiteralString, in order to override string
  # literalization, e.g.:
  #
  #   DB[:items].filter(:abc => 'def').sql #=>
  #     "SELECT * FROM items WHERE (abc = 'def')"
  #
  #   DB[:items].filter(:abc => 'def'.lit).sql #=>
  #     "SELECT * FROM items WHERE (abc = def)"
  #
  def lit
    Sequel::LiteralString.new(self)
  end
  
  alias_method :expr, :lit
  
  # Converts a string into a Time object.
  def to_time
    begin
      Time.parse(self)
    rescue Exception => e
      raise Sequel::Error::InvalidValue, "Invalid time value '#{self}' (#{e.message})"
    end
  end

  # Converts a string into a Date object.
  def to_date
    begin
      Date.parse(self)
    rescue Exception => e
      raise Sequel::Error::InvalidValue, "Invalid date value '#{self}' (#{e.message})"
    end
  end
end


module Sequel
  module SQL
    module ColumnMethods
      AS = 'AS'.freeze
      DESC = 'DESC'.freeze
      ASC = 'ASC'.freeze
      
      def as(a); ColumnExpr.new(self, AS, a); end
      
      def desc; ColumnExpr.new(self, DESC); end
      
      def asc; ColumnExpr.new(self, ASC); end

      def cast_as(t)
        if t.is_a?(Symbol)
          t = t.to_s.lit
        end
        Sequel::SQL::Function.new(:cast, self.as(t))
      end
    end

    class Expression
      include ColumnMethods
      def lit; self; end
    end
    
    class ColumnExpr < Expression
      attr_reader :l, :op, :r
      def initialize(l, op, r = nil); @l, @op, @r = l, op, r; end
      
      def to_s(ds)
        @r ? \
          "#{ds.literal(@l)} #{@op} #{ds.literal(@r)}" : \
          "#{ds.literal(@l)} #{@op}"
      end
    end
    
    class QualifiedColumnRef < Expression
      def initialize(t, c); @t, @c = t, c; end
      
      def to_s(ds)
        "#{@t}.#{ds.literal(@c)}"
      end 
    end
    
    class Function < Expression
      attr_reader :f, :args
      def initialize(f, *args); @f, @args = f, args; end

      # Functions are considered equivalent if they
      # have the same class, function, and arguments.
      def ==(x)
         x.class == self.class && @f == x.f && @args == x.args
      end

      def to_s(ds)
        args = @args.empty? ? '' : ds.literal(@args)
        "#{@f}(#{args})"
      end
    end
    
    class Subscript < Expression
      def initialize(f, sub); @f, @sub = f, sub; end
      def |(sub)
        unless Array === sub
          sub = [sub]
        end
        Subscript.new(@f, @sub << sub)
      end
      
      COMMA_SEPARATOR = ", ".freeze
      
      def to_s(ds)
        "#{@f}[#{@sub.join(COMMA_SEPARATOR)}]"
      end
    end
    
    class ColumnAll < Expression
      def initialize(t); @t = t; end
      def to_s(ds); "#{@t}.*"; end
    end
  end
end

class String
  include Sequel::SQL::ColumnMethods
end

class Symbol
  include Sequel::SQL::ColumnMethods
  def *
    Sequel::SQL::ColumnAll.new(self);
  end

  def [](*args); Sequel::SQL::Function.new(self, *args); end
  def |(sub)
    unless Array === sub
      sub = [sub]
    end
    Sequel::SQL::Subscript.new(self, sub)
  end
  
  COLUMN_REF_RE1 = /^(\w+)__(\w+)___(\w+)/.freeze
  COLUMN_REF_RE2 = /^(\w+)___(\w+)$/.freeze
  COLUMN_REF_RE3 = /^(\w+)__(\w+)$/.freeze

  # Converts a symbol into a column name. This method supports underscore
  # notation in order to express qualified (two underscores) and aliased 
  # (three underscores) columns:
  #
  #   ds = DB[:items]
  #   :abc.to_column_ref(ds) #=> "abc"
  #   :abc___a.to_column_ref(ds) #=> "abc AS a"
  #   :items__abc.to_column_ref(ds) #=> "items.abc"
  #   :items__abc___a.to_column_ref(ds) #=> "items.abc AS a"
  #
  def to_column_ref(ds)
    case s = to_s
    when COLUMN_REF_RE1
      "#{$1}.#{ds.quote_column_ref($2)} AS #{ds.quote_column_ref($3)}"
    when COLUMN_REF_RE2
      "#{ds.quote_column_ref($1)} AS #{ds.quote_column_ref($2)}"
    when COLUMN_REF_RE3
      "#{$1}.#{ds.quote_column_ref($2)}"
    else
      ds.quote_column_ref(s)
    end
  end
end