visual_query 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +59 -0
- data/Rakefile +23 -0
- data/app/assets/images/visual_query/ajax-loader.gif +0 -0
- data/app/assets/javascripts/visual_query/index.js +288 -0
- data/app/assets/stylesheets/visual_query/visual_query.css +161 -0
- data/app/controllers/queries_controller.rb +157 -0
- data/app/helpers/application_helper.rb +5 -0
- data/app/helpers/queries_helper.rb +43 -0
- data/app/views/queries/_columns.html.erb +5 -0
- data/app/views/queries/_command.html.erb +5 -0
- data/app/views/queries/_command_results_browser.html.erb +1 -0
- data/app/views/queries/_command_save.html.erb +1 -0
- data/app/views/queries/_commands_results.html.erb +2 -0
- data/app/views/queries/_filter.html.erb +6 -0
- data/app/views/queries/_list_all_relations.html.erb +8 -0
- data/app/views/queries/_list_joinable_relations.html.erb +10 -0
- data/app/views/queries/_list_relation.html.erb +8 -0
- data/app/views/queries/_name.html.erb +8 -0
- data/app/views/queries/_results.html.erb +14 -0
- data/app/views/queries/_results_column_filter.html.erb +3 -0
- data/app/views/queries/_results_column_hide.html.erb +3 -0
- data/app/views/queries/_results_column_human_name.html.erb +8 -0
- data/app/views/queries/_results_empty.html.erb +3 -0
- data/app/views/queries/_sort.html.erb +12 -0
- data/app/views/queries/_sort_condition.html.erb +18 -0
- data/app/views/queries/_url_root.html.erb +1 -0
- data/app/views/queries/_warning_large_result_set.html.erb +12 -0
- data/app/views/queries/filters/_boolean.html.erb +6 -0
- data/app/views/queries/filters/_date.html.erb +17 -0
- data/app/views/queries/filters/_datetime.html.erb +1 -0
- data/app/views/queries/filters/_decimal.html.erb +1 -0
- data/app/views/queries/filters/_integer.html.erb +1 -0
- data/app/views/queries/filters/_numeric.html.erb +6 -0
- data/app/views/queries/filters/_string.html.erb +1 -0
- data/app/views/queries/filters/_text.html.erb +6 -0
- data/app/views/queries/index.html.erb +39 -0
- data/app/views/queries/new.html.erb +27 -0
- data/app/views/queries/not_found.html.erb +1 -0
- data/app/views/queries/show.html.erb +40 -0
- data/app/views/queries/sql_form.html.erb +16 -0
- data/config/initializers/visual_query.rb +11 -0
- data/config/routes.rb +26 -0
- data/db/migrate/20130927090319_create_visual_query_schema.rb +9 -0
- data/db/migrate/20130927090400_create_visual_query_metadata_table.rb +16 -0
- data/lib/tasks/visual_query_tasks.rake +12 -0
- data/lib/tutuf/visual_query.rb +6 -0
- data/lib/tutuf/visual_query/base.rb +268 -0
- data/lib/tutuf/visual_query/common.rb +58 -0
- data/lib/tutuf/visual_query/metadata.rb +35 -0
- data/lib/tutuf/visual_query/single.rb +111 -0
- data/lib/tutuf/visual_query/sql.rb +20 -0
- data/lib/visual_query.rb +4 -0
- data/lib/visual_query/engine.rb +4 -0
- data/lib/visual_query/version.rb +3 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/account.rb +3 -0
- data/test/dummy/app/models/address.rb +3 -0
- data/test/dummy/app/models/category.rb +3 -0
- data/test/dummy/app/models/composite_pk.rb +3 -0
- data/test/dummy/app/models/customer.rb +4 -0
- data/test/dummy/app/models/order.rb +4 -0
- data/test/dummy/app/models/person.rb +4 -0
- data/test/dummy/app/models/post.rb +3 -0
- data/test/dummy/app/models/product.rb +4 -0
- data/test/dummy/app/models/product_in.rb +3 -0
- data/test/dummy/app/models/product_out.rb +3 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +53 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +12 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +29 -0
- data/test/dummy/config/environments/production.rb +65 -0
- data/test/dummy/config/environments/test.rb +33 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/visual_query.rb +1 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +3 -0
- data/test/dummy/db/migrate/20130927112446_create_tables.rb +77 -0
- data/test/dummy/db/structure.sql +651 -0
- data/test/dummy/log/development.log +1548 -0
- data/test/dummy/log/test.log +36575 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/-h/-hj2e_RSTanfbfrP0tso5Q7actRM6_clE5hetFlQ2y8.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/3B/3B_zqoNDfkO8wvAME66zxm9KzQaeDVSjnH0qC08yufM.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/5g/5g7dhxVp4YbZmFw_-T3aU2oYq2Z9Jgtps0CKneXYSS0.cache +2 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/6-/6-CtZO6uG0yfwU8-098Sdy2wnfO0W6DbFu6B6DKYuiw.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/8k/8kFIVN4cTS9KCQt_UIF5s_rcY-bMYlQpM489D98hvP4.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Au/AukU7t3xLnyCh7qW4u45q9YFmjVcYujmIFbnaOhF4Mo.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Bm/Bmsq0vYQqlrtfq5s-W8kcfLvwcsPT_6_5XxXt9J_QOw.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/CQ/CQLNg7a2TsUWgc7JXjDkjseMig_dPVm6AvqO2IWk5zg.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/FX/FX9ZXN3HEHR5hPzvxW8rakWEt2ot4IPJyDB67O7KPZk.cache +2 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Hv/HvOStITEkFHlcJCgaDnND6wzPw4dMmdAdZB1Xm6JfSA.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/NW/NWCtuFzfIgixavqY71NIAa4ajbsXxRuiLNjceHgQ24M.cache +2 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Ta/TaG641Ow168nkagg10mh6zuWS8RwWuawpHuMGakCVjU.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/VN/VNCapNKJLeponthNeFJhaBYs92UBT3P8PugENHP0474.cache +2 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/VR/VRi0Hz7tc62H5Of9XVjyAk7vSNmMr8xeYowo6lSBnZg.cache +2 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Vg/Vgl_u6t3BvczgGi_ZJlyyo7xYSe-GgEshLofx-3QorI.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Vq/VqGJMF3Cpvp3fw2IEIkE2tzdFo_OdcEmxN58BQwbVDY.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/W6/W6DnXCIMHJ2i5hUkEiNeDLroWxW3VU18nq292n5jlts.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Z7/Z7fH8ST-O3GMnDUKvtKHHTSObfH2Nbs0J1QS79i80yE.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Zh/ZhrmuPgfbHthzikN8QSHR0Q0bLtSYS1Bzl9HauWeDfU.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/bY/bYzYCY_bAGQGVGMbcxtKIhUYrgDQhmQVTmK50JrMNb8.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/eT/eTmCDSnc2m9ER5Cn85g84xyTkVLWKLbbwPFQo8eUAIg.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/h-/h-taE7cHlbq76GUb5kHenjih_y66mO0w0lIZs7vY-0s.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/ji/jikmiWyu-cXN_ZJ4hgLc3kuCAY-QJY2jmPeXS4_9vZY.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/kC/kCijps52gsNlkYgT2Qzdz9UcSaxhcMGNfNL7MIiWk54.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/kQ/kQt-OsUDJg_sl1be-FqJ6Vhw4XVguw9_msZEwXP0Nh0.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/nd/ndbe-ZZWBqU5gLx5nxauCFvv963Zm3xqVEwVYQ7X_X8.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/q6/q6BYa32YJF11eGVapO4ouNl6gayPIsARgMavlzZmoi0.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/sm/sm7AdmddDYbFx4-eo_y_kaZspanmc-jiJeM8j2DXX5k.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/ux/uxPH9lLOW42lEQxJXnBizEObZReD8qkz6Eb6fdS6Ur4.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/wn/wn0ayyM-chRdwEPI11SLkFT-7G2-GcOX8UIIC2kOWLY.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/x7/x7KkTV3ibfIEysLB_ug5bfmnn2VLV_BldukPR3EoPBk.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/xK/xKo0PfDcZuMh8oO-6Gr4j4S8eR2qUNY9Gau4kAxKIH0.cache +1 -0
- data/test/fixtures/accounts.yml +4 -0
- data/test/fixtures/categories.yml +3 -0
- data/test/fixtures/categories_posts.yml +3 -0
- data/test/fixtures/people.yml +8 -0
- data/test/fixtures/posts.yml +8 -0
- data/test/fixtures/product_ins.yml +15 -0
- data/test/fixtures/product_outs.yml +5 -0
- data/test/fixtures/products.yml +6 -0
- data/test/fixtures/saved_queries.rb +7 -0
- data/test/functional/queries_controller_test.rb +267 -0
- data/test/functional/routes_test.rb +111 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/test_helper.rb +15 -0
- data/test/unit/metadata_test.rb +10 -0
- data/test/unit/sql_test.rb +37 -0
- data/test/unit/visual_query_test.rb +648 -0
- metadata +381 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
<%= render 'url_root' %>
|
2
|
+
<span class="strong"><%=_('Таблици:')%></span>
|
3
|
+
<div id="relations_container">
|
4
|
+
<%= render :partial => 'list_all_relations' %>
|
5
|
+
</div>
|
6
|
+
<%= form_tag(visual_query.results_queries_path(''), :method=> "post", :id => "query") do -%>
|
7
|
+
<div id="joined_relations_container">
|
8
|
+
<span class="strong"><%=_('Таблици в справката:') %></span>
|
9
|
+
<ul class="relation_path">
|
10
|
+
<li>
|
11
|
+
<input type="hidden" name="rows[]" value="0"/>
|
12
|
+
<input type="button" name="add_balanced_query" value="<%= _("Б") %>" onclick="Tutuf.VisualQuery.add_balanced_query()" />
|
13
|
+
</li>
|
14
|
+
</ul>
|
15
|
+
</div>
|
16
|
+
<%= render :partial => 'sort' %>
|
17
|
+
<%= render :partial => 'command' %>
|
18
|
+
<span class="strong"><%=_('Колони:')%></span>
|
19
|
+
<table id="results">
|
20
|
+
<thead>
|
21
|
+
<tr id="hide"></tr>
|
22
|
+
<tr id="filter"></tr>
|
23
|
+
<tr id="relation_column"></tr>
|
24
|
+
</thead>
|
25
|
+
<tbody/>
|
26
|
+
</table>
|
27
|
+
<% end %>
|
@@ -0,0 +1 @@
|
|
1
|
+
Няма справка с име <strong><%= params[:name] %></strong>
|
@@ -0,0 +1,40 @@
|
|
1
|
+
<%= render 'url_root' %>
|
2
|
+
<%= form_tag(visual_query.results_queries_path(@query.name), :method=> "post", :id => "query") do -%>
|
3
|
+
<div id="command">
|
4
|
+
<h1><%= @query.name %></h1>
|
5
|
+
<input id="name" type="hidden" name="name" value="<%= @query.name %>"/>
|
6
|
+
<%= render :partial => 'sort' %>
|
7
|
+
<%= render :partial => 'commands_results' %>
|
8
|
+
</div>
|
9
|
+
<input type="hidden" name="relations[][schema]" value="<%= @query.schema %>"/>
|
10
|
+
<input type="hidden" name="relations[][rel_name]" value="<%= @query.name %>"/>
|
11
|
+
<span class="strong"><%=_('Колони:')%></span>
|
12
|
+
<table id="results">
|
13
|
+
<thead>
|
14
|
+
<tr id="hide">
|
15
|
+
<%- for column in @query.columns do -%>
|
16
|
+
<%= render :partial => 'results_column_hide' %>
|
17
|
+
<%- end -%>
|
18
|
+
</tr>
|
19
|
+
<tr id="filter">
|
20
|
+
<%- for column in @query.columns do -%>
|
21
|
+
<%= render :partial => 'results_column_filter', :locals => {:column => {:schema => @query.schema,
|
22
|
+
:rel_name => @query.name,
|
23
|
+
:col_name => column[:col_name] }} %>
|
24
|
+
<%- end -%>
|
25
|
+
</tr>
|
26
|
+
<tr id="relation_column">
|
27
|
+
<%- for column in @query.columns do -%>
|
28
|
+
<th class="relation_column">
|
29
|
+
<%= _(column[:col_name]) %>
|
30
|
+
<%- for param_name in ['columns', 'all_columns'] do-%>
|
31
|
+
<input type="hidden" name="<%= param_name %>[][rel_name]" value="<%= column[:rel_name] %>"/>
|
32
|
+
<input type="hidden" name="<%= param_name %>[][col_name]" value="<%= column[:col_name] %>"/>
|
33
|
+
<%- end -%>
|
34
|
+
</th>
|
35
|
+
<%- end -%>
|
36
|
+
</tr>
|
37
|
+
</thead>
|
38
|
+
<tbody/>
|
39
|
+
</table>
|
40
|
+
<% end %>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<%= render 'url_root' %>
|
2
|
+
<%= form_tag(visual_query.results_queries_path(@query.nil? ? '' : @query.name), :method=> "get", :id => "query") do -%>
|
3
|
+
<%= text_area_tag :sql, @sql, :rows => 20, :cols => 120 %>
|
4
|
+
<%= render :partial => 'command_results_browser' %>
|
5
|
+
<% if controller.action_name == "edit_sql" -%>
|
6
|
+
<%= hidden_field_tag 'name', @query.name %>
|
7
|
+
<input type="button" name="update" value="<%= _("Запис") %>" onclick="Tutuf.VisualQuery.update_sql()" />
|
8
|
+
<% else -%>
|
9
|
+
<%= render :partial => 'name' %>
|
10
|
+
<%= render :partial => 'command_save' %>
|
11
|
+
<% end -%>
|
12
|
+
<span class="notice"></span>
|
13
|
+
<table id="results">
|
14
|
+
<tbody/>
|
15
|
+
</table>
|
16
|
+
<% end %>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module VisualQuery::PseudoGettext
|
3
|
+
def _(text) text end
|
4
|
+
def n_(singular,plural,arity) singular end
|
5
|
+
end
|
6
|
+
|
7
|
+
Object.class_eval do
|
8
|
+
include VisualQuery::PseudoGettext unless defined?(_) || defined?(n_)
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'tutuf/visual_query'
|
data/config/routes.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# encode: UTF-8
|
2
|
+
VisualQuery::Engine.routes.draw do
|
3
|
+
resources :queries, path: '', only: [:index, :create] do
|
4
|
+
collection do
|
5
|
+
get '/new' => 'queries#new'
|
6
|
+
|
7
|
+
get '/columns/:klass' => 'queries#columns'
|
8
|
+
get '/list_joinable/:klass' => 'queries#list_joinable'
|
9
|
+
get '/join_data/:klass' => 'queries#join_data'
|
10
|
+
|
11
|
+
get :filter
|
12
|
+
get :remove_filter
|
13
|
+
get :sort_condition
|
14
|
+
|
15
|
+
get :new_sql
|
16
|
+
get '/edit_sql/:name' => 'queries#edit_sql', as: 'edit_sql'
|
17
|
+
put '/update_sql/:name' => 'queries#update_sql'
|
18
|
+
|
19
|
+
# some queries have too many params to fit in the server's meagre buffer for GETs
|
20
|
+
match '/results/:name' => 'queries#results', as: 'results', via: ['get','post']
|
21
|
+
|
22
|
+
get '/:name' => 'queries#show', as: 'show'
|
23
|
+
delete '/:name' => 'queries#destroy'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CreateVisualQueryMetadataTable < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
create_table :tutuf_visual_query_metadata do |t|
|
4
|
+
t.string :name, :limit => 63, :null => false
|
5
|
+
t.text :params
|
6
|
+
t.text :order_by
|
7
|
+
t.timestamp :created_at
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :tutuf_visual_query_metadata, :name, :unique => true
|
11
|
+
end
|
12
|
+
|
13
|
+
def down
|
14
|
+
drop_table :tutuf_visual_query_metadata
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
namespace :visual_query do
|
3
|
+
desc "Drops all query views, but leaves metadata"
|
4
|
+
task :disable => :environment do
|
5
|
+
Tutuf::VisualQuery::Base.disable
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "Recreates all query views from metadata"
|
9
|
+
task :enable => :environment do
|
10
|
+
Tutuf::VisualQuery::Base.enable
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Tutuf
|
3
|
+
module VisualQuery
|
4
|
+
class Base < Common
|
5
|
+
class << self
|
6
|
+
|
7
|
+
# PostgreSQL schema in which the saved queries live
|
8
|
+
def schema
|
9
|
+
"tutuf::visual_query"
|
10
|
+
end
|
11
|
+
|
12
|
+
# All direct ascendants of ActiveRecord::Base
|
13
|
+
def klasses
|
14
|
+
return @@klasses if defined?(@@klasses)
|
15
|
+
@@klasses = []
|
16
|
+
all_classes = model_filenames.map do |model_filename|
|
17
|
+
klass = model_filename.gsub(/\.rb$/, '').split(%r{/}).map { |c| c.camelize }.join('::')
|
18
|
+
klass.constantize
|
19
|
+
end
|
20
|
+
all_classes.each do |klass|
|
21
|
+
@@klasses << klass if klass.respond_to?(:superclass) && klass.superclass == ActiveRecord::Base
|
22
|
+
end
|
23
|
+
@@klasses.uniq
|
24
|
+
end
|
25
|
+
|
26
|
+
# List of all relations that the +klass+ can be joined with. Currently self joins are not supported.
|
27
|
+
def joinable_relations(klass)
|
28
|
+
klass.reflections.values.reject{ |reflection| reflection.options[:polymorphic] || reflection.klass == klass }
|
29
|
+
end
|
30
|
+
|
31
|
+
def columns(klass)
|
32
|
+
inheritance_column = klass.columns.detect{|c|c.name == klass.inheritance_column}
|
33
|
+
col_names = (klass.primary_key.is_a?(Array) ? klass.primary_key : [klass.primary_key]) + klass.content_columns.collect{ |col| col.name} + (inheritance_column ? [inheritance_column.name] : [])
|
34
|
+
col_names.map(&:to_s)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Array of relations to be joinded
|
38
|
+
def join_relations(reflection)
|
39
|
+
if reflection.macro == :has_and_belongs_to_many
|
40
|
+
[reflection.join_table, reflection.table_name]
|
41
|
+
else
|
42
|
+
[reflection.klass.table_name]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Join condition between +reflection+ (ActiveRecord::Reflection::MacroReflection) and its +klass+ in format
|
47
|
+
# [[{'rel_name' => reflection relation, 'col_name' => reflection column},
|
48
|
+
# {'rel_name' => klass relation, 'col_name' => klass column}, ... ]]
|
49
|
+
# The order of relations in the array is important for parsing afterwards
|
50
|
+
def join_condition(reflection)
|
51
|
+
case reflection.macro
|
52
|
+
when :belongs_to
|
53
|
+
[[{'rel_name' => reflection.active_record.table_name, 'col_name' => reflection.foreign_key},
|
54
|
+
{'rel_name' => reflection.table_name, 'col_name' => reflection.klass.primary_key}]]
|
55
|
+
when :has_many, :has_one
|
56
|
+
[[{'rel_name' => reflection.active_record.table_name, 'col_name' => reflection.active_record.primary_key},
|
57
|
+
{'rel_name' => reflection.table_name, 'col_name' => reflection.foreign_key}]]
|
58
|
+
when :has_and_belongs_to_many
|
59
|
+
[[ {'rel_name' => reflection.active_record.table_name, 'col_name' => reflection.active_record.primary_key},
|
60
|
+
{'rel_name' => reflection.join_table, 'col_name' => reflection.foreign_key}],
|
61
|
+
[ {'rel_name' => reflection.join_table, 'col_name' => reflection.association_foreign_key},
|
62
|
+
{'rel_name' => reflection.klass.table_name, 'col_name' => reflection.klass.primary_key}]]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def data_type(params)
|
67
|
+
qualified_relation_name = quote_relation_name('schema' => params['schema'], 'rel_name' => params['rel_name'])
|
68
|
+
res = connection.columns(qualified_relation_name).find{|c| c.name == params['col_name']}
|
69
|
+
if res
|
70
|
+
res.type
|
71
|
+
else
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns array of metadata for all queries (Tutuf::VisualQuery::Metadata)
|
77
|
+
def find_all
|
78
|
+
Metadata.all
|
79
|
+
end
|
80
|
+
|
81
|
+
def find_by_name(name)
|
82
|
+
if metadata = Metadata.find_by_name(name)
|
83
|
+
object = new(metadata.params)
|
84
|
+
object.instance_variable_set("@metadata", metadata)
|
85
|
+
object
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def disable
|
90
|
+
connection.execute "DROP SCHEMA IF EXISTS #{quote_ident(schema)} CASCADE"
|
91
|
+
end
|
92
|
+
|
93
|
+
def enable
|
94
|
+
connection.execute "CREATE SCHEMA #{quote_ident(schema)}"
|
95
|
+
find_all.each do |metadata|
|
96
|
+
vq = new(metadata.params.merge('metadata' => metadata))
|
97
|
+
vq.save
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
def model_filenames
|
103
|
+
Dir.chdir("#{Rails.root.to_s}/app/models"){ Dir["**/*.rb"] }
|
104
|
+
end
|
105
|
+
end #end class methods
|
106
|
+
|
107
|
+
def initialize(params)
|
108
|
+
# params were kept in array in an older version
|
109
|
+
params = {} if params.blank? || Array === params
|
110
|
+
@relations = params['relations']
|
111
|
+
rows = (params['rows'].blank? ? [0] : params['rows'])
|
112
|
+
if balanced = (rows.size > 1)
|
113
|
+
@single_queries = rows.map do |row|
|
114
|
+
relations = params['relations'].select{|h| h['row'] == row} if params['relations']
|
115
|
+
join_conditions = params['join_conditions'].select{|jc| jc['row'] == row} if params['join_conditions']
|
116
|
+
Tutuf::VisualQuery::Single.new({'action' => params['action'],
|
117
|
+
'relations' => relations,
|
118
|
+
'join_conditions' => join_conditions,
|
119
|
+
'all_columns' => params['all_columns'],
|
120
|
+
'all_selected_columns' => params['columns'],
|
121
|
+
'sort_conditions' => params['sort_conditions'],
|
122
|
+
'balanced' => balanced}.merge(%w[columns filters].inject({}){|res,key| res[key] = select(relations,params[key]); res}))
|
123
|
+
end
|
124
|
+
elsif params['sql']
|
125
|
+
@single_queries = [Tutuf::VisualQuery::Sql.new(params)]
|
126
|
+
else
|
127
|
+
@single_queries = [Tutuf::VisualQuery::Single.new(params)]
|
128
|
+
end
|
129
|
+
|
130
|
+
name = params['query']['name'] if params['query']
|
131
|
+
@metadata = params.delete('metadata') || Metadata.new(:name => name, :params => params)
|
132
|
+
end
|
133
|
+
|
134
|
+
attr_reader :single_queries, :relations
|
135
|
+
|
136
|
+
def sql?
|
137
|
+
single_queries.size == 1 && Tutuf::VisualQuery::Sql === single_queries.first
|
138
|
+
end
|
139
|
+
|
140
|
+
def filters
|
141
|
+
single_queries.map{|sq| sq.filters.nil? ? [] : sq.filters}.flatten
|
142
|
+
end
|
143
|
+
|
144
|
+
def query
|
145
|
+
single_queries.size > 1 ? single_queries.map{|sq| "(#{sq.query})"}.join(" UNION ALL ") : single_queries.first.query
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
def select(relations,arr)
|
150
|
+
return if relations.blank? || arr.blank?
|
151
|
+
relations = relations.map{|h| h['rel_name']}
|
152
|
+
arr.select{|h| relations.member?(h['rel_name'])}
|
153
|
+
end
|
154
|
+
|
155
|
+
def results(headers=false)
|
156
|
+
return [] if(!sql? && (relations.nil? || relations == ""))
|
157
|
+
@results = []
|
158
|
+
res = connection.execute(query)
|
159
|
+
@results << res.fields if headers
|
160
|
+
@results += res.values
|
161
|
+
res.clear
|
162
|
+
@results
|
163
|
+
end
|
164
|
+
|
165
|
+
public
|
166
|
+
|
167
|
+
def to_a(headers=false)
|
168
|
+
results(headers)
|
169
|
+
end
|
170
|
+
|
171
|
+
def to_csv
|
172
|
+
data = results(true)
|
173
|
+
CSV.generate { |csv| data.each { |row| csv << row } }
|
174
|
+
end
|
175
|
+
|
176
|
+
def to_json
|
177
|
+
return [] if(!sql? && (relations.nil? || relations == ""))
|
178
|
+
JSON.generate(connection.select_all(query))
|
179
|
+
end
|
180
|
+
|
181
|
+
def schema
|
182
|
+
Tutuf::VisualQuery::Base.schema
|
183
|
+
end
|
184
|
+
|
185
|
+
def name
|
186
|
+
@metadata.name
|
187
|
+
end
|
188
|
+
|
189
|
+
def errors
|
190
|
+
@metadata.errors
|
191
|
+
end
|
192
|
+
|
193
|
+
def valid?
|
194
|
+
@metadata.valid?
|
195
|
+
end
|
196
|
+
|
197
|
+
def new?
|
198
|
+
@metadata.new_record?
|
199
|
+
end
|
200
|
+
|
201
|
+
def max_result_length
|
202
|
+
20000
|
203
|
+
end
|
204
|
+
|
205
|
+
def large_result_set?
|
206
|
+
@results && !@results.empty? && (@results.length * @results[0].length > max_result_length)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Array of hashes representing qualified column names that look like { 'rel_name' => "some relation", 'col_name' => "some column"}.
|
210
|
+
# Raises an exception if the query has a name and is not saved yet. Returns nil if it does not have a name and is not saved.
|
211
|
+
def columns
|
212
|
+
if name
|
213
|
+
connection.columns(quote_relation_name('schema' => schema, 'rel_name' => name)).map{|c| {'rel_name' => name, 'col_name' => c.name}.with_indifferent_access}
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def save
|
218
|
+
if @metadata.valid?
|
219
|
+
begin
|
220
|
+
Metadata.transaction do
|
221
|
+
@metadata.save
|
222
|
+
connection.execute("CREATE VIEW #{view_name.to_s.force_encoding('UTF-8')} AS #{query.to_s.force_encoding('UTF-8')}")
|
223
|
+
return true
|
224
|
+
end
|
225
|
+
rescue ActiveRecord::StatementInvalid => e
|
226
|
+
raise e
|
227
|
+
end
|
228
|
+
else
|
229
|
+
return false
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def update_sql(params)
|
234
|
+
@metadata.params = params
|
235
|
+
@single_queries = [Tutuf::VisualQuery::Sql.new('sql' => params['sql'])]
|
236
|
+
if @metadata.valid?
|
237
|
+
begin
|
238
|
+
Metadata.transaction do
|
239
|
+
@metadata.save
|
240
|
+
connection.execute("DROP VIEW #{view_name}")
|
241
|
+
connection.execute("CREATE VIEW #{view_name} AS #{query}")
|
242
|
+
return true
|
243
|
+
end
|
244
|
+
rescue ActiveRecord::StatementInvalid => e
|
245
|
+
@metadata.errors.add(:base, e.to_s)
|
246
|
+
return false
|
247
|
+
end
|
248
|
+
else
|
249
|
+
return false
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def view_name
|
254
|
+
"#{quote_ident(self.class.schema)}.#{quote_ident(name)}"
|
255
|
+
end
|
256
|
+
|
257
|
+
def destroy
|
258
|
+
begin
|
259
|
+
Metadata.transaction do
|
260
|
+
@metadata.destroy
|
261
|
+
connection.execute("DROP VIEW #{quote_ident(self.class.schema)}.#{quote_ident(name)}")
|
262
|
+
end
|
263
|
+
return true
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|