querybuilder 1.1.3 → 1.1.4

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 CHANGED
@@ -1,3 +1,8 @@
1
+ == 1.1.4 2012-10-07
2
+
3
+ * Major enhancement
4
+ * Key/value tables are now 'LEFT' joined, enabling null testing and simplifying complex queries (with OR).
5
+
1
6
  == 1.1.3 2012-08-30
2
7
 
3
8
  * Major enhancement
@@ -1,3 +1,3 @@
1
1
  module QueryBuilder
2
- VERSION = '1.1.3'
2
+ VERSION = '1.1.4'
3
3
  end
@@ -791,16 +791,14 @@ module QueryBuilder
791
791
  end
792
792
 
793
793
  # Add a new table and apply scoping when needed
794
- def add_table(use_name, table_name = nil)
795
- if use_name == main_table && first?
796
- # we are now using final table
797
- context[:table_alias] = use_name
798
- avoid_alias = true
799
- else
800
- avoid_alias = false
801
- end
802
-
794
+ def add_table(use_name, table_name = nil, type = nil, &block)
803
795
  if use_name == main_table
796
+ if first?
797
+ # we are now using final table
798
+ context[:table_alias] = use_name
799
+ avoid_alias = true
800
+ end
801
+
804
802
  if context[:scope_type] == :join
805
803
  context[:scope_type] = nil
806
804
  # pre scope
@@ -808,7 +806,7 @@ module QueryBuilder
808
806
  @query.add_table(main_table, main_table, avoid_alias)
809
807
  apply_scope(context[:scope])
810
808
  end
811
- @query.add_table(use_name, table_name, avoid_alias)
809
+ @query.add_table(use_name, table_name, avoid_alias, type, &block)
812
810
  elsif context[:scope_type] == :filter
813
811
  context[:scope_type] = nil
814
812
  # post scope
@@ -816,12 +814,12 @@ module QueryBuilder
816
814
  apply_scope(context[:scope] || default_scope(context))
817
815
  else
818
816
  # scope already applied / skip
819
- @query.add_table(use_name, table_name, avoid_alias)
817
+ @query.add_table(use_name, table_name, avoid_alias, type, &block)
820
818
  end
821
819
  else
822
820
  # no scope
823
821
  # can only scope main_table
824
- @query.add_table(use_name, table_name)
822
+ @query.add_table(use_name, table_name, true, type, &block)
825
823
  end
826
824
  end
827
825
 
@@ -119,9 +119,9 @@ module QueryBuilder
119
119
 
120
120
  # 'avoid_alias' is used when parsing the last element so that it takes the real table name (nodes, not no1). We need
121
121
  # this because we can use 'OR' between parts and we thus need the same table reference.
122
- def add_table(use_name, table_name = nil, avoid_alias = true)
122
+ def add_table(use_name, table_name = nil, avoid_alias = true, type = nil, &block)
123
123
  alias_name = get_alias(use_name, table_name, avoid_alias)
124
- add_alias_to_tables(table_name || use_name, alias_name)
124
+ add_alias_to_tables(table_name || use_name, alias_name, type, &block)
125
125
  end
126
126
 
127
127
  # Add a table to 'import' a key/value based field. This method ensures that
@@ -133,10 +133,8 @@ module QueryBuilder
133
133
  # done, the index_table has been used for the given key in the current context
134
134
  else
135
135
  # insert the new table
136
- add_table(use_name, index_table, false)
136
+ add_table(use_name, index_table, false, :left, &block)
137
137
  alias_table = key_table[key] = table(use_name)
138
- # Let caller configure the filter (join).
139
- block.call(alias_table)
140
138
  end
141
139
  alias_table
142
140
  end
@@ -225,27 +223,51 @@ module QueryBuilder
225
223
  def quote_column_name(name)
226
224
  connection.quote_column_name(name)
227
225
  end
228
-
226
+
227
+ def has_table?(use_name)
228
+ !@table_alias[use_name].nil?
229
+ end
230
+
231
+
229
232
  private
