visual_query 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|