google_data_source 0.7.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +25 -0
- data/Rakefile +31 -0
- data/google_data_source.gemspec +32 -0
- data/lib/assets/images/google_data_source/chart_bar_add.png +0 -0
- data/lib/assets/images/google_data_source/chart_bar_delete.png +0 -0
- data/lib/assets/images/google_data_source/loader.gif +0 -0
- data/lib/assets/javascripts/google_data_source/data_source_init.js +3 -0
- data/lib/assets/javascripts/google_data_source/extended_data_table.js +76 -0
- data/lib/assets/javascripts/google_data_source/filter_form.js +180 -0
- data/lib/assets/javascripts/google_data_source/google_visualization/combo_table.js.erb +113 -0
- data/lib/assets/javascripts/google_data_source/google_visualization/table.js +116 -0
- data/lib/assets/javascripts/google_data_source/google_visualization/timeline.js +13 -0
- data/lib/assets/javascripts/google_data_source/google_visualization/visualization.js.erb +141 -0
- data/lib/assets/javascripts/google_data_source/index.js +7 -0
- data/lib/dummy_engine.rb +5 -0
- data/lib/google_data_source.rb +33 -0
- data/lib/google_data_source/base.rb +281 -0
- data/lib/google_data_source/column.rb +31 -0
- data/lib/google_data_source/csv_data.rb +23 -0
- data/lib/google_data_source/data_date.rb +17 -0
- data/lib/google_data_source/data_date_time.rb +17 -0
- data/lib/google_data_source/helper.rb +69 -0
- data/lib/google_data_source/html_data.rb +6 -0
- data/lib/google_data_source/invalid_data.rb +14 -0
- data/lib/google_data_source/json_data.rb +78 -0
- data/lib/google_data_source/railtie.rb +36 -0
- data/lib/google_data_source/sql/models.rb +266 -0
- data/lib/google_data_source/sql/parser.rb +239 -0
- data/lib/google_data_source/sql_parser.rb +82 -0
- data/lib/google_data_source/template_handler.rb +31 -0
- data/lib/google_data_source/test_helper.rb +26 -0
- data/lib/google_data_source/version.rb +3 -0
- data/lib/google_data_source/xml_data.rb +25 -0
- data/lib/locale/de.yml +5 -0
- data/lib/reporting/action_controller_extension.rb +19 -0
- data/lib/reporting/grouped_set.rb +58 -0
- data/lib/reporting/helper.rb +110 -0
- data/lib/reporting/reporting.rb +352 -0
- data/lib/reporting/reporting_adapter.rb +27 -0
- data/lib/reporting/reporting_entry.rb +147 -0
- data/lib/reporting/sql_reporting.rb +220 -0
- data/test/lib/empty_reporting.rb +2 -0
- data/test/lib/test_reporting.rb +33 -0
- data/test/lib/test_reporting_b.rb +9 -0
- data/test/lib/test_reporting_c.rb +3 -0
- data/test/locales/en.models.yml +6 -0
- data/test/locales/en.reportings.yml +5 -0
- data/test/rails/reporting_renderer_test.rb +47 -0
- data/test/test_helper.rb +50 -0
- data/test/units/base_test.rb +340 -0
- data/test/units/csv_data_test.rb +36 -0
- data/test/units/grouped_set_test.rb +60 -0
- data/test/units/json_data_test.rb +68 -0
- data/test/units/reporting_adapter_test.rb +20 -0
- data/test/units/reporting_entry_test.rb +149 -0
- data/test/units/reporting_test.rb +374 -0
- data/test/units/sql_parser_test.rb +111 -0
- data/test/units/sql_reporting_test.rb +307 -0
- data/test/units/xml_data_test.rb +32 -0
- metadata +286 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
module GoogleDataSource
|
2
|
+
module DataSource
|
3
|
+
class Column
|
4
|
+
attr_accessor :type, :id, :label, :pattern
|
5
|
+
|
6
|
+
COLTYPES = %w(boolean number string date datetime timeofday)
|
7
|
+
#COLKEYS = [:type, :id, :label, :pattern]
|
8
|
+
|
9
|
+
def initialize(params)
|
10
|
+
@type = (params[:type] || :string).to_s
|
11
|
+
@id = params[:id].to_sym
|
12
|
+
@label = params[:label]
|
13
|
+
@pattern = params[:pattern]
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid?
|
17
|
+
COLTYPES.include?(type)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_h
|
21
|
+
{
|
22
|
+
:id => id,
|
23
|
+
:type => type,
|
24
|
+
:label => label,
|
25
|
+
:pattern => pattern
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'csv'
|
2
|
+
#require 'action_pack'
|
3
|
+
|
4
|
+
module GoogleDataSource
|
5
|
+
module DataSource
|
6
|
+
class CsvData < Base
|
7
|
+
#include ActionView::Helpers::NumberHelper
|
8
|
+
def response
|
9
|
+
result = CSV.generate(:col_sep => ';') do |csv|
|
10
|
+
csv << columns.map { |col| col.label || col.id || col.type }
|
11
|
+
data.each do |datarow|
|
12
|
+
csv << datarow.map do |c|
|
13
|
+
c.is_a?(Hash) ? c[:v] : c
|
14
|
+
# TODO
|
15
|
+
#value.is_a?(Float) ? number_with_delimiter(value) : value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
result.force_encoding 'UTF-8'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module GoogleDataSource
|
2
|
+
module DataSource
|
3
|
+
class DataDate
|
4
|
+
def initialize(date)
|
5
|
+
@date = date
|
6
|
+
end
|
7
|
+
|
8
|
+
def as_json(options=nil)
|
9
|
+
if @date
|
10
|
+
"Date(#{@date.year}, #{@date.month-1}, #{@date.day})"
|
11
|
+
else
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module GoogleDataSource
|
2
|
+
module DataSource
|
3
|
+
class DataDateTime
|
4
|
+
def initialize(datetime)
|
5
|
+
@datetime = datetime
|
6
|
+
end
|
7
|
+
|
8
|
+
def as_json(options=nil)
|
9
|
+
if @datetime
|
10
|
+
"Date(#{@datetime.year}, #{@datetime.month-1}, #{@datetime.day}, #{@datetime.hour}, #{@datetime.min}, #{@datetime.sec})"
|
11
|
+
else
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module GoogleDataSource
|
2
|
+
module DataSource
|
3
|
+
module Helper
|
4
|
+
# Includes the JavaScript files neccessary for data source visualization
|
5
|
+
# The helper should be called the header of the layout
|
6
|
+
def google_data_source_includes(ssl = false)
|
7
|
+
html = "<script src='http#{"s" if ssl}://www.google.com/jsapi' type='text/javascript'></script>"
|
8
|
+
html.html_safe
|
9
|
+
end
|
10
|
+
|
11
|
+
# Shows a Google visualization.
|
12
|
+
# Available +types+ include:
|
13
|
+
# * Table
|
14
|
+
# * TimeLine
|
15
|
+
# +url+ defines the URL to the data source and defaults to +url_for(:format => 'datasource').
|
16
|
+
# The options are generally passed to the visualization JS objects after camlizing the keys
|
17
|
+
# Options that are not passed include:
|
18
|
+
# * +:container_id+ : The Dom id of the container element
|
19
|
+
def google_visualization(type, url = nil, options = {})
|
20
|
+
# extract options that are not meant for the javascript part
|
21
|
+
container_id = options.delete(:container_id) || "google_#{type.underscore}"
|
22
|
+
|
23
|
+
# camelize option keys
|
24
|
+
js_options = options.to_a.inject({}) { |memo, opt| memo[opt.first.to_s.camelize(:lower)] = opt.last; memo }
|
25
|
+
|
26
|
+
url ||= url_for(:format => 'datasource')
|
27
|
+
html = content_tag(:div, :id => container_id) { }
|
28
|
+
html << javascript_tag("DataSource.Visualization.create('#{type.camelize}', '#{url}', '#{container_id}', #{js_options.to_json});")
|
29
|
+
|
30
|
+
html << reporting_controls(container_id, options)
|
31
|
+
html
|
32
|
+
end
|
33
|
+
|
34
|
+
def reporting_controls(container_id, options)
|
35
|
+
html = tag(:div, {:id => "#{container_id}_controls", :class => "data_source_controls"}, true)
|
36
|
+
|
37
|
+
# Add Export links
|
38
|
+
unless options[:exportable_as].nil? || options[:exportable_as].empty?
|
39
|
+
html << tag(:div, {:id => "#{container_id}_export_as"}, true)
|
40
|
+
html << t('google_data_source.export_links.export_as')
|
41
|
+
html << ' '
|
42
|
+
options[:exportable_as].each do |format|
|
43
|
+
html << google_datasource_export_link(format)
|
44
|
+
end
|
45
|
+
html << ActiveSupport::SafeBuffer.new("</div>")
|
46
|
+
end
|
47
|
+
|
48
|
+
html << ActiveSupport::SafeBuffer.new("</div>") # ugly, any ideas?
|
49
|
+
html
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns a export link for the given format
|
53
|
+
def google_datasource_export_link(format)
|
54
|
+
label = t("google_data_source.export_links.#{format}")
|
55
|
+
link_to(label, '#', :class => "export_as_#{format}")
|
56
|
+
end
|
57
|
+
|
58
|
+
# Shows a Google data table
|
59
|
+
def google_datatable(url = nil, options = {})
|
60
|
+
google_visualization('Table', url, options)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Shows a Google annotated timeline
|
64
|
+
def google_timeline(url = nil, options = {})
|
65
|
+
google_visualization('TimeLine', url, options)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module GoogleDataSource
|
2
|
+
module DataSource
|
3
|
+
class InvalidData < Base
|
4
|
+
def initialize(gviz_params)
|
5
|
+
super(gviz_params)
|
6
|
+
end
|
7
|
+
|
8
|
+
def validate
|
9
|
+
super
|
10
|
+
add_error(:out, "Invalid output format: #{@params[:out]}. Valid ones are json,csv,html")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module GoogleDataSource
|
2
|
+
module DataSource
|
3
|
+
class JsonData < Base
|
4
|
+
|
5
|
+
def initialize(gdata_params)
|
6
|
+
super(gdata_params)
|
7
|
+
@responseHandler = "google.visualization.Query.setResponse"
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns the datasource in JSON format. It supports the +responseHandler+
|
11
|
+
# parameter. All the errors are returned with the +invalid_request+ key.
|
12
|
+
# Warnings are unsupported (yet).
|
13
|
+
def response
|
14
|
+
rsp = {}
|
15
|
+
rsp[:version] = @version
|
16
|
+
rsp[:reqId] = @params[:reqId] if @params.key?(:reqId)
|
17
|
+
if valid?
|
18
|
+
rsp[:status] = "ok"
|
19
|
+
rsp[:table] = datatable unless data.nil?
|
20
|
+
else
|
21
|
+
rsp[:status] = "error"
|
22
|
+
rsp[:errors] = @errors.values.collect do |error|
|
23
|
+
{ :reason => "invalid_request" , :message => error }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
"#{@params[:responseHandler] || @responseHandler}(#{rsp.to_json});#{callback}"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Renders the part of the JSON response that contains the dataset.
|
30
|
+
def datatable
|
31
|
+
dt = {}
|
32
|
+
dt[:cols] = columns.collect(&:to_h)
|
33
|
+
dt[:rows] = []
|
34
|
+
data.each do |datarow|
|
35
|
+
row = []
|
36
|
+
datarow.each_with_index do |datacell, colnum|
|
37
|
+
if datacell.is_a?(Hash)
|
38
|
+
row << {
|
39
|
+
:v => convert_cell(datacell[:v], columns[colnum].type),
|
40
|
+
:f => datacell[:f]
|
41
|
+
}
|
42
|
+
else
|
43
|
+
row << { :v => convert_cell(datacell, columns[colnum].type) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
dt[:rows] << { :c => row }
|
48
|
+
end
|
49
|
+
return dt
|
50
|
+
end
|
51
|
+
protected :datatable
|
52
|
+
|
53
|
+
# Converts a value in the dataset into a format suitable for the
|
54
|
+
# column it belongs to.
|
55
|
+
#
|
56
|
+
# Datasets are expected to play nice, and try to adhere to the columns they
|
57
|
+
# intend to export as much as possible. This method doesn't do anything more
|
58
|
+
# than the very minimum to ensure a formally valid gviz export.
|
59
|
+
def convert_cell(value, coltype)
|
60
|
+
case coltype
|
61
|
+
when "boolean"
|
62
|
+
!!value
|
63
|
+
when "number"
|
64
|
+
value # TODO to_i ???
|
65
|
+
when "string"
|
66
|
+
value
|
67
|
+
when "date"
|
68
|
+
DataDate.new(value.is_a?(String) ? Date.parse(value) : value) rescue nil
|
69
|
+
when "datetime"
|
70
|
+
DataDateTime.new(value.is_a?(String) ? DateTime.parse(value) : value) rescue nil
|
71
|
+
when "timeofday"
|
72
|
+
[ value.hour, value.min, value.sec, value.usec / 1000 ]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
protected :convert_cell
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# all rails specific extensions
|
4
|
+
require 'reporting/action_controller_extension'
|
5
|
+
require 'reporting/helper'
|
6
|
+
require 'google_data_source/helper'
|
7
|
+
require 'google_data_source/test_helper'
|
8
|
+
|
9
|
+
module GoogleDataSource
|
10
|
+
class Railtie < Rails::Railtie
|
11
|
+
initializer 'google_data_source.initialize', :after => :after_initialize do
|
12
|
+
# register helper
|
13
|
+
ActionView::Base.send :include, GoogleDataSource::DataSource::Helper
|
14
|
+
ActionView::Base.send :include, GoogleDataSource::Reporting::Helper
|
15
|
+
|
16
|
+
# Register controller extension
|
17
|
+
#ActionController::Base.class_eval do
|
18
|
+
# include GoogleDataSource::Reporting::ActionControllerExtension
|
19
|
+
# alias_method_chain :render, :reporting
|
20
|
+
#end
|
21
|
+
|
22
|
+
# I18n
|
23
|
+
I18n.load_path.unshift *Dir[File.join(File.dirname(__FILE__), '..', 'locale', '*.{rb,yml}')]
|
24
|
+
|
25
|
+
# Register TemplateHandler
|
26
|
+
# TODO set mime type to CSV / HTML according to the output format
|
27
|
+
Mime::Type.register "application/json", :datasource
|
28
|
+
|
29
|
+
ActionView::Template.register_template_handler(:datasource,
|
30
|
+
GoogleDataSource::DataSource::TemplateHandler
|
31
|
+
)
|
32
|
+
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
module GoogleDataSource
|
2
|
+
module DataSource
|
3
|
+
module Sql
|
4
|
+
class WithHelpers
|
5
|
+
class << self
|
6
|
+
include RParsec::DefHelper
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Expr < WithHelpers
|
11
|
+
def self.binary(*ops)
|
12
|
+
ops.each do |op|
|
13
|
+
define_method(op) do |other|
|
14
|
+
BinaryExpr.new(self, op, other)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
binary :+,:-,:*,:/,:%
|
19
|
+
def -@
|
20
|
+
PrefixExpr.new(:-, self)
|
21
|
+
end
|
22
|
+
def self.compare(*ops)
|
23
|
+
ops.each do |op|
|
24
|
+
define_method(op) do |other|
|
25
|
+
ComparePredicate.new(self, op, other)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
compare :'==', :'>', :'<', :'>=', :'<='
|
30
|
+
end
|
31
|
+
|
32
|
+
class LiteralExpr < Expr
|
33
|
+
def_readable :lit
|
34
|
+
def to_s
|
35
|
+
lit
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class VarExpr < Expr
|
40
|
+
def_readable :name
|
41
|
+
def to_s
|
42
|
+
"$#{name}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class WordExpr < Expr
|
47
|
+
def_readable :name
|
48
|
+
def to_s
|
49
|
+
name
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class QualifiedColumnExpr < Expr
|
54
|
+
def_readable :owner, :col
|
55
|
+
def to_s
|
56
|
+
"#{owner}.#{col}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class WildcardExpr < Expr
|
61
|
+
Instance = WildcardExpr.new
|
62
|
+
def to_s
|
63
|
+
'*'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class BinaryExpr < Expr
|
68
|
+
def_readable :left, :op, :right
|
69
|
+
def to_s
|
70
|
+
"(#{left} #{op} #{right})"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class PostfixExpr < Expr
|
75
|
+
def_readable :expr, :op
|
76
|
+
def to_s
|
77
|
+
"(#{expr} #{op})"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class PrefixExpr < Expr
|
82
|
+
def_readable :op, :expr
|
83
|
+
def to_s
|
84
|
+
"(#{op} #{expr})"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def cases_string cases, default, result
|
89
|
+
cases.each do |cond, val|
|
90
|
+
result << " when #{cond}: #{val}"
|
91
|
+
end
|
92
|
+
unless default.nil?
|
93
|
+
result << " else #{default}"
|
94
|
+
end
|
95
|
+
result << " end"
|
96
|
+
result
|
97
|
+
end
|
98
|
+
|
99
|
+
class SimpleCaseExpr < Expr
|
100
|
+
def_readable :expr, :cases, :default
|
101
|
+
def to_s
|
102
|
+
cases_string cases, default, "case #{expr}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class CaseExpr < Expr
|
107
|
+
def_readable :cases, :default
|
108
|
+
def to_s
|
109
|
+
cases_string cases, default, 'case'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
############Predicate########################
|
115
|
+
class Predicate < WithHelpers
|
116
|
+
include RParsec::DefHelper
|
117
|
+
end
|
118
|
+
|
119
|
+
class ComparePredicate < Predicate
|
120
|
+
def_readable :left, :op, :right
|
121
|
+
|
122
|
+
def to_s
|
123
|
+
"#{left} #{op_name} #{right}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def op_name
|
127
|
+
case op when :"!=" then "<>" else op.to_s end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class CompoundPredicate < Predicate
|
132
|
+
def_readable :left, :op, :right
|
133
|
+
def to_s
|
134
|
+
"(#{left} #{op} #{right})"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class NotPredicate < Predicate
|
139
|
+
def_readable :predicate
|
140
|
+
def to_s
|
141
|
+
"(not #{predicate})"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class ExistsPredicate < Predicate
|
146
|
+
def_readable :relation
|
147
|
+
def to_s
|
148
|
+
"exists(#{relation})"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class NotExistsPredicate < Predicate
|
153
|
+
def_readable :relation
|
154
|
+
def to_s
|
155
|
+
"not exists(#{relation})"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class InRelationPredicate < Predicate
|
160
|
+
def_readable :expr, :relation
|
161
|
+
def to_s
|
162
|
+
"#{expr} in (#{relation})"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class NotInRelationPredicate < Predicate
|
167
|
+
def_readable :expr, :relation
|
168
|
+
def to_s
|
169
|
+
"#{expr} not in (#{relation})"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
class InPredicate < Predicate
|
174
|
+
def_readable :expr, :vals
|
175
|
+
def to_s
|
176
|
+
"#{expr} in (#{vals.join(', ')})"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class NotInPredicate < Predicate
|
181
|
+
def_readable :expr, :vals
|
182
|
+
def to_s
|
183
|
+
"#{expr} not in (#{vals.join(', ')})"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class BetweenPredicate < Predicate
|
188
|
+
def_readable :expr, :from, :to
|
189
|
+
def to_s
|
190
|
+
"#{expr} between #{from} and #{to}"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
class NotBetweenPredicate < Predicate
|
195
|
+
def_readable :expr, :from, :to
|
196
|
+
def to_s
|
197
|
+
"#{expr} not between #{from} and #{to}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
class GroupComparisonPredicate < Predicate
|
202
|
+
def_readable :group1, :op, :group2
|
203
|
+
def to_s
|
204
|
+
"#{list_exprs group1} #{op} #{list_exprs group2}"
|
205
|
+
end
|
206
|
+
def list_exprs exprs
|
207
|
+
"(#{exprs.join(', ')})"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
#############Relations######################
|
211
|
+
|
212
|
+
class OrderElement < WithHelpers
|
213
|
+
def_readable :expr, :asc
|
214
|
+
def to_s
|
215
|
+
result = "#{expr}"
|
216
|
+
unless asc
|
217
|
+
result << ' desc'
|
218
|
+
end
|
219
|
+
result
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
class GroupByClause < WithHelpers
|
224
|
+
def_readable :exprs, :having
|
225
|
+
def to_s
|
226
|
+
result = exprs.join(', ')
|
227
|
+
unless having.nil?
|
228
|
+
result << " having #{having}"
|
229
|
+
end
|
230
|
+
result
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
class Relation < WithHelpers
|
235
|
+
def as_inner
|
236
|
+
to_s
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
class SelectRelation < Relation
|
241
|
+
def_readable :select, :where, :groupby, :orderby, :limit, :offset
|
242
|
+
|
243
|
+
def to_s
|
244
|
+
result = "select"
|
245
|
+
result << " #{select.join(', ')}"
|
246
|
+
unless where.nil?
|
247
|
+
result << " where #{where}"
|
248
|
+
end
|
249
|
+
unless groupby.nil?
|
250
|
+
result << " group by #{groupby}"
|
251
|
+
end
|
252
|
+
unless orderby.nil?
|
253
|
+
result << " order by #{orderby.join(', ')}"
|
254
|
+
end
|
255
|
+
unless limit.nil?
|
256
|
+
result << " limit #{limit}"
|
257
|
+
end
|
258
|
+
unless offset.nil?
|
259
|
+
result << " offset #{offset}"
|
260
|
+
end
|
261
|
+
result
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|