querybuilder 0.9.0 → 0.9.1
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.
- data/History.txt +5 -0
- data/lib/query_builder/info.rb +1 -1
- data/lib/query_builder/processor.rb +9 -1
- data/lib/query_builder/query.rb +23 -4
- data/querybuilder.gemspec +2 -2
- data/test/mock/dummy_processor.rb +14 -10
- data/test/querybuilder/custom.yml +7 -1
- data/test/querybuilder/filters.yml +25 -4
- data/test/querybuilder/joins.yml +2 -2
- metadata +3 -3
data/History.txt
CHANGED
data/lib/query_builder/info.rb
CHANGED
@@ -138,7 +138,8 @@ module QueryBuilder
|
|
138
138
|
def initialize(source, opts = {})
|
139
139
|
@default = opts.delete(:default) || {}
|
140
140
|
@opts = opts
|
141
|
-
@rubyless_helper
|
141
|
+
@rubyless_helper = @opts[:rubyless_helper]
|
142
|
+
|
142
143
|
if source.kind_of?(Processor)
|
143
144
|
# experimental: class change
|
144
145
|
@context = source.context
|
@@ -739,6 +740,7 @@ module QueryBuilder
|
|
739
740
|
end
|
740
741
|
end
|
741
742
|
|
743
|
+
# Add a new table and apply scoping when needed
|
742
744
|
def add_table(use_name, table_name = nil)
|
743
745
|
if use_name == main_table && first?
|
744
746
|
# we are now using final table
|
@@ -773,6 +775,12 @@ module QueryBuilder
|
|
773
775
|
end
|
774
776
|
end
|
775
777
|
|
778
|
+
# Add a table to 'import' a key/value based field. This method ensures that
|
779
|
+
# a given field is only included once for each context.
|
780
|
+
def add_key_value_table(use_name, index_table, key = :any, &block)
|
781
|
+
@query.add_key_value_table(use_name, index_table, key, &block)
|
782
|
+
end
|
783
|
+
|
776
784
|
# Hook to use dummy scopes (such as 'in site')
|
777
785
|
def need_join_scope?(scope_name)
|
778
786
|
true
|
data/lib/query_builder/query.rb
CHANGED
@@ -4,7 +4,7 @@ module QueryBuilder
|
|
4
4
|
class Query
|
5
5
|
attr_accessor :processor_class, :distinct, :select, :tables, :table_alias, :where,
|
6
6
|
:limit, :offset, :page_size, :order, :group, :error, :attributes_alias,
|
7
|
-
:pagination_key, :main_class, :context
|
7
|
+
:pagination_key, :main_class, :context, :key_value_tables
|
8
8
|
|
9
9
|
class << self
|
10
10
|
def adapter
|
@@ -19,6 +19,7 @@ module QueryBuilder
|
|
19
19
|
@join_tables = {}
|
20
20
|
@needed_join_tables = {}
|
21
21
|
@attributes_alias = {}
|
22
|
+
@key_value_tables = {}
|
22
23
|
@where = []
|
23
24
|
end
|
24
25
|
|
@@ -119,6 +120,23 @@ module QueryBuilder
|
|
119
120
|
add_alias_to_tables(table_name || use_name, alias_name)
|
120
121
|
end
|
121
122
|
|
123
|
+
# Add a table to 'import' a key/value based field. This method ensures that
|
124
|
+
# a given field is only included once for each context.
|
125
|
+
def add_key_value_table(use_name, index_table, key, &block)
|
126
|
+
key_tables = (@key_value_tables[table] ||= {})
|
127
|
+
key_table = (key_tables[use_name] ||= {})
|
128
|
+
if alias_table = key_table[key]
|
129
|
+
# done, the index_table has been used for the given key in the current context
|
130
|
+
else
|
131
|
+
# insert the new table
|
132
|
+
add_table(use_name, index_table, false)
|
133
|
+
alias_table = key_table[key] = table(use_name)
|
134
|
+
# Let caller configure the filter (join).
|
135
|
+
block.call(alias_table)
|
136
|
+
end
|
137
|
+
alias_table
|
138
|
+
end
|
139
|
+
|
122
140
|
def add_select(clause)
|
123
141
|
@select ||= []
|
124
142
|
@select << clause
|
@@ -130,7 +148,8 @@ module QueryBuilder
|
|
130
148
|
end
|
131
149
|
|
132
150
|
# Use this method to add a join to another table (added only once for each join name).
|
133
|
-
#
|
151
|
+
# nodes JOIN idx_nodes_string AS id1 ON ...
|
152
|
+
# FIXME: can we remove this ? It seems buggy (JOIN in or clauses)
|
134
153
|
def needs_join_table(table_name1, type, table_name2, clause, join_name = nil)
|
135
154
|
join_name ||= "#{table_name1}=#{type}=#{table_name2}"
|
136
155
|
@needed_join_tables[join_name] ||= {}
|
@@ -149,10 +168,10 @@ module QueryBuilder
|
|
149
168
|
end
|
150
169
|
end
|
151
170
|
|
152
|
-
# Duplicate query, avoiding
|
171
|
+
# Duplicate query, avoiding sharing some arrays and hash
|
153
172
|
def dup
|
154
173
|
other = super
|
155
|
-
%w{tables table_alias where}.each do |k|
|
174
|
+
%w{tables table_alias where tables key_value_tables}.each do |k|
|
156
175
|
other.send("#{k}=", other.send(k).dup)
|
157
176
|
end
|
158
177
|
other
|
data/querybuilder.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{querybuilder}
|
8
|
-
s.version = "0.9.
|
8
|
+
s.version = "0.9.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Gaspard Bucher"]
|
12
|
-
s.date = %q{2010-09-
|
12
|
+
s.date = %q{2010-09-22}
|
13
13
|
s.description = %q{QueryBuilder is an interpreter for the "pseudo sql" language. This language
|
14
14
|
can be used for two purposes:
|
15
15
|
|
@@ -27,23 +27,27 @@ class DummyProcessor < QueryBuilder::Processor
|
|
27
27
|
end
|
28
28
|
|
29
29
|
# Overwrite this and take care to check for valid fields.
|
30
|
-
def process_field(
|
31
|
-
if ['id', 'parent_id', 'project_id', 'section_id', 'kpath', 'name', 'event_at', 'custom_a'].include?(
|
32
|
-
"#{table}.#{
|
33
|
-
elsif
|
30
|
+
def process_field(field_name)
|
31
|
+
if ['id', 'parent_id', 'project_id', 'section_id', 'kpath', 'name', 'event_at', 'custom_a'].include?(field_name)
|
32
|
+
"#{table}.#{field_name}"
|
33
|
+
elsif field_name == 'REF_DATE'
|
34
34
|
context[:ref_date] ? insert_bind(context[:ref_date]) : 'now()'
|
35
|
-
elsif
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
elsif %w{age size}.include?(field_name)
|
36
|
+
tbl = add_key_value_table('idx', 'idx_nodes', field_name) do |tbl_name|
|
37
|
+
# This block is only executed once
|
38
|
+
add_filter "#{tbl_name}.node_id = #{table}.id"
|
39
|
+
add_filter "#{tbl_name}.key = #{quote(field_name)}"
|
40
|
+
end
|
41
|
+
|
42
|
+
"#{tbl}.value"
|
39
43
|
else
|
40
44
|
super # raises an error
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
44
|
-
def resolve_missing_table(query,
|
48
|
+
def resolve_missing_table(query, table_name, table_alias)
|
45
49
|
case table_name
|
46
|
-
when '
|
50
|
+
when 'idx_nodes'
|
47
51
|
query.where.insert 0, "#{table_alias}.id = 0"
|
48
52
|
when 'links'
|
49
53
|
query.where.insert 0, "#{table_alias}.id = 0"
|
@@ -59,4 +59,10 @@ it_should_paginate_the_entries:
|
|
59
59
|
context:
|
60
60
|
custom_query_group: test
|
61
61
|
src: "abc limit 4 paginate foo"
|
62
|
-
res: "[%Q{SELECT a,34 AS number,c FROM test WHERE 3 AND 2 AND 1 ORDER BY a ASC LIMIT 4 OFFSET ?}, ((foo.to_i > 0 ? foo.to_i : 1)-1)*4]"
|
62
|
+
res: "[%Q{SELECT a,34 AS number,c FROM test WHERE 3 AND 2 AND 1 ORDER BY a ASC LIMIT 4 OFFSET ?}, ((foo.to_i > 0 ? foo.to_i : 1)-1)*4]"
|
63
|
+
|
64
|
+
it_should_use_alias_in_filter:
|
65
|
+
context:
|
66
|
+
custom_query_group: test
|
67
|
+
src: "abc where number > 10"
|
68
|
+
res: "%Q{SELECT a,34 AS number,c FROM test WHERE (34) > 10 AND 3 AND 2 AND 1 ORDER BY a ASC}"
|
@@ -86,12 +86,33 @@ equation_and_or_par:
|
|
86
86
|
res: "[%Q{SELECT objects.* FROM objects WHERE (objects.event_at > '2006-04-01' OR objects.name LIKE 'foo%') AND objects.parent_id = ? GROUP BY id}, id]"
|
87
87
|
|
88
88
|
or_with_same_tables:
|
89
|
-
src: "objects where
|
90
|
-
res: "[%Q{SELECT objects.* FROM
|
89
|
+
src: "objects where age = 5 or age = 7"
|
90
|
+
res: "[%Q{SELECT objects.* FROM idx_nodes AS id1,objects WHERE ((id1.value = 5 AND id1.key = 'age' AND id1.node_id = objects.id) OR (id1.value = 7 AND id1.key = 'age' AND id1.node_id = objects.id)) AND objects.parent_id = ? GROUP BY objects.id}, id]"
|
91
91
|
|
92
92
|
or_with_missing_table:
|
93
|
-
src: "objects where
|
94
|
-
res: "[%Q{SELECT objects.* FROM
|
93
|
+
src: "objects where age = 5 or 7"
|
94
|
+
res: "[%Q{SELECT objects.* FROM idx_nodes AS id1,objects WHERE ((id1.value = 5 AND id1.key = 'age' AND id1.node_id = objects.id) OR (7 AND id1.id = 0)) AND objects.parent_id = ? GROUP BY objects.id}, id]"
|
95
|
+
|
96
|
+
or_different_table:
|
97
|
+
src: "objects where age = 5 or size = 15"
|
98
|
+
res: "[%Q{SELECT objects.* FROM idx_nodes AS id1,objects WHERE ((id1.value = 5 AND id1.key = 'age' AND id1.node_id = objects.id) OR (id1.value = 15 AND id1.key = 'size' AND id1.node_id = objects.id)) AND objects.parent_id = ? GROUP BY objects.id}, id]"
|
99
|
+
|
100
|
+
and_with_same_tables:
|
101
|
+
src: "objects where age > 5 and age < 7"
|
102
|
+
res: "[%Q{SELECT objects.* FROM idx_nodes AS id1,objects WHERE id1.value > 5 AND id1.value < 7 AND id1.key = 'age' AND id1.node_id = objects.id AND objects.parent_id = ?}, id]"
|
103
|
+
|
104
|
+
and_with_same_tables_different_key:
|
105
|
+
src: "objects where age > 5 and size = 10"
|
106
|
+
res: "[%Q{SELECT objects.* FROM idx_nodes AS id1,idx_nodes AS id2,objects WHERE id1.value > 5 AND id2.value = 10 AND id2.key = 'size' AND id2.node_id = objects.id AND id1.key = 'age' AND id1.node_id = objects.id AND objects.parent_id = ?}, id]"
|
107
|
+
|
108
|
+
|
109
|
+
filter_and_order_index:
|
110
|
+
src: "objects where age > 5 order by age asc"
|
111
|
+
res: "[%Q{SELECT objects.* FROM idx_nodes AS id1,objects WHERE id1.value > 5 AND id1.key = 'age' AND id1.node_id = objects.id AND objects.parent_id = ? ORDER BY id1.value ASC}, id]"
|
112
|
+
|
113
|
+
order_without_idx_filter:
|
114
|
+
src: "objects order by age asc"
|
115
|
+
res: "[%Q{SELECT objects.* FROM idx_nodes AS id1,objects WHERE id1.key = 'age' AND id1.node_id = objects.id AND objects.parent_id = ? ORDER BY id1.value ASC}, id]"
|
95
116
|
|
96
117
|
equation_par:
|
97
118
|
src: "objects where (1 > 2 or 2 > 3) and 4 = 5 "
|
data/test/querybuilder/joins.yml
CHANGED
@@ -18,7 +18,7 @@ parent_from_parent:
|
|
18
18
|
|
19
19
|
children_from_objects_in_project:
|
20
20
|
res: "[%Q{SELECT objects.* FROM objects,objects AS ob1 WHERE objects.parent_id = ob1.id AND ob1.project_id = ? GROUP BY objects.id}, project_id]"
|
21
|
-
|
21
|
+
|
22
22
|
tags:
|
23
23
|
sxp: '[:query, [:relation, "tags"]]'
|
24
24
|
res: "%Q{SELECT objects.* FROM objects INNER JOIN tags ON objects.id = tags.node_id}"
|
@@ -31,7 +31,7 @@ complex_from_with_scopes_and_typed_scope:
|
|
31
31
|
# instead of 'project', we give it a class with 'jobs:project'
|
32
32
|
src: "letters where name = 'foo' in jobs:project from letters in section"
|
33
33
|
sxp: '[:query, [:from, [:scope, [:filter, [:relation, "letters"], [:"=", [:field, "name"], [:string, "foo"]]], "jobs:project"], [:scope, [:relation, "letters"], "section"]]]'
|
34
|
-
|
34
|
+
|
35
35
|
letters_in_project_from_letters:
|
36
36
|
sxp: '[:query, [:from, [:scope, [:relation, "letters"], "project"], [:relation, "letters"]]]'
|
37
37
|
res: "[%Q{SELECT objects.* FROM objects,objects AS ob1 WHERE objects.kpath LIKE 'NNL%' AND objects.project_id = ob1.id AND ob1.kpath LIKE 'NNL%' AND ob1.parent_id = ? GROUP BY objects.id}, id]"
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 9
|
8
|
-
-
|
9
|
-
version: 0.9.
|
8
|
+
- 1
|
9
|
+
version: 0.9.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Gaspard Bucher
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-09-
|
17
|
+
date: 2010-09-22 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|