230
- # Make sure each used table gets a unique name
233
+ # Make sure each used table gets a unique name. By default, this uses an alias
234
+ # But if 'avoid_alias' is true and it is the first call for this table, return the
235
+ # table without an alias.
231
236
  def get_alias(use_name, table_name = nil, avoid_alias = true)
232
237
  table_name ||= use_name
233
- @table_alias[use_name] ||= []
234
- if avoid_alias && !@tables.include?(table_name)
238
+ list = (@table_alias[use_name] ||= [])
239
+ if avoid_alias && !list.include?(use_name)
235
240
  alias_name = use_name
236
241
  elsif @tables.include?(use_name)
237
242
  # links, li1, li2, li3
238
- alias_name = "#{use_name[0..1]}#{@table_alias[use_name].size}"
243
+ alias_name = "#{use_name[0..1]}#{list.size}"
239
244
  else
240
245
  # ob1, obj2, objects
241
- alias_name = "#{use_name[0..1]}#{@table_alias[use_name].size + 1}"
246
+ alias_name = "#{use_name[0..1]}#{list.size + 1}"
242
247
  end
243
248
 
244
- @table_alias[use_name] << alias_name
249
+ list << alias_name
245
250
  alias_name
246
251
  end
247
252
 
248
- def add_alias_to_tables(table_name, alias_name)
253
+ def add_alias_to_tables(table_name, alias_name, type = nil, &block)
254
+ if type
255
+ key = "#{table_name}=#{type}=#{alias_name}"
256
+
257
+ @needed_join_tables[key] ||= begin
258
+ clause = "#{type.to_s.upcase} JOIN #{table_name}"
259
+ if alias_name && alias_name != table_name
260
+ clause << " AS #{alias_name}"
261
+ end
262
+ if block_given?
263
+ clause << " ON #{block.call(alias_name || table_name)}"
264
+ end
265
+ joins = (@join_tables[main_table] ||= [])
266
+ joins << clause
267
+ end
268
+ return
269
+ end
270
+
249
271
  if alias_name != table_name
250
272
  @tables << "#{table_name} AS #{alias_name}"
251
273
  else
@@ -278,7 +300,7 @@ module QueryBuilder
278
300
 
279
301
  group = @group
280
302
  if !group && @distinct
281
- key = @tables.size > 1 ? "#{main_table}.id" : 'id'
303
+ key = "#{main_table}.id"
282
304
 
283
305
  case self.class.adapter
284
306
  when 'postgresql'
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 = "1.1.3"
8
+ s.version = "1.1.4"
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{2012-08-30}
12
+ s.date = %q{2012-10-10}
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
 
data/test/database.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'active_record'
2
+ require 'logger'
2
3
 
3
4
  begin
4
5
  class QueryBuilderTestMigration < ActiveRecord::Migration
@@ -35,8 +35,7 @@ class DummyProcessor < QueryBuilder::Processor
35
35
  elsif %w{age size}.include?(field_name)
36
36
  tbl = add_key_value_table('idx', 'idx_nodes', field_name) do |tbl_name|
37
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)}"
38
+ "#{tbl_name}.node_id = #{table}.id AND #{tbl_name}.key = #{quote(field_name)}"
40
39
  end
41
40
 
42
41
  "#{tbl}.value"
@@ -89,7 +89,7 @@ after_process_callback:
89
89
  context:
90
90
  after_filter: '(1 = 1)'
91
91
  src: "objects where name like 'a%' or name like 'b%' in site"
92
- res: "%Q{SELECT objects.* FROM objects WHERE (1 = 1) AND (objects.name LIKE 'a%' OR objects.name LIKE 'b%') GROUP BY id}"
92
+ res: "%Q{SELECT objects.* FROM objects WHERE (1 = 1) AND (objects.name LIKE 'a%' OR objects.name LIKE 'b%') GROUP BY objects.id}"
93
93
 
94
94
  function:
95
95
  src: "objects where event_at.year = 2005"
@@ -93,36 +93,36 @@ equation_with_date_interval:
93
93
  equation_and_or_par:
94
94
  src: "objects where event_at > '2006-04-01' or name like 'foo%'"
95
95
  sxp: '[:query, [:filter, [:relation, "objects"], [:or, [:>, [:field, "event_at"], [:string, "2006-04-01"]], [:like, [:field, "name"], [:string, "foo%"]]]]]'
96
- 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]"
96
+ res: "[%Q{SELECT objects.* FROM objects WHERE (objects.event_at > '2006-04-01' OR objects.name LIKE 'foo%') AND objects.parent_id = ? GROUP BY objects.id}, id]"
97
97
 
98
98
  or_with_same_tables:
99
99
  src: "objects where age = 5 or age = 7"
100
- 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]"
100
+ res: "[%Q{SELECT objects.* FROM objects LEFT JOIN idx_nodes AS id1 ON id1.node_id = objects.id AND id1.key = 'age' WHERE (id1.value = 5 OR id1.value = 7) AND objects.parent_id = ? GROUP BY objects.id}, id]"
101
101
 
102
102
  or_with_missing_table:
103
- src: "objects where age = 5 or 7"
104
- 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]"
103
+ src: "objects where age = 5 or id = 7"
104
+ res: "[%Q{SELECT objects.* FROM objects LEFT JOIN idx_nodes AS id1 ON id1.node_id = objects.id AND id1.key = 'age' WHERE (id1.value = 5 OR objects.id = 7) AND objects.parent_id = ? GROUP BY objects.id}, id]"
105
105
 
106
106
  or_different_table:
107
107
  src: "objects where age = 5 or size = 15"
108
- 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]"
108
+ res: "[%Q{SELECT objects.* FROM objects LEFT JOIN idx_nodes AS id1 ON id1.node_id = objects.id AND id1.key = 'age' WHERE (id1.value = 5 OR id1.value = 15) AND objects.parent_id = ? GROUP BY objects.id}, id]"
109
109
 
110
110
  and_with_same_tables:
111
111
  src: "objects where age > 5 and age < 7"
112
- 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]"
112
+ res: "[%Q{SELECT objects.* FROM objects LEFT JOIN idx_nodes AS id1 ON id1.node_id = objects.id AND id1.key = 'age' WHERE id1.value > 5 AND id1.value < 7 AND objects.parent_id = ?}, id]"
113
113
 
114
114
  and_with_same_tables_different_key:
115
115
  src: "objects where age > 5 and size = 10"
116
- 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]"
116
+ res: "[%Q{SELECT objects.* FROM objects LEFT JOIN idx_nodes AS id1 ON id1.node_id = objects.id AND id1.key = 'age' LEFT JOIN idx_nodes AS id2 ON id2.node_id = objects.id AND id2.key = 'size' WHERE id1.value > 5 AND id2.value = 10 AND objects.parent_id = ?}, id]"
117
117
 
118
118
 
119
119
  filter_and_order_index:
120
120
  src: "objects where age > 5 order by age asc"
121
- 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]"
121
+ res: "[%Q{SELECT objects.* FROM objects LEFT JOIN idx_nodes AS id1 ON id1.node_id = objects.id AND id1.key = 'age' WHERE id1.value > 5 AND objects.parent_id = ? ORDER BY id1.value ASC}, id]"
122
122
 
123
123
  order_without_idx_filter:
124
124
  src: "objects order by age asc"
125
- 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]"
125
+ res: "[%Q{SELECT objects.* FROM objects LEFT JOIN idx_nodes AS id1 ON id1.node_id = objects.id AND id1.key = 'age' WHERE objects.parent_id = ? ORDER BY id1.value ASC}, id]"
126
126
 
127
127
  equation_par:
128
128
  src: "objects where (1 > 2 or 2 > 3) and 4 = 5 "
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: querybuilder
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 1
9
- - 3
10
- version: 1.1.3
9
+ - 4
10
+ version: 1.1.4
11
11
  platform: ruby
12
12
  authors:
13
13
  - Gaspard Bucher
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-08-30 00:00:00 +02:00
18
+ date: 2012-10-10 00:00:00 +02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